nodeJS實現基於Promise爬蟲 定時發送信息到指定郵件

英國人Robert Pitt曾在Github上公佈了他的爬蟲腳本,致使任何人均可以容易地取得Google Plus的大量公開用戶的ID信息。至今大概有2億2千5百萬用戶ID遭曝光。javascript

亮點在於,這是個nodejs腳本,很是短,包括註釋只有71行。html

毫無疑問,nodeJS改變了整個前端開發生態。
本文一步步完成了一個基於promise的nodeJS爬蟲程序,收集簡書任意指定做者的文章信息。並最終把爬下來結果以郵件的形式,自動發給目標對象。千萬不要被nodeJS的外表嚇到,即便你是初入前端的小菜鳥,或是剛接觸nodeJS不久的新同窗,都不妨礙對這篇文章的閱讀和理解。前端

爬蟲的全部代碼能夠在個人Github倉庫找到,往後這個爬蟲程序還會進行不斷升級和更新,歡迎關注。java

nodeJS VS Python實現爬蟲

咱們先從爬蟲提及。對比一下,討論爲何nodeJS適合/不適合做爲爬蟲編寫語言。
首先,總結一下:node

NodeJS單線程、事件驅動的特性能夠在單臺機器上實現極大的吞吐量,很是適合寫網絡爬蟲這種資源密集型的程序。python

可是,對於一些複雜場景,須要更加全面的考慮。如下內容總結自知乎相關問題,感謝@知乎網友,對答案的貢獻。git

  • 若是是定向爬取幾個頁面,作一些簡單的頁面解析,爬取效率不是核心要求,那麼用什麼語言差別不大。 github

  • 若是是定向爬取,且主要目標是解析js動態生成的內容 :
    此時,頁面內容是由js/ajax動態生成的,用普通的請求頁面+解析的方法就無論用了,須要藉助一個相似firefox、chrome瀏覽器的js引擎來對頁面的js代碼作動態解析。ajax

  • 若是爬蟲是涉及大規模網站爬取,效率、擴展性、可維護性等是必須考慮的因素時候:
    1) PHP:對多線程、異步支持較差,不建議採用。
    2) NodeJS:對一些垂直網站爬取倒能夠。但因爲分佈式爬取、消息通信等支持較弱,根據本身狀況判斷。
    3) Python:建議,對以上問題都有較好支持。chrome

固然,咱們今天所實現的是一個簡易爬蟲,不會對目標網站帶來任何壓力,也不會對我的隱私形成很差影響。畢竟,他的目的只是熟悉nodeJS環境。適用於新人入門和練手。

一樣,任何惡意的爬蟲性質是惡劣的,咱們應當全力避免影響,共同維護網絡環境的健康。

爬蟲實例

今天要編寫的爬蟲目的是爬取簡書做者:LucasHC(我本人)在簡書平臺上,發佈過的全部文章信息,包括每篇文章的:

  • 發佈日期;
  • 文章字數;
  • 評論數;
  • 瀏覽數、讚揚數;
    等等。

最終爬取結果的輸出以下:

爬取輸出

同時,以上結果,咱們須要經過腳本,自動發送郵件到指定郵箱。收件內容以下:

郵件內容

所有操做只須要一鍵即可完成。

爬蟲設計

咱們的程序一共依賴三個模塊/類庫:

const http = require("http");
const Promise = require("promise");
const cheerio = require("cheerio");複製代碼

發送請求

http是nodeJS的原生模塊,自身就能夠用來構建服務器,並且http模塊是由C++實現的,性能可靠。
咱們使用Get,來請求簡書做者相關文章的對應頁面:

http.get(url, function(res) {
    var html = "";
    res.on("data", function(data) {
        html += data;
    });

    res.on("end", function() {
        ...
    });
}).on("error", function(e) {
    reject(e);
    console.log("獲取信息出錯!");
});複製代碼

由於我發現,簡書中每一篇文章的連接形式以下:
完整形式:「www.jianshu.com/p/ab2741f78…
即 「www.jianshu.com/p/」 + 「文章id」。

因此,上述代碼中相關做者的每篇文章url:由baseUrl和相關文章id拼接組成:

articleIds.forEach(function(item) {
    url = baseUrl + item;
});複製代碼

articleIds天然是存儲做者每篇文章id的數組。

最終,咱們把每篇文章的html內容存儲在html這個變量中。

異步promise封裝

因爲做者可能存在多篇文章,因此對於每篇文章的獲取和解析咱們應該異步進行。這裏我使用了promise封裝上述代碼:

function getPageAsync (url) {
    return new Promise(function(resolve, reject){
        http.get(url, function(res) {
            ...
        }).on("error", function(e) {
            reject(e);
            console.log("獲取信息出錯!");
        });
    });
};複製代碼

這樣一來,好比我寫過14篇原創文章。那麼對每一片文章的請求和處理全都是一個promise對象。咱們存儲在預先定義好的數組當中:

const articlePromiseArray = [];複製代碼

接下來,我使用了Promise.all方法進行處理。

Promise.all方法用於將多個Promise實例,包裝成一個新的Promise實例。

該方法接受一個promise實例數組做爲參數,實例數組中全部實例的狀態都變成Resolved,Promise.all返回的實例纔會變成Resolved,並將Promise實例數組的全部返回值組成一個數組,傳遞給回調函數。

也就是說,個人14篇文章的請求對應14個promise實例,這些實例都請求完畢後,執行如下邏輯:

Promise.all(articlePromiseArray).then(function onFulfilled (pages) {
    pages.forEach(function(html) {
        let info = filterArticles(html);
        printInfo(info);        
    });
}, function onRejected (e) {
    console.log(e);
});複製代碼

他的目的在於:對每個返回值(這個返回值爲單篇文章的html內容),進行filterArticles方法處理。處理所得結果進行printInfo方法輸出。
接下來,咱們看看filterArticles方法作了什麼。

html解析

其實很明顯,若是您理解了上文的話。filterArticles方法就是對單篇文章的html內容進行有價值的信息提取。這裏有價值的信息包括:
1)文章標題;
2)文章發表時間;
3)文章字數;
4)文章瀏覽量;
5)文章評論數;
6)文章讚揚數。

function filterArticles (html) {
    let $ = cheerio.load(html);
    let title = $(".article .title").text();
    let publishTime = $('.publish-time').text();
    let textNum = $('.wordage').text().split(' ')[1];
    let views = $('.views-count').text().split('閱讀')[1];
    let commentsNum = $('.comments-count').text();
    let likeNum = $('.likes-count').text();

    let articleData = {
        title: title,
        publishTime: publishTime,
        textNum: textNum
        views: views,
        commentsNum: commentsNum,
        likeNum: likeNum
    }; 

    return articleData;
};複製代碼

你也許會奇怪,爲何我能使用相似jQuery中的$對html信息進行操做。其實這歸功於cheerio類庫。

filterArticles方法返回了每篇文章咱們感興趣的內容。這些內容存儲在articleData對象當中,最終由printInfo進行輸出。

郵件自動發送

到此,爬蟲的設計與實現到了一段落。接下來,就是把咱們爬取的內容以郵件方式進行發送。
這裏我使用了nodemailer模塊進行發送郵件。相關邏輯放在Promise.all當中:

Promise.all(articlePromiseArray).then(function onFulfilled (pages) {
    let mailContent = '';
    var transporter = nodemailer.createTransport({
        host : 'smtp.sina.com',
        secureConnection: true, // 使用SSL方式(安全方式,防止被竊取信息)
        auth : {
            user : '**@sina.com',
            pass : ***
        },
    });
    var mailOptions = {
        // ...
    };
    transporter.sendMail(mailOptions, function(error, info){
        if (error) {
            console.log(error);
        }
        else {
            console.log('Message sent: ' + info.response);
        }
    });
}, function onRejected (e) {
    console.log(e);
});複製代碼

郵件服務的相關配置內容我已經進行了適當隱藏。讀者能夠自行配置。

總結

本文,咱們一步一步實現了一個爬蟲程序。涉及到的知識點主要有:nodeJS基本模塊用法、promise概念等。若是拓展下去,咱們還能夠作nodeJS鏈接數據庫,把爬取內容存在數據庫當中。固然也可使用node-schedule進行定時腳本控制。固然,目前這個爬蟲目的在於入門,實現還相對簡易,目標源並非大型數據。

所有內容只涉及nodeJS的冰山一角,但願你們一塊兒探索。若是你對完整代碼感興趣,請點擊這裏。

Happy Coding!

相關文章
相關標籤/搜索