我写了一个小爬虫,现在看起来不完美,但是很多地方处理不好。比如知乎点击一个问题,它所有的答案都没有加载。当您到达答案的末尾时,单击加载更多,答案将被再次加载。因此,如果您直接发送问题的请求链接,获得的页面将是不完整的。还有,我们通过发送链接下载图片的时候,是一张张下载的。如果图片太多,睡觉后还是会下载。此外,我们用nodejs编写的爬虫没有使用nodejs最强大的异步和并发特性,这是一种浪费。
思考
这个爬虫是上一个的升级版。不过最后一个虽然简单,但是非常适合初学者学习。这个爬虫代码可以在我的github as=nodepider上找到。
整个爬虫的思路是这样的:一开始我们通过请求问题的链接抓取一些页面数据,然后我们模拟ajax请求截取代码中剩余页面的数据。当然,并发也可以通过异步来实现。对于小规模异步过程控制,我们可以用这个模块=eventproxy,但是我在这里没用!我们通过对获得的页面进行分析,剪出所有图片的链接,然后通过异步并发的方式批量下载这些图片。
抓取页面的初始数据非常简单,这里就不解释了。
/*获取第一屏所有图片链接*/var getiniturllist=function(){ request . get(' https://www . zhi Hu.com/question/')。end (function (err,RES){ if(err){ console . log(err);} else { var $=cheerio . load(RES . text);var answerList=$('。zm-item-答案');answerList.map(函数(I,答案){var images=$(答案)。查找('。zm-item-rich-text img’);images.map(函数(I,image){photos.push($(image))。attr(' src ');});});Console.log('已成功爬网' photos.length' pictures '的链接);getiaxurllist();}});}模拟ajax请求以获取整个页面。
接下来,如何在点击加载更多时模拟ajax请求,去知乎看看吧!
有了这些信息,您可以模拟发送相同的请求来获取这些数据。
/*模拟每毫秒发送一次ajax请求。并获取请求结果中的所有图片链接*/var getiajxurllist=function(offset){ request . post(' https://www . zhi Hu.com/node/questaanswerlistv ')。设置(配置)。send(' method=next params=% B % URL _ token % % A % C % page size % % A % C % offset % % A ' offset ' % D _ xsrf=ADF deee ')。end(function(err,RES){ if(err){ console . log(err);} else { var response=JSON . parse(RES . text);/*如果要序列化json,提交json的话需要反序列化JSON */If(response . msgresponse . msg . length){ var $=chef . load(response . msg . join(' '));/*将所有数组元素拼接在一起,并用空格隔开。不要这样加入()。默认情况下,用逗号分隔数组元素*/var answerlist=$('。zm-item-答案');answerList.map(函数(I,答案){var images=$(答案)。查找('。zm-item-rich-text img’);images.map(函数(I,image){photos.push($(image))。attr(' src ');});});setTimeout(函数(){ offset=;Console.log('已成功爬网' photos.length' pictures '的链接);getIAjaxUrlList(偏移量);},);}else{console.log('所有图片链接都已获取,完全有' photos.length '图片链接');//console.log(照片);返回downloadImg();}}});}在代码中发布此请求https://www.zhihu.com/node/QuestionAnswerListV2,并复制原始请求头和请求参数。作为我们的请求头和请求参数,superagent的set方法可以用来设置请求头和发送请求参数。我们最初将request参数中的offset设置为20,每隔一段时间在offset上加20,然后重新发送请求,相当于每隔一段时间发送一个ajax请求,获取最新的20条数据。每次得到数据,我们都会对数据进行一定程度的处理,并将其转化为一整段html,方便后期的链接提取处理。通过异步控制下载图片并获取所有图片链接后,即确定response.msg为空时,我们将下载这些图片。不可能一个个下载,对吧?因为如你所见,我们有足够的图片。
是的,有2万多张照片,但幸运的是,nodejs有一个神奇的单线程异步功能,所以我们可以同时下载这些照片。但这一次问题来了。我听说如果同时发送太多请求,就会被网站屏蔽。是真的吗?/你不说。我不知道,我没有尝试过,因为我也不想尝试( ̄),所以我们需要在这个时候控制异步并发的数量。
这里使用了一个神奇的模块=async,不仅可以帮助我们取悦难以维护的回调金字塔的魔鬼,还可以帮助我们轻松管理异步进程。看看文档,因为我自己不太擅长,所以这里只使用了一个强大的async.mapLimit方法。真的很神奇。
var requestAndwrite=函数(url,回调){request.get(url)。end(function(err,RES){ if(err){ console . log(err);控制台日志('图片请求失败.');} else { var FIlename=path . basename(URL);fs.writeFile('。/img/' fileName,res.body,function(err){ if(err){ console . log(err);Console.log('无法写入图片.');}else{console.log('图片下载成功');回调(null,“成功!”);/*回调似乎必须调用,第二个参数将传递给下一个回调函数的结果,它是一个数组*/} });}});} var download img=function(async num){/*有一些图片链接地址不完整,没有“http:”头,所以帮助它们完整拼接*/for(var I=;iphotos.lengthi ){if(照片[i]。index of(' http ')===-){照片[i]='http: '照片[I];}}console.log('图片将异步并发下载,当前并发数为: ' async num);Async.maplimit(照片、asyncNum、函数(照片、回调){ console . log(‘async num’图片已进入下载队列));requestAndwrite(照片,回调);},函数(err,result){ if(err){ console . log(err);}else{//console.log(结果);=将输出一个包含超过10,000个“成功”字符串的array console.log('全部下载!').);}});};先看这里=
mapLimit方法的第一个参数photos是所有图片链接的数组,这也是我们并发请求的对象。asyncNum是为了限制并发请求的数量。如果没有此参数,将同时发送20,000多个请求。嗯,您的ip将被成功阻止,但是当我们有这个参数时,例如,它的值是10,它将只帮助我们一次从阵列中获取10个链接并执行并发请求。告诉孟母,同时发给100件物品。下载速度超快。如果你上去,你不会知道。请告诉我。
以上已经介绍了Nodejs爬虫高级教程中异步并发控制的相关知识,希望对大家有所帮助。