基于Nodejs爬蟲簡單對比Callback、Promise與Async

愛好三維立體圖多年疫赎,近期打算將網(wǎng)絡(luò)上能找到的資源收集一下盛撑。本著“偷懶至上”的原則,寫一簡單爬蟲腳本解放手指捧搞。作為前端狗抵卫,不敢忘本職工作。于是一式三份胎撇,分別用Callback介粘,PromiseAsync實(shí)現(xiàn)一遍,權(quán)當(dāng)學(xué)習(xí)ES6/7了晚树。源碼戳我

callback形式

  • 目標(biāo)網(wǎng)站:http://www.3wtu.com/
  • 流程簡述:
    圖片url分別存儲在http://www.3wtu.com/picture/${i}.html (9 < i <183)這些網(wǎng)頁姻采。首先遍歷這些網(wǎng)址,分別執(zhí)行獲取圖片url => 獲取圖片數(shù)據(jù) => 保存至本地爵憎。
  • 不相關(guān)技術(shù)點(diǎn):編碼轉(zhuǎn)換慨亲。

1. 取圖片鏈接

首先我們封裝一個(gè)單次請求的方法。由于我們的目標(biāo)網(wǎng)站使用的gb2312的編碼纲堵,因此我引入iconv模塊用來解碼巡雨。注意,不可用chunk += chunk 取代chunks.push(chunk)席函,前者隱含了操作chunk += chunk.toString('utf8')铐望。 詳見github.com/ashtuchkin/iconv-lite
了解Nodejs的朋友應(yīng)該對cheerio模塊不會陌生茂附,它相當(dāng)于一個(gè)服務(wù)端的JQuery正蛙。

const http = require("http")
const fs = require("fs")
const cheerio = require("cheerio")
const iconv = require('iconv-lite')

var domian = 'http://www.3wtu.com'
var config = {
    dirPath: __dirname + '/' + 'imagesByNormal/',  // 圖片存儲目錄
    interval: 300,  // 單次請求的時(shí)間間隔
}

function getPicsUrl(url, callback) {
    http.get(url, function(res) {
        var chunks = []

        res.on("data" ,function(chunk) {
            chunks.push(chunk)
        })

        res.on("end",function() {
            // 轉(zhuǎn)編碼后的html
            var decodedBody = iconv.decode(Buffer.concat(chunks), 'gb2312')

            // 服務(wù)端版本的JQuery
            var $ = cheerio.load(decodedBody, { decodeEntities: false })

            // 圖片的絕對地址
            var pic = domian + $('.detailed-pic img').attr('src')

            // 圖片名字
            var name = $('.detailed-title h4').html()

            callback({ url: pic, name: name })
        })

    })
}

2. 請求圖片數(shù)據(jù)

拿到圖片的鏈接之后,我們就需要請求圖片的數(shù)據(jù)营曼。

function getPicData(pic, callback) {

    // 文件類型后綴名
    var fileType = pic.url.split('.').pop()

    // 命名時(shí)帶上3位時(shí)間戳乒验,降低重名的概率
    var diff = new Date().getTime().toString().substring(10)

    // 圖片路徑與名字
    var name = config.dirPath + pic.name + '#' + diff + '.' + fileType

    // 請求圖片數(shù)據(jù)
    http.get(pic.url, function(res) {
        var data = ''
        res.setEncoding('binary')

        res.on('data', function(chunk) {
            data += chunk
        })

        res.on('end', function() {
            callback(data, name)
        })
    })
}

3. File System 下載圖片至本地

最后把圖片存儲到我們的本地。

function download(data, name, callback) {
    fs.writeFile(name, data, 'binary', callback)
}

4.啟動(dòng)

以上三步就是針對目標(biāo)網(wǎng)站將一張圖片爬下來的全部過程蒂阱。
現(xiàn)在我們只要啟動(dòng)遍歷所有的目標(biāo)網(wǎng)站即可

for (var i = 10; i < 183; i++) {
    (function (index) {
        var interval = (index - 10) * config.interval + Math.random() * 100
        var url = 'http://www.3wtu.com/picture/' + index + '.html'

        setTimeout(function () {  // 等待锻全,防止請求太快
            getPicsUrl(url, function(picLink) {  // 網(wǎng)頁 => 圖片url
                getPicData(picLink, function (picData) {  // 圖片url => 圖片數(shù)據(jù)
                    download(picData.data, picData.name, function (err) {  // 圖片數(shù)據(jù) => 本地圖片
                        if (err) {
                            console.log(err)
                        } else {
                            console.log(picData.name + ' downloaded successfully')
                        }
                    }) 
                })
            })
        }, interval)

    })(i)
}

以上代碼重點(diǎn)看setTimeout狂塘,getPicsUrlgetPicData鳄厌,download四連回調(diào)荞胡。這樣寫代碼是不是特別地不舒服呢?如果再多幾個(gè)嵌套回調(diào)了嚎,代碼的可讀性就會非常差泪漂。感受到了痛點(diǎn),才能更好的理解“我們需要一些新東西取解決痛點(diǎn)”歪泳。而新東西就是指PromiseAsync萝勤。

Promise與Async

本文主要目的在于結(jié)合實(shí)例闡述PromiseAsync對開發(fā)效率及體驗(yàn)的友好度。如果一點(diǎn)都不了解Promise的朋友呐伞,可以先看看阮一峰老師的ES6標(biāo)準(zhǔn)入門敌卓。
另外,這里將Promise和Async放在一起荸哟,就是希望大家不要把兩者對立起來假哎。
Promise本身是用于封裝異步操作,同時(shí)提供了流程控制的API鞍历。而Async 函數(shù)只是對異步操作的流程控制舵抹,比Promise更加的直觀和簡潔,進(jìn)一步提高了代碼可讀性劣砍。如果讀到這句話一點(diǎn)概念也沒有惧蛹,可以先戳這里Generator,都說AsyncGenerator的語法糖刑枝,學(xué)習(xí)Async之前還是需要對Generator有一定了解的香嗓。(其實(shí)我覺得語法糖這個(gè)說法不太好,Class那種東西才是純粹的語法糖好么装畅?)


  • 目標(biāo)網(wǎng)站:http://www.360doc.com/content/13/0905/08/11561215_312316659.shtml
  • 流程簡述:
    首先從目標(biāo)網(wǎng)站中獲取所有的圖片url靠娱,遍歷url數(shù)組 => 獲取圖片數(shù)據(jù) => 保存至本地 。
  • 不相關(guān)技術(shù)點(diǎn):設(shè)置Request Header模擬瀏覽器行為掠兄。

1. 將異步請求封裝成Promise

首先我們需要把上述的異步函數(shù)封裝成Promise像云。

  • 定時(shí)器
function _setTimeout(i) {
    var interval = i * config.interval + Math.random() * 100
    return new Promise(resolve => {
        setTimeout(() => {
            resolve()
        }, interval)
    })
}
  • 獲取圖片url數(shù)組
function getPicsUrl(url) {
    console.log(`開始向${url}請求圖片地址...`)
    var html = ''
    return new Promise(resolve => {
        http.get(url, res => {
            res.on('data', data => { html += data })
            res.on('end', data => {
                var $ = cheerio.load(html)
                var $pics = $('#artContent img')
                var pics = [].slice.call($pics).map(pic => {
                    return pic.attribs.src
                })

                console.log(`圖片鏈接獲取完畢,共${pics.length}張圖片蚂夕。`)
                resolve(pics)
            })
        })
    })
}
  • 獲取圖片數(shù)據(jù)
    以下這段代碼運(yùn)用到了設(shè)置header模擬瀏覽器迅诬,我對這方面并無過多的了解,僅僅是針對需求而解決問題一種方案婿牍。就不過多解釋了侈贷。(基礎(chǔ)的http常識是必須具備的,只是應(yīng)用層面上各取所需就好等脂,學(xué)習(xí)是需要成本的俏蛮,應(yīng)該珍惜時(shí)間才對)
function getPicData(urlStr) {
    var name = urlStr.substring(56)
    var urlJson = url.parse(urlStr)
    var data = ''

    var option = {
        hostname: urlJson.hostname,
        path: urlJson.pathname,
        // 對方網(wǎng)站有限制爬蟲撑蚌,需要設(shè)置header模擬瀏覽器
        headers: {
            "User-Agent": `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36`,
            "Referer":  `http://www.360doc.com/content/13/0905/08/11561215_312316659.shtml`,
        }
    }

    return new Promise(resolve => {
        http.get(option, res => {
            res.setEncoding('binary')
            res.on('data', chunk => { data += chunk })
            res.on('end', () => {
                resolve({name, data})
            })
        })
    })
}
  • 下載圖片至本地
function download(pic) {
    return new Promise(resolve => {
        fs.writeFile(config.dirPath + pic.name, pic.data, 'binary', err => {
            resolve({ err: err, name: pic.name })
        })
    })
}

2. Promise的流程控制

是不是很熟悉JQuery return this的鏈?zhǔn)讲僮鳎?code>Promise的流程控制就特別相似,這樣寫代碼是不是比嵌套回調(diào)舒服很多吧嫁蛇?

getPicsUrl(domain).then(picsArr => {  // 獲取圖片url數(shù)組
    for (let i = 0; i < picsArr.length; i++) {
        _setTimeout(i)  // 定時(shí)器等待
        .then(() => getPicData(picsArr[i]))  // 獲取圖片數(shù)據(jù)
        .then(pic => download(pic))  // 下載圖片至本地
        .then(resJson => {
            console.log(resJson.err || `${resJson.name} downloaded successfully`)
        })
    }
})

3. Async的流程控制

是不是已經(jīng)非常接近同步代碼了锨并?畢竟號稱異步編程之終極方案的~

async function crawler() {
    if ( !isExit(config.dirPath) ) {
        fs.mkdirSync(config.dirPath)
    }
    var picsArr = await getPicsUrl(domain)  // 獲取圖片url數(shù)組
    for (let i = 0; i < picsArr.length; i++) {
        await _setTimeout()  // 定時(shí)器等待
        var pic = await getPicData(picsArr[i])  // 獲取圖片數(shù)據(jù)
        var resJson = await download(pic)  // 下載圖片至本地
        console.log(resJson.err || `${resJson.name} downloaded successfully`)
    }
}

作品展示

源碼戳我
展示一下成果露该,一共330張三維立體圖

www.3wtu.com
www.360doc.com

如果看到這里你對三維立體圖感興趣睬棚,可以試試看破解下面這張。


雪人.jpg

原文首發(fā)于我的博客:https://www.vq0599.com/p/3
轉(zhuǎn)載請注明出處

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末解幼,一起剝皮案震驚了整個(gè)濱河市抑党,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌撵摆,老刑警劉巖底靠,帶你破解...
    沈念sama閱讀 216,402評論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異特铝,居然都是意外死亡暑中,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評論 3 392
  • 文/潘曉璐 我一進(jìn)店門鲫剿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鳄逾,“玉大人,你說我怎么就攤上這事灵莲〉癜迹” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評論 0 353
  • 文/不壞的土叔 我叫張陵政冻,是天一觀的道長枚抵。 經(jīng)常有香客問我,道長明场,這世上最難降的妖魔是什么汽摹? 我笑而不...
    開封第一講書人閱讀 58,165評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮苦锨,結(jié)果婚禮上逼泣,老公的妹妹穿的比我還像新娘。我一直安慰自己逆屡,他們只是感情好圾旨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著魏蔗,像睡著了一般砍的。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上莺治,一...
    開封第一講書人閱讀 51,146評論 1 297
  • 那天廓鞠,我揣著相機(jī)與錄音帚稠,去河邊找鬼。 笑死床佳,一個(gè)胖子當(dāng)著我的面吹牛滋早,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播砌们,決...
    沈念sama閱讀 40,032評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼杆麸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了浪感?” 一聲冷哼從身側(cè)響起昔头,我...
    開封第一講書人閱讀 38,896評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎影兽,沒想到半個(gè)月后揭斧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡峻堰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評論 2 332
  • 正文 我和宋清朗相戀三年讹开,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捐名。...
    茶點(diǎn)故事閱讀 39,696評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡旦万,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出桐筏,到底是詐尸還是另有隱情纸型,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評論 5 343
  • 正文 年R本政府宣布梅忌,位于F島的核電站狰腌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏牧氮。R本人自食惡果不足惜琼腔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望踱葛。 院中可真熱鬧丹莲,春花似錦、人聲如沸尸诽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽性含。三九已至洲赵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背叠萍。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評論 1 269
  • 我被黑心中介騙來泰國打工芝发, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人苛谷。 一個(gè)月前我還...
    沈念sama閱讀 47,698評論 2 368
  • 正文 我出身青樓辅鲸,卻偏偏與公主長得像,于是被迫代替她去往敵國和親腹殿。 傳聞我的和親對象是個(gè)殘疾皇子独悴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 《ijs》速成開發(fā)手冊3.0 官方用戶交流:iApp開發(fā)交流(1) 239547050iApp開發(fā)交流(2) 10...
    葉染柒丶閱讀 5,120評論 0 7
  • Node基本 node的最大特性莫過于基于事件驅(qū)動(dòng)的非阻塞I/O模型。 node通過事件驅(qū)動(dòng)的方式處理請求赫蛇,無須為...
    AkaTBS閱讀 2,170評論 0 11
  • //本文內(nèi)容起初摘抄于 阮一峰 作者的譯文绵患,用于記錄和學(xué)習(xí),建議觀者移步于原文 概念: 所謂的Promise悟耘,...
    曾經(jīng)過往閱讀 1,237評論 0 7
  • 個(gè)人入門學(xué)習(xí)用筆記、不過多作為參考依據(jù)织狐。如有錯(cuò)誤歡迎斧正 目錄 簡書好像不支持錨點(diǎn)暂幼、復(fù)制搜索(反正也是寫給我自己看...
    kirito_song閱讀 2,467評論 1 37
  • 我很失望希瑟晚上沒有和我在一起,原因有三:一移迫,我喜歡有她在身邊陪伴旺嬉;二,我要確保在父親家發(fā)生的事情厨埋,她不會受其影響...
    橘小芃閱讀 1,131評論 0 5