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