這篇文章介紹如何使用 pttR 從 PTT 網頁版 抓取資料。
文章會用到這些函數:
index2df()
:爬取 PTT 某版的文章列表,並將資料轉換成 data frame。down_html()
:從 index2df()
回傳的網址,下載 PTT 文章至本地電腦。post2df()
:將多篇 PTT 文章轉換成 data frame 。index2df()
是用來抓取 PTT 網頁版的看板文章資料,index
即對應到https://www.ptt.cc/bbs/<看板名稱>/index.html
這個頁面。
index2df()
提供 3 種搜尋頁面的方式:
這裡以含特定關鍵字的頁面範圍為例:
使用關鍵字搜尋,實際上相當於在 PTT 網頁版上方的“搜尋文章…”搜尋關鍵字,而最新的頁面的頁數是 1,越老的頁面數字越大。
set.seed(2018)
library(pttR)
index_df <- index2df(board = "gossiping",
search_term = "魯蛇",
search_page = sample(1:20, 3))
#> Searching gossiping for '魯蛇' ...
我們在八卦板中搜尋關鍵字:魯蛇, 並抓取 3, 12, 15 這幾頁的資料。
index2df()
會將網頁的資料整理成一個 data frame,各變項的資料對照實際網頁去看很容易就可了解,例如:
pop | category | title | link | author | date | idx_n |
---|---|---|---|---|---|---|
1 | 問卦 | [問卦] 表特板是不是一堆垃圾魯蛇處男宅在評論女人? | Gossiping/M.1547567712.A.8E2.html | antivenom | 1/15 | 15 |
3 | 問卦 | [問卦]為何鄉民這麼愛裝 肥宅 單身魯蛇 | Gossiping/M.1547562012.A.9AF.html | mowkow0611 | 1/15 | 15 |
2 | 問卦 | [問卦] 早 魯蛇們 | Gossiping/M.1547532100.A.F5A.html | MiLuDaiBoom | 1/15 | 15 |
0 | 問卦 | [問卦] 現在還沒睡的人是不是魯蛇? | Gossiping/M.1547483124.A.52D.html | Kuru991 | 1/15 | 15 |
0 | 問卦 | [問卦] 魯蛇肥宅脫光光看起來還會像魯蛇嗎? | Gossiping/M.1547387468.A.67B.html | nobel777 | 1/13 | 15 |
0 | 問卦 | [問卦] 為什麼失敗者叫「魯蛇」? | Gossiping/M.1547213038.A.CDC.html | ihl123456 | 1/11 | 15 |
str(index_df, nchar.max = 20)
#> Classes 'tbl_df', 'tbl' and 'data.frame': 60 obs. of 7 variables:
#> $ pop : chr "1" "3" "2" "0" ...
#> $ category: chr "問卦" "問卦" "問卦" "問卦" ...
#> $ title : chr "[問"| __truncated__ "[問"| __truncated__ "[問卦] 早 魯蛇們" "[問"| __truncated__ ...
#> $ link : chr "Gos"| __truncated__ "Gos"| __truncated__ "Gos"| __truncated__ "Gos"| __truncated__ ...
#> $ author : chr "antivenom" "mowkow0611" "MiLuDaiBoom" "Kuru991" ...
#> $ date : chr " 1/15" " 1/15" " 1/15" " 1/15" ...
#> $ idx_n : int 15 15| __truncated__ ...
由於頁面的頁碼會不斷改變 (關鍵字搜尋最新頁面永遠是 第一頁1),文章也常常會被刪除,因此將文章頁面下載至電腦再擷取頁面資訊會是比較建議的方式。
urls <- sample(index_df$link, 5)
if (!dir.exists("./loser")) dir.create("./loser")
down_html(as_url(urls), dir = "./loser")
as_url()
是一個方便的函數,將部份 URL 轉換成完整的 URL。
在下載完網頁之後,需要將檔案讀進 R,因此需要取得這些檔案在電腦裡的位置:
接著,將下載下來的 5 個檔案 (5 篇文章) 用post_df
讀入並轉成data_frame
:
接著看看跑出來的資料:
str(head(post_df, 3), vec.len = 3, nchar.max = 17, max.level = 2)
#> 'data.frame': 3 obs. of 12 variables:
#> $ author : chr "Joker3 (丑角3)" ""| __truncated__ ""| __truncated__
#> $ category : chr "Re:" "問卦" "新聞"
#> $ title : chr ""| __truncated__ ""| __truncated__ ""| __truncated__
#> $ date : chr ""| __truncated__ ""| __truncated__ ""| __truncated__
#> $ content : chr ""| __truncated__ ""| __truncated__ ""| __truncated__
#> $ n_comment : int 14 20 65
#> $ n_push : int 7 7 29
#> $ n_boo : int 1 2 9
#> $ link : chr ""| __truncated__ ""| __truncated__ ""| __truncated__
#> $ comment :List of 3
#> ..$ :Classes 'tbl_df', 'tbl' and 'data.frame': 14 obs. of 6 variables:
#> ..$ :Classes 'tbl_df', 'tbl' and 'data.frame': 20 obs. of 6 variables:
#> ..$ :Classes 'tbl_df', 'tbl' and 'data.frame': 65 obs. of 6 variables:
#> $ content_char: int 1136 80 935
#> $ content_urls:List of 3
#> ..$ : chr ""| __truncated__
#> ..$ : chr
#> ..$ : chr ""| __truncated__
可以發現這個 data frame 的comment
與content_urls
是 list-column2。
post_df$comment
的每個元素是一個 data frame,需用[[
,]]
檢視其內容。 這裡檢視post_df
中第一篇文章的留言:
tag | user | comment | ip | time | author_reply |
---|---|---|---|---|---|
Neu | vickyshan | 好像是假的 | 2018-04-19 18:31:00 | ||
Boo | linda17a3 | 你是改排氣管的廢物8+9人家是林志穎 | 2018-04-19 18:32:00 | ||
Push | BrandyEye | 其實不管是不是假的 說他是魯蛇本身就很笑 | 2018-04-19 18:32:00 | ||
Push | tending | 這些都是家裡很有錢才玩得起的項目 | 2018-04-19 18:33:00 | ||
Push | kutkin | 全滿分獲總統獎退伍 | 2018-04-19 18:33:00 | ||
Neu | ghost008 | 老實講就算買遙控車給我 我也拿不到什麼獎 | 2018-04-19 18:34:00 | ||
Neu | ghost008 | 光是在最紅的時候當兵就幹死一堆人了 | 2018-04-19 18:35:00 | ||
Push | xiangming | 對啊 搖控車不簡單蠻難操作的…有心申請學校的話應該 | 2018-04-19 18:35:00 | ||
Neu | xiangming | 會加分 | 2018-04-19 18:35:00 | ||
Push | bruce2248 | 二樓明明對8+9一見鍾情 | 2018-04-19 18:38:00 | ||
Push | eric999 | 我對林沒什麼感覺,但說林沒有大紅過真的是笑話。連港片 | 2018-04-19 18:47:00 | ||
Neu | eric999 | 都提過小旋風這個哏。 | 2018-04-19 18:48:00 | ||
Push | salmouson | 卡丁車是職業車手的入門,他們小時候都是練卡丁車 | 2018-04-19 19:02:00 | ||
Neu | ga595528 | 是最近出唱片一直業配? | 2018-04-19 19:19:00 |
這點和一般頁面不同,第一頁是最舊的,頁碼越大頁面越新,但頁碼的上限似乎是 4 萬頁。目前八卦板最新的頁面是 3 萬多頁, 最舊的頁面是Gossiping/index1.html
。↩
一般來說,data frame 的一個 cell 都是儲存一個值,因此一個 column 通常是一個atomic vector
。list-column 則為一個 list
,其下的每個 cell 也是一個list
(list[1]
回傳的依然是list
)。在這裡,df$comment
回傳一個長度為 的list
。
這是個有點複雜的資料結構,但在這裡卻非常合適:運用list
的 recursive 特性,我們可以將一篇 PTT 文章中的所有留言整理成一個 data frame,將其 儲存在list
的一個 element 中。因此要取得某篇文章的資訊,例如僅需用[[
,]]
脫開 list 結構,取得原來的 data frame↩