async和enterproxy控制併發數量

聊聊併發與並行

併發咱們常常說起之,不論是web server,app併發無處不在,操做系統中,指一個時間段中幾個程序處於已經啓動運行到完畢之間,且這幾個程序都是在同一處理機上運行,而且任一個時間點只有一個程序在處理機上運行。不少網站都有併發鏈接數量的限制,因此當請求發送太快的時候會致使返回值爲空或報錯。更有甚者,有些網站可能由於你發出的併發鏈接數量過多而當你是在惡意請求,封掉你的ip。html

相對於併發,並行可能陌生了很多,並行指一組程序按獨立異步的速度執行,不等於時間上的重疊(同一個時刻發生),經過增長cpu核心來實現多個程序(任務)的同時進行。沒錯,並行作到了多任務的同時進行node

使用enterproxy控制併發數量

enterproxy是樸靈大大爲主要貢獻的工具,帶來一種事件式編程的思惟變化,利用事件機制解耦複雜業務邏輯,解決了回調函數耦合性的詬病,將串行等待變成並行等待,提高多異步協做場景下的執行效率git

咱們如何使用enterproxy控制併發數量?一般若是咱們不使用enterproxy和自制的計數器,咱們若是抓取三個源:github

這種深層嵌套,串行的方式web

var render = function (template, data) {
  _.template(template, data);
};
$.get("template", function (template) {
  // something
  $.get("data", function (data) {
    // something
    $.get("l10n", function (l10n) {
      // something
      render(template, data, l10n);
    });
  });
});

除去這種過去深層嵌套的方法,咱們常規的寫法的本身維護一個計數器npm

(function(){
    var count = 0;
    var result  = {};
    
    $.get('template',function(data){
        result.data1 = data;
        count++;
        handle();
    })
    $.get('data',function(data){
        result.data2 = data;
        count++;
        handle();
    })
    $.get('l10n',function(data){
        result.data3 = data;
        count++;
        handle();
    })

    function handle(){
        if(count === 3){
            var html = fuck(result.data1,result.data2,result.data3);
            render(html);
        }
    }
})();

在這裏,enterproxy就能夠起到這個計數器的做用,它幫你管理這些異步操做是否完成,完成以後,他會自動調用你提供的處理函數,並將抓取到數據當作參數傳遞過來編程

var ep = new enterproxy();
ep.all('data_event1','data_event2','data_event3',function(data1,data2,data3){
    var html = fuck(data1,data2,data3);
    render(html);
})

$.get('http:example1',function(data){
    ep.emit('data_event1',data);
})

$.get('http:example2',function(data){
    ep.emit('data_event2',data);
})

$.get('http:example3',function(data){
    ep.emit('data_event3',data);
})

enterproxy還提供了其餘很多場景所需的API,能夠自行學習下這個API enterproxyjson

使用async控制併發數量

假如咱們有40個請求須要發出,不少網站可能會由於你發出的併發鏈接數太多而當你是在惡意請求,把你的IP封掉。
因此咱們老是須要控制併發數量,而後慢慢抓取完這40個連接。數組

使用async中mapLimit控制一次性併發數量爲5,一次性只抓取5個連接。服務器

async.mapLimit(arr, 5, function (url, callback) {
      // something
    }, function (error, result) {
      console.log("result: ")
      console.log(result);
    })

咱們首先應該知道什麼是併發,爲何須要限制併發數量,都有哪些處理方案。而後就能夠去文檔具體看一下API如何使用。async文檔能夠很好的學習這些語法。

模擬一組數據,這裏返回的數據是假的,返回的延時是隨機的。

var concurreyCount = 0;
var fetchUrl = function(url,callback){
    // delay 的值在 2000 之內,是個隨機的整數 模擬延時
    var delay =  parseInt((Math.random()* 10000000) % 2000,10);
    concurreyCount++;
    console.log('如今併發數是 ' , concurreyCount , ' 正在抓取的是' , url , ' 耗時' + delay + '毫秒');
    setTimeout(function(){
        concurreyCount--;
        callback(null,url + ' html content');
    },delay);
}

var urls = [];
for(var i = 0;i<30;i++){
    urls.push('http://datasource_' + i)
}

而後咱們使用async.mapLimit來併發抓取,並獲取結果。

async.mapLimit(urls,5,function(url,callback){
    fetchUrl(url,callbcak);
},function(err,result){
    console.log('result: ');
    console.log(result);
})

模擬摘自alsotang

運行輸出後獲得如下結果

咱們發現,併發數從1開始增加,可是增加到5時,就不在增長。然有任務時就繼續抓取,併發鏈接數量始終控制在5個。

完成node簡易爬蟲系統

由於alsotang前輩的《node包教不包會》教程例子中使用的eventproxy控制的併發數量,咱們就來完成一個使用async控制併發數量的node簡易爬蟲。

爬取的目標就是本站首頁(手動護臉)

第一步,首先咱們須要用到如下的模塊:

  • url : 用於url解析,這裏用到url.resolve()生成一個合法的域名
  • async : 一個實用的模塊,提供了強大的功能和異步JavaScript工做
  • cheerio : 爲服務器特別定製的,快速,靈活,實施的jQuery核心實現
  • superagent : nodejs裏一個很是方便的客戶端請求代理模塊

    經過npm安裝依賴模塊

第二步,經過require引入依賴模塊,肯定爬取對象URL:

var url = require("url");
var async = require("async");
var cheerio = require("cheerio");
var superagent = require("superagent");

var baseUrl = 'http://www.chenqaq.com';

第三步:使用superagent請求目標URL,並使用cheerio處理baseUrl獲得目標內容url,並保存在數組arr中

superagent.get(baseUrl)
  .end(function (err, res) {
    if (err) {
      return console.error(err);
    }
    var arr = [];
    var $ = cheerio.load(res.text);
    // 下面和jQuery操做是同樣同樣的..
    $(".post-list .post-title-link").each(function (idx, element) {
      $element = $(element);
      var _url = url.resolve(baseUrl, $element.attr("href"));
      arr.push(_url);
    });

    // 驗證獲得的全部文章連接集合
    output(arr);
    // 第四步:接下來遍歷arr,解析每個頁面須要的信息

})

咱們須要一個函數驗證抓取的url對象,很簡單咱們只須要一個函數遍歷arr並打印出來就能夠:

function output(arr){
    for(var i = 0;i<arr.length;i++){
        console.log(arr[i]);
    }
}

第四步:咱們須要遍歷獲得的URL對象,解析每個頁面須要的信息。

這裏就須要用到async控制併發數量,若是你上一步獲取了一個龐大的arr數組,有多個url須要請求,若是同時發出多個請求,一些網站就可能會把你的行爲當作惡意請求而封掉你的ip

async.mapLimit(arr,3,function(url,callback){
    superagent.get(url)
        .end(function(err,mes){
            if(err){
                console.error(err);
                console.log('message info ' + JSON.stringify(mes));
            }
            console.log('「fetch」' + url + ' successful!');
            var $ = cheerio.load(mes.text);
            var jsonData = {
                title:$('.post-card-title').text().trim(),
                href: url,
            };
            callback(null,jsonData);
        },function(error,results){
            console.log('results ');
            console.log(results);
        })
    })

獲得上一步保存url地址的數組arr,限制最大併發數量爲3,而後用一個回調函數處理 「該回調函數比較特殊,在iteratee方法中必定要調用該回調函數,有三種方式」

  • callback(null) 調用成功
  • callback(null,data) 調用成功,而且返回數據data追加到results
  • callback(data) 調用失敗,不會再繼續循環,直接到最後的callback

好了,到這裏咱們的node簡易的小爬蟲就完成了,來看看效果吧

嗨呀,首頁數據好少,可是成功了呢。

參考資料

Node.js 包教不包會 - alsotang

enterproxy

async

async Documentation

相關文章
相關標籤/搜索