聊聊並發與並行
並發我們經常提及之,不管是web server,app並發無處不在,操作系統中,指一個時間段中幾個程序處於已經啟動運行到完畢之間,且這幾個程序都是在同一處理機上運行,並且任一個時間點只有一個程序在處理機上運行。很多網站都有並發連接數量的限制,所以當請求發送太快的時候會導致返回值為空或報錯。更有甚者,有些網站可能因為你發出的並發連接數量過多而當你是在惡意請求,封掉你的ip。
相對於並發,並行可能陌生了不少,並行指一組程序按獨立異步的速度執行,不等於時間上的重疊(同一個時刻發生),通過增加cpu核心來實現多個程序(任務)的同時進行。沒錯,並行做到了多任務的同時進行
使用enterproxy控制並發數量
enterproxy是朴靈大大為主要貢獻的工具,帶來一種事件式編程的思維變化,利用事件機制解耦復雜業務邏輯,解決了回調函數耦合性的詬病,將串行等待變成並行等待,提升多異步協作場景下的執行效率
我們如何使用enterproxy控制並發數量?通常如果我們不使用enterproxy和自制的計數器,我們如果抓取三個源:
這種深層嵌套,串行的方式
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);
});
});
});
除去這種過去深層嵌套的方法,我們常規的寫法的自己維護一個計數器
(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 enterproxy
使用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追加到resultscallback(data)
調用失敗,不會再繼續循環,直接到最后的callback
好了,到這里我們的node簡易的小爬蟲就完成了,來看看效果吧
嗨呀,首頁數據好少,但是成功了呢。