1. 程式人生 > >Node爬蟲之使用 async 控制併發

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 控制併發