nodejs抓取动态网页(异步特性()(2)_知乎点开(图))
优采云 发布时间: 2021-10-21 07:02nodejs抓取动态网页(异步特性()(2)_知乎点开(图))
写了个小爬虫,貌似很不完善。很多地方都没有处理。例如,当在 知乎 中打开一个问题时,并不是所有的答案都被加载。当你拉到答案的结尾时,点击加载更多来加载一部分答案,所以如果你直接发送一个问题的请求链接,你得到的页面是不完整的。还有就是我们通过访问链接下载图片的时候,是一张一张的下载的。如果图片太多,真的会下载到你睡着了。
这个爬虫是上一个的升级版。爬虫代码可以在我的github=>NodeSpider上找到。
整个爬虫的思路是这样的:一开始我们通过请求问题的链接来抓取部分页面数据,然后我们在代码中模拟ajax请求拦截剩余页面的数据,当然,这里也可以通过异步来实现并发。对于小规模的异步过程控制,可以使用这个module=>eventproxy,但是我这里没有用!我们从获取到的页面中截取所有图片的链接,然后通过异步并发实现这些图片的批量下载。
抓取页面的初始数据很简单,这里就不解释了。
1 /*获取首屏所有图片链接*/
2 var getInitUrlList=function(){
3 request.get("https://www.zhihu.com/question/34937418")
4 .end(function(err,res){
5 if(err){
6 console.log(err);
7 }else{
8 var $=cheerio.load(res.text);
9 var answerList=$(".zm-item-answer");
10 answerList.map(function(i,answer){
11 var images=$(answer).find('.zm-item-rich-text img');
12 images.map(function(i,image){
13 photos.push($(image).attr("src"));
14 });
15 });
16 console.log("已成功抓取"+photos.length+"张图片的链接");
17 getIAjaxUrlList(20);
18 }
19 });
20 }
模拟ajax请求获取完整页面
下一步是如何模拟点击加载更多时发送的ajax请求。去知乎看看吧!
有了这些信息,您可以模拟发送相同的请求来获取数据。
1 /*每隔100毫秒模拟发送ajax请求,并获取请求结果中所有的图片链接*/
2 var getIAjaxUrlList=function(offset){
3 request.post("https://www.zhihu.com/node/QuestionAnswerListV2")
4 .set(config)
5 .send("method=next¶ms=%7B%22url_token%22%3A34937418%2C%22pagesize%22%3A20%2C%22offset%22%3A" +offset+ "%7D&_xsrf=98360a2df02783902146dee374772e51")
6 .end(function(err,res){
7 if(err){
8 console.log(err);
9 }else{
10 var response=JSON.parse(res.text);/*想用json的话对json序列化即可,提交json的话需要对json进行反序列化*/
11 if(response.msg&&response.msg.length){
12 var $=cheerio.load(response.msg.join(""));/*把所有的数组元素拼接在一起,以空白符分隔,不要这样join(),它会默认数组元素以逗号分隔*/
13 var answerList=$(".zm-item-answer");
14 answerList.map(function(i,answer){
15 var images=$(answer).find('.zm-item-rich-text img');
16 images.map(function(i,image){
17 photos.push($(image).attr("src"));
18 });
19 });
20 setTimeout(function(){
21 offset+=20;
22 console.log("已成功抓取"+photos.length+"张图片的链接");
23 getIAjaxUrlList(offset);
24 },100);
25 }else{
26 console.log("图片链接全部获取完毕,一共有"+photos.length+"条图片链接");
27 // console.log(photos);
28 return downloadImg(50);
29 }
30 }
31 });
32 }
在代码中post这条请求https://www.zhihu.com/node/QuestionAnswerListV2,把原请求头和请求参数复制下来,作为我们的请求头和请求参数,superagent的set方法可用来设置请求头,send方法可以用来发送请求参数。我们把请求参数中的offset初始为20,每隔一定时间offset再加20,再重新发送请求,这样就相当于我们每隔一定时间发送了一条ajax请求,获取到最新的20条数据,每获取到了数据,我们再对这些数据进行一定的处理,让它们变成一整段的html,便于后面的提取链接处理。
异步并发控制下载图片
再获取完了所有的图片链接之后,即判定response.msg为空时,我们就要对这些图片进行下载了,不可能一条一条下对不对,因为如你所看到的,我们的图片足足有
没错,2万多,还好nodejs有神奇的单线程异步功能,我们可以同时下载这些图片。但是这个时候问题就来了。听说如果同时发送太多请求,会被网站拦截!所以我们绝对不能同时下载超过 20,000 张图片。这时候就需要控制异步并发的数量。
这里使用了一个神奇的模块=> async,它不仅可以帮助我们询问难以维护的回调金字塔恶魔,还可以轻松帮助我们管理异步进程。具体看文档,这里只使用了一个强大的 async.mapLimit 方法。
<p> 1 var requestAndwrite=function(url,callback){
2 request.get(url).end(function(err,res){
3 if(err){
4 console.log(err);
5 console.log("有一张图片请求失败啦...");
6 }else{
7 var fileName=path.basename(url);
8 fs.writeFile("./img1/"+fileName,res.body,function(err){
9 if(err){
10 console.log(err);
11 console.log("有一张图片写入失败啦...");
12 }else{
13 console.log("图片下载成功啦");
14 callback(null,"successful !");
15 /*callback貌似必须调用,第二个参数将传给下一个回调函数的result,result是一个数组*/
16 }
17 });
18 }
19 });
20 }
21
22 var downloadImg=function(asyncNum){
23 /*有一些图片链接地址不完整没有“http:”头部,帮它们拼接完整*/
24 for(var i=0;i