zoukankan      html  css  js  c++  java
  • Nodejs爬虫进阶=>异步并发控制

    之前写了个现在看来很不完美的小爬虫,很多地方没有处理好,比如说在知乎点开一个问题的时候,它的所有回答并不是全部加载好了的,当你拉到回答的尾部时,点击加载更多,回答才会再加载一部分,所以说如果直接发送一个问题的请求链接,取得的页面是不完整的。还有就是我们通过访问链接下载图片的时候,是一张一张来下的,如果图片数量太多的话,真的是会下到你睡完觉它还在下。

    这次的的爬虫是上次那个的升级版,爬虫代码在我的github上可以找到=>NodeSpider。

    整个爬虫的思路是这样的:在一开始我们通过请求问题的链接抓取到部分页面数据,接下来我们在代码中模拟ajax请求截取剩余页面的数据,当然在这里也是可以通过异步来实现并发的,对于小规模的异步流程控制,可以用这个模块=>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&params=%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拥有神奇的单线程异步特性,我们可以同时对这些图片进行下载。但这个时候问题来了,听说同时发送请求太多的话会被网站封ip的啊!所有我们肯定不能同时并发下载这两万多张图片,这个时候就需要对异步并发数量进行一些控制了。

    在这里用到了一个神奇的模块=>async,它不仅能帮我们拜托难以维护的回调金字塔恶魔,还能轻松的帮我们进行异步流程的管理。具体看文档啦,这里就只用到了一个强大的async.mapLimit方法。

     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<photos.length;i++){
    25         if(photos[i].indexOf("http")===-1){
    26             photos[i]="http:"+photos[i];
    27         }
    28     }
    29     console.log("即将异步并发下载图片,当前并发数为:"+asyncNum);
    30     async.mapLimit(photos,asyncNum,function(photo,callback){
    31         console.log("已有"+asyncNum+"张图片进入下载队列");
    32         requestAndwrite(photo,callback);
    33     },function(err,result){
    34         if(err){
    35             console.log(err);
    36         }else{
    37             // console.log(result);<=会输出一个有2万多个“successful”字符串的数组
    38             console.log("全部已下载完毕!");
    39         }
    40     });
    41 
    42 };

    先看这里=>

    mapLimit方法的第一个参数photos是所有图片链接的数组,也是我们并发请求的对象,asyncNum是限制并发请求的数量。当我们有这个参数时,比如它的值是10,则它一次就只会帮我们从数组中取10条链接,执行并发的请求,这10条请求都得到响应后,再发送下10条请求。

    结尾

    哦哦~,明天就是除夕了~

  • 相关阅读:
    每天一篇经济学人 2020-09-15 分享在 特朗普的“疫苗政治”:科学与政治之争 | 经济学人
    english notes
    new word
    gilbert strang
    news etc
    对自己的要求
    JDBC添加数据
    题目-1031-字符串反转
    题目-1002-字符串分类统计
    ERROR:格式化hdfs后,datanode只启动了一个
  • 原文地址:https://www.cnblogs.com/tianheila/p/5183733.html
Copyright © 2011-2022 走看看