Node爬蟲之使用 async 控制併發
上文 《Node爬蟲之使用 eventproxy 控制併發》我們說到用 node 爬了csdn首頁資料,但是這些請求完全是併發的,如果某些網站有 “反爬” 機制,就很有可能封鎖你的 IP。這樣的情況下我們就可以使用 async 模組。主要用到的是 async 的 mapLimit(arr, limit, iterator, callback)
介面。
async.mapLimit(arr, 5, function (url, callback) { fetchUrl(url, callback); }, function (err, result) { res.send(result); });
第一個引數 arr為陣列,儲存了需要爬取頁面的 url,第二個引數 limit 表示併發爬取數量。第三個引數是迭代函式(每個 url 需要執行這個函式),其第一個引數 url,是 urls 陣列的每個 item,第二個引數 callback 與 mapLimit 方法第四個引數有關,callback 會往 result 引數裡存放資料。如何理解?callback 是第三個引數 iterator 的回撥,以爬蟲為例,爬完頁面肯定會分析一些資料,然後儲存,執行 callback 函式就能把結果儲存在 result(第四個引數函式中的引數) 中。
首先,我們採集 需要爬取頁面的 url,儲存在 urls 陣列中:
$('.clearfix .list_con .title h2 a').each(function(idx, element) {
var $element = $(element);
// console.log('$element',$element);
var href = url.resolve(csdn, $element.attr('href'));
urls.push(href);
});
接著我們用 async 進行非同步爬取:
async.mapLimit(urls, 5, function(url, callback) { fetchUrl(url, callback); },function(err, result) { console.log('final: ', result); response.send(result); })
通過fetchUrl函式來抓起頁面內容:
var fetchUrl = function(url, callback) {
superagent.get(url)
.end(function(err, res) {
if(err) {
console.log(err);
}
console.log('fetch ' + url + ' successful!');
var $ = cheerio.load(res.text);
var jsonData = {
title: $('.title-article').text().trim(),
href: url,
comment1: $('.comment').eq(0).text().trim()
};
console.log(JSON.stringify(jsonData));
callback(null, jsonData);
})
}
這裡要注意的是 callback 中的第二個引數,其實 jsonData 已經儲存在了 mapLimit 方法第四個引數的 result 引數中。
完整程式碼:
var express = require('express');
var superagent = require('superagent');
var cheerio = require('cheerio');
var async = require('async');
var url = require('url');
var csdn = 'https://blog.csdn.net/';
var app = express();
app.get('/', function(request, response, next) {
superagent.get(csdn)
.end(function(err, res) {
if(err) {
return console.error(err);
}
var urls = [];
var $ = cheerio.load(res.text);
$('.clearfix .list_con .title h2 a').each(function(idx, element) {
var $element = $(element);
// console.log('$element',$element);
var href = url.resolve(csdn, $element.attr('href'));
urls.push(href);
});
console.log('urls', urls);
var concurrencyCount = 0;
var fetchUrl = function(url, callback) {
superagent.get(url)
.end(function(err, res) {
if(err) {
console.log(err);
}
console.log('fetch ' + url + ' successful!');
var $ = cheerio.load(res.text);
var jsonData = {
title: $('.title-article').text().trim(),
href: url,
comment1: $('.comment').eq(0).text().trim()
};
console.log(JSON.stringify(jsonData));
callback(null, jsonData);
})
}
async.mapLimit(urls, 5, function(url, callback) {
fetchUrl(url, callback);
},function(err, result) {
console.log('final: ', result);
response.send(result);
})
})
})
app.listen(3002, function() {
console.log('app is listenling at port 3002');
})
結果:
還有個問題是,什麼時候用 eventproxy,什麼時候使用 async 呢?它們不都是用來做非同步流程控制的嗎?
當你需要去多個源(一般是小於 10 個)彙總資料的時候,用 eventproxy方便;當你需要用到佇列,需要控制併發數,或者你喜歡函數語言程式設計思維時,使用 async。大部分場景是前者。
參考:使用 async 控制併發