熱點(diǎn)技術(shù):使用CasperJS構(gòu)建Web爬蟲
從你的應(yīng)用中收集數(shù)據(jù)有時(shí)候可能有點(diǎn)困難和艱辛。可能是缺少一個(gè)必須的API,或者是有太多的數(shù)據(jù)需要處理。這時(shí)候你就需要借助于web抓取。
不用說了,這可能是個(gè)法律雷區(qū),所以要確保你沒有逾越法律的邊界。
目前有很多工具可以幫助你抓取內(nèi)容,例如Import.io,但是有時(shí)這些工具并不能完全滿足你的需要。又或者,像我一樣,充滿好奇心,希望深入地了解web抓取。
挑戰(zhàn)
讓我們從一個(gè)簡(jiǎn)單地挑戰(zhàn)——網(wǎng)絡(luò)爬蟲開始,讓這個(gè)爬蟲爬取Techmeme,并獲得一個(gè)當(dāng)天熱門新聞列表!
注意: 在這里我將會(huì)使用DZone,但在獲取頁面時(shí)會(huì)出現(xiàn)問題。后面會(huì)詳細(xì)說明這個(gè)問題。
機(jī)器設(shè)置
您只需要做很少的工作來完成安裝。我假設(shè)您已經(jīng)安裝了Node.js(我的意思是誰沒有安裝呢?。?。盡管我們并不直接使用PhantomJS,但是您依然需要安裝它。版本2.0.1目前已經(jīng)可以使用了——您可以從其官網(wǎng)下載或使用homebrew或其他等效的包管理器安裝。
如果您使用具有homebrew的Mac,您可以這樣安裝PhantomJS
brew install phantomjs
下載完成之后,您將需要用相同的方式安裝CasperJS。您可以將CasperJS看做PhantomJS的伴侶。它實(shí)際上是給您提供相似的網(wǎng)頁處理API。盡管它是為網(wǎng)頁測(cè)試設(shè)計(jì)的。與PhantomJS相同,它具有豐富的功能使其也非常適合于抓取內(nèi)容。
CasperJS允許我們編寫JavaScript腳本。您可以通過在終端中輸入casperjs以測(cè)試其是否正確安裝并加入到PATH中。
編寫腳本
下面我們將編寫一個(gè)新的JavaScript腳本文件。在我的例子中,我稱其為index.js。您需要做的***件事就是在您的代碼中創(chuàng)建一個(gè)casper實(shí)例。您還需要加入依賴的模塊并向其傳遞一些基本參數(shù)。
- var casper = require("casper").create({
- waitTimeout: 10000,
- stepTimeout: 10000,
- verbose: true,
- pageSettings: {
- webSecurityEnabled: false
- },
- onWaitTimeout: function() {
- this.echo('** Wait-TimeOut **');
- },
- onStepTimeout: function() {
- this.echo('** Step-TimeOut **');
- }
- });
當(dāng)您等待一個(gè)元素可見時(shí),上面的onWaitTimeout回調(diào)將會(huì)被調(diào)用。例如,點(diǎn)擊一個(gè)按鈕之后,waitTimeout將被超出。
現(xiàn)在,您可以啟動(dòng)casper實(shí)例并將其指向我們希望爬取的頁面。
- casper.start();
- casper.open("http://techmeme.com");
Casper使用一個(gè)可靠地框架來幫助您一步一步地運(yùn)行所有任務(wù)。對(duì)于***步,您將希望使用then函數(shù)。
- casper.then(function() {
- //logic here
- });
- //start your script
- casper.run();
為了使Casper打開網(wǎng)頁并按您的想法運(yùn)行,您需要調(diào)用run函數(shù)。
檢查網(wǎng)頁以獲取想要的元素
當(dāng)抓取到一個(gè)網(wǎng)頁,您可以假設(shè)它具有特定的結(jié)構(gòu)。在您編寫腳本之前,可能已經(jīng)瀏覽過了網(wǎng)頁的源代碼,或者已經(jīng)使用開發(fā)者工具觀察了頁面對(duì)特定行為的變化。
所以,讓我們開始于一個(gè)簡(jiǎn)單地邏輯,使用CasperJS維護(hù)系統(tǒng)確保一個(gè)特定的元素在繼續(xù)之前處于合適的位置。如果元素不存在,腳本將會(huì)停止,但 是至少您將會(huì)知道其為何停止。這個(gè)維護(hù)行為對(duì)于觀察您之前抓取頁面的變化是無價(jià)的,但是可能會(huì)與您之前見到的頁面具有不一樣的結(jié)構(gòu)。
如果您檢查了Techmeme首頁的元素,您將會(huì)注意到頭條新聞部分在一個(gè)id為,topcol1的div中。
讓我們使用維護(hù)功能確保這個(gè)元素存在:
- casper.then(function() {
- this.test.assertExists("#topcol1");
如果這個(gè)元素不存在,測(cè)試(例如我們的腳本)將會(huì)停止,否則它將繼續(xù)運(yùn)行。
您還可以使用waitForSelector函數(shù)來獲得更為細(xì)致的結(jié)果:
- this.waitForSelector("#topcol1",
- function pass () {
- console.log("Continue");
- },
- function fail () {
- this.die("Did not load element... something is wrong");
- }
- );
使用這個(gè)函數(shù)的優(yōu)點(diǎn)就是它允許頁面加載元素并一直等待到執(zhí)行。您在初始配置中指定的waitTimeout將會(huì)被用于確定失敗前等待多久。
注意:有時(shí),使用CasperJS查找元素可能會(huì)出問題。使用capture()函數(shù)截取一個(gè)CasperJS看到的頁面的截圖。
this.capture(‘screener.png’);
從頁面中提取內(nèi)容
下面,讓我們看看怎樣從頁面中找出標(biāo)題。首先,找到包含您需要的內(nèi)容的元素,在我們的例子中,為class=ii的div。
CasperJS自帶一個(gè)evaluate函數(shù),可以讓您在頁面中運(yùn)行JavaScript,并且您還可以讓函數(shù)返回一個(gè)值以供進(jìn)一步處理。
這個(gè)JavaScript寫起來并沒有什么不同,您可能注意到,在本例中,我使用的是原始的純DOM方法,而不是jQuery,同樣,如果您愿意,您也可以在evaluate函數(shù)中使用jQuery;
- var links = this.evaluate(function(){
- var results = [];
- var elts = document.getElementsByClassName("ii");
- for(var i = 0; i < elts.length; i++){
- var link = elts[i].getElementsByTagName("a")[0].getAttribute("href");
- var headline = elts[i].firstChild.textContent;
- results.push({link: link, headline: headline});
- }
- return results;
- });
如果您在evaluate函數(shù)中使用console.log語句,它們將會(huì)通過remote.message句柄打印到您的控制臺(tái),這將會(huì)在下一節(jié)中詳細(xì)介紹。
一旦運(yùn)行結(jié)束,結(jié)果將會(huì)返回給您。您可以將它們寫入文件系統(tǒng),或者將它們打印到屏幕上:
- console.log("There were " + links.length + " stories");
- for(var i = 0; i < links.length; i++){
- console.log(links[i].headline);
- }
輸出的結(jié)果如:
抓取中的錯(cuò)誤處理
有時(shí),您運(yùn)行的JavaScript中可能存在錯(cuò)誤,或者其對(duì)您抓取的頁面的處理存在問題。這些情況中,您可以捕獲錯(cuò)誤并使用remote.message和page.error事件將其打印到控制臺(tái):
- casper.on('remote.message', function(msg) {
- this.echo('remote message caught: ' + msg);
- });
- casper.on('page.error', function(msg, trace) {
- this.echo('Error: ' + msg, 'ERROR');
- });
- 您同樣還能觀察到即將請(qǐng)求的資源,這些資源的加載使用的是resource.error和resource.received事件:
- casper.on('resource.error', function(msg) {
- this.echo('resource error: ' + msg);
- });
- casper.on('resource.received', function(resource) {
- console.log(resource.url);
- });
了解更多
本文只寫了點(diǎn)關(guān)于使用CasperJS你所能做到的皮毛的東西。該項(xiàng)目的文檔是***的,所以要確保查看過 API ,看看你還可以用它來做些什么。
在本系列的下一篇文章中,我回來看看如何從網(wǎng)頁上下載圖片,而且我也會(huì)討論下如何使用構(gòu)建到CasperJS中的文件系統(tǒng)函數(shù),其使用會(huì)受到比Node.js更多的限制。