前言
從新數據庫 mongodb 到基於內存的 key-value 數據庫 Redis,從 scrapy 爬蟲框架到 re 正則表達式模塊,尤其正則,以前不會寫的時候總是依賴 string 的各種方法,部分時候顯得有些繁瑣,會正則了之后在字符串的匹配、查找、替換、分隔方面打開了另一扇便捷之窗。另外,將 Redis 引入爬蟲架構來實現分布式,也算是一個技術理念的突破吧,也為后面研究高並發站點打下了基礎。
其實前面說了那么多唬人的東西,用的時候倒是沒有說有實在適應不來的,作為一個敲了快三年代碼的磚工來說算不上什么技術攻堅,可能在 Nosql 和正則模塊(包括正則語法和 re 模塊)投入的精力相對多一點。業務上,對於網頁結構的分析和一個高覆蓋率 url 爬取策略的制定還是得跟有經驗的開發人員多討論,就爬蟲來說,技術不是重點,更多的是對站點頁面跳轉套路的分析。
下面就結合實際開發中常遇到的一些問題分節對爬蟲這塊的注意事項做一個簡要講解。
取消合法性檢驗以改進爬取方案
我這里要講的是在確認程序語法無誤后,以故意制造 AtttributeError 來完善爬取策略,算不上什么方案,一點寫法上的小技巧吧。
這個報錯具體解釋起來就是 'NoneType' object has no attribute ,類似於 java 里面的空指針異常。首先,解決這個異常的方式很簡單,比如讓 soup 在查找上一級標簽的時候做一次是否為空的判斷,如果不為空則繼續查找到下一級目標:(if parentNode: ...)。但實際上最開始寫這塊內容的時候要避免這種條件判斷,因為我們並不知道我們要找的目標在該類網頁當中是否只存在一種結構,有可能有的頁面壓根兒沒那個標簽,也有可能標簽名不一樣或者類名變了等等情況,這需要我們在測試過程中去記錄下所有出錯的頁面,逐一排查這些頁面的特殊結構,這樣才能有效地保證我們所爬取的頁面覆蓋面更廣,數據更全。下面拿實際代碼舉個例子:
soup = BeautifulSoup(response.body_as_unicode(),'lxml')
citys = soup.find('div',class_="piList").find_all('span') for city in citys: href = city.find('a').get('href')
yield Request(href, callback=self.get_url)
這里在獲取網頁中城市的鏈接的時候,並沒有首先去對 span 的祖先(前端術語) class 為 piList 是否存在做判斷,因為在爬這類的網頁的時候當然會有可能說是部分頁面的城市信息壓根不放在這里面,類名為 piList 的標簽壓根不存在,這時候程序會在這里報 AtttributeError 但不會影響爬蟲的繼續運行,等整個程序執行完畢,scrapy 會有一個總的出錯統計在日志的末尾,我們就順着總數一一找出出錯位置對應的頁面 url 再去瀏覽器找到該頁面,重新分析其文檔結構,或改類名,或改標簽名,最后完善成類似下面的代碼:
soup = BeautifulSoup(response.body_as_unicode(),'lxml') city_list = soup.find('div',class_="piList")
if not city_list:
city_list = soup.find('div',id="citys")
citys = city_list.find_all('span') for city in citys: href = city.find('a').get('href') yield Request(href, callback=self.get_url)
關於使用瀏覽器開發者工具查看網頁
我們在用瀏覽器的開發者工具分析網頁的時候,最好是在 Sources 下面查看網站源碼,因為我們爬蟲爬下來的內容是未經瀏覽器渲染的(盡管有這樣的工具,比如 PyQt 中的 webkit)。但是大多數情況下我們在 Elements 審查/檢查 窗口看到的內容與 Sources 下面是一致的,而且在這下面進行標簽的查看和文檔結構的分析也更方便,所以可在其下面定位目標后到 Sources 中確認是否能找到,再確定 beautifulsoup 或 xpath 的寫法,尤其對於翻頁和通過事件響應跳出的彈窗。