查看原文
其他

R语言爬虫系列6|动态数据抓取范例

louwill 机器学习实验室 2019-04-07


通过前面几期的推送,小编基本上已经将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语言是当今最值得学习的数据科学语言






一个数据科学狂热者的学习历程

长按二维码.关注数据科学家养成记





    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存