CUHK 上學期有門課叫做 Semantic Web,課程 project 是要搜集整個系裡面的教授信息,輸入到一個系統里,能夠完成諸如“如果選了A教授的課,因時間衝突,B教授的哪些課不能選”、“和A教授實驗室相鄰的實驗室都是哪些教授的”這一類的查詢。這就是所謂的“語義網”了啊。。。然而最坑爹的是,所有這些信息,老師並沒有給一個文檔或者數據庫,全要靠自己去系主頁上搜集。唯一的想法是寫個爬蟲,令人悲哀的是,所有做這個 project 的同學,都是純人肉手工完成,看得我只想扶牆。。。

從網頁中抓取特定信息,我覺得這是一個普遍性的問題,以後經常會遇到。幸虧那個 project 只是需要我們系的所有教授的信息,大家人工也就算了。如果需要抓取的信息是海量的,舉個栗子,把淘寶上所有的商品目錄抓下來,那豈不是要吐血而亡?我決定好好把爬蟲研究一下。

之前波波寫過一個 java 程序,利用 HTML Parser 去解析團購網站 meituan.com 然後把每天的團購信息存到數據庫里。稍微改改再爬爬拉手糯米,做個前端,一個團購導航站就問世了。我把程序跑了一下,全自動搜集,不算太複雜。

但是,我覺得 java 太啰嗦,不夠簡潔。Python 這個腳本語言開發起來速度很快,一個活生生的例子是因有關政策 verycd 開始自我閹割,有網友為了搶救資源,把整個 verycd 站爬了下來,鏡像為 SimpleCD.org。看了一下爬蟲 源代碼,其實挺簡單。使用方法:

python sitecopy.py http://www.163.com

看看效果:http://www.lovelucy.info/demo/www.163.com

1. 獲取html頁面

其實,最基本的抓站,兩句話就可以了

import urllib2
content = urllib2.urlopen('http://XXXX').read()

這樣可以得到整個 html 文檔,關鍵的問題是我們可能需要從這個文檔中獲取我們需要的有用信息,而不是整個文檔。這就需要解析充滿了各種標籤的 html。

2. 解析 html

SGMLParser

Python 默認自帶 HTMLParser 以及 SGMLParser 等等解析器,前者實在是太難用了,我就用 SGMLParser 寫了一個示例程序:

import urllib2
from sgmllib import SGMLParser
 
class ListName(SGMLParser):
	def __init__(self):
		SGMLParser.__init__(self)
		self.is_h4 = ""
		self.name = []
	def start_h4(self, attrs):
		self.is_h4 = 1
	def end_h4(self):
		self.is_h4 = ""
	def handle_data(self, text):
		if self.is_h4 == 1:
			self.name.append(text)
 
content = urllib2.urlopen('http://list.taobao.com/browse/cat-0.htm').read()
listname = ListName()
listname.feed(content)
for item in listname.name:
	print item.decode('gbk').encode('utf8')

很簡單,這裡定義了一個叫做 ListName 的類,繼承 SGMLParser 裡面的方法。使用一個變量 is_h4 做標記判定 html 文件中的 h4 標籤,如果遇到 h4 標籤,則將標籤內的內容加入到 List 變量 name 中。解釋一下 start_h4()end_h4() 函數,他們原型是 SGMLParser 中的

start_tagname(self, attrs)
end_tagname(self)

tagname 就是標籤名稱,比如當遇到 <pre>,就會調用 start_pre,遇到 </pre>,就會調用 end_preattrs 為標籤的參數,以 [(attribute, value), (attribute, value), ...] 的形式傳回。

輸出:

虛擬票務
數碼市場
家電市場
女裝市場
男裝市場
童裝童鞋
女鞋市場
男鞋市場
內衣市場
箱包市場
服飾配件
珠寶飾品
美容市場
母嬰市場
家居市場
日用市場
食品/保健
運動鞋服
運動戶外
汽車用品
玩具市場
文化用品市場
愛好市場
生活服務

如果有亂碼,可能是與網頁編碼不一致,需要替換最後一句 deconde() 的參數,我在香港淘寶默認用的是繁體編碼。各位可以 copy 上面的代碼自己試試,把淘寶的商品目錄抓下來,就是這麼簡單。稍微改改,就可以抽取二級分類等其他信息。

pyQuery

pyQuery 是 jQuery 在 python 中的實現,能夠以 jQuery 的語法來操作解析 HTML 文檔,十分方便。使用前需要安裝,easy_install pyquery 即可,或者 Ubuntu 下

sudo apt-get install python-pyquery

以下例子:

from pyquery import PyQuery as pyq
doc=pyq(url=r'http://list.taobao.com/browse/cat-0.htm')
cts=doc('.market-cat')
 
for i in cts:
	print '====',pyq(i).find('h4').text() ,'===='
	for j in pyq(i).find('.sub'):
		print pyq(j).text() ,
	print '\n'

BeautifulSoup

有個頭痛的問題是,大部分的網頁都沒有完全遵照標準來寫,各種莫名其妙的錯誤令人想要找出那個寫網頁的人痛打一頓。為了解決這個問題,我們可以選擇著名的 BeautifulSoup 來解析 html 文檔,它具有很好的容錯能力。

還是用例子來說明吧。我在工作時遇到一個需求,是要查詢某一個 NS 服務器的所有域名,通過搜索我找到 sitedossier.com 這個網站可以提供域名服務器的信息。那麼就要寫個爬蟲來抓查詢結果了,看上去查詢結果頁面就是一個 ol 列表,並不複雜。

sitedossier-result

有點棘手的是如果結果太多,它會進行分頁。爬蟲需要能夠(1)自動翻頁(2)知道最後一頁。最後一頁有“End of list”字符串,所以可以通過正則表達式搞定。

代碼開源在 Github 上,幾個函數都還算清晰可讀,這裡就不貼了。感覺這段腳本還是有點小用處的,比如你可以查到迄今為止有 304373 個域名在用 DNSPOD 解析,或者你想看看新浪 SAE 上到底有多少個網站,又或者了解下百度有些什麼域名,觀察域名規律搶注近似域名神馬的……

用法:

$ python crawler_ns.py -ns dns.baidu.com
# 保存結果到文件:
$ python crawler_ns.py -ns dns.baidu.com >> result.txt

BeautifulSoup 功能強大,我還在研究學習。有進展會更新本文。