Node非同步爬蟲引出的非同步流程控制的一些問題
前記:
想寫一個電影天堂的爬蟲,因為node很長時間落下,就想用node去寫一下。結果遇到了一些列的問題,這些問題歸根到底就是非同步流程控制的問題,在以前就一直會接觸到很多回調地獄,Promise為什麼會出現諸如此類的話題,現在終於是深刻體會到了!
開始的程式碼是:
const cheerio = require('cheerio'); const http = require('http'); const iconv = require('iconv-lite'); let baseUrl = "http://www.ygdy8.net/html/gndy/dyzz/list_23_"; let Host = "http://www.ygdy8.net/"; let titleHref = []; const totalPage = 1; //指定爬多少頁資料 let res = []; //獲取頁面電影資料 function getTitleHref(url,page) { let startUrl = url+page+".html"; http.get(startUrl,function(res) { let chunks = []; res.on('data',function(chunk){ chunks.push(chunk); }); res.on('end',function(){ let title = []; let html = iconv.decode(Buffer.concat(chunks),'gb2312'); let $ = cheerio.load(html, {decodeEntities: false}); // console.log($); $('.co_content8 .ulink').each(function(i,d) { let $d = $(d); titleHref.push({ href: $d.attr('href') }); }); console.log(titleHref); }); if(page <= totalPage) { getTitleHref(url,++page); }else { console.log(page); getLink(titleHref); } }); } //獲取種子連結 function getLink(titleHref) { console.log('進入getLink'); titleHref.forEach(function(v,k) { console.log('~~~~~~~~~~~~~~~~~~~~'); let infoUrl = Host + v.href; console.log(infoUrl); // try { http.get(infoUrl,function(res) { console.log('進入getlink http'); let chunks = []; res.on('data',function(chunk) { chunks.push(chunk); }); res.on('end', function(){ let html = iconv.decode(Buffer.concat(chunks),'gb2312'); let $ = cheerio.load(html, {decodeEntities: false}); let reg = /.*譯 名/; let info = ''; let bt = ''; let textInfo = $('.co_content8 #Zoom p').eq(0).text(); info = textInfo.match(reg)[0]; bt = $('#Zoom td').children('a').attr('href'); res.push({ Info:info, Bt:bt }); console.log(res); }) //怎麼捕獲錯誤!!! //res.on('error',function(){ // console.log('error'); //}) }) // }catch(e) { // console.log(e); // } }); }; getTitleHref(baseUrl,1)
所以寫node程式碼切記大多數都是非同步的,上面程式碼就出了一個問題:
當前程式碼就不能保證下面的程式碼, 在 res.end 後執行,因為res.end在非同步佇列裡可能沒執行完,就進入了下面的if,就算最後進入getLink後就會出現titleHref.forEach進不去的情況的,因為titleHref是空的。
當時遇到這個問題如果不考慮到非同步流程控制的解決流程的話,一個解決方案是在each函式裡,獲取到一個titleHref就getLink下,titileHref定義成區域性函式,getLink函式放在each裡面,這樣就保證titleHref不會是空的了。然後程式碼如下:
const cheerio = require('cheerio'); const http = require('http'); const iconv = require('iconv-lite'); let baseUrl = "http://www.ygdy8.net/html/gndy/dyzz/list_23_"; let Host = "http://www.ygdy8.net/"; const totalPage = 2; //指定爬多少頁資料 let ans = []; //獲取頁面電影資料 function getTitleHref(url,page) { let startUrl = url+page+".html"; http.get(startUrl,function(res) { const { statusCode } = res; let chunks = []; res.on('data',function(chunk){ chunks.push(chunk); }); res.on('end',function(){ let title = []; let html = iconv.decode(Buffer.concat(chunks),'gb2312'); let $ = cheerio.load(html, {decodeEntities: false}); // console.log($); $('.co_content8 .ulink').each(function(i,d) { let $d = $(d); let titleHref = []; titleHref.push({ href: $d.attr('href') }); getLink(titleHref); }); // console.log(ans); }); }); } // /* //獲取種子連結 function getLink(titleHref) { console.log('進入getLink'); console.log(titleHref); if(titleHref) { titleHref.forEach(function(v,k) { console.log('~~~~~~~~~~~~~~~~~~~~'); let infoUrl = Host + v.href; // console.log(infoUrl); http.get(infoUrl,function(res) { const { statusCode } = res; const contentType = res.headers['content-type']; let error; if (statusCode !== 200) { error = new Error('請求失敗。\n' + `狀態碼: ${statusCode}`); } if (error) { console.error(error.message); // 消耗響應資料以釋放記憶體 res.resume(); return; } console.log('進入getlink http'); let chunks = []; res.on('data',function(chunk) { chunks.push(chunk); }); res.on('end', function(){ try { let html = iconv.decode(Buffer.concat(chunks),'gb2312'); let $ = cheerio.load(html, {decodeEntities: false}); let bt = ''; bt = $('#Zoom td').children('a').attr('href'); // console.log(bt); // console.log(typeof bt) ans.push(bt); // cb(ans); }catch (e) { console.error('bt',e.message); } }) }).on('error', (e) => { console.error(`錯誤: ${e.message}`); }); }); } }; // */ for(let i = 1; i <= totalPage; i++) { getTitleHref(baseUrl,i); console.log(ans); };
但是這樣的程式碼你還會發現一個問題,我們最後儲存的bt連結的ans結果,列印的還是空的,同樣是非同步的問題,我們如果要存入資料庫或者需要ans資料的話,我們不知道何時返回了這個資料。
所以最終我們還是要用到ES6/7提出的方案Promise和async/await。
修改之後程式碼如下:
const cheerio = require('cheerio') const http = require('http') const iconv = require('iconv-lite') const baseUrl = 'http://www.ygdy8.net/html/gndy/dyzz/list_23_' const Host = 'http://www.ygdy8.net/' const totalPage = 2 //指定爬多少頁資料 let ans = [] //獲取頁面電影資料 function getTitleHref(url, page) { return new Promise((resolve, reject) => { let startUrl = url + page + '.html' http.get(startUrl, function(res) { const { statusCode } = res let chunks = [] res.on('data', function(chunk) { chunks.push(chunk) }) res.on('end', function() { let title = [] let html = iconv.decode(Buffer.concat(chunks), 'gb2312') let $ = cheerio.load(html, { decodeEntities: false }) let titleHref = [] $('.co_content8 .ulink').each(function(i, d) { let $d = $(d) titleHref.push({ href: $d.attr('href') }) }) resolve(getLink(titleHref)) }) }) }) } // /* //獲取種子連結 function getLink(titleHref, cb) { console.log('進入getLink') console.log(titleHref) if (titleHref) { return Promise.all( titleHref.map(function(v, k) { return new Promise((resolve, reject) => { console.log('~~~~~~~~~~~~~~~~~~~~') let infoUrl = Host + v.href http .get(infoUrl, function(res) { const { statusCode } = res const contentType = res.headers['content-type'] let error if (statusCode !== 200) { error = new Error('請求失敗。\n' + `狀態碼: ${statusCode}`) } if (error) { console.error(error.message) // 消耗響應資料以釋放記憶體 res.resume() return } let chunks = [] res.on('data', function(chunk) { chunks.push(chunk) }) res.on('end', function() { try { let html = iconv.decode(Buffer.concat(chunks), 'gb2312') let $ = cheerio.load(html, { decodeEntities: false }) let bt = '' bt = $('#Zoom td') .children('a') .attr('href') resolve(bt) } catch (e) { reject(e) } }) }) .on('error', e => { reject(e) }) }) }) ) } else { return Promise.resolve() } } async function main() { // */ let results = await Promise.all( new Array(totalPage).fill().map((_, i) => getTitleHref(baseUrl, i + 1)) ) ans = ans.concat(...results) console.log('get data:', ans) } main()
每個函式都封裝成Promise,最後在主函式中用await強制同步得到最後的結果results。(注意:1。new Array出來的是稀疏陣列empty,最後fill()一下填充成undefine,2。47行傳遞的已經不是一個只有一條資料的陣列了,而是將一個頁面each執行完成後的彙總,所以在函式內部會有Promise.all
3。93行則聊勝於無,即使return null也會正確的觸發resolve的,這麼寫只是提高一些可讀性罷了。)
這次告訴我實踐很重要!要把所學和書中所看運用到業務和程式碼邏輯中!
相關推薦
Node非同步爬蟲引出的非同步流程控制的一些問題
前記: 想寫一個電影天堂的爬蟲,因為node很長時間落下,就想用node去寫一下。結果遇到了一些列的問題,這些問題歸根到底就是非同步流程控制的問題,在以前就一直會接觸到很多回調地獄,Promise為什麼會出現諸如此類的話題,現在終於是深刻體會到了! 開始的程式碼是: const cheerio = requi
node js 非同步執行流程控制模組Async介紹
sync是一個流程控制工具包,提供了直接而強大的非同步功能。基於Javascript為Node.js設計,同時也可以直接在瀏覽器中使用。 Async提供了大約20個函式,包括常用的 map, reduce, filter, forEach 等,非同步流程控制模式包括,序列(series),並行(para
Node.js系列--非同步流程控制
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span><span style="font-
js非同步操作的流程控制——20181114
序列執行(程式碼可直接在console控制檯執行) var items = [ 1, 2, 3, 4, 5, 6 ]; var results = []; function async(arg, callback) { console.log('引數為 ' + arg +' , 1秒後返回
JavaScript非同步流程控制全攻略
一.js非同步流程的由來 眾所周知,Javascript語言的執行環境是單執行緒(single thread),作為瀏覽器指令碼語言,JavaScript的主要用途是與使用者互動,以及操作DOM。若以多執行緒的方式操作這些DOM,則可能出現操作
Nodejs:非同步流程控制(下)
//非同步操作,序列有關聯(瀑布流模式) //npm install async --save-dev var async = require('async'); function exec() { async.waterfall( [ fun
Nodejs:非同步流程控制(上)
function oneFun() { // setTimeout(function () { // console.log("a"); // }, 1000); i = 0; setInterval(function () {
JS的四種非同步方式:回撥/監聽/流程控制庫/promise
你可能知道,Javascript語言的執行環境是”單執行緒”(single thread)。 所謂”單執行緒”,就是指一次只能完成一件任務。如果有多個任務,就必須排隊,前面一個任務完成,再執行後面一個任務,以此類推。 這種模式的好處是實現起來比較簡單,執行環
一道promise的小題目(Promise非同步流程控制)
用Promise控制非同步流程,三個非同步任務,時間可能有先後,但是要按照想要的順序輸出。 我這裡用四種方法解決,其實也就是考察你對Promise的理解,基礎題了。 //實現mergePromise函式,把傳進去的陣列順序先後執行, //並且把返回的資料先後放到陣列data中 const timeout =
async 非同步流程控制
async是一個強大的非同步流程控制庫,其語義類似於js對陣列的操作。它提供了一系列非常強大而便捷的方法,有助於我們在javascript單執行緒模型背景下寫出優雅的邏輯控制程式碼。 牛刀小試 先從檔案操作開始初步瞭解async函式庫的作用:
Node.js 使用回調函數實現串行流程控制
exc pre 流程 示例 clas ons con 函數 span 下面是一個使用Node.js回調函數實現串行流程控制的示例: setTimeout(function() { console.log(‘I excute first.‘); setTim
Node.js 使用Nimble實現串行流程控制
串行 nod clas exc ast div span 流程控制 logs Nimble是Node.js下的流程控制工具。 使用如下命令進行安裝: npm install nimble 測試代碼: var flow = require(‘nimble‘); flo
[js高手之路]Node.js模板引擎教程-jade速學與實戰2-流程控制,轉義與非轉義
title 學習 != 下一步 cas ase spa back name 一、轉義與非轉義 jade模板文件代碼: 1 doctype html 2 html 3 head 4 meta(charset=‘utf-8‘)
node.js對mongodb的連接&增刪改查(附async同步流程控制)
color var literal int lba node () n! node.js 1.啟動mongodb數據庫 官網下載mongodb數據庫 在mongodb根目錄下創建文件夾:假設取名為test。 我們認為test就是mongodb新建的數據庫一枚。 創建批處理文
Node爬蟲之使用 async 控制併發
上文 《Node爬蟲之使用 eventproxy 控制併發》我們說到用 node 爬了csdn首頁資料,但是這些請求完全是併發的,如果某些網站有 “反爬” 機制,就很有可能封鎖你的 IP。這樣的情況下我們就可以使用 async 模組。主要用到的是 async 的 mapLimit(arr,
Node爬蟲之使用 eventproxy 控制併發
上一篇文章《Node實現簡單爬蟲》我們介紹瞭如何使用 superagent和cheerio來取主頁內容,那隻需要發起一次 http get 請求就能辦到。但這次,我們需要取出每個主題的第一條評論,這就要求我們對每個主題的連結發起請求,並用 cheerio 去取出其中的第一條評論。 eve
node單執行緒非同步模型
提到nodejs都知道單執行緒非同步I/O,但是能說清楚為什麼單執行緒非同步I/O,為什麼能增加網路吞吐量,怎麼充分利用cpu資源嗯,這個知道的就不多了。 首先要說的是I/O,I/O是計算機的抽象概念,指的是鍵盤,滑鼠,印表機,套接字等和記憶體之間的資料交換,I/O的速度是很慢的,知道計算機存貯模型的都知道
node.js對mongodb的連線&增刪改查(附async同步流程控制)
最近嘗試了node.js和mongodb的使用。下面來一波步驟。 1.啟動mongodb資料庫 官網下載mongodb資料庫 在mongodb根目錄下建立資料夾:假設取名為test。 我們認為test就是mongodb新建的資料庫一枚。 建立批處理
python多執行緒、非同步、多程序+非同步爬蟲
安裝Tornado 非同步用到了tornado,根據官方文件的例子修改得到一個簡單的非同步爬蟲類。可以參考下最新的文件學習下。 pip install tornado 非同步爬蟲 import time from datetime import ti
利用aiohttp製作非同步爬蟲!
簡介 asyncio可以實現單執行緒併發IO操作,是Python中常用的非同步處理模組。關於asyncio模組的介紹,筆者會在後續的文章中加以介紹,本文將會講述一個基於asyncio實現的HTTP框架——aiohttp,它可以幫助我們非同步地實現HTTP請求,從而使得我們的程式效率大大提高。 本文將