Python爬蟲之三種網頁抓取方法效能比較__Python

來源:互聯網
上載者:User

  下面我們將介紹三種抓取網頁資料的方法,首先是Regex,然後是流行的 BeautifulSoup 模組,最後是強大的 lxml 模組。

1. Regex

  如果你對Regex還不熟悉,或是需要一些提示時,可以查閱Regular Expression HOWTO 獲得完整介紹。

  當我們使用Regex抓取國家面積資料時,首先要嘗試匹配元素中的內容,如下所示:

>>> import re>>> import urllib2>>> url = 'http://example.webscraping.com/view/United-Kingdom-239'>>> html = urllib2.urlopen(url).read()>>> re.findall('<td class="w2p_fw">(.*?)</td>', html)['<img src="/places/static/images/flags/gb.png" />', '244,820 square kilometres', '62,348,447', 'GB', 'United Kingdom', 'London', '<a href="/continent/EU">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 href="/iso/IE">IE </a></div>']>>> 

   從上述結果看出,多個國家屬性都使用了< td class=”w2p_fw” >標籤。要想分離出面積屬性,我們可以只選擇其中的第二個元素,如下所示:

>>> re.findall('<td class="w2p_fw">(.*?)</td>', html)[1]'244,820 square kilometres'

   雖然現在可以使用這個方案,但是如果網頁發生變化,該方案很可能就會失效。比如表格發生了變化,去除了第二行中的國土面積資料。如果我們只在現在抓取資料,就可以忽略這種未來可能發生的變化。但是,如果我們希望未來還能再次抓取該資料,就需要給出更加健壯的解決方案,從而儘可能避免這種布局變化所帶來的影響。想要該Regex更加健壯,我們可以將其父元素< tr >也加入進來。由於該元素具有ID屬性,所以應該是唯一的。

>>> re.findall('<tr id="places_area__row"><td class="w2p_fl"><label for="places_area" id="places_area__label">Area: </label></td><td class="w2p_fw">(.*?)</td>', html)['244,820 square kilometres']

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

>>> re.findall('<tr id="places_area__row">.*?<td\s*class=["\']w2p_fw["\']>(.*?)</td>',html)['244,820 square kilometres']

  雖然該Regex更容易適應未來變化,但又存在難以構造、可讀性差的問題。此外,還有一些微小的布局變化也會使該Regex無法滿足,比如在< td >標籤裡添加title屬性。
  從本例中可以看出,Regex為我們提供了抓取資料的捷徑,但是,該方法過於脆弱,容易在網頁更新後出現問題。幸好還有一些更好的解決方案,後期將會介紹。

2. Beautiful Soup

  Beautiful Soup是一個非常流行的 Python 模組。該模組可以解析網頁,並提供定位內容的便捷介面。如果你還沒有安裝該模組,可以使用下面的命令安裝其最新版本(需要先安裝 pip,請自行百度):

pip install beautifulsoup4

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

<ul class=country>    <li>Area    <li>Population</ul>

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

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

  從上面的執行結果中可以看出,Beautiful Soup 能夠正確解析缺失的引號並閉合標籤。現在可以使用 find()find_all() 方法來定位我們需要的元素了。

>>> ul = soup.find('ul', attrs={'class':'country'})>>> ul.find('li') # return just the first match<li>Area<li>Population</li></li>>>> ul.find_all('li') # return all matches[<li>Area<li>Population</li></li>, <li>Population</li>]

Note: 由於不同版本的Python內建庫的容錯能力有所區別,可能處理結果和上述有所不同,具體請參考: https://www.crummy.com/software/BeautifulSoup/bs4/doc/#installing-a-parser。想瞭解全部方法和參數,可以查閱 Beautiful Soup 的 官方文檔

  下面是使用該方法抽取樣本國家面積資料的完整代碼。

>>> from bs4 import BeautifulSoup>>> import urllib2>>> url = 'http://example.webscraping.com/view/United-Kingdom-239'>>> html = urllib2.urlopen(url).read()>>> # locate the area row>>> tr = soup.find(attrs={'id':'places_area__row'})>>> # locate the area tag>>> td = tr.find(attrs={'class':'w2p_fw'})>>> area = td.text # extract the text from this tag>>> print area244,820 square kilometres

  這段代碼雖然比Regex的代碼更加複雜,但更容易構造和理解。而且,像多餘的空格和標籤屬性這種布局上的小變化,我們也無需再擔心了。

3. Lxml

  Lxml 是基於 libxml2 這一 XML 解析庫的 Python 封裝。該模組使用 C語言 編寫,解析速度比 Beautiful Soup 更快,不過安裝過程也更為複雜。最新的安裝說明可以參考 http://lxml.de/installation.html .**

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

>>> import lxml.html>>> broken_html = '<ul class=country><li>Area<li>Population</ul>'>>> # parse the HTML>>> tree = lxml.html.fromstring(broken_html)>>> fixed_html = lxml.html.tostring(tree, pretty_print=True)>>> print fixed_html<ul class="country"><li>Area</li><li>Population</li></ul>

  同樣地,lxml 也可以正確解析屬性兩側缺失的引號,並閉合標籤,不過該模組沒有額外添加 < html > 和 < body > 標籤。

  解析完輸入內容之後,進入選擇元素的步驟,此時 lxml 有幾種不同的方法,比如 XPath 選取器和類似 Beautiful Soupfind() 方法。不過,後續我們將使用 CSS 選取器,因為它更加簡潔,並且能夠在解析動態內容時得以複用。此外,一些擁有 jQuery 選取器相關經驗的讀者會對其更加熟悉。

  下面是使用 lxmlCSS 選取器抽取面積資料的範例程式碼:

>>> import urllib2>>> import lxml.html>>> url = 'http://example.webscraping.com/view/United-Kingdom-239'>>> html = urllib2.urlopen(url).read()>>> tree = lxml.html.fromstring(html)>>> td = tree.cssselect('tr#places_area__row > td.w2p_fw')[0] # *行代碼>>> area = td.text_content()>>> print area244,820 square kilometres

   *行代碼首先會找到 ID 為 places_area__row 的表格行元素,然後選擇 classw2p_fw 的表格式資料子標籤。

   CSS 選取器表示選擇元素所使用的模式,下面是一些常用的選取器樣本:

選擇所有標籤: *選擇 <a> 標籤: a選擇所有 class="link" 的元素: .link選擇 class="link" 的 <a> 標籤: a.link選擇 id="home" 的 <a> 標籤: a#home選擇父元素為 <a> 標籤的所有 <span> 子標籤: a > span選擇 <a> 標籤內部的所有 <span> 標籤: a span 選擇 title 屬性為"Home"的所有 <a> 標籤: a[title=Home]

   W3C 已提出 CSS3 規範,其網址為 https://www.w3.org/TR/2011/REC-css3-selectors-20110929/

  Lxml 已經實現了大部分 CSS3 屬性,其不支援的功能可以參見: https://cssselect.readthedocs.io/en/latest/ .

Note: lxml在內部的實現中,實際上是將 CSS 選取器轉換為等價的 XPath 選取器。

4. 效能對比

   在以下這段代碼中,每個爬蟲都會執行 1000 次,每次執行都會檢查抓取結果是否正確,然後列印總用時。

# -*- coding: utf-8 -*-import csvimport timeimport urllib2import reimport timeitfrom bs4 import BeautifulSoupimport lxml.htmlFIELDS = ('area', 'population', 'iso', 'country', 'capital', 'continent', 'tld', 'currency_code', 'currency_name', 'phone', 'postal_code_format', 'postal_code_regex', 'languages', 'neighbours')def regex_scraper(html):    results = {}    for field in FIELDS:        results[field] = re.search('<tr id="places_{}__row">.*?<td class="w2p_fw">(.*?)</td>'.format(field), html).groups()[0]    return resultsdef beautiful_soup_scraper(html):    soup = BeautifulSoup(html, 'html.parser')     results = {}    for field in FIELDS:        results[field] = soup.find('table').find('tr', id='places_{}__row'.format(field)).find('td', class_='w2p_fw').text    return resultsdef lxml_scraper(html):    tree = lxml.html.fromstring(html)    results = {}    for field in FIELDS:        results[field] = tree.cssselect('table > tr#places_{}__row > td.w2p_fw'.format(field))[0].text_content()    return resultsdef main():    times = {}    html = urllib2.urlopen('http://example.webscraping.com/view/United-Kingdom-239').read()    NUM_ITERATIONS = 1000 # number of times to test each scraper    for name, scraper in ('Regular expressions', regex_scraper), ('Beautiful Soup', beautiful_soup_scraper), ('Lxml', lxml_scraper):        times[name] = []        # record start time of scrape        start = time.time()        for i in range(NUM_ITERATIONS):            if scraper == regex_scraper:                # the regular expression module will cache results                # so need to purge this cache for meaningful timings                re.purge()     # *行代碼            result = scraper(html)            # check scraped result is as expected            assert(result['area'] == '244,820 square kilometres')            times[name].append(time.time() - start)        # record end time of scrape and output the total        end = time.time()        print '{}: {:.2f} seconds'.format(name, end - start)    writer = csv.writer(open('times.csv', 'w'))    header = sorted(times.keys())    writer.writerow(header)    for row in zip(*[times[scraper] for scraper in header]):        writer.writerow(row)if __name__ == '__main__':    main()


   注意,我們在 *行代碼 中調用了 re.purge() 方法。預設情況下,Regex會緩衝搜尋結果,為了公平起見,我們需要使用該方法清除緩衝。

下面是我的電腦運行該指令碼的結果:


   由於硬體條件的區別,不同電腦的執行結果也會存在一定差異。不過,每種方法之間的相對差異應當是相當的。從結果中可以看出,在抓取我們的樣本網頁時,Beautiful Soup 比其他兩種方法慢了超過 7 倍之多。實際上這一結果是符合預期的,因為 lxml 和Regex模組都是 C 語言編寫的,而 Beautiful Soup 則是純 Python 編寫的。一個有趣的事實是,lxml 表現的和Regex差不多好。由於 lxml 在搜尋元素之前,必須將輸入解析為內部格式,因此會產生額外的開銷。而當抓取同一網頁的多個特徵時,這種初始化解析產生的開銷就會降低,lxml 也就更具競爭力,所以說,lxml 是一個強大的模組。

5. 總結

三種網頁抓取方法優缺點:

       抓取方法     效能       使用難度       安裝難度
Regex 困難 簡單(內建模組)
Beautiful Soup 簡單 簡單(純Python)
Lxml 簡單 相對困難



   如果你的爬蟲瓶頸是下載網頁,而不是抽取資料的話,那麼使用較慢的方法(如 Beautiful Soup)也不成問題。Regex在一次性抽取中非常有用,此外還可以避免解析整個網頁帶來的開銷,如果只需抓取少量資料,並且想要避免額外依賴的話,那麼Regex可能更加適合。不過,通常情況下,lxml 是抓取資料的最好選擇,這是因為它不僅速度快,功能也更加豐富,而Regex和 Beautiful Soup只在某些特定情境下有用。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.