左手用R右手Python系列之——表格數據抓取之道
在抓取數據時,很大一部分需求是抓取網頁上的關係型表格。
對於表格而言,R語言和Python中都封裝了表格抓取的快捷函數,R語言中XML包中的readHTMLTables函數封裝了提取HTML內嵌表格的功能,rvest包的read_table()函數也可以提供快捷表格提取需求。Python中read_html同樣提供直接從HTML中抽取關係表格的功能。
HTML語法中內嵌表格有兩類,一類是table,這種是通常意義上所說的表格,另一類是list,這種可以理解為列表,但從瀏覽器渲染後的網頁來看,很難區分這兩種,因為效果上幾乎沒有差異,但是通過開發者工具的後台代碼界面,table和list是兩種截然不同的HTML元素。
以上所說到的函數是針對HTML文檔中不同標籤設計的,所以說如果不加區分的使用這些函數提取表格,很可能對於那些你認為是表格,但是是實際上是list的內容無效。
library("RCurl")library("XML")library("magrittr")library("rvest")
針對XML包而言,一共有三個HTML元素提取的快捷函數,分別是針對HTML表格元素,列表元素,和鏈接元素,這些快捷函數都是:
readHTMLTable() #獲取網頁表格readHTMLList() #獲取網頁列表getHTMLlinks() #從HTML網頁獲取鏈接
readHTMLTable
readHTMLTable(doc,header=TRUE)#the HTML document which can be a file name or a URL or an #already parsed HTMLInternalDocument, or an HTML node of class #XMLInternalElementNode, or a character vector containing the HTML #content to parse and process.
該函數支持的HTML文檔格式非常廣泛,doc可以是一個url鏈接,可以是一個本地html文檔,可以是一個已經解析過的HTMLInternalDocument部件,或者提取出來的HTML節點,甚至包含HTML語法元素的字元串向量。
以下是一個案例,也是我自學爬蟲時爬過的網頁,後來可能有改版,很多小夥伴兒用那些代碼爬不出來,問我咋回事兒。自己試了以下也不行,今天藉機重新梳理思路。
大連市2016年空氣質量數據可視化~
URL<-"https://www.aqistudy.cn/historydata/monthdata.php?city=北京" %>% xml2::url_escape(reserved ="][!$&"()*+,;=:/?@#")####關於網址轉碼,如果你不想使用函數進行編碼轉換,可以通過在線轉碼平台轉碼後賦值黏貼使用,但是這不是一個好習慣,在封裝程序代碼時無法自動化。#http://tool.oschina.net/encode?type=4#R語言自帶的轉碼函數URLencode()轉碼與瀏覽器轉碼結果不一致,所以我找了很多資料,在xml2包里找打了rvest包的url轉碼函數,稍微做了修改,現在這個函數你可以放心使用了!(注意裡面的保留字)###mydata<-readHTMLTable(URL,header=TRUE)#Warning message:#XML content does not seem to be XML: #"https://www.aqistudy.cn/historydata/monthdata.php?city=%E5%8C%97%E4%BA%AC" header<-c("User-Agent"="Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36")mytable<-getURL(URL,httpheader=header,.encoding="UTF-8") %>% htmlParse(encoding ="UTF-8") %>% readHTMLTable(header=TRUE)
結果竟然是空的,我猜測這個網頁一定是近期做過改版,裡面加入了一些數據隱藏措施,這樣除了瀏覽器初始化解析可以看到數據表之外,瀏覽器後台的network請求鏈接里都看不到具體數據。
這樣既沒有API鏈接,又無法請求道完整網頁怎麼辦呢?別怕,我們不是還有Selenium大法,不行我們就暴力抓取呀!
本次使用Rselenium包,結合plantomjs瀏覽器來抓取網頁。(關於配置可以直接百度,此類帖子很多,主要是版本對應,相應路徑加入環境變數)。
###啟動selenium服務:cd D:java -jar selenium-server-standalone-3.3.1.jar###以上代碼在PowerShell中運行,啟動selenium伺服器。#創建一個remoteDriver對象,並打開library("RSelenium")remDr <- remoteDriver(browserName = "phantomjs")remDr$open() #訪問登錄的頁面remDr$navigate("https://www.aqistudy.cn/historydata/monthdata.php?city=%E5%8C%97%E4%BA%AC") mytable<-remDr$getPageSource()[[1]] %>% htmlParse(encoding ="UTF-8") %>% readHTMLTable(header=TRUE,which =1)mytable<-remDr$getPageSource()[[1]] %>% read_html(encoding ="UTF-8") %>% html_table(header=TRUE) %>% `[[`(1)#關閉remoteDriver對象remDr$close()
以上兩者是等價的,我們獲取了一模一樣的表格數據,數據預覽如下:
DT::datatable(mytable)
readHTMLTable函數和rvest函數中的html_table都可以讀取HTML文檔中的內嵌表格,他們是很好的高級封裝解析器,但是並不代表它們可以無所不能。
畢竟巧婦難為無米之炊,首先需要拿米才能下鍋,所以我們在讀取表格的時候,最好的方式是先利用請求庫請求(RCurl或者httr),請求回來的HTML文檔再使用readHTMLTable函數或者html_table函數進行表格提取,否則將無功而反,遇到今天這種情況的,明明瀏覽器渲染後可以看到完整表格,然後後台抓取沒有內容,不提供API訪問,也拿不到完整的html文檔,就應該想到是有什麼數據隱藏的設置。
沒關係見招拆招嘛,既然瀏覽器能夠解析,那我就驅動瀏覽器獲取解析後的HTML文檔,返回解析後的HTML文檔,之後的工作就是使用這些高級函數提取內嵌表格了。
那麼selenium伺服器+plantomjs無頭瀏覽器幫我們做了什麼事呢,其實只做了一件事——幫我們做了一個真實的瀏覽器請求,這個請求是由plantomjs無頭瀏覽器完成的,它幫我們把經過渲染後的完整HTML文檔傳送過來,這樣我們就可以使用readHTMLTable函數或者read_table()
在XML包中,還有另外兩個非常好用的高階封裝函數:
一個用於抓取鏈接,一個用於抓取列表。
readHTMLList
getHTMLLinkshttp://www.tianqi.com/air/
我隨便找了一個天氣網首頁,有全國各大城市的空氣指數數據。這個看似是一個表格,實際不一定,我們可以使用現有表格函數試一試。
url<-"http://www.tianqi.com/air/"mylist <-getURL(url,httpheader=header,.encoding="UTF-8") %>% htmlParse(encoding ="gbk") %>% readHTMLTable(header=TRUE)mylist < url %>% read_html(encoding ="gbk") %>% html_table(header=TRUE) %>% `[[`(1)NULL
使用以上代碼抓內容是空的,原因有兩種情況,一種是html裡面標籤根本不是table格式,有可能是list,另外一種情況可能跟上例一樣,表格數據被隱藏。看一下源碼就知道這個版塊其實是list無序列表存儲的,所以使用readtable肯定行不通,這時候就是readHTMLList函數大顯身手的時候了。
header<-c( "User-Agent"="Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36" )mylist <-getURL(url,httpheader=header,.encoding="windows-1253") %>% htmlParse() %>% readHTMLList() %>% `[[`(4) %>% .[2:length(.)]mylist <-read_html(url,encoding="UTF-8") %>% html_nodes(".thead li") %>% html_text() %>% `[[`(4) %>% .[2:length(.)]mylist <-read_html(url,encoding="UTF-8") %>% htmlParse() %>% readHTMLList() %>% `[[`(4)
雖然成功的獲取到了結果,但是遇到了令人厭惡的編碼問題,不想跟各種編碼鬥智斗勇,再次使用了phantomjs無頭瀏覽器,畢竟作為瀏覽器總是可以正確的解析並渲染網頁內容,無論HTML文檔的編碼聲明有多麼糟糕!
#cd D:#java -jar selenium-server-standalone-3.3.1.jar#創建一個remoteDriver對象,並打開library("RSelenium")remDr <- remoteDriver(browserName = "phantomjs")remDr$open() #訪問登錄的頁面remDr$navigate("http://www.tianqi.com/air/") mylist<-remDr$getPageSource()[[1]] %>% htmlParse(encoding="utf-8") %>% readHTMLList() %>% `[[`(8) %>% .[2:length(.)]#關閉remoteDriver對象remDr$close()
這次終於看到了希望,果然plantomjs瀏覽器的渲染效果非同一般!
使用str_extract()函數提取城市id、城市名稱、城市污染物指數、污染狀況。
library("stringr")pattern<-"(\d{1,})([\u4e00-\u9fa5]{1,})"mylist<-data.frame(ID = mylist %>% str_extract_all(pattern) %>% do.call(rbind,.) %>% .[,1] %>% str_extract("\d{1,}"),City = mylist %>% str_extract_all(pattern) %>% do.call(rbind,.) %>% .[,1] %>% str_extract("[\u4e00-\u9fa5]{1,}"),AQI = mylist %>% str_extract_all(pattern) %>% do.call(rbind,.) %>% .[,2] %>% str_extract("\d{1,}"),Quity= mylist %>% str_extract_all(pattern) %>% do.call(rbind,.) %>% .[,2] %>% str_extract("[\u4e00-\u9fa5]{1,}"))DT::datatable(mylist)
最後一個函數便是抓取網址鏈接的高級封裝函數,因為在html中,網址的tag一般都比較固定,跳轉的網址鏈接一般在<a>標籤的href屬性中,圖片鏈接一般在<img>標籤下的src屬性內,比較好定位。
隨便找一個知乎的攝影帖子,高清圖多的那種!
url<-"https://www.zhihu.com/question/35017762"mylink <-getURL(url,httpheader=header,.encoding="utf-8") %>% htmlParse() %>% getHTMLLinks() [1] "/" "/" "/explore" [4] "/topic" "/topic/19551388" "/topic/19555444" [7] "/topic/19559348" "/topic/19569883" "/topic/19626553" [10] "/people/geng-da-shan-ren" "/people/geng-da-shan-ren" "/question/35017762/answer/240404907"[13] "/people/he-xiao-pang-zi-30" "/people/he-xiao-pang-zi-30" "/question/35017762/answer/209942092"
getHTMLLinks(doc, externalOnly = TRUE, xpQuery = 「//a/@href」,baseURL = docName(doc), relative = FALSE)
通過getHTMLLinks的源碼可以看到,該函數過濾的鏈接的條件僅僅是標籤下的href屬性內的鏈接,我們可以通過修改xpQuery內的apath表達式參數來獲取圖片鏈接。
mylink <-getURL(url,httpheader=header,.encoding="utf-8") %>% htmlParse() %>% getHTMLLinks(xpQuery = "//img/@data-original")
這樣輕而易舉的就拿到了該知乎攝影帖子的所有高清圖片原地址,效率也高了很多。
Python:
python中如果不用爬蟲工具,目前我所知道的表格提取工具就是pandas中的read_html函數了,他相當於一個I/O函數(同其他的read_csv,read_table,read_xlsx等函數一樣)。同樣適用以上R語言中第一個案例的天氣數據,直接利用pd.read_html函數也無法獲取表格數據,原因相同,html文檔中有數據隱藏設定。
import pandas as pdurl="https://www.aqistudy.cn/historydata/monthdata.php?city=%E5%8C%97%E4%BA%AC"dfs = pd.read_html(url)
這裡我們同樣使用Python中的selenium+plantomjs工具來請求網頁,獲取完整的源文檔之後,使用pd.read_html函數進行提取。
from selenium import webdriverdriver = webdriver.PhantomJS()driver.get("https://www.aqistudy.cn/historydata/monthdata.php?city=%E5%8C%97%E4%BA%AC")dfs = pd.read_html(driver.page_source,header=0)[0]driver.quit()
OK,簡直不能再完美,對於網頁表格數據而言,pd.read_html函數是一個及其高效封裝,但是前提是你要確定這個網頁中的數據確實是table格式,並且網頁沒有做任何的隱藏措施。
在線課程請點擊文末原文鏈接:
Hellobi Live | 9月12日 R語言可視化在商務場景中的應用
往期案例數據請移步本人GitHub:https://github.com/ljtyduyu/DataWarehouse/tree/master/File推薦閱讀:
※用Python-Markdown和google-prettify來處理Markdown和代碼高亮
※Python · 神經網路(三)· 網路
※《機器學習實戰》學習總結(五)——Logistic回歸
※R & Python-機器學習演算法速查表
※【小林的OpenCV基礎課 4】滑動條什麼的