首頁  >  文章  >  web前端  >  Nodejs爬蟲進階教學之非同步並發控制_node.js

Nodejs爬蟲進階教學之非同步並發控制_node.js

WBOY
WBOY原創
2016-05-16 15:15:381848瀏覽

之前寫了個現在看來很不完美的小爬蟲,很多地方沒有處理好,比如說在知乎點開一個問題的時候,它的所有回答並不是全部加載好了的,當你拉到回答的尾部時,點擊加載更多,回答才會再加載一部分,所以說如果直接發送一個問題的請求鏈接,取得的頁面是不完整的。還有我們透過發送連結下載圖片的時候,是一張一張來下的,如果圖片數量太多的話,真的是下到你睡完覺它還在下,而且我們用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&params=%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.length;i++){
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爬蟲進階教學之異步並發控制的相關知識,希望對大家有所幫助。

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn