Node Crawler 使用說明

image

node-crawler Doc
Crawler DOC 翻譯

在使用這個框架一段時間之后阅虫,發(fā)現(xiàn)這個doc 有些亂托酸,并且缺少完整的案例褒颈。 面對不同的情況,想讓crawler正常運作的話励堡,需要查看依賴的庫谷丸,如request , cheerio等应结。在此我記錄一下我個人對這個框架的使用說明刨疼,附案例。本人非科班出身摊趾,非javaScript專業(yè)用戶币狠,文內如有偏頗游两,歡迎指正砾层!
pre:如果你對javaScript、Nodejs沒有基礎贱案,建議先閱讀我的學習筆記:(目前沒整理出來肛炮,比較懶!23333)

目錄:
一宝踪、框架機制 :
二侨糟、常用參數(shù):
三、案例:豆瓣圖書
四瘩燥、How to Debug


一秕重、框架機制

nodejs 作用機制是“單線程異步”“非阻塞式IO模型”,贅述一下厉膀,就是主線程是單線程溶耘,而處理主線程“分發(fā)”的事件是交由ChromeV8異步處理的二拐。


機制.jpg

1.1 crawler

所以針對這個機制,crawler 維護一個任務隊列/請求隊列queue凳兵, 主線程遇到加入queue的請求百新,會把新請求丟入隊列,如果這個請求中有callback庐扫,則callback會被交給異步線程處理饭望,主線程繼續(xù)向下執(zhí)行,直到程序done形庭。隨后铅辞,主線程會不斷從queue頭部取新任務處理,形成閉環(huán)碘勉,直到隊列為空巷挥。

類似上圖中右側圖,在額外操作一個任務隊列验靡。

1.2 other spider

當然倍宾,nodejs 的單線程異步,可能會讓其他語言“轉職”過來的人迷惑胜嗓。由于我沒了解過其他語言爬蟲機制高职,說一下我對其機制的猜測(阻塞式IO):

爬蟲程序由function1請求入口頁面。假設入口頁面有100個list url辞州,在function1中循環(huán)100次怔锌,請求fucntion2進入list頁面。假設每個list頁面有10個detail url变过, 則在function2中循環(huán)10次請求detail函數(shù)寫入database埃元,程序完成交回爬蟲程序done。這樣完成了一個單線程阻塞式模型媚狰,清楚知道爬蟲程序運行到哪里岛杀,該在哪debug。在這種情況下要開多線程崭孤,則可以先準備好10個線程的線程池类嗤,在入口頁面函數(shù)function1中將100個list request交給10個線程處理,每條線程依照上面的步驟跑到底辨宠,空閑則回到線程池遗锣,爬蟲主線程會再從剩下的90個list req中分給線程任務,直到threadPool 為空嗤形。

上述過程相對來說更符合自然人操作邏輯精偿,更好理解。具體不同框架肯定對線程池的調度有著不同的優(yōu)化,例如開啟的額外線程可能會每完成一個請求函數(shù)笔咽,就回到線程池墓阀, 在總爬蟲程序程序構造方法處進行線程池設置。也可能添加callback函數(shù)優(yōu)化翻頁邏輯拓轻,這些我不得而知斯撮。

1.3 總結

在理解了框架工作機制后,不難發(fā)現(xiàn)盡管crawler只有一個主線程扶叉,但工作效率并不低勿锅,可以用于生產(chǎn)環(huán)境。唯一不足是因為框架本身輕量枣氧,欠缺了一些魯棒性溢十。

其實,爬蟲無非就是請求request和響應response达吞,下文簡寫req與res张弛。如果你的req與瀏覽器一致,那么你的到的res也必然相同酪劫,剩下的事情就是解析res得到自己想要的數(shù)據(jù)吞鸭。至于所有的爬蟲框架就是在這最本質的內核上錦上添花、方便使用覆糟,crawler 的分布式版本 floodesh 刻剥,即,將crawler維護的queue 改為分布式DB MongoDB滩字,增加了主機index與客戶端worker造虏,分別負責任務調度與爬取工作。
floodesh DOC文檔


二麦箍、常用參數(shù)

2.1 依賴包

java 習慣稱之為包漓藕, 也可叫模塊、輪子……whatever挟裂!源碼如下:

var path = require('path')//解決一些path 問題享钞,如不同系統(tǒng)\ /,絕對话瞧、相對路徑
    , util = require('util')//node核心模塊嫩与,解決一些回調繼承的問題
    , EventEmitter = require('events').EventEmitter//nodejs異步io事件隊列
    , request = require('request')//發(fā)送請求
    , _ = require('lodash')//優(yōu)化一些js對象操作寝姿,提供方便使用的接口
    , cheerio = require('cheerio')//jquery選擇器
    , fs = require('fs')//file 的io操作
    , Bottleneck = require('bottleneckp')//任務調度以及限制速率
    , seenreq = require('seenreq')//req url 去重
    , iconvLite = require('iconv-lite')//編碼轉換
    , typeis = require('type-is').is;//js 類型檢查器

日常使用的話交排,不需要了解所有包的全部功能, 需要的話可以查閱文檔:
https://www.npmjs.com/
最常用的的如request 饵筑、cheerio還是建議了解一下 DOC埃篓。

2.2 參數(shù)

對于crawler維護的任務隊列, 其實是一個包含options對象的json數(shù)組根资,源碼:

Crawler.prototype.queue = function queue (options) {
    var self = this;

    // Did you get a single object or string? Make it compatible.
    options = _.isArray(options) ? options : [options];

    options = _.flattenDeep(options);

    for(var i = 0; i < options.length; ++i) {
        if(self.isIllegal(options[i])) {
            log('warn','Illegal queue option: ', JSON.stringify(options[i]));
            continue;
        }
        self._pushToQueue(
            _.isString(options[i]) ? {uri: options[i]} : options[i]
        );
    }
};

option可以全局傳給crawler架专,這樣會對每一次請求生效同窘, 也可以給把獨立的option傳給queue,關于這點doc寫的很清楚部脚。option常用參數(shù)和默認值見源碼:

var defaultOptions = {
        autoWindowClose:        true,
        forceUTF8:              true,
        gzip:                   true,
        incomingEncoding:       null,
        jQuery:                 true,//res 是否注入 cheerio想邦,doc有詳細說明
        maxConnections:         10,//只有在rateLimit == 0時起作用,限制并發(fā)數(shù)
        method:                 'GET',
        priority:               5,//queue請求優(yōu)先級委刘,模擬用戶行為
        priorityRange:          10,
        rateLimit:             0,//請求最小間隔
        referer:                false,
        retries:                3,//重試次數(shù)丧没,請求不成功會重試3次
        retryTimeout:           10000,//重試間隔
        timeout:                15000,//15s req無響應,req失敗
        skipDuplicates:         false,//url去重锡移,建議框架外單讀使用seenreq
        rotateUA:               false,//數(shù)組多組UA
        homogeneous:            false
    };

第一章有提到呕童,爬蟲最重要的是req和res , crawler在req部分使用的是 request.js API :https://github.com/request/request#requestoptions-callback
可以在options中使用request.js 淆珊,諸如body夺饲、form 、header…具體可以見第三章的實例代碼施符。

2.3 常識

在簡介 crawler event 之前往声,要提到一些爬蟲的常識,因為我有看到github上有人對crawler提問戳吝,提問的原因是自己常識不足烁挟!
爬蟲實際情況大概分兩種: 一、針對異步API接口 骨坑,二撼嗓、針對url返回的html頁面。一般來講欢唾,前者返回可解析的json數(shù)據(jù)且警,而后者返回的是html文本,你需要用正則regex匹配自己想要的礁遣,也可以cheerio注入jquery得到自己想要的斑芜。
如果已經(jīng)想清楚自己是那種情況,仍然得不到res的話祟霍,排除服務器端加密情況杏头,多半就是你req沒有發(fā)對,建議chrome 多按按F12沸呐、request.js doc醇王、cheerio doc。

2.4 事件

crawler doc中這部份表述非常清晰崭添,提一下常用情況:

queue : 推任務到queue
schedule : 任務被推queue時候觸發(fā)寓娩,多用于添加代理
drain : queue為空時觸發(fā) , 多用于關閉數(shù)據(jù)庫、關閉寫入流

如果你在queue為空后棘伴,異步重新把任務推入queue寞埠,會頻繁觸發(fā)drain。


三焊夸、案例:豆瓣圖書

爬取豆瓣圖書TOP250總榜仁连,這是一個返回html頁面的案例,算是爬蟲屆的HelloWorld 阱穗!

"use strict";
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
const fs = require('fs');
const moment = require('moment');
const Crawler = require('crawler');

const _prgname = 'doubanTop250';
class Douban{
    constructor() {
        this.writeStream = fs.createWriteStream('../result/' + _prgname + '_book_' + moment().format('YYYY-MM-DD') + '.csv');
        this.header = ['排名','標題','信息','評分','url','抓取時間'];
        this.rank = 1;
        this.crawler = new Crawler({
            maxConnection: 1,
            forceUTF8: true,
            rateLimit: 2000,
            jar: true,
            time: true,
            headers: {
                'User-Agent':`Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36`//,
            }
        });
        this.crawler.on('drain', () => {
            console.log('Job done');
            //end stream
            this.writeStream.end();
        }).on('schedule', options => {
            //options.proxy = 'http://xxx.xxx.xx.xxx:xxxx';
            options.limiter = Math.floor(Math.random() * 10);//并發(fā)10
        });
    }

    start() {
        let self = this;
        self.writeStream.write(`\ufeff${self.header}\n`);
        console.log(`start`);
        this.crawler.queue({
            uri: 'https://book.douban.com/top250?icn=index-book250-all' ,
            method:'GET',
            gene:{
                page : 1
            },
            callback: this.pageList.bind(this)
        });
    }
    
    pageList(err, res, done) {
        let self = this;
        if (err) {
            console.log(`pageList got erro : ${err.stack}`);
            return done();
        }
        const gene = res.options.gene;
        const $ = res.$;
        $('#content > div > div.article > div.indent >table').map(function (){
            const title = $('tr > td:nth-child(2) > div.pl2 a ',this).text().trim().replace(/[,\r\n]/g, '');
            const src = $('tr > td:nth-child(2) > div.pl2 a',this).attr("href");
            const info = $('tr > td:nth-child(2) p.pl',this).text();
            const rate = $('tr > td:nth-child(2) span.rating_nums',this).text();
            const time = moment().format('YYYY-MM-DD HH:mm:ss');
            
            const result = [self.rank++, title, info, rate, src, time];
            console.log(`${result}\n`);
            self.writeStream.write(`${result}\n`);
        });

        if(gene.page <= 10){
            console.log(`currentPage : ${gene.page}`);
            this.crawler.queue({
                uri: 'https://book.douban.com/top250?start=' + gene.page*25,
                method:'GET',
                gene : {
                    page : gene.page + 1
                },
                callback: self.pageList.bind(self)
            });
        }
        return done();
    }
}
const douban = new Douban();
douban.start();

install 相關的包怖糊,在上級目錄建好result文件夾,腳本可以直接跑颇象。
注:
1伍伤、 gene 為自定義通過option傳入回調的json對象。
2遣钳、 使用jquery 時扰魂,作用域this覆蓋問題,可以用self指向本類this蕴茴。


四劝评、How to Debug

crawler 可以使用docker debug 稍微復雜有空單起一篇文章。
但是一般比較簡單的腳本使用log在關鍵節(jié)點記錄一下就可以查出問題倦淀。
見案例代碼console.log()多為debug服務蒋畜。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市撞叽,隨后出現(xiàn)的幾起案子姻成,更是在濱河造成了極大的恐慌,老刑警劉巖愿棋,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件科展,死亡現(xiàn)場離奇詭異,居然都是意外死亡糠雨,警方通過查閱死者的電腦和手機才睹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來甘邀,“玉大人琅攘,你說我怎么就攤上這事∷尚埃” “怎么了坞琴?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長测摔。 經(jīng)常有香客問我置济,道長,這世上最難降的妖魔是什么锋八? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任浙于,我火速辦了婚禮,結果婚禮上挟纱,老公的妹妹穿的比我還像新娘羞酗。我一直安慰自己,他們只是感情好紊服,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布檀轨。 她就那樣靜靜地躺著,像睡著了一般欺嗤。 火紅的嫁衣襯著肌膚如雪参萄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天煎饼,我揣著相機與錄音讹挎,去河邊找鬼。 笑死吆玖,一個胖子當著我的面吹牛筒溃,可吹牛的內容都是我干的。 我是一名探鬼主播沾乘,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼怜奖,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了翅阵?” 一聲冷哼從身側響起歪玲,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎掷匠,沒想到半個月后读慎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡槐雾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年夭委,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片募强。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡株灸,死狀恐怖,靈堂內的尸體忽然破棺而出擎值,到底是詐尸還是另有隱情慌烧,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布鸠儿,位于F島的核電站屹蚊,受9級特大地震影響厕氨,放射性物質發(fā)生泄漏。R本人自食惡果不足惜汹粤,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一命斧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嘱兼,春花似錦国葬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至踢涌,卻和暖如春通孽,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背睁壁。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工利虫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人堡僻。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓糠惫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親钉疫。 傳聞我的和親對象是個殘疾皇子硼讽,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內容

  • 今日閑來無事在洗池里刷了一雙球鞋。洗的過程中很自然地想起了數(shù)年前在杭州遇到的一位洗鞋女士牲阁,她洗鞋的價格好像是10固阁、...
    化濁閱讀 409評論 4 4
  • 亞青寺距離甘孜縣城100公里左右,一路向西翻越卓達拉山(海拔4600米)和海子山(海拔4410米)兩座山峰城菊,圍繞寺...
    Yn大漠沙如雪閱讀 600評論 0 3
  • 我從來不是一個很會生活的人凌唬,沒有遠大的人生大計并齐,過好每一日每一時。我會從街首到街尾客税,只為吃一碗羊肉面状答,我也會精心準...
    花若兮閱讀 1,118評論 2 8
  • 一滴陽光閱讀 351評論 0 0