Node.js實(shí)現(xiàn)的簡(jiǎn)易網(wǎng)頁(yè)抓取功能示例
現(xiàn)今,網(wǎng)頁(yè)抓取已經(jīng)是一種人所共知的技術(shù)了,然而依然存在著諸多復(fù)雜性, 簡(jiǎn)單的網(wǎng)頁(yè)爬蟲(chóng)依然難以勝任Ajax輪訓(xùn)、XMLHttpRequest,WebSockets,F(xiàn)lash Sockets等各種復(fù)雜技術(shù)所開(kāi)發(fā)出來(lái)的現(xiàn)代化網(wǎng)站。
我們以我們?cè)贖ubdoc這個(gè)項(xiàng)目上的基礎(chǔ)需求為例,在這個(gè)項(xiàng)目中,我們從銀行,公共事業(yè)和信用卡公司的網(wǎng)站上抓取帳單金額,到期日期,賬戶號(hào)碼,以及最重要的:近期賬單的pdf。對(duì)于這個(gè)項(xiàng)目,我一開(kāi)始采用了很簡(jiǎn)單的方案(暫時(shí)并沒(méi)有使用我們正在評(píng)估的昂貴的商業(yè)化產(chǎn)品)——我以前在MessageLab/Symantec使用Perl做過(guò)的一個(gè)簡(jiǎn)單的爬蟲(chóng)項(xiàng)目。但是結(jié)果很不順利,垃圾郵件發(fā)送者所制作的網(wǎng)站要比銀行和公共事業(yè)公司的網(wǎng)站簡(jiǎn)單的多得多。
那么如何解決這個(gè)問(wèn)題呢?我們主要從使用Mikea開(kāi)發(fā)的優(yōu)秀 request庫(kù)開(kāi)始。在瀏覽器中發(fā)出請(qǐng)求,并在Network窗口中查看到底發(fā)送出去了什么請(qǐng)求頭,然后把這些請(qǐng)求頭拷貝到代碼里。這個(gè)過(guò)程很簡(jiǎn)單。僅僅是跟蹤從登陸開(kāi)始,到下載Pdf文件結(jié)束的這個(gè)過(guò)程,然后模擬這個(gè)過(guò)程的所有的請(qǐng)求而已。為了使類似的事情處理起來(lái)變得容易,并且能讓網(wǎng)絡(luò)開(kāi)發(fā)者們更加合理地寫爬蟲(chóng)程序,我把從HTML上取到結(jié)果的方把導(dǎo)出到j(luò)Query中(使用輕量級(jí) cheerio庫(kù)),這使得相似的工作變得簡(jiǎn)單,也使利用CSS選擇子選取一個(gè)頁(yè)面中的元素變得較為簡(jiǎn)單。整個(gè)過(guò)程被包裝進(jìn)一個(gè)框架,而這個(gè)框架也可以做額外的工作,例如從數(shù)據(jù)庫(kù)中拾取證書,加載個(gè)體機(jī)器人,和UI通過(guò)socket.io溝通。
對(duì)于一些web站點(diǎn)來(lái)說(shuō)這個(gè)是有效的,但這僅僅是JS腳本,而不是我那個(gè)被這些公司放在他們站點(diǎn)上的node.js的code。他們對(duì)遺留下來(lái)的問(wèn)題,針對(duì)復(fù)雜性就行分層,使得你非常難去弄明白該做什么來(lái)得到登錄的信息點(diǎn)。對(duì)于一些站點(diǎn)我嘗試了幾天通過(guò)與request()庫(kù)結(jié)合來(lái)獲取,但仍是徒然。
在幾近崩潰后,我發(fā)現(xiàn)了node-phantomjs,這個(gè)庫(kù)可以讓我從node中控制phantomjs headless webkit瀏覽器(譯者注:這個(gè)我沒(méi)想到一個(gè)對(duì)應(yīng)的名詞,headless這里的意思是渲染頁(yè)面在后臺(tái)完成,無(wú)需顯示設(shè)備)。這看起來(lái)是一種簡(jiǎn)單的解決方案,但是還有一些phantomjs無(wú)法回避的問(wèn)題需要解決:
1.PhantomJS只能告訴你頁(yè)面是否完成了加載,但是你無(wú)法確定這個(gè)過(guò)程中是否存在通過(guò)JavaScript或者meta標(biāo)簽實(shí)現(xiàn)的重定向(redirect)。特別是JavaScript使用setTimeout()來(lái)延遲調(diào)用的時(shí)候。
2.PhantomJS為你提供了一個(gè)頁(yè)面加載開(kāi)始(pageLoadStarted)的鉤子,允許你處理上面提到的問(wèn)題,但是這個(gè)機(jī)能只能在你確定要加載的頁(yè)面數(shù),在每個(gè)頁(yè)面加載完成時(shí)減少這個(gè)數(shù)字,并且為可能的超時(shí)提供處理(因?yàn)檫@種事情并不總是會(huì)發(fā)生),這樣當(dāng)你的數(shù)字減少為0,就可以調(diào)用你的回調(diào)函數(shù)了。這種方式可以工作,但是總讓人覺(jué)得有點(diǎn)像是黑客手段。
3.PhantomJS每抓取一個(gè)頁(yè)面需要一個(gè)完整獨(dú)立的進(jìn)程,因?yàn)槿绻贿@樣,無(wú)法分離每個(gè)頁(yè)面之間的cookies。如果你是用同一個(gè)phantomjs進(jìn)程,已經(jīng)登錄的頁(yè)面中的session會(huì)被發(fā)送到另一個(gè)頁(yè)面中。
4.無(wú)法使用PhantomJS下載資源 - 你只能將頁(yè)面保存為png或者pdf。這很有用,但是這意味著我們需要求助于request()來(lái)下載pdf。
5.由于上述的原因,我必須找到一個(gè)方法來(lái)將cookie從PhantomJS的session中分發(fā)到request()的session庫(kù)中去。只需要將document.cookie的字符串分發(fā)過(guò)去,解析它,然后將其注入到request()的cookie jar中去。
6.將變量注入到瀏覽器session中并不是件容易的事情。要這么做我需要?jiǎng)?chuàng)建一個(gè)字符串來(lái)建立一個(gè)Javascript函數(shù)。
Robot.prototype.add_page_data = function (page, name, data) {
page.evaluate(
"function () { var " + name + " = window." + name + " = " + JSON.stringify(data) + "}"
);
}
7.一些網(wǎng)站總是充斥著console.log()之類的代碼,也需要將他們重新定義,輸出到我們希望的位置。為了完成這個(gè),我這么做:
if (!console.log) {
var iframe = document.createElement("iframe");
document.body.appendChild(iframe);
console = window.frames[0].console;
}
8.一些網(wǎng)站總是充斥著console.log()之類的代碼,也需要將他們重新定義,輸出到我們希望的位置。為了完成這個(gè),我這么做:
if (!console.log) {
var iframe = document.createElement("iframe");
document.body.appendChild(iframe);
console = window.frames[0].console;
}
9.告訴瀏覽器我點(diǎn)擊了a標(biāo)簽也是件很不容易的事情,為了完成這些事情,我加入了以下的代碼:
var clickElement = window.clickElement = function (id){
var a = document.getElementById(id);
var e = document.createEvent("MouseEvents");
e.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
a.dispatchEvent(e);
};
10.我還需要限制瀏覽器session的最大并發(fā)量,從而保障我們不會(huì)爆掉服務(wù)器。雖然這么說(shuō),可是這個(gè)限制要比昂貴的商業(yè)解決方案所能提供的高很多。(譯者注:即商業(yè)解決方案的并發(fā)量比這個(gè)解決方案大)
所有的工作結(jié)束后,我就有一個(gè)比較體面的 PhantomJS + request 的爬蟲(chóng)解決方案。必須使用 PhantomJS 登錄后才可以返回去 request() 請(qǐng)求,它將使用在 PhantomJS 中設(shè)置的 Cookie 來(lái)驗(yàn)證登錄的會(huì)話。這是一個(gè)巨大的勝利,因?yàn)槲覀兛梢允褂?request() 的流來(lái)下載 pdf文件。
整個(gè)的計(jì)劃就是為了讓 Web 開(kāi)發(fā)者相對(duì)容易的理解如何使用 jQuery 和 CSS 選擇器來(lái)創(chuàng)建不同 Web 網(wǎng)站的爬蟲(chóng),我還沒(méi)有成功證明這個(gè)思路可行,但相信很快會(huì)了。
相關(guān)文章
koa2服務(wù)配置SSL的實(shí)現(xiàn)方法
這篇文章主要介紹了koa2服務(wù)配置SSL的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05vscode安裝教程以及配置node.js環(huán)境全過(guò)程
這篇文章主要給大家介紹了關(guān)于vscode安裝教程以及配置node.js環(huán)境的相關(guān)資料,VSCode是一款由微軟開(kāi)發(fā)的輕量級(jí)編輯器,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2023-10-10node.js學(xué)習(xí)總結(jié)之調(diào)式代碼的方法
調(diào)式代碼很多時(shí)候類似于查案一樣,只是結(jié)果的重要程度不同,警察查案為的是人民安穩(wěn),而我們調(diào)式則是為了系統(tǒng)的安穩(wěn)。既然這樣我們就不要冤枉任何一段代碼和程序,以免他們受到不合理的懲罰。2014-06-06完美解決node.js中使用https請(qǐng)求報(bào)CERT_UNTRUSTED的問(wèn)題
下面小編就為大家?guī)?lái)一篇完美解決node.js中使用https請(qǐng)求報(bào)CERT_UNTRUSTED的問(wèn)題。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-01-01node.js實(shí)現(xiàn)為PDF添加水印的示例代碼
這篇文章主要介紹了node.js實(shí)現(xiàn)為PDF添加水印的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-12-12Puppeteer 爬取動(dòng)態(tài)生成的網(wǎng)頁(yè)實(shí)戰(zhàn)
這篇文章主要介紹了Puppeteer 爬取動(dòng)態(tài)生成的網(wǎng)頁(yè)實(shí)戰(zhàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-11-11詳解如何模擬實(shí)現(xiàn)node中的Events模塊(通俗易懂版)
這篇文章主要介紹了如何模擬實(shí)現(xiàn)node中的Events模塊(通俗易懂版),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04