英國人Robert Pitt曾在Github上公佈了他的爬蟲腳本,致使任何人均可以容易地取得Google Plus的大量公開用戶的ID信息。至今大概有2億2千5百萬用戶ID遭曝光。javascript
亮點在於,這是個nodejs腳本,很是短,包括註釋只有71行。html
毫無疑問,nodeJS改變了整個前端開發生態。
本文一步步完成了一個基於promise的nodeJS爬蟲程序,收集簡書任意指定做者的文章信息。並最終把爬下來結果以郵件的形式,自動發給目標對象。千萬不要被nodeJS的外表嚇到,即便你是初入前端的小菜鳥,或是剛接觸nodeJS不久的新同窗,都不妨礙對這篇文章的閱讀和理解。前端
爬蟲的全部代碼能夠在個人Github倉庫找到,往後這個爬蟲程序還會進行不斷升級和更新,歡迎關注。java
咱們先從爬蟲提及。對比一下,討論爲何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封裝上述代碼:
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方法作了什麼。
其實很明顯,若是您理解了上文的話。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!