Nodejs爬蟲進階=>異步併發控制

以前寫了個如今看來很不完美的小爬蟲,不少地方沒有處理好,好比說在知乎點開一個問題的時候,它的全部回答並非所有加載好了的,當你拉到回答的尾部時,點擊加載更多,回答纔會再加載一部分,因此說若是直接發送一個問題的請求連接,取得的頁面是不完整的。還有就是咱們經過訪問連接下載圖片的時候,是一張一張來下的,若是圖片數量太多的話,真的是會下到你睡完覺它還在下。html

此次的的爬蟲是上次那個的升級版,爬蟲代碼在個人github上能夠找到=>NodeSpider。node

整個爬蟲的思路是這樣的:在一開始咱們經過請求問題的連接抓取到部分頁面數據,接下來咱們在代碼中模擬ajax請求截取剩餘頁面的數據,固然在這裏也是能夠經過異步來實現併發的,對於小規模的異步流程控制,能夠用這個模塊=>eventproxy,但這裏我就沒有用啦!咱們經過分析獲取到的頁面從中截取出全部圖片的連接,再經過異步併發來實現對這些圖片的批量下載。git

抓取頁面初始的數據很簡單啊,這裏就不作多解釋了github

 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

接下來就是怎麼去模擬點擊加載更多時發出的ajax請求了,去知乎看一下吧!json

 

有了這些信息,就能夠來模擬發送相同的請求來得到這些數據啦。數組

 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 };

先看這裏=>async

mapLimit方法的第一個參數photos是全部圖片連接的數組,也是咱們併發請求的對象,asyncNum是限制併發請求的數量。當咱們有這個參數時,好比它的值是10,則它一次就只會幫咱們從數組中取10條連接,執行併發的請求,這10條請求都獲得響應後,再發送下10條請求。

結尾

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

相關文章
相關標籤/搜索