如何提高爬虫爬取的速度

JavaScript020

如何提高爬虫爬取的速度,第1张

1.尽可能减少网站访问次数

单次爬虫的主要把时间消耗在网络请求等待响应上面,所以能减少网站访问就减少网站访问,既减少自身的工作量,也减轻网站的压力,还降低被封的风险。 第一步要做的就是流程优化,尽量精简流程,避免在多个页面重复获取。

随后去重,同样是十分重要的手段,一般根据url或者id进行唯一性判别,爬过的就不再继续爬了。

2.分布式爬虫

即便把各种法子都用尽了,单机单位时间内能爬的网页数仍是有限的,面对大量的网页页面队列,可计算的时间仍是很长,这种情况下就必须要用机器换时间了,这就是分布式爬虫。

第一步,分布式并不是爬虫的本质,也并不是必须的,对于互相独立、不存在通信的任务就可手动对任务分割,随后在多个机器上各自执行,减少每台机器的工作量,费时就会成倍减少。

例如有200W个网页页面待爬,可以用5台机器各自爬互不重复的40W个网页页面,相对来说单机费时就缩短了5倍。

可是如果存在着需要通信的状况,例如一个变动的待爬队列,每爬一次这个队列就会发生变化,即便分割任务也就有交叉重复,因为各个机器在程序运行时的待爬队列都不一样了——这种情况下只能用分布式,一个Master存储队列,其他多个Slave各自来取,这样共享一个队列,取的情况下互斥也不会重复爬取。IPIDEA提供高匿稳定的IP同时更注重用户隐私的保护,保障用户的信息安全。含有240+国家地区的ip,支持API批量使用,支持多线程高并发使用。

var superagent = require('superagent')

var k = 0

var n = 200

for (var i = 1i <ni++) {

superagent.get("www.douban.com")

.end(function (err, res) {

console.log(res.statusCode)

console.log(k++)

})

}

参考:

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

思路

这次的的爬虫是上次那个的升级版,不过呢,上次那个虽然是简单,但是很适合新手学习啊。这次的爬虫代码在我的github上可以找到=>NodeSpider。

整个爬虫的思路是这样的:在一开始我们通过请求问题的链接抓取到部分页面数据,接下来我们在代码中模拟ajax请求截取剩余页面的数据,当然在这里也是可以通过异步来实现并发的,对于小规模的异步流程控制,可以用这个模块=>eventproxy,但这里我就没有用啦!我们通过分析获取到的页面从中截取出所有图片的链接,再通过异步并发来实现对这些图片的批量下载。

抓取页面初始的数据很简单啊,这里就不做多解释啦

/*获取首屏所有图片链接*/ var getInitUrlList=function(){ request.get("https://www.zhihu.com/question/") .end(function(err,res){ if(err){ console.log(err) }else{ var $=cheerio.load(res.text) var answerList=$(".zm-item-answer") answerList.map(function(i,answer){ var images=$(answer).find('.zm-item-rich-text img') images.map(function(i,image){ photos.push($(image).attr("src")) }) }) console.log("已成功抓取"+photos.length+"张图片的链接") getIAjaxUrlList() } }) }

模拟ajax请求获取完整页面

接下来就是怎么去模拟点击加载更多时发出的ajax请求了,去知乎看一下吧!

有了这些信息,就可以来模拟发送相同的请求来获得这些数据啦。

/*每隔毫秒模拟发送ajax请求,并获取请求结果中所有的图片链接*/ var getIAjaxUrlList=function(offset){ request.post("https://www.zhihu.com/node/QuestionAnswerListV") .set(config) .send("method=next¶ms=%B%url_token%%A%C%pagesize%%A%C%offset%%A" +offset+ "%D&_xsrf=adfdeee") .end(function(err,res){ if(err){ console.log(err) }else{ var response=JSON.parse(res.text)/*想用json的话对json序列化即可,提交json的话需要对json进行反序列化*/ if(response.msg&&response.msg.length){ var $=cheerio.load(response.msg.join(""))/*把所有的数组元素拼接在一起,以空白符分隔,不要这样join(),它会默认数组元素以逗号分隔*/ var answerList=$(".zm-item-answer") answerList.map(function(i,answer){ var images=$(answer).find('.zm-item-rich-text img') images.map(function(i,image){ photos.push($(image).attr("src")) }) }) setTimeout(function(){ offset+= console.log("已成功抓取"+photos.length+"张图片的链接") getIAjaxUrlList(offset) },) }else{ console.log("图片链接全部获取完毕,一共有"+photos.length+"条图片链接") // console.log(photos) return downloadImg() } } }) }

在代码中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方法。真的很厉害哦。

var requestAndwrite=function(url,callback){ request.get(url).end(function(err,res){ if(err){ console.log(err) console.log("有一张图片请求失败啦...") }else{ var fileName=path.basename(url) fs.writeFile("./img/"+fileName,res.body,function(err){ if(err){ console.log(err) console.log("有一张图片写入失败啦...") }else{ console.log("图片下载成功啦") callback(null,"successful !") /*callback貌似必须调用,第二个参数将传给下一个回调函数的result,result是一个数组*/ } }) } }) } var downloadImg=function(asyncNum){ /*有一些图片链接地址不完整没有“http:”头部,帮它们拼接完整*/ for(var i=i<photos.lengthi++){ if(photos[i].indexof("http")="==-){" photos[i]="http:" +photos[i]="" }="" console.log("即将异步并发下载图片,当前并发数为:"+asyncnum)="" async.maplimit(photos,asyncnum,function(photo,callback){="" console.log("已有"+asyncnum+"张图片进入下载队列")="" requestandwrite(photo,callback)="" },function(err,result){="" if(err){="" console.log(err)="" }else{="" console.log(result)<="会输出一个有万多个“successful”字符串的数组" console.log("全部已下载完毕!")="" })="" }="" 先看这里=>

mapLimit方法的第一个参数photos是所有图片链接的数组,也是我们并发请求的对象,asyncNum是限制并发请求的数量,如果没有这个参数的话,将会有同时两万多条请求发送过去,嗯,你的ip就会被成功的封掉,但当我们有这个参数时,比如它的值是10,则它一次就只会帮我们从数组中取10条链接,执行并发的请求,这10条请求都得到响应后,再发送下10条请求。告诉泥萌,并发到同时100条没有事的,下载速度超级快,再往上就不知道咯,你们来告诉我...

以上所述给大家介绍了Nodejs爬虫进阶教程之异步并发控制的相关知识,希望对大家有所帮助。