在前面的內容中,咱們已經學習了 HTML、CSS 和 XPath 的基礎知識。從真實世界的
網頁中獲取數據,關鍵在於如何編寫合適的 CSS 或者 XPath 選擇器。本節介紹一些肯定選
擇器的簡單方法。
假設從https://cran.rstudio.com/web/packages/available_packages_by_name.html 這個網頁
上獲取全部可用的 R 程序包。網頁看起來很簡單。想知道選擇器的表達式,在頁面上右擊,
選擇菜單中的審查元素選項(檢查大部分現代瀏覽器中都有),而後就會出現檢查面板,如
圖 14-6 所示。html
圖 14-6
咱們即可以看到網頁底層的 HTML 代碼。在火狐瀏覽器和谷歌瀏覽器中,選擇的節點
是高亮的,更容易定位,如圖 14-7 所示。node
圖 14-7
HTML 包含惟一的 <table>,所以能夠直接將 CSS 選擇器設爲 table,再用 html_
table( ) 提取表格並返回一個數據框:
page <-
read_ _html("https://cran.rstudio.com/web/packages/available_packages_by_name
.html")
pkg_table <- page %>%
html_ _node("table") %>%
html_ _table(fill = TRUE)
head(pkg_table, 5)
## X1
## 1
## 2 A3
## 3 abbyyR
## 4 abc
## 5 ABCanalysis
## X2
## 1 <NA>
## 2 Accurate, Adaptable, and Accessible Error Metrics for Predictive\nModels
## 3 Access to Abbyy Optical Character Recognition (OCR) API
## 4 Tools for Approximate Bayesian Computation (ABC)
## 5 Computed ABC Analysis
注意,原始表沒有表頭。而結果數據框使用了默認表頭,而且第一行是空的。下面的
代碼解決了這些問題:
pkg_table <- pkg_table[complete.cases(pkg_table), ]
colnames(pkg_table) <- c("name", "title")
head(pkg_table, 3)
## name
## 2 A3
## 3 abbyyR
## 4 abc
##
title
## 2 Accurate, Adaptable, and Accessible Error Metrics for Predictive\nModels
## 3 Access to Abbyy Optical Character Recognition (OCR) API
## 4 Tools for Approximate Bayesian Computation (ABC)
在下一個例子中,咱們想要提取微軟最新的股票價格 http://finance.yahoo.com/quote/
MSFT。使用元素查看器,發現價格包含在 <span> 中,其中經過程序生成的 class 屬性
值特別長,如圖 14-8 所示。web
圖 14-8
查看幾個層級後,咱們找到一條路徑 div#quote-header-info > section >
span,就是它導向這個價格節點。所以,能夠使用這個 CSS 選擇器尋找並提取股票價格:
page <- read_ _html("https://finance.yahoo.com/quote/MSFT")
page %>%
html_ _node("div#quote-header-info > section > span") %>%
html_ _text() %>%
as.numeric()
## [1] 56.68
在網頁的右側,有一張包含企業關鍵數據的統計表格,如圖 14-9 所示。
取出表格以前,咱們能夠經過查看器找到它所屬的節點和可以定位到這個表格的選擇器。網頁爬蟲
圖 14-9 瀏覽器
圖 14-10
顯然,咱們感興趣的 <table> 節點是 <div id = 」key-statustics」> 的子節點。所以,
咱們能夠直接使用 #key-statistics table 匹配這個表格節點,並將它轉換爲一個數據框:
page %>%
html_ _node("#key-statistics table") %>%
html_ _table()
## X1 X2
## 1 Market Cap 442.56B
## 2 P/E Ratio (ttm) 26.99
## 3 Diluted EPS N/A
## 4 Beta 1.05
## 5 Earnings Date N/A
## 6 Dividend & Yield 1.44 (2.56%)
## 7 Ex-Dividend Date N/A
## 8 1y Target Est N/A
使用相似的方法,咱們也能建立一個函數,給定一個股票代碼(例如 MSFT)就能夠
返回公司名稱和股票價格:
get_price <- function(symbol) {
page <- read_ _html(sprintf("https://finance.yahoo.com/quote/%s", symbol))
list(symbol = symbol,
company = page %>%
html_ _node("div#quote-header-info > div:nth-child(1) > h6") %>%
html_ _text(),
price = page %>%
html_ _node("div#quote-header-info > section > span:nth-child(1)") %>%
html_ _text() %>%
as.numeric())
}
CSS 選擇器充分的限制條件可以保證定位到正確的 HTML 節點。咱們運行如下代碼來
測試這個函數:
get_ _price("AAPL")
## $symbol
## [1] "AAPL"
##
## $company
## [1] "Apple Inc."
##
## $price
## [1] 104.19
另外一個例子是獲取 http://stackoverflow.com/questions/tagged/r?sort=votes 上關於 R 的投
票數最多的問題,如圖 14-11 所示。app
圖 14-11
使用相似的方法,很容易就能找到問題列表包含在一個 id 是 questions 的集合中。
所以,咱們載入頁面並用#questions 選擇和存儲問題集合:
page <-
read_ _html("https://stackoverflow.com/questions/tagged/r?sort = votes&pageSize
= 5")
questions <- page %>%
html_ _node("#questions")
爲了提取問題標題,咱們仔細查看第 1 個問題的 HTML 結構,如圖 14-12 所示。ide
圖 14-12
很容易便發現每一個問題的標題都包含在 <div class = "summary"><h3> 中:
questions %>%
html_ _nodes(".summary h3") %>%
html_ _text()
## [1] "How to make a great R reproducible example?"
## [2] "How to sort a dataframe by column(s)?"
## [3] "R Grouping functions: sapply vs. lapply vs. apply. vs. tapply vs.
by vs. aggregate"
## [4] "How to join (merge) data frames (inner, outer, left, right)?"
## [5] "How can we make xkcd style graphs?"
要注意的是, <a class = "question-hyperlink"> 還提供了一個更簡單的 CSS
選擇器,可以返回相同的結果:
questions %>%
html_ _nodes(".question-hyperlink") %>%
html_ _text()
若是對每一個問題的票數也感興趣,能夠返回去檢查投票狀況,並查看它們是如何
用 CSS 選擇器描述的,如圖 14-13 所示。函數
圖 14-13
幸運的是,全部的投票面板共享同一個結構,而且很是容易找到它們的模式。每一個問
題 被 包 含 在 一 個 question-summary 類 的 <div> 節 點 中 , 其 中 , 票 數 位
於 .vote-count-post 類的 <span> 節點中:
questions %>%
html_ _nodes(".question-summary .vote-count-post") %>%
html_ _text() %>%
as.integer()
## [1] 1429 746 622 533 471
相似地,下面這段代碼用於提取回答數量:
questions %>%
html_ _nodes(".question-summary .status strong") %>%
html_ _text() %>%
as.integer()
## [1] 21 15 8 11 7
可是,若想繼續提取每一個問題的標籤,就比較棘手了,由於不一樣問題的標籤數量可能不一樣。
在下面的代碼中,咱們先選擇全部問題的標籤集合,再經過迭代提取每一個集合中的標籤:
questions %>%
html_ _nodes(".question-summary .tags") %>%
lapply(function(node) {
node %>%
html_ _nodes(".post-tag") %>%
html_ _text()
}) %>%
str
## List of 5
## $ : chr [1:2] "r" "r-faq"
## $ : chr [1:4] "r" "sorting" "dataframe" "r-faq"
## $ : chr [1:4] "r" "sapply" "tapply" "r-faq"
## $ : chr [1:5] "r" "join" "merge" "dataframe" ...
## $ : chr [1:2] "r" "ggplot2"
上面全部的數據抓取過程都是在同一個網頁上進行的。試想一下,若是須要從多個網頁收
集數據,應該怎樣作呢?假設咱們訪問該頁面中的每一個問題(例如 http://stackoverflow.com/
q/5963269/2906900)。注意到,右上角有一個「信息盒」(信息框),我
們以列表形式提取每一個問題的「信息盒」,如圖 14-14 所示。
經過檢查,咱們發現 #qinfo 正是每一個問題頁面「信息盒」的
「鑰匙」(鍵)。而後,選擇全部問題的超連接,提取每一個問題的 URL
並進行迭代,讀取每一個問題頁面,再使用 #qinfo 從中提取信息框:
questions %>%
html_ _nodes(".question-hyperlink") %>%
html_ _attr("href") %>%
lapply(function(link) {
paste0("https://stackoverflow.com", link) %>%
read_ _html() %>%
html_ _node("#qinfo") %>%
html_ _table() %>%
setNames(c("item", "value"))
})
## [[1]]
## item value
## 1 asked 5 years ago
## 2 viewed 113698 times
## 3 active 7 days ago
##工具
圖 14-14
## [[2]]
## item value
## 1 asked 6 years ago
## 2 viewed 640899 times
## 3 active 2 months ago
##
## [[3]]
## item value
## 1 asked 5 years ago
## 2 viewed 221964 times
## 3 active 1 month ago
##
## [[4]]
## item value
## 1 asked 6 years ago
## 2 viewed 311376 times
## 3 active 15 days ago
##
## [[5]]
## item value
## 1 asked 3 years ago
## 2 viewed 53232 times
## 3 active 4 months ago
除了這些,rvest 擴展包還支持建立 HTTP 會話來模擬導航過程。想了解更多內容,請
閱讀 rvest 擴展包的幫助文檔。對於多個爬蟲任務,能夠使用 http://selectorgadget.com/ 提供
的工具簡化選擇器的尋找過程。
還有不少更高級的網頁爬蟲技術,例如使用 JavaScript 處理 AJAX 和動態網頁,可是
它們超出了本章的範圍。想學習更多關於 rvest 的用法,請閱讀 rvest 擴展包的幫助文檔。
值得一提的是,在網頁爬蟲方面,rvest 在很大程度上是受到 Python 的 Robobrowser
和 BeautifulSoup 的啓發。這些包更強大,甚至某些方面要比 rvest 更流行。若是源代
碼很複雜而且規模很大的話,可能最好仍是要學習使用 Python 的擴展包。讀者能夠訪
問 https://www. crummy.com/software/BeautifulSoup/ 獲取更多信息。post