標籤:style blog http io color ar os 使用 java
由於最近要實現一個爬取H5遊戲的Proxy 伺服器,隧看到這麼一篇不錯的文章(http://blog.miguelgrinberg.com/post/easy-web-scraping-with-nodejs),加之最近在學習Node.js,所以就準備翻譯出來加深一下印象。
轉載請註明來源:(www.cnblogs.com/xdxer )
在這篇文章中,我將會向大家展示如何用JavaScript配合Node.js寫一個網路爬取的指令碼。
網路抓取工具
在大部分情況下,一個網路抓取的指令碼只需要一種方法去下載整個頁面,然後搜尋裡面的資料。所有的現代語言都會提供方法去下載網頁,或者至少會有人實現了某個library或者一些其他的擴充包,所以這並不是一個痛點。然後,要精確定位並且找出在HTML中的資料是比較有難度的。一個HTML頁面混雜了許多內容、布局和樣式的變數,所以去解釋並且識別出那些我們關注的部分是一個挺不容易的工作。
舉個例子,考慮如下的HTML頁面:
<html> <head>...</head> <body> <div id="content"> <div id="sidebar"> ... </div> <div id="main"> <div class="breadcrumbs"> ... </div> <table id="data"> <tr><th>Name</th><th>Address</th></tr> <tr><td class="name">John</td><td class="address">Address of John</td></tr> <tr><td class="name">Susan</td><td class="address">Address of Susan</td></tr> </table> </div> </div> </body></html>
如果我們需要擷取到出現在 id = “data”這個表中的人名,那麼應該怎麼做呢?
一般的,網頁會被下載成一個字串的形式,然後只需要很簡單的對這個網頁進行檢索,檢索出那些出現在<td class = “name”> 之後,以</td>結尾的字串就可以了。
但是這種方式很容易會擷取到不正確的資料。網頁可能會有別的table,或者更加糟糕的是,原先的<td class="name"> 變成了 <td align="left" class="name"> ,這將會讓我們之前所制定的方案什麼都找不到。雖然說網頁的變化很容易導致一個爬取指令碼失效,但是假如我們可以清楚的知道元素是如何在HTML中組織的,那麼我們就不必總是重寫我們的爬取指令碼,當網頁改變的時候。
如果你寫過前端的js代碼,使用過jQuery,那麼你就會發現使用CSS selector 來選擇DOM中的元素是一件非常簡單的事情。舉個例子,在瀏覽器中,我們可以很簡單的爬取到那些名字使用如下的方式:
$(‘#data .name‘).each(function() { alert($(this).text());});
介紹一下Node.js
http://nodejs.org (get it here!)
Javascript 是一個嵌入web瀏覽器的語言,感謝Node.js工程,我們現在可以編寫能夠獨立運行,並且甚至可以作為一個web server 的程式設計語言。
有很多現成的庫,例如jQuery那樣的。所以使用Javrscript+Node.js去實現這麼一個任務就非常便利了,因為我們可以使用那些現有的操作DOM元素的技術,這些技術在web瀏覽器上已經應用的比較成熟了。
Node.js有很多的庫,它是模組化的。本例子中需要用到兩個庫,request 和 cheerio。 request主要是用於下載那些網頁,cheerio 會在本地產生一棵DOM樹,然後提供一個jQuery子集去操作它們。安裝Node.js模組需要用到npm操作,類似於Ruby的gem 或者 Python的easy_install
有關於cheerio的一些API 可以參考這一篇CNode社區的文章 (https://cnodejs.org/topic/5203a71844e76d216a727d2e)
$ mkdir scraping$ cd scraping$ npm install request cheerio
如以上代碼所示,首先我們建立了一個目錄“scraping”,並且我們在在這個目錄下安裝了request 和 cheerio模組,事實上,nodejs的模組是可以進行全域性的安裝的,但是我更加喜歡locally的安裝,安裝的效果如所示。
那接下來我們就看看如何使用cheerio,來爬取上面的例子中的name,我們建立一個.js檔案 example.js,代碼如下:
var cheerio = require(‘cheerio‘);$ = cheerio.load(‘<html><head></head><body><div id="content">
<div id="sidebar"></div><div id="main">
<div id="breadcrumbs"></div><table id="data"><tr>
<th>Name</th><th>Address</th></tr><tr><td class="name">
John</td><td class="address">Address of John</td></tr>
<tr><td class="name">Susan</td><td class="address">
Address of Susan</td></tr></table></div></div></body></html>‘);$(‘#data .name‘).each(function() { console.log($(this).text());});
輸出如下:
$ node example.jsJohnSusan
實際例子
http://www.thprd.org/schedules/schedule.cfm?cs_id=15 爬取這個網站中的議程表
代碼如下:
var request = require(‘request‘);var cheerio = require(‘cheerio‘);days = [‘Monday‘, ‘Tuesday‘, ‘Wednesday‘, ‘Thursday‘, ‘Friday‘, ‘Saturday‘, ‘Sunday‘];pools = { ‘Aloha‘: 3, ‘Beaverton‘: 15, ‘Conestoga‘: 12, ‘Harman‘: 11, ‘Raleigh‘: 6, ‘Somerset‘: 22, ‘Sunset‘: 5, ‘Tualatin Hills‘: 2};for (pool in pools) { var url = ‘http://www.thprd.org/schedules/schedule.cfm?cs_id=‘ + pools[pool]; request(url, (function(pool) { return function(err, resp, body) { $ = cheerio.load(body); $(‘#calendar .days td‘).each(function(day) { $(this).find(‘div‘).each(function() { event = $(this).text().trim().replace(/\s\s+/g, ‘,‘).split(‘,‘); if (event.length >= 2 && (event[1].match(/open swim/i) || event[1].match(/family swim/i))) console.log(pool + ‘,‘ + days[day] + ‘,‘ + event[0] + ‘,‘ + event[1]); }); }); }})(pool));}
輸出如下:
$ node thprd.jsConestoga,Monday,4:15p-5:15p,Open Swim - M/LConestoga,Monday,7:45p-9:00p,Open Swim - M/LConestoga,Tuesday,7:30p-9:00p,Open Swim - M/LConestoga,Wednesday,4:15p-5:15p,Open Swim - M/LConestoga,Wednesday,7:45p-9:00p,Open Swim - M/LConestoga,Thursday,7:30p-9:00p,Open Swim - M/LConestoga,Friday,6:30p-8:30p,Open Swim - M/LConestoga,Saturday,1:00p-4:15p,Open Swim - M/LConestoga,Sunday,2:00p-4:15p,Open Swim - M/LAloha,Monday,1:05p-2:20p,Open SwimAloha,Monday,7:50p-8:25p,Open SwimAloha,Tuesday,1:05p-2:20p,Open SwimAloha,Tuesday,8:45p-9:30p,Open SwimAloha,Wednesday,1:05p-2:20p,Open SwimAloha,Wednesday,7:50p-8:25p,Open SwimAloha,Thursday,1:05p-2:20p,Open SwimAloha,Thursday,8:45p-9:30p,Open SwimAloha,Friday,1:05p-2:20p,Open SwimAloha,Friday,7:50p-8:25p,Open SwimAloha,Saturday,2:00p-3:30p,Open SwimAloha,Saturday,4:30p-6:00p,Open SwimAloha,Sunday,2:00p-3:30p,Open SwimAloha,Sunday,4:30p-6:00p,Open SwimHarman,Monday,4:25p-5:30p,Open Swim*Harman,Monday,7:30p-8:55p,Open SwimHarman,Tuesday,4:25p-5:10p,Open Swim*Harman,Wednesday,4:25p-5:30p,Open Swim*Harman,Wednesday,7:30p-8:55p,Open SwimHarman,Thursday,4:25p-5:10p,Open Swim*Harman,Friday,2:00p-4:55p,Open Swim*Harman,Saturday,1:30p-2:25p,Open SwimHarman,Sunday,2:00p-2:55p,Open SwimBeaverton,Tuesday,10:45a-12:55p,Open Swim (No Diving Well)Beaverton,Tuesday,8:35p-9:30p,Open Swim No Diving WellBeaverton,Thursday,10:45a-12:55p,Open Swim (No Diving Well)Beaverton,Thursday,8:35p-9:30p,Open Swim No Diving WellBeaverton,Saturday,2:30p-4:00p,Open SwimBeaverton,Sunday,4:15p-6:00p,Open SwimSunset,Tuesday,1:00p-2:30p,Open Swim/One Lap LaneSunset,Thursday,1:00p-2:30p,Open Swim/One Lap LaneSunset,Sunday,1:30p-3:00p,Open Swim/One Lap LaneTualatin Hills,Monday,7:35p-9:00p,Open Swim-Diving area opens at 8pmTualatin Hills,Wednesday,7:35p-9:00p,Open Swim-Diving area opens at 8pmTualatin Hills,Sunday,1:30p-3:30p,Open SwimTualatin Hills,Sunday,4:00p-6:00p,Open Swim
要注意的幾個問題: 非同步js的範圍問題,還有對網站結構的分析,我會在其他部落格中提到。
其實我只翻譯了很少的一部分,有興趣的可以去看一下原文,每一步都說的很仔細。
使用Node.js實現簡單的網路爬取