通過前面幾期的推送,小編基本上已經將R語言爬蟲所需要的基本知識介紹完了。R雖然是以一門統計分析工具出現在大多數人印象中的,但其畢竟本質上是一門程式語言,對於爬蟲的支持雖不如Python那樣多快好省,但悉心研究一下總能做出一些讓你驚喜的效果。
大約很早之前,小編就寫過關於R語言爬蟲新貴rvest的抓取介紹,之前說rvest+SelectGadgetor是結構化網頁抓取的實戰利器,大家的溢美之詞不斷。詳情可見推文:
R語言爬蟲利器:rvest包+SelectorGadget抓取鏈家杭州二手房數據
但網絡爬蟲這個江湖太險惡,單靠一招rvest行走江湖必然兇多吉少,一不小心碰到什麼AJAX和動態網頁憑僅掌握rvest的各位必定束手無策。本文小編就簡單介紹下在用R語言進行實際的網絡數據抓取時如何將動態數據給弄到手。
所謂動態網頁和異步加載,在之前的系列4的時候小編已通過AJAX介紹過了,簡單而言就是我明明在網頁中看到了這個數據,但到後臺HTML中卻找不到了,這通常就是XHR在作祟。這時候我們就不要看原始的HTML數據了,需要進行二次請求,通過web開發者工具找到真實請求的url。下面小編就以兩個網頁為例,分別通過GET和POST請求拿到動態網頁數據,全過程主要使用httr包來實現,httr包可謂是RCurl包的精簡版,說其短小精悍也不為過。httr包與RCurl包在主要函數的區別如下所示:
GET請求抓取微信好友列表數據
很早之前圈子裡就看到過用Python抓取自己微信好友數據的案例分享,今天便以微信網頁版為例,探一探其網頁結構。首先登錄個人微信網頁版,右鍵打開web開發者工具,下來一大堆請求:
簡單找一下發現網頁中的微信好友列表信息並沒有呈現在HTML 中,大概可以斷定微信好友數據是通過動態加載來顯示的,所以直接定位到XHR中,經過幾番嘗試,結合右側的preview,我們會發現大量整齊劃一的數據,所以二次請求的url真的就是它了:
找到真正的url之後,接下來就是獲取請求信息了,切換到Headers版塊,Header版塊下的4個子信息我們都需要關注,它們是我們構造爬蟲請求頭的關鍵。
從Header中我們可以看到該信息是通過GET方法請求得到的,General信息下的Request URL,Request Method, Status Code; Response Headers信息下的Connection, Content-Type; Request Headers信息下的Accept, Cookie, Referer, User-Agent以及最後的Query String Parameters都是我們需要重點關注的。
找到相應的信息之後,我們便可以直接利用httr包在R中構建爬蟲請求:
#傳入微信cookie信息
Cookie <- 「我的微信cookie」
#構造請求頭
headers <- c('Accept'='application/json',
'Content-Type'='text/plain',
'User-Agent'='Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537. 36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X Met aSr 1.0',
'Referer'='https://wx.qq.com/',
'Connection'='keep-alive',
'cookie'=Cookie
)
二次請求實際的url:
#實際請求的url
url<-"https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?r=1507597918348&seq=0&skey=@crypt_ee7cd3e3_70091604da65a07600cfdca47b81cfaf"
GET方法單次執行請求:
#執行請求
louwill <- GET(url,add_headers(.headers =headers))
響應結果如下:
-> GET /cgi-bin/mmwebwx-bin/webwxgetcontact?r=1507597918348&seq=0&skey=@crypt_ee7cd3e3_70091604da65a07600cfdca47b81cfaf HTTP/1.1
-> Host: wx.qq.com
-> Accept-Encoding: gzip, deflate
-> Accept: application/json
-> Content-Type: text/plain
-> User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0
-> Referer: https://wx.qq.com/
-> Connection: keep-alive
-> cookie: 我的微信cookie
->
<- HTTP/1.1 200 OK
<- Connection: keep-alive
<- Content-Type: text/plain
<- Content-Encoding: gzip
<- Content-Length: 90977
<-
響應狀態碼為200,okay。
從響應中提取原始字符內容:
content(louwill)
[1] "{\n\"BaseResponse\": {\n\"Ret\": 0,\n\"ErrMsg\": \"\"\n}\n,\n\"MemberCount\": 658,\n\"MemberList\": [{\n\"Uin\": 0,\n\"UserName\": \"weixin\",\n\"NickName\": \"微信團隊\",\n\"HeadImgUrl\": \"/cgi-bin/mmwebwx-bin/webwxgeticon?seq=570002&username=weixin&skey=@crypt_ee7cd3e3_70091604da65a07600cfdca47b81cfaf\",\n\"ContactFlag\": 1,\n\"MemberCount\": 0,\n\"MemberList\": [],\n\"RemarkName\": \"\",\n\"HideInputBarFlag\": 0,\n\"Sex\": 0,\n\"Signature\": \"微信團隊官方帳號\",\n\"VerifyFlag\": 56,\n\"OwnerUin\": 0,\n\"PYInitial\": \"WXTD\",\n\"PYQuanPin\": \"weixintuandui\",\n\"RemarkPYInitial\": \"\",\n\"RemarkPYQuanPin\": \"\",\n\"StarFriend\": 0,\n\"AppAccountFlag\": 0,\n\"Statues\": 0,\n\"AttrStatus\": 4,\n\"Province\": \"\",\n\"City\": \"\",\n\"Alias\": \"\",\n\"SnsFlag\": 0,\n\"UniFriend\": 0,\n\"DisplayName\": \"\",\n\"ChatRoomId\": 0,\n\"KeyWord\": \"wei\",\n\"EncryChatRoomId\": \"\",\n\"IsOwner\": 0\n}\n,{\n\"Uin\": 0,\n\"UserName\": \"@34c5cc09db0a616522f7ccc7309b1d29\",\n\"NickName\": \"微信支付... <truncated>
從結果中可以看出,微信好友列表的信息就被抓取下來了,數據信息非常雜亂,需要進一步清洗整理,小編這裡重在展示GET請求獲取動態網頁數據(主要是懶)就不往下整理啦。
POST請求抓取網易雲課堂數據
雖說動態網站數據請求也有GET方法的,但小編發現POST方法才是動態網頁的主要的請求方式。受杜老師小魔方文章啟發,小編也試一下這個網頁上的效果。登錄網易雲課堂帳號,右鍵開發者工具,直接定位到XHR,查找課程數據屬於哪個url。通過嘗試和preview,可以發現課程信息都被封裝在一個studycourse.json的文件中:
跟GET請求方法一樣,切換到Header版塊後繼續關注General等四個子信息,但POST請求下我們需要注意的一點是:POST請求下沒有像GET請求一樣的Query String Parameters,而是由Request Payload來構造請求頭表單參數,這一點和GET方法大不相同。總而言之,在動態網頁的HTTP請求中,如果是GET請求,請求頭表單參數以name=value&name1=value1的形式直接附在url後面,如果是POST請求,請求頭表單參數以相同的形式放在構造的表單體中,所以對於網易雲課堂的數據請求在R中構造如下:
#構造請求頭
#這裡小編沒有登錄帳號,cookie就不要了
headers <- c('Accept'='application/json',
'Content-Type'='application/json',
'User-Agent'='ozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0',
'edu-script-token'= '37aa682d1473455c8a77e6a4476e8f9e',
'Referer'='http://study.163.com/courses',
'Connection'='keep-alive'
)
#POST請求需要構造請求頭表單參數
payload<-list(
'pageIndex'=1,
'pageSize'=50,
'relativeOffset'=0,
'frontCategoryId'=-1
)
二次請求實際的url:
url <- "http://study.163.com/p/search/studycourse.json"
POST方法單次執行請求:
louwill2<-POST(url,add_headers(.headers =headers),body =payload, encode="json")
結果如下:
-> POST /p/search/studycourse.json HTTP/1.1
-> Host: study.163.com
-> Accept-Encoding: gzip, deflate
-> Cookie: EDUWEBDEVICE=5d0eadccd2314c4d8bc6e758b8b23d4e; NTESSTUDYSI=d3d36984547a43d6924334ee6a184a08
-> Accept: application/json
-> Content-Type: application/json
-> User-Agent: ozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0
-> edu-script-token: 83de95a25f5d45eb84bfeff8ec334e15
-> Referer: http://study.163.com/courses
-> Connection: keep-alive
-> cookie: 網易雲課堂cookie
-> Content-Length: 69
->
>> {"pageIndex":1,"pageSize":50,"relativeOffset":0,"frontCategoryId":-1}
<- HTTP/1.1 200 OK
<- Server: nginx
<- Date: Tue, 10 Oct 2017 08:29:51 GMT
<- Content-Type: application/json;charset=UTF-8
<- Transfer-Encoding: chunked
<- Connection: keep-alive
<- Vary: Accept-Encoding
<- Server-Host: hzayq-study-platform7
<- Content-Encoding: gzip
<-
Response [http://study.163.com/p/search/studycourse.json]
Date: 2017-10-10 08:29
Status: 200
Content-Type: application/json;charset=UTF-8
Size: 71 kB
請求狀態碼200,也okay。
從響應中提取原始字符內容:
head(content(louwill2))
$result$list[[39]]
$result$list[[39]]$productId
[1] 1002971001
$result$list[[39]]$courseId
[1] 1002971001
$result$list[[39]]$productName
[1] "英語知識點解析及小學單詞帶讀"
$result$list[[39]]$productType
[1] 0
$result$list[[39]]$startTime
[1] -1
$result$list[[39]]$endTime
[1] 9.223372e+18
$result$list[[39]]$description
[1] "通過幾分鐘的微課片段,精講中小學的英語知識點,讓學生通過比較學習,把這些知識點編織成有系統的知識網。"
$result$list[[39]]$provider
[1] "中小學英語語法王"
跟前面一樣,後續的數據處理與清洗小編就懶得弄啦。POST方法與GET方法略有區別,就是需要構造請求頭表單參數。R語言針對動態網頁抓取,使用RCurl/httr包,認真分析網頁結構,一般都能搞定。
參考資料:
Automated Data Collection with R
R語言自動數據採集
R語言爬蟲實戰——網易雲課堂數據分析課程板塊數據爬取
往期精彩:
R語言爬蟲系列4|AJAX與動態網頁介紹
R語言爬蟲系列3|HTTP協議
R語言爬蟲系列2|XML&XPath表達式與R爬蟲應用
R語言爬蟲系列1|HTML基礎與R語言解析
如何寫出整潔規範的R代碼?是時候討論一下代碼規範性了
【機器學習】決策樹總結|ID3 C4.5/C5.0 CHAID CART與QUEST
R語言向量化運算:apply函數族用法心得
Python面向對象編程:數據封裝、繼承和多態
[譯]為什麼R語言是當今最值得學習的數據科學語言
一個數據科學狂熱者的學習歷程