分類路徑:/Datazen/DataMining/Crawler/
前段時間,一朋友讓我做個小腳本,抓一下某C2C商城上競爭對手的銷售/價格數據,好讓他可以實時調整自己的營銷策略。自己之前也有過寫爬蟲抓某寶數據的經歷,實現的問題不大,於是就答應了。初步想法是利用pyhton中的urllib.request和re兩個lib(本文示例用的是Pyhton 3.4 ,2.x的請自行切換),外加上其他的統計分析功能的話,最多兩個晚上(白天要工作)可以搞定。實際上做的過程中,遇到了兩個主要困難:
(1)電商網站對於交易數據的保護很好。小爬蟲動不動就會被ban掉或者采用一些其他的保護措施使得其無法正常采集所需的數據,需要添加額外的代碼處理各種虐心的情況;
(2)正則表達式實在是難寫,而且很復雜和很難維護。於是自己也思考有沒有其他的解決方案——本文就是對其中一解決方案的初步介紹。
一開始想到的當然是著名的第三方庫
Beautifulsoup(作為一個廣東男人,我習慣把它稱為”靚湯“)。這個庫很強大,但正因為它強大,需要一點學習時間而我需要快點上手,於是只好日后再學(到時再寫一篇Beautifulsoup學習總結)。權衡以后,最后目光轉向了Python Standard Library中的html.parser。
html.parser是一個非常簡單和實用的庫,它的核心是HTMLParser類。從源碼來看,它內部封裝了一系列regular expression。工作的流程是:當你feed給它一個類似HTML格式的字符串時,它會調用goahead方法向前迭代各個標簽,並調用對應的parse_xxxx方法提取start_tag, tag, attrs data comment和end_tag等等標簽信息和數據,然后調用對應的方法對這些抽取出來的內容進行處理。整個HTMLParser的大致結構如下圖所示:

可以發現,處理開始標簽(handle_starttag)、結束標簽(handle_endtag)和處理數據(handle_data)等處理函數在HTMLParser里是沒有實現的(pass),這需要我們繼承HTMLParser這個類的並覆蓋這些方法。詳細可以參閱python文檔,這里重點介紹幾個常用的方法:
- feed(data):主要用於接受帶html標簽的str,當調用這個方法時並提供相應的data時,整個實例(instance)開始執行,結束執行close()。
- handle_starttag(tag, attrs): 這個方法接收Parse_starttag返回的tag和attrs,並進行處理,處理方式通常由使用者進行覆蓋,本身為空。例如,連接的start tag是<a>,那么對應的參數tag=’a’(小寫)。attrs是start tag <>中的屬性,以元組形式(name, value)返回(所有這些內容都是小寫)。例如,對於<A HREF="http://www.baidu.com“>,那么內部調用形式為:handle_starttag(’a’,[(‘href’,’http://www.baidu.com)]).
- handle_endtag(tag):跟上述一樣,只是處理的是結束標簽,也就是以</開頭的標簽。
- handle_data(data):處理的是網頁的數據,也就是開始標簽和結束標簽之間的內容。例如:<script>...</script>的省略號內容
- reset():將實例重置,包括作為參數輸入的數據進行清空。
舉個例子吧。例如我們有以下一堆帶HTML標簽的數據,
<h3 class="tb-main-title" data-title="【金冠現貨/全色/頂配版】Xiaomi/小米 小米note移動聯通4G手機">
【金冠現貨/全色/頂配版】Xiaomi/小米 小米note移動聯通4G手機
</h3>
<p class="tb-subtitle">
【購機即送布丁套+高清貼膜+線控耳機+剪卡器+電影支架等等,套餐更多豪禮更優惠】 【購機即送布丁套+高清貼膜+線控耳機+剪卡器+電影支架等等,套餐更多豪禮更優惠】 【金冠信譽+順豐包郵+全國聯保---多重保障】
</p>
<div id="J_TEditItem" class="tb-editor-menu"></div>
</h3>
<p class="tb-subtitle">
【購機即送布丁套+高清貼膜+線控耳機+剪卡器+電影支架等等,套餐更多豪禮更優惠】 【購機即送布丁套+高清貼膜+線控耳機+剪卡器+電影支架等等,套餐更多豪禮更優惠】 【金冠信譽+順豐包郵+全國聯保---多重保障】
</p>
<div id="J_TEditItem" class="tb-editor-menu"></div>
</div>
<h3 class="tb-main-title" data-title="【現貨增強/標准】MIUI/小米 紅米手機2紅米2移動聯通電信4G雙卡">
【現貨增強/標准】MIUI/小米 紅米手機2紅米2移動聯通電信4G雙卡
</h3>
<p class="tb-subtitle">
[紅米手機2代顏色版本較多,請親們閱讀購買說明按需選購---感謝光臨] 【金皇冠信譽小米手機集市銷量第一】【購買套餐送高清鋼化膜+線控通話耳機+ 剪卡器(含還原卡托)+ 防輻射貼+專用高清貼膜+ 擦機布+ 耳機繞線器+手機電影支架+ 一年延保服務+ 默認享受順豐包郵 !
</p>
<div id="J_TEditItem" class="tb-editor-menu"></div>
【現貨增強/標准】MIUI/小米 紅米手機2紅米2移動聯通電信4G雙卡
</h3>
<p class="tb-subtitle">
[紅米手機2代顏色版本較多,請親們閱讀購買說明按需選購---感謝光臨] 【金皇冠信譽小米手機集市銷量第一】【購買套餐送高清鋼化膜+線控通話耳機+ 剪卡器(含還原卡托)+ 防輻射貼+專用高清貼膜+ 擦機布+ 耳機繞線器+手機電影支架+ 一年延保服務+ 默認享受順豐包郵 !
</p>
<div id="J_TEditItem" class="tb-editor-menu"></div>
</div>
很明顯,這里面包含了兩台手機,我們的目標是提取兩個手機的名字出來。
由於當我們feed這個html到HTMLParser中后,他們所有的標簽都迭代,如果需要它只提取我們需要的數據時,我們需要設置當handle_starttag遇到那個標簽和屬性時,才調用handle_data並print出我們的結果,這個時候我們可以使用一個flg作為判定,代碼如下:
#定義一個MyParser繼承自HTMLParser class MyParser(HTMLParser): re=[]#放置結果 flg=0#標志,用以標記是否找到我們需要的標簽 def handle_starttag(self, tag, attrs): if tag=='h3':#目標標簽 for attr in attrs: if attr[0]=='class' and attr[1]=='tb-main-title':#目標標簽具有的屬性 self.flg=1#符合條件則將標志設置為1 break else: pass def handle_data(self, data): if self.flg==1: self.re.append(data.strip())#如果標志為我們需要的標志,則將數據添加到列表中 self.flg=0#重置標志,進行下次迭代 else: pass my=MyParser() my.feed(html)
運行結果如下,達到了我們的預期:

上面只是HTMLParser一個非常簡單的應用,但卻可以反應了HTMLParser這個類的一些特質。有了這些基本的認識后,我們就可以將相關功能進行擴展,從而形成一個標准的爬蟲了。下次,我們將利用相關的知識,構建一個基本的網絡爬蟲,敬請期待哦。
--------------------------------------------------
本文為作者原創文章,轉摘請注明出處:@Datazen