1. 程式人生 > 實用技巧 >新手小白必看,3種網頁抓取方法。

新手小白必看,3種網頁抓取方法。

3種抓取其中資料的方法。首先是正則表示式,然後是流行的BeautifulSoup模組,最後是強大的lxml模組。

1 正則表示式

當我們使用正則表示式抓取國家(或地區)面積資料時,首先需要嘗試匹配``元素中的內容,如下所示。

>>> import re

>>> from chp1.advanced_link_crawler import download>>> url = 'http://example.python-scraping.com/view/UnitedKingdom-239'>>> html = download(url)>>> re.findall(r'
(.*?)', html)['<img />', '244,820 square kilometres', '62,348,447', 'GB', 'United Kingdom', 'London', '<a>EU</a>', '.uk', 'GBP', 'Pound', '44', '@# #@@|@## #@@|@@# #@@|@@## #@@|@#@ #@@|@@#@ #@@|GIR0AA', '^(([A-Z]d{2}[A-Z]{2})|([A-Z]d{3}[A-Z]{2})|([A-Z]{2}d{2} [A-Z]{2})|([A-Z]{2}d{3}[A-Z]{2})|([A-Z]d[A-Z]d[A-Z]{2}) |([A-Z]{2}d[A-Z]d[A-Z]{2})|(GIR0AA))$
', 'en-GB,cy-GB,gd', '<div><a>IE </a></div>'] 點選並拖拽以移動

從上述結果中可以看出,多個國家(或地區)屬性都使用了``標籤。如果我們只想抓取國家(或地區)面積,可以只選擇第二個匹配的元素,如下所示。

>>> re.findall('(.*?)', html)[1]

'244,820 square kilometres'

雖然現在可以使用這個方案,但是如果網頁發生變化,該方案很可能就會失效。比如表格發生了變化,去除了第二個匹配元素中的面積資料。如果我們只在當下抓取資料,就可以忽略這種未來可能發生的變化。但是,如果我們希望在未來某一時刻能夠再次抓取該資料,就需要給出更加健壯的解決方案,從而儘可能避免這種佈局變化所帶來的影響。想要該正則表示式更加明確,我們可以將其父元素``也加入進來,由於該元素具有ID屬性,所以應該是唯一的。

>>> re.findall('<label for="places_area">Area: </label>

(.*?)', html)['244,820 square kilometres']

這個迭代版本看起來更好一些,但是網頁更新還有很多其他方式,同樣可以讓該正則表示式無法滿足。比如,將雙引號變為單引號,`標籤之間新增多餘的空格,或是變更area_label`等。下面是嘗試支援這些可能性的改進版本。

>>> re.findall('''.*?<tds>(.*?)''', html)

['244,820 square kilometres']

雖然該正則表示式更容易適應未來變化,但又存在難以構造、可讀性差的問題。此外,還有很多其他微小的佈局變化也會使該正則表示式無法滿足,比如在`標籤裡新增title屬性,或者tr、td`元素修改了它們的CSS類或ID。

從本例中可以看出,正則表示式為我們提供了抓取資料的快捷方式,但是該方法過於脆弱,容易在網頁更新後出現問題。幸好,還有更好的資料抽取解決方案,比如我們將在本章介紹的其他抓取庫。

2 Beautiful Soup

Beautiful Soup

是一個非常流行的Python庫,它可以解析網頁,並提供了定位內容的便捷介面。如果你還沒有安裝該模組,可以使用下面的命令安裝其最新版本。

pip install beautifulsoup4

使用Beautiful Soup的第一步是將已下載的HTML內容解析為soup文件。由於許多網頁都不具備良好的HTML格式,因此Beautiful Soup需要對其標籤開合狀態進行修正。例如,在下面這個簡單網頁的列表中,存在屬性值兩側引號缺失和標籤未閉合的問題。

<ul>

<li>Area </li><li>Population</li></ul>

如果Population列表項被解析為Area列表項的子元素,而不是並列的兩個列表項的話,我們在抓取時就會得到錯誤的結果。下面讓我們看一下Beautiful Soup是如何處理的。

>>> from bs4 import BeautifulSoup

>>> from pprint import pprint>>> broken_html = '<ul><li>Area</li><li>Population</li></ul>'>>> # parse the HTML>>> soup = BeautifulSoup(broken_html, 'html.parser')>>> fixed_html = soup.prettify()>>> pprint(fixed_html)<ul> <li> Area </li><li> Population </li></ul>

我們可以看到,使用預設的html.parser並沒有得到正確解析的HTML。從前面的程式碼片段可以看出,由於它使用了巢狀的li元素,因此可能會導致定位困難。幸運的是,我們還有其他解析器可以選擇。我們可以安裝LXML(2.2.3節中將會詳細介紹),或使用html5lib。要想安裝html5lib,只需使用pip。

pip install html5lib

現在,我們可以重複這段程式碼,只對解析器做如下變更。

>>> soup = BeautifulSoup(broken_html, 'html5lib')

>>> fixed_html = soup.prettify()>>> pprint(fixed_html) <ul> <li> Area </li> <li> Population </li> </ul>

此時,使用了html5lib的BeautifulSoup已經能夠正確解析缺失的屬性引號以及閉合標籤,並且還添加了和標籤,使其成為完整的HTML文件。當你使用lxml時,也可以看到類似的結果。

現在,我們可以使用find()和find_all()方法來定位我們需要的元素了。

>>> ul = soup.find('ul', attrs={'class':'country_or_district'})

>>> ul.find('li') # returns just the first match<li>Area</li>>>> ul.find_all('li') # returns all matches[<li>Area</li>, <li>Population</li>

想要了解可用方法和引數的完整列表,請訪問Beautiful Soup的官方文件。

下面是使用該方法抽取示例網站中國家(或地區)面積資料的完整程式碼。

>>> from bs4 import BeautifulSoup

>>> url = 'http://example.python-scraping.com/places/view/United-Kingdom-239'>>> html = download(url)>>> soup = BeautifulSoup(html)>>> # locate the area row>>> tr = soup.find(attrs={'id':'places_area__row'})>>> td = tr.find(attrs={'class':'w2p_fw'}) # locate the data element>>> area = td.text # extract the text from the data element>>> print(area)244,820 square kilometres

這段程式碼雖然比正則表示式的程式碼更加複雜,但又更容易構造和理解。而且,像多餘的空格和標籤屬性這種佈局上的小變化,我們也無須再擔心了。我們還知道即使頁面中包含了不完整的HTML,Beautiful Soup也能幫助我們整理該頁面,從而讓我們可以從非常不完整的網站程式碼中抽取資料。

3 Lxml

Lxml

是基於libxml2這一XML解析庫構建的Python庫,它使用C語言編寫,解析速度比Beautiful Soup更快,不過安裝過程也更為複雜,尤其是在Windows中。如果你在自行安裝該庫時遇到困難,也可以使用Anaconda來實現。

你可能對Anaconda不太熟悉,它是由Continuum Analytics公司員工建立的主要專注於開源資料科學包的包和環境管理器。你可以按照其安裝說明下載及安裝Anaconda。需要注意的是,使用Anaconda的快速安裝會將你的PYTHON_PATH設定為Conda的Python安裝位置。

和Beautiful Soup一樣,使用lxml模組的第一步也是將有可能不合法的HTML解析為統一格式。下面是使用該模組解析同一個不完整HTML的例子。

>>> from lxml.html import fromstring, tostring

>>> broken_html = '<ul><li>Area</li><li>Population</li></ul>'>>> tree = fromstring(broken_html) # parse the HTML>>> fixed_html = tostring(tree, pretty_print=True)>>> print(fixed_html)<ul> <li>Area</li> <li>Population</li></ul>

同樣地,lxml也可以正確解析屬性兩側缺失的引號,並閉合標籤,不過該模組沒有額外新增和標籤。這些都不是標準XML的要求,因此對於lxml來說,插入它們並不是必要的。

解析完輸入內容之後,進入選擇元素的步驟,此時lxml有幾種不同的方法,比如XPath選擇器和類似Beautiful Soup的find()方法。不過,在本例中,我們將會使用CSS選擇器,因為它更加簡潔,並且能夠在第5章解析動態內容時得以複用。一些讀者可能由於他們在jQuery選擇器方面的經驗或是前端Web應用開發中的使用對它們已經有所熟悉。在本章的後續部分,我們將對比這些選擇器與XPath的效能。要想使用CSS選擇器,你可能需要先安裝cssselect庫,如下所示。

pip install cssselect

現在,我們可以使用lxml的CSS選擇器,抽取示例頁面中的面積資料了。

>>> tree = fromstring(html)

>>> td = tree.cssselect('tr#places_area__row > td.w2p_fw')[0]>>> area = td.text_content()>>> print(area)244,820 square kilometres

通過對程式碼樹使用cssselect方法,我們可以利用CSS語法來選擇表格中ID為places_area__row的行元素,然後是類為w2p_fw的子表格資料標籤。由於cssselect返回的是一個列表,我們需要獲取其中的第一個結果,並呼叫text_content方法,以迭代所有子元素並返回每個元素的相關文字。