可愛的 Python: 使用 mechanize 和 Beautiful Soup 輕松收集 Web 數據
使用 Python 工具簡化 Web 站點數據的提取和組織
簡介:?需要從 Web 頁面收集數據時,可以使用 mechanize 庫自動從 Web 站點收集數據并與之交互。通過 Mechanize 您可以填充表單并設置和保存 cookies,此外,Mechanize 提供了各種各樣的工具來使 Python 腳本具備真正的 Web 瀏覽器的功能,可以與交互式 Web 站點進行交互。一款名為 Beautiful Soup 的常用配套工具幫助 Python 程序理解 Web 站點中包含的臟亂“基本是 HTML” 內容。
查看本系列更多內容
本文的標簽:? python, web
?
?
發布日期:?2010 年 1 月 18 日
級別:?中級
其他語言版本:?英文
訪問情況?4309 次瀏覽
建議:?0?(添加評論)





?
使用基本的 Python 模塊,可以 編寫腳本來與 Web 站點交互,但是如果沒有必要的話,那么您就不希望這樣做。Python 2.x 中的模塊 urllib
和 urllib2
,以及 Python 3.0 中的統一的 urllib.*
子包,可以在 URL 的末尾獲取資源。然而,當您希望與 Web 頁面中找到的內容進行某種比較復雜的交互時,您需要使用 mechanize 庫(參見 參考資料 獲得下載鏈接)。
在自動化 Web scrap 或用戶與 Web 站點的交互模擬中,最大的困難之一就是服務器使用 cookies 跟蹤會話進度。顯然,cookies 是 HTTP 頭部的一部分,在 urllib
打開資源時會自然顯示出來。而且,標準模塊 Cookie
(Python 3 中的 http.cookie
)和 cookielib
(Python 3 中的 http.cookiejar
)有助于在比原始的文本處理更高的層次上處理這些頭部。即使如此,在這個層次上執行處理也非常的繁瑣。mechanize 庫將這種處理提升到一個更高程度的抽象并使您的腳本 — 或交互性 Python shell — 表現出非常類似實際 Web 瀏覽器的行為。
Python 的 mechanize 受到 Perl 的 WWW:Mechanize
的啟發,后者具有類似的一組功能。當然,作為長期的 Python 支持者,我認為 mechanize 更健壯,它看上去似乎繼承了兩種語言的通用模式。
mechanize 的一個親密伙伴是同樣出色的 Beautiful Soup 庫(參見 參考資料 獲得下載鏈接)。這是一個非常神奇的 “粗糙的解析器”,用于解析實際 Web 頁面中包含的有效 HTML。您不需要 將 Beautiful Soup 用于 mechanize,反之亦然,但是多半情況下,當您與 “實際存在的 Web” 交互時,您將希望同時使用這兩種工具。
一個實際示例
我曾在多個編程項目中使用過 mechanize。最近一個項目是從一個流行的 Web 站點中收集匹配某種條件的名稱的列表。該站點提供了一些搜索工具,但是沒有提供任何正式的 API 來執行此類搜索。雖然訪問者可能能夠更明確地猜出我過去在做什么,但我將修改給出的代碼的細節,以避免暴露有關被 scrap 的站點或我的客戶機的過多信息。一般情況下,我給出的代碼對于類似任務是通用的。
回頁首
入門工具
在實際開發 Web scrap/分析代碼的過程中,我發現以交互式方式查看、處理和分析 Web 頁面的內容以了解相關 Web 頁面實際發生的操作是非常重要的功能。通常,站點中的一些頁面是由查詢動態生成(但是具有一致的模式),或是根據非常嚴格的模板預先生成。
完成這種交互式體驗的一種重要方法就是在 Python shell 內使用 mechanize 本身,特別是在一個增強的 shell 內,比如 IPython(參見 參考資料 獲得一個鏈接)。通過這種方式,您可以在編寫執行希望用于生產中的交互的最終腳本之前,請求各種已鏈接的資源、提交表單、維護或操作站點 cookies,等等。
然而,我發現我與 Web 站點的許多實驗性質的交互在實際的現代 Web 瀏覽器中得到了更好的執行。方便地呈現頁面可以使您更加快速地了解給定頁面或表單中正在發生的事情。問題在于,呈現頁面僅僅完成了事情的一半,可能還不到一半。獲得 “頁面源代碼” 會讓您更進一步。要真正理解給定 Web 頁面或與 Web 服務器的一系列交互的背后的原理,需要了解更多。
要了解這些內容,我常常使用 Firebug(參見 參考資料 獲得鏈接)或面向 Firefox 的 Web Developer 插件(或最新 Safari 版本中的內置的可選 Develop 菜單,但是所針對的目標人群不同)。所有這些工具都可以執行諸如顯示表單字段、顯示密碼、檢查頁面的 DOM、查看或運行 Javascript、觀察 Ajax 通信等操作。比較這些工具的優劣需要另外撰寫一篇文章,但是如果您要進行面向 Web 的編程的話,那么必須熟悉這些工具。
不管使用哪一種工具來對準備實現自動化交互的 Web 站點做實驗,您都需要花比編寫簡潔的 mechanize 代碼(用于執行您的任務)更多的時間來了解站點實際發生的行為。
回頁首
搜索結果 scraper
考慮到上面提到的項目的意圖,我將把包含 100 行代碼的腳本分為兩個功能:
- 檢索所有感興趣的結果
- 從被檢索的頁面中拉取我感興趣的信息
使用這種方式組織腳本是為了便于開發;當我開始任務時,我需要知道如何完成這兩個功能。我覺得我需要的信息位于一個普通的頁面集合中,但是我還沒有檢查這些頁面的具體布局。
首先我將檢索一組頁面并將它們保存到磁盤,然后執行第二個任務,從這些已保存的文件中拉取所需的信息。當然,如果任務涉及使用檢索到的信息構成同一會話內的新交互,那么您將需要使用順序稍微不同的開發步驟。
因此,首先讓我們查看我的 fetch()
函數:
清單 1. 獲取頁面內容
import sys, time, os from mechanize import BrowserLOGIN_URL = 'http://www.example.com/login' USERNAME = 'DavidMertz' PASSWORD = 'TheSpanishInquisition' SEARCH_URL = 'http://www.example.com/search?' FIXED_QUERY = 'food=spam&' 'utensil=spork&' 'date=the_future&' VARIABLE_QUERY = ['actor=%s' % actor for actor in('Graham Chapman','John Cleese','Terry Gilliam','Eric Idle','Terry Jones','Michael Palin')]def fetch():result_no = 0 # Number the output filesbr = Browser() # Create a browserbr.open(LOGIN_URL) # Open the login pagebr.select_form(name="login") # Find the login formbr['username'] = USERNAME # Set the form valuesbr['password'] = PASSWORDresp = br.submit() # Submit the form# Automatic redirect sometimes fails, follow manually when neededif 'Redirecting' in br.title():resp = br.follow_link(text_regex='click here')# Loop through the searches, keeping fixed query parametersfor actor in in VARIABLE_QUERY:# I like to watch what's happening in the consoleprint >> sys.stderr, '***', actor# Lets do the actual query nowbr.open(SEARCH_URL + FIXED_QUERY + actor)# The query actually gives us links to the content pages we like,# but there are some other links on the page that we ignorenice_links = [l for l in br.links()if 'good_path' in l.urland 'credential' in l.url]if not nice_links: # Maybe the relevant results are emptybreakfor link in nice_links:try:response = br.follow_link(link)# More console reporting on title of followed link pageprint >> sys.stderr, br.title()# Increment output filenames, open and write the fileresult_no += 1out = open(result_%04d' % result_no, 'w')print >> out, response.read()out.close()# Nothing ever goes perfectly, ignore if we do not get pageexcept mechanize._response.httperror_seek_wrapper:print >> sys.stderr, "Response error (probably 404)"# Let's not hammer the site too much between fetchestime.sleep(1) |
對感興趣的站點進行交互式研究后,我發現我希望執行的查詢含有一些固定的元素和一些變化的元素。我僅僅是將這些元素連接成一個大的 GET
請求并查看 “results” 頁面。而結果列表包含了我實際需要的資源的鏈接。因此,我訪問這些鏈接(當此過程出現某些錯誤時,會拋出 try
/except
塊)并保存在這些內容頁面上找到的任何內容。
很簡單,是不是?Mechanize 可以做的不止這些,但是這個簡單的例子向您展示了 Mechanize 的大致功能。
回頁首
處理結果
現在,我們已經完成了對 mechanize 的操作;剩下的工作是理解在 fetch()
循環期間保存的大量 HTML 文件。批量處理特性讓我能夠在一個不同的程序中將這些文件整齊、明顯地分離開來,fetch()
和 process()
可能交互得更密切。Beautiful Soup 使得后期處理比初次獲取更加簡單。
對于這個批處理任務,我們希望從獲取的各種 Web 頁面的零散內容中生成表式的以逗號分隔的值(CSV)數據。
清單 2. 使用 Beautiful Soup 從無序的內容中生成整齊的數據
from glob import glob from BeautifulSoup import BeautifulSoupdef process():print "!MOVIE,DIRECTOR,KEY_GRIP,THE_MOOSE"for fname in glob('result_*'):# Put that sloppy HTML into the soupsoup = BeautifulSoup(open(fname))# Try to find the fields we want, but default to unknown valuestry:movie = soup.findAll('span', {'class':'movie_title'})[1].contents[0]except IndexError:fname = "UNKNOWN"try:director = soup.findAll('div', {'class':'director'})[1].contents[0]except IndexError:lname = "UNKNOWN"try:# Maybe multiple grips listed, key one should be in theregrips = soup.findAll('p', {'id':'grip'})[0]grips = " ".join(grips.split()) # Normalize extra spacesexcept IndexError:title = "UNKNOWN"try:# Hide some stuff in the HTML <meta> tagsmoose = soup.findAll('meta', {'name':'shibboleth'})[0]['content']except IndexError:moose = "UNKNOWN"print '"%s","%s","%s","%s"' % (movie, director, grips, moose) |
第一次查看 Beautiful Soup,process()
中的代碼令人印象深刻。讀者應當閱讀有關文檔來獲得關于這個模塊的更多細節,但是這個代碼片段很好地體現了它的整體風格。大多數 soup 代碼包含一些對只含有格式良好的 HTML 的頁面的 .findAll()
調用。這里是一些類似 DOM 的 .parent
、nextSibling
和 previousSibling
屬性。它們類似于 Web 瀏覽器的 “quirks” 模式。我們在 soup 中找到的內容并不完全 是一個解析樹。
回頁首
結束語
諸如我之類的守舊者,甚至于一些更年輕的讀者,都會記住使用 TCL Expect(或使用用 Python 和其他許多語言編寫的類似內容)編寫腳本帶來的愉悅。自動化與 shell 的交互,包括 telnet、ftp、ssh 等等遠程 shell,變得非常的直觀,因為會話中的所有內容都被顯示出來。Web 交互變得更加細致,因為信息被分為頭部和內容體,并且各種相關的資源常常通過 href
鏈接、框架、Ajax 等被綁定在一起。然而,總的來說,您可以 使用 wget
之類的工具來檢索 Web 服務器提供的所有字節,然后像使用其他連接協議一樣運行與 Expect 風格完全相同的腳本。
在實踐中,幾乎沒有編程人員過分執著于過去的老方法,比如我建議的 wget
+ Expect 方法。Mechanize 保留了許多與出色的 Expect 腳本相同的東西,令人感覺熟悉和親切,并且和 Expect 一樣易于編寫(如果不是更簡單的話)。Browser()
對象命令,比如 .select_form()
、.submit()
和 .follow_link()
,真的是實現 “查找并發送” 操作的最簡單、最明顯的方法,同時綁定了我們希望在 Web 自動化框架中具備的復雜狀態和會話處理的所有優點。