聊聊并發(fā)與并行
成都創(chuàng)新互聯(lián)公司服務(wù)項(xiàng)目包括下冶網(wǎng)站建設(shè)、下冶網(wǎng)站制作、下冶網(wǎng)頁制作以及下冶網(wǎng)絡(luò)營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,下冶網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到下冶省份的部分城市,未來相信會繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
并發(fā),在操作系統(tǒng)中,是指一個時間段中有幾個程序都處于已啟動運(yùn)行到運(yùn)行完畢之間,且這幾個程序都是在同一個處理機(jī)上運(yùn)行,但任一個時刻點(diǎn)上只有一個程序在處理機(jī)上運(yùn)行。
并發(fā)我們經(jīng)常提及之,不管是web server,app并發(fā)無處不在,操作系統(tǒng)中,指一個時間段中幾個程序處于已經(jīng)啟動運(yùn)行到完畢之間,且這幾個程序都是在同一處理機(jī)上運(yùn)行,并且任一個時間點(diǎn)只有一個程序在處理機(jī)上運(yùn)行。很多網(wǎng)站都有并發(fā)連接數(shù)量的限制,所以當(dāng)請求發(fā)送太快的時候會導(dǎo)致返回值為空或報(bào)錯。更有甚者,有些網(wǎng)站可能因?yàn)槟惆l(fā)出的并發(fā)連接數(shù)量過多而當(dāng)你是在惡意請求,封掉你的ip。
相對于并發(fā),并行可能陌生了不少,并行指一組程序按獨(dú)立異步的速度執(zhí)行,不等于時間上的重疊(同一個時刻發(fā)生),通過增加cpu核心來實(shí)現(xiàn)多個程序(任務(wù))的同時進(jìn)行。沒錯,并行做到了多任務(wù)的同時進(jìn)行
使用enterproxy控制并發(fā)數(shù)量
enterproxy是樸靈大大為主要貢獻(xiàn)的工具,帶來一種事件式編程的思維變化,利用事件機(jī)制解耦復(fù)雜業(yè)務(wù)邏輯,解決了回調(diào)函數(shù)耦合性的詬病,將串行等待變成并行等待,提升多異步協(xié)作場景下的執(zhí)行效率
我們?nèi)绾问褂胑nterproxy控制并發(fā)數(shù)量?通常如果我們不使用enterproxy和自制的計(jì)數(shù)器,我們?nèi)绻ト∪齻€源:
這種深層嵌套,串行的方式
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); }); }); });
除去這種過去深層嵌套的方法,我們常規(guī)的寫法的自己維護(hù)一個計(jì)數(shù)器
(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就可以起到這個計(jì)數(shù)器的作用,它幫你管理這些異步操作是否完成,完成之后,他會自動調(diào)用你提供的處理函數(shù),并將抓取到數(shù)據(jù)當(dāng)做參數(shù)傳遞過來
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,可以自行學(xué)習(xí)下這個API enterproxy
使用async控制并發(fā)數(shù)量
假如我們有40個請求需要發(fā)出,很多網(wǎng)站可能會因?yàn)槟惆l(fā)出的并發(fā)連接數(shù)太多而當(dāng)你是在惡意請求,把你的IP封掉。
所以我們總是需要控制并發(fā)數(shù)量,然后慢慢抓取完這40個鏈接。
使用async中mapLimit控制一次性并發(fā)數(shù)量為5,一次性只抓取5個鏈接。
async.mapLimit(arr, 5, function (url, callback) { // something }, function (error, result) { console.log("result: ") console.log(result); })
我們首先應(yīng)該知道什么是并發(fā),為什么需要限制并發(fā)數(shù)量,都有哪些處理方案。然后就可以去文檔具體看一下API如何使用。async文檔可以很好的學(xué)習(xí)這些語法。
模擬一組數(shù)據(jù),這里返回的數(shù)據(jù)是假的,返回的延時是隨機(jī)的。
var concurreyCount = 0; var fetchUrl = function(url,callback){ // delay 的值在 2000 以內(nèi),是個隨機(jī)的整數(shù) 模擬延時 var delay = parseInt((Math.random()* 10000000) % 2000,10); concurreyCount++; console.log('現(xiàn)在并發(fā)數(shù)是 ' , 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來并發(fā)抓取,并獲取結(jié)果。
async.mapLimit(urls,5,function(url,callback){ fetchUrl(url,callbcak); },function(err,result){ console.log('result: '); console.log(result); })
模擬摘自alsotang
運(yùn)行輸出后得到以下結(jié)果
我們發(fā)現(xiàn),并發(fā)數(shù)從1開始增長,但是增長到5時,就不在增加。然有任務(wù)時就繼續(xù)抓取,并發(fā)連接數(shù)量始終控制在5個。
完成node簡易爬蟲系統(tǒng)
因?yàn)閍lsotang前輩的《node包教不包會》教程例子中使用的eventproxy控制的并發(fā)數(shù)量,我們就來完成一個使用async控制并發(fā)數(shù)量的node簡易爬蟲。
爬取的目標(biāo)就是本站首頁(手動護(hù)臉)
第一步,首先我們需要用到以下的模塊:
通過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請求目標(biāo)URL,并使用cheerio處理baseUrl得到目標(biāo)內(nèi)容url,并保存在數(shù)組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); }); // 驗(yàn)證得到的所有文章鏈接集合 output(arr); // 第四步:接下來遍歷arr,解析每一個頁面需要的信息 })
我們需要一個函數(shù)驗(yàn)證抓取的url對象,很簡單我們只需要一個函數(shù)遍歷arr并打印出來就可以:
function output(arr){ for(var i = 0;i
第四步:我們需要遍歷得到的URL對象,解析每一個頁面需要的信息。
這里就需要用到async控制并發(fā)數(shù)量,如果你上一步獲取了一個龐大的arr數(shù)組,有多個url需要請求,如果同時發(fā)出多個請求,一些網(wǎng)站就可能會把你的行為當(dāng)做惡意請求而封掉你的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地址的數(shù)組arr,限制最大并發(fā)數(shù)量為3,然后用一個回調(diào)函數(shù)處理 「該回調(diào)函數(shù)比較特殊,在iteratee方法中一定要調(diào)用該回調(diào)函數(shù),有三種方式」
callback(null)
調(diào)用成功callback(null,data)
調(diào)用成功,并且返回?cái)?shù)據(jù)data追加到resultscallback(data)
調(diào)用失敗,不會再繼續(xù)循環(huán),直接到最后的callback好了,到這里我們的node簡易的小爬蟲就完成了,來看看效果吧
嗨呀,首頁數(shù)據(jù)好少,但是成功了呢。
參考資料
Node.js 包教不包會 - alsotang
enterproxy
async
async Documentation
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對創(chuàng)新互聯(lián)的支持。