摘要:也就是說,我的篇文章的請求對應(yīng)個實例,這些實例都請求完畢后,執(zhí)行以下邏輯他的目的在于對每一個返回值這個返回值為單篇文章的內(nèi)容,進(jìn)行方法處理。
英國人Robert Pitt曾在Github上公布了他的爬蟲腳本,導(dǎo)致任何人都可以容易地取得Google Plus的大量公開用戶的ID信息。至今大概有2億2千5百萬用戶ID遭曝光。
亮點在于,這是個nodejs腳本,非常短,包括注釋只有71行。
毫無疑問,nodeJS改變了整個前端開發(fā)生態(tài)。本文一步步完成了一個基于promise的nodeJS爬蟲程序,收集簡書任意指定作者的文章信息。并最終把爬下來結(jié)果以郵件的形式,自動化發(fā)給目標(biāo)對象。千萬不要被nodeJS的外表嚇到,既是你是初入前端的小菜鳥,或是剛接觸nodeJS不久的新同學(xué),都不妨礙對這篇文章的閱讀和理解。
爬蟲的所有代碼可以在我的Github倉庫找到,日后這個爬蟲程序還會進(jìn)行不斷升級和更新,歡迎關(guān)注。
nodeJS VS Python實現(xiàn)爬蟲我們先從爬蟲說起。對比一下,討論為什么nodeJS適合/不適合作為爬蟲編寫語言。
首先,總結(jié)一下:
NodeJS單線程、事件驅(qū)動的特性可以在單臺機(jī)器上實現(xiàn)極大的吞吐量,非常適合寫網(wǎng)絡(luò)爬蟲這種資源密集型的程序。
但是,對于一些復(fù)雜場景,需要更加全面的考慮。以下內(nèi)容總結(jié)自知乎相關(guān)問題,感謝@知乎網(wǎng)友,對答案的貢獻(xiàn)。
如果是定向爬取幾個頁面,做一些簡單的頁面解析,爬取效率不是核心要求,那么用什么語言差異不大。
如果是定向爬取,且主要目標(biāo)是解析js動態(tài)生成的內(nèi)容 :
此時,頁面內(nèi)容是由js/ajax動態(tài)生成的,用普通的請求頁面+解析的方法就不管用了,需要借助一個類似firefox、chrome瀏覽器的js引擎來對頁面的js代碼做動態(tài)解析。
如果爬蟲是涉及大規(guī)模網(wǎng)站爬取,效率、擴(kuò)展性、可維護(hù)性等是必須考慮的因素時候:
大規(guī)模爬蟲爬取涉及諸多問題:多線程并發(fā)、I/O機(jī)制、分布式爬取、消息通訊、判重機(jī)制、任務(wù)調(diào)度等等,此時候語言和所用框架的選取就具有極大意義了。具體來看:
PHP:對多線程、異步支持較差,不建議采用。
NodeJS:對一些垂直網(wǎng)站爬取倒可以。但由于分布式爬取、消息通訊等支持較弱,根據(jù)自己情況判斷。
Python:建議,對以上問題都有較好支持。
當(dāng)然,我們今天所實現(xiàn)的是一個簡易爬蟲,不會對目標(biāo)網(wǎng)站帶來任何壓力,也不會對個人隱私造成不好影響。畢竟,他的目的只是熟悉nodeJS環(huán)境。適用于新人入門和練手。
當(dāng)然,任何惡意的爬蟲性質(zhì)是惡劣的,我們應(yīng)當(dāng)全力避免影響,共同維護(hù)網(wǎng)絡(luò)環(huán)境的健康。
爬蟲實例今天要編寫的爬蟲目的是爬取簡書作者:LucasHC(我本人)在簡書平臺上,發(fā)布過的所有文章信息,包括:每篇文章的:
發(fā)布日期;
文章字?jǐn)?shù);
評論數(shù);
瀏覽數(shù)、贊賞數(shù);
等等。
最終爬取結(jié)果的輸出如下:
以下結(jié)果,我們需要通過腳本,自動地發(fā)送郵件到指定郵箱。收件內(nèi)容如下:
以上結(jié)果只需要一鍵操作便可完成。
爬蟲設(shè)計我們的程序一共依賴三個模塊/類庫:
const http = require("http"); const Promise = require("promise"); const cheerio = require("cheerio");發(fā)送請求
http是nodeJS的原生模塊,自身就可以用來構(gòu)建服務(wù)器,而且http模塊是由C++實現(xiàn)的,性能可靠。
我們使用Get,來請求簡書作者相關(guān)文章的對應(yīng)頁面:
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("獲取信息出錯!"); });
因為我發(fā)現(xiàn),簡書中每一篇文章的鏈接形式如下:
完整形式:“http://www.jianshu.com/p/ab27...”,
即 “http://www.jianshu.com/p/” + “文章id”。
所以,上述代碼中相關(guān)作者的每篇文章url:由baseUrl和相關(guān)文章id拼接組成:
articleIds.forEach(function(item) { url = baseUrl + item; });
articleIds自然是存儲作者每篇文章id的數(shù)組。
最終,我們把每篇文章的html內(nèi)容存儲在html這個變量中。
異步promise封裝由于作者可能存在多篇文章,所以對于每篇文章的獲取和解析我們應(yīng)該異步進(jìn)行。這里我使用了promise封裝上述代碼:
function getPageAsync (url) { return new Promise(function(resolve, reject){ http.get(url, function(res) { ... }).on("error", function(e) { reject(e); console.log("獲取信息出錯!"); }); }); };
這樣一來,比如我寫過14篇原創(chuàng)文章。這樣對每一片文章的請求和處理全都是一個promise對象。我們存儲在預(yù)先定義好的數(shù)組當(dāng)中:
const articlePromiseArray = [];
接下來,我使用了Promise.all方法進(jìn)行處理。
Promise.all方法用于將多個Promise實例,包裝成一個新的Promise實例。
該方法接受一個promise實例數(shù)組作為參數(shù),實例數(shù)組中所有實例的狀態(tài)都變成Resolved,Promise.all返回的實例才會變成Resolved,并將Promise實例數(shù)組的所有返回值組成一個數(shù)組,傳遞給回調(diào)函數(shù)。
也就是說,我的14篇文章的請求對應(yīng)14個promise實例,這些實例都請求完畢后,執(zhí)行以下邏輯:
Promise.all(articlePromiseArray).then(function onFulfilled (pages) { pages.forEach(function(html) { let info = filterArticles(html); printInfo(info); }); }, function onRejected (e) { console.log(e); });
他的目的在于:對每一個返回值(這個返回值為單篇文章的html內(nèi)容),進(jìn)行filterArticles方法處理。處理所得結(jié)果進(jìn)行printInfo方法輸出。
接下來,我們看看filterArticles方法做了什么。
其實很明顯,如果您理解了上文的話。filterArticles方法就是對單篇文章的html內(nèi)容進(jìn)行有價值的信息提取。這里有價值的信息包括:
1)文章標(biāo)題;
2)文章發(fā)表時間;
3)文章字?jǐn)?shù);
4)文章瀏覽量;
5)文章評論數(shù);
6)文章贊賞數(shù)。
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信息進(jìn)行操作。其實這歸功于cheerio類庫。
filterArticles方法返回了每篇文章我們感興趣的內(nèi)容。這些內(nèi)容存儲在articleData對象當(dāng)中,最終由printInfo進(jìn)行輸出。
郵件自動發(fā)送到此,爬蟲的設(shè)計與實現(xiàn)到了一段落。接下來,就是把我們爬取的內(nèi)容以郵件方式進(jìn)行發(fā)送。
這里我使用了nodemailer模塊進(jìn)行發(fā)送郵件。相關(guān)邏輯放在Promise.all當(dāng)中:
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); });
郵件服務(wù)的相關(guān)配置內(nèi)容我已經(jīng)進(jìn)行了適當(dāng)隱藏。讀者可以自行配置。
總結(jié)本文,我們一步一步實現(xiàn)了一個爬蟲程序。涉及到的知識點主要有:nodeJS基本模塊用法、promise概念等。如果拓展下去,我們還可以做nodeJS連接數(shù)據(jù)庫,把爬取內(nèi)容存在數(shù)據(jù)庫當(dāng)中。當(dāng)然也可以使用node-schedule進(jìn)行定時腳本控制。當(dāng)然,目前這個爬蟲目的在于入門,實現(xiàn)還相對簡易,目標(biāo)源并不是大型數(shù)據(jù)。
本文只涉及nodeJS的冰山一角,希望大家一起探索。如果你對完整代碼感興趣,請點擊這里。
Happy Coding!
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/82252.html
摘要:本文首發(fā)于個人博客項目源碼,歡迎,說不定哪天脫單了就能用到了寫在前面自從用郵箱注冊了很多賬號后,便會收到諸如以下類似的郵件剛開始還以為是一張圖片,后來仔細(xì)一看不是圖片呀,好像還是呀,于是好奇寶寶我一下,查閱多篇資料后總結(jié)出怎么用前端知識和做 本文首發(fā)于個人博客:VinceBlog 項目源碼:NodeMail,歡迎star,說不定哪天脫單了就能用到了 寫在前面 自從用郵箱注冊了很多賬號后...
摘要:支持一鍵部署項目到集群。添加郵箱帳號設(shè)置郵件工作時間和基本觸發(fā)器,以下示例代表每隔小時或當(dāng)某一任務(wù)完成時,并且當(dāng)前時間是工作日的點,點和點,將會發(fā)送通知郵件。除了基本觸發(fā)器,還提供了多種觸發(fā)器用于處理不同類型的,包括和等。 showImg(https://segmentfault.com/img/remote/1460000018772067?w=1680&h=869); 安裝和配置 ...
摘要:以上示例代表當(dāng)發(fā)現(xiàn)條或條以上的級別的時,自動停止當(dāng)前任務(wù),如果當(dāng)前時間在郵件工作時間內(nèi),則同時發(fā)送通知郵件。 showImg(https://segmentfault.com/img/remote/1460000018052810); 一、需求分析 初級用戶: 只有一臺開發(fā)主機(jī) 能夠通過 Scrapyd-client 打包和部署 Scrapy 爬蟲項目,以及通過 Scrapyd JS...
摘要:并利用提供的云引擎服務(wù)實現(xiàn)在周五給全員發(fā)送郵件提醒填寫周報,周六周日分別再次對未填人員發(fā)送郵件進(jìn)行填寫提醒。雖然提供的免費云引擎,本身就支持服務(wù),但是免費版是做測試用的,會自動休眠,不夠穩(wěn)定,經(jīng)常掛掉。 This just is a README. showImg(https://segmentfault.com/img/remote/1460000013260535);showImg...
閱讀 972·2019-08-30 15:54
閱讀 499·2019-08-30 12:51
閱讀 2098·2019-08-29 16:28
閱讀 2895·2019-08-29 16:10
閱讀 2391·2019-08-29 14:21
閱讀 474·2019-08-29 14:09
閱讀 2199·2019-08-23 16:13
閱讀 1287·2019-08-23 13:59