[英]What is the best practice for writing maintainable web scrapers?
我需要實現一些爬蟲來抓取一些網頁(因為該站點沒有開放的 API),提取信息並保存到數據庫。 我目前正在使用美麗的湯來編寫這樣的代碼:
discount_price_text = soup.select("#detail-main del.originPrice")[0].string;
discount_price = float(re.findall('[\d\.]+', discount_price_text)[0]);
我想這樣的代碼很容易在網頁發生變化時變得無效,即使是輕微的變化。 除了編寫回歸測試以定期運行以捕獲故障之外,我應該如何編寫不易受這些變化影響的抓取工具?
特別是,即使原始 xpath/css 選擇器不再有效,是否有任何現有的“智能刮刀”可以“盡力猜測”?
頁面有可能發生如此巨大的變化,以至於構建一個非常“智能”的爬蟲可能非常困難; 如果可能的話,即使使用機器學習等奇特的技術,scraper 也會有些不可預測。 很難制作出兼具可信賴性和自動化靈活性的刮刀。
可維護性在某種程度上是一種圍繞如何定義和使用選擇器的藝術形式。
過去,我推出了自己的“兩階段”選擇器:
(查找)第一階段非常不靈活,它會檢查頁面結構以尋找所需元素。 如果第一階段失敗,則會拋出某種“頁面結構已更改”錯誤。
(檢索)第二階段有點靈活,從頁面上的所需元素中提取數據。
這允許刮板通過某種程度的自動檢測將自己與劇烈的頁面變化隔離開來,同時仍然保持一定程度的可信賴的靈活性。
我經常使用 xpath 選擇器,它真的很令人驚訝,通過一些練習,你可以使用一個好的選擇器有多靈活,同時仍然非常准確。 我確信 css 選擇器是相似的。 頁面設計越語義化和“扁平化”,這就越容易。
需要回答的幾個重要問題是:
您希望頁面上發生什么變化?
您希望頁面上的哪些內容保持不變?
在回答這些問題時,您越准確,您的選擇器就越好。
最后,您可以選擇承擔多少風險,選擇器的可信度如何,當在頁面上查找和檢索數據時,您如何制作它們會產生很大的不同; 理想情況下,最好從 web-api 獲取數據,希望更多來源將開始提供。
編輯:小例子
使用您的場景,您想要的元素位於.content > .deal > .tag > .price
,一般.content .price
選擇器在頁面更改方面非常“靈活”; 但是,如果出現誤報元素,我們可能希望避免從這個新元素中提取。
使用兩階段選擇器,我們可以指定一個不太通用、更不靈活的第一階段,比如.content > .deal
,然后是第二個更通用的階段,比如.price
使用相對於第一個結果的查詢來檢索最終元素。
那么為什么不直接使用像.content > .deal .price
這樣的選擇器呢?
對於我的使用,我希望能夠檢測大頁面更改而無需單獨運行額外的回歸測試。 我意識到我可以編寫第一個階段來包含重要的頁面結構元素,而不是一個大的選擇器。 如果結構元素不再存在,則第一階段將失敗(或報告)。 然后我可以編寫第二階段來更優雅地檢索與第一階段結果相關的數據。
我不應該說這是一種“最佳”實踐,但它運作良好。
與 Python 完全無關,也不是自動靈活的,但我認為我的Xidel 刮板的模板具有最好的可維護性。
你會這樣寫:
<div id="detail-main">
<del class="originPrice">
{extract(., "[0-9.]+")}
</del>
</div>
模板的每個元素都與網頁上的元素匹配,如果它們相同,則評估{}
中的表達式。
頁面上的其他元素將被忽略,因此如果您找到包含元素和刪除元素的正確平衡,模板將不會受到所有微小更改的影響。 另一方面,重大更改將觸發匹配失敗,這比 xpath/css 只返回一個空集要好得多。 然后您可以在模板中僅更改更改的元素,在理想情況下,您可以直接將舊/更改頁面之間的差異應用於模板。 在任何情況下,您都不需要搜索哪個選擇器受到影響或為單個更改更新多個選擇器,因為模板可以包含單個頁面的所有查詢。
編輯:哎呀,我現在看到你已經在使用 CSS 選擇器了。 我認為它們為您的問題提供了最佳答案。 所以不,我認為沒有更好的方法。
但是,有時您可能會發現沒有結構更容易識別數據。 例如,如果您想抓取價格,則可以進行匹配價格( \\$\\s+[0-9.]+
)的正則表達式搜索,而不是依賴結構。
就我個人而言,我嘗試過的開箱即用的網頁抓取庫都有一些令人渴望的東西(機械化、 Scrapy和其他)。
我通常自己動手,使用:
cssselect 允許您使用 CSS 選擇器(就像 jQuery)來查找特定的 div、表格等。 這證明是非常寶貴的。
從 SO 主頁獲取第一個問題的示例代碼:
import urllib2
import urlparse
import cookielib
from lxml import etree
from lxml.cssselect import CSSSelector
post_data = None
url = 'http://www.stackoverflow.com'
cookie_jar = cookielib.CookieJar()
http_opener = urllib2.build_opener(
urllib2.HTTPCookieProcessor(cookie_jar),
urllib2.HTTPSHandler(debuglevel=0),
)
http_opener.addheaders = [
('User-Agent', 'Mozilla/5.0 (X11; Linux i686; rv:25.0) Gecko/20100101 Firefox/25.0'),
('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'),
]
fp = http_opener.open(url, post_data)
parser = etree.HTMLParser()
doc = etree.parse(fp, parser)
elem = CSSSelector('#question-mini-list > div:first-child > div.summary h3 a')(doc)
print elem[0].text
當然,您不需要 cookiejar,也不需要用戶代理來模擬 FireFox,但是我發現在抓取站點時我經常需要它。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.