亚洲国产精品无码久久大片,亚洲AV无码乱码麻豆精品国产,亚洲品质自拍网站,少妇伦子伦精品无码STYLES,国产精久久久久久久

c爬蟲(chóng)抓取網(wǎng)頁(yè)數據(本節書(shū)摘來(lái)自異步社區《用Python寫(xiě)網(wǎng)絡(luò )爬蟲(chóng)》第2章)

優(yōu)采云 發(fā)布時(shí)間: 2022-04-19 15:08

  c爬蟲(chóng)抓取網(wǎng)頁(yè)數據(本節書(shū)摘來(lái)自異步社區《用Python寫(xiě)網(wǎng)絡(luò )爬蟲(chóng)》第2章)

  本節節選自異步社區作者【澳大利亞】理查德·勞森(Richard Lawson)所著(zhù)的《用Python編寫(xiě)Web爬蟲(chóng)》一書(shū)第2章第2.2節,李斌翻譯,更多章節可訪(fǎng)問(wèn)云棲社區“異步社區”公眾號查看。

  2.2 三種網(wǎng)頁(yè)抓取方法

  現在我們已經(jīng)了解了這個(gè)網(wǎng)頁(yè)的結構,有三種方法可以從中獲取數據。首先是正則表達式,然后是流行的 BeautifulSoup 模塊,最后是強大的 lxml 模塊。

  2.2.1 正則表達式

  如果您不熟悉正則表達式,或者需要一些提示,請查看完整介紹。

  當我們使用正則表達式抓取區域數據時(shí),首先需要嘗試匹配

  里面的內容

  元素,如下圖。

   >>> import re

>>> url = 'http://example.webscraping.com/view/United

-Kingdom-239'

>>> html = download(url)

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

['/places/static/images/flags/gb.png',

'244,820 square kilometres',

'62,348,447',

'GB',

'United Kingdom',

'London',

'EU',

'.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',

'IE ']

  從上面的結果可以看出,標簽被用于幾個(gè)國家屬性。為了隔離area屬性,我們可以只選擇其中的第二個(gè)元素,如下圖。

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

'244,820 square kilometres'

  雖然此方案目前可用,但如果頁(yè)面更改,它可能會(huì )失敗。例如,該表已更改為刪除第二行中的土地面積數據。如果我們現在只抓取數據,我們可以忽略這種未來(lái)可能發(fā)生的變化。但是,如果我們以后想再次獲取這些數據,我們需要一個(gè)更健壯的解決方案,盡可能避免這種布局更改的影響。為了使這個(gè)正則表達式更健壯,我們可以將它作為父對象

   >>> re.findall('Area: (.*?)', html)

['244,820 square kilometres']

  這個(gè)迭代版本看起來(lái)更好,但是網(wǎng)頁(yè)更新還有很多其他方式也可以使這個(gè)正則表達式不滿(mǎn)足。例如,要將雙引號改為單引號,

  在之間添加額外的空格

  標簽,或更改 area_label 等。下面是嘗試支持這些可能性的改進(jìn)版本。

   >>> re.findall('.*?>> from bs4 import BeautifulSoup

>>> broken_html = 'AreaPopulation'

>>> # parse the HTML

>>> soup = BeautifulSoup(broken_html, 'html.parser')

>>> fixed_html = soup.prettify()

>>> print fixed_html

Area

Population

  從上面的執行結果可以看出,Beautiful Soup 能夠正確解析缺失的引號并關(guān)閉標簽,除了添加和

  標記使它成為一個(gè)完整的 HTML 文檔?,F在我們可以使用 find() 和 find_all() 方法來(lái)定位我們需要的元素。

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

>>> ul.find('li') # returns just the first match

Area

>>> ul.find_all('li') # returns all matches

[Area, Population]

  要了解所有的方法和參數,可以參考 BeautifulSoup 的官方文檔:.

  以下是使用該方法提取樣本國家地區數據的完整代碼。

   >>> from bs4 import BeautifulSoup

>>> url = 'http://example.webscraping.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 area tag

>>> area = td.text # extract the text from this tag

>>> print area

244,820 square kilometres

  此代碼雖然比正則表達式代碼更復雜,但更易于構建和理解。此外,布局中的小變化,例如額外的空白和選項卡屬性,我們不必再擔心了。

  2.2.3 Lxml

  Lxml 是基于 XML 解析庫 libxml2 的 Python 包裝器。這個(gè)模塊是用C語(yǔ)言編寫(xiě)的,解析速度比Beautiful Soup快,但是安裝過(guò)程比較復雜。最新的安裝說(shuō)明可供參考。

  與 Beautiful Soup 一樣,使用 lxml 模塊的第一步是將可能無(wú)效的 HTML 解析為統一格式。以下是使用此模塊解析相同的不完整 HTML 的示例。

   >>> import lxml.html

>>> broken_html = 'AreaPopulation'

>>> tree = lxml.html.fromstring(broken_html) # parse the HTML

>>> fixed_html = lxml.html.tostring(tree, pretty_print=True)

>>> print fixed_html

Area

Population

  同樣,lxml可以正確解析屬性和關(guān)閉標簽周?chē)鄙俚囊?,但是模塊不加和

  標簽。

  解析輸入內容后,進(jìn)入選擇元素的步驟。此時(shí),lxml 有幾種不同的方法,例如類(lèi)似于 Beautiful Soup 的 XPath 選擇器和 find() 方法。但是,在本例和后續示例中,我們將使用 CSS 選擇器,因為它們更簡(jiǎn)潔,可以在第 5 章解析動(dòng)態(tài)內容時(shí)重復使用。另外,一些有 jQuery 選擇器經(jīng)驗的讀者會(huì )更熟悉它。

  以下是使用 lxml 的 CSS 選擇器提取區域數據的示例代碼。

   >>> tree = lxml.html.fromstring(html)

>>> td = tree.cssselect('tr#places_area__row > td.w2p_fw')[0]

>>> area = td.text_content()

>>> print area

244,820 square kilometres

  CSS 選擇器的關(guān)鍵行已加粗。這行代碼會(huì )先找到ID為places_area__row的表格行元素,然后選擇w2p_fw類(lèi)的表格數據子標簽。

  CSS 選擇器

  CSS 選擇器表示用于選擇元素的模式。下面是一些常用選擇器的示例。

   選擇所有標簽:*

選擇<a>標簽:a

選擇所有class="link"的元素:.link

選擇class="link"的<a>標簽:a.link

選擇id="home"的<a>標簽:a#home

選擇父元素為<a>標簽的所有子標簽:a > span

選擇<a>標簽內部的所有標簽:a span

選擇title屬性為"Home"的所有<a>標簽:a[title=Home]

  CSS3 規范已由 W3C 在 `/2011/REC-css3-selectors-20110929/ 提出。

  Lxml 已經(jīng)實(shí)現了大部分 CSS3 屬性,不支持的功能可以在這里找到。

  需要注意的是,lxml 的內部實(shí)現實(shí)際上將 CSS 選擇器轉換為等效的 XPath 選擇器。

  2.2.4 性能對比

  為了更好地評估本章描述的三種抓取方法之間的權衡,我們需要比較它們的相對效率。通常,爬蟲(chóng)從網(wǎng)頁(yè)中提取多個(gè)字段。因此,為了使比較更加真實(shí),我們將在本章中實(shí)現每個(gè)爬蟲(chóng)的擴展版本,從國家頁(yè)面中提取每個(gè)可用數據。首先,我們需要回到Firebug,檢查國家頁(yè)面其他功能的格式,如圖2.4.

  

  您可以從 Firebug 的顯示中看到,表格中的每一行都有一個(gè)以 places_ 開(kāi)頭并以 __row 結尾的 ID。這些行中收錄的國家/地區數據的格式與上面示例中的格式相同。下面是使用上述信息提取所有可用國家/地區數據的實(shí)現代碼。

   FIELDS = ('area', 'population', 'iso', 'country', 'capital',

'continent', 'tld', 'currency_code', 'currency_name', 'phone',

'postal_code_format', 'postal_code_regex', 'languages',

'nei*敏*感*詞*ours')

import re

def re_scraper(html):

results = {}

for field in FIELDS:

results[field] = re.search('.*?(.*?)' % field, html).groups()[0]

return results

from bs4 import BeautifulSoup

def bs_scraper(html):

soup = BeautifulSoup(html, 'html.parser')

results = {}

for field in FIELDS:

results[field] = soup.find('table').find('tr',

id='places_%s__row' % field).find('td',

class_='w2p_fw').text

return results

import lxml.html

def lxml_scraper(html):

tree = lxml.html.fromstring(html)

results = {}

for field in FIELDS:

results[field] = tree.cssselect('table > tr#places_%s__row

> td.w2p_fw' % field)[0].text_content()

return results

  獲取結果

  現在我們已經(jīng)完成了所有爬蟲(chóng)的代碼實(shí)現,接下來(lái)通過(guò)下面的代碼片段來(lái)測試這三種方法的相對性能。

   import time

NUM_ITERATIONS = 1000 # number of times to test each scraper

html = download('http://example.webscraping.com/places/view/

United-Kingdom-239')

for name, scraper in [('Regular expressions', re_scraper),

('BeautifulSoup', bs_scraper),

('Lxml', lxml_scraper)]:

# record start time of scrape

start = time.time()

for i in range(NUM_ITERATIONS):

if scraper == re_scraper:

re.purge()

result = scraper(html)

# check scraped result is as expected

assert(result['area'] == '244,820 square kilometres')

# record end time of scrape and output the total

end = time.time()

print '%s: %.2f seconds' % (name, end – start)

  在這段代碼中,每個(gè)爬蟲(chóng)會(huì )被執行1000次,每次執行都會(huì )檢查爬取結果是否正確,然后打印總時(shí)間。這里使用的下載函數還是上一章定義的。請注意,我們在粗體代碼行中調用了 re.purge() 方法。默認情況下,正則表達式模塊會(huì )緩存搜索結果,為了與其他爬蟲(chóng)比較公平,我們需要使用這種方法來(lái)清除緩存。

  下面是在我的電腦上運行腳本的結果。

  $ python performance.py

Regular expressions: 5.50 seconds

BeautifulSoup: 42.84 seconds

Lxml: 7.06 seconds

  由于硬件條件的不同,不同計算機的執行結果也會(huì )有一定差異。但是,每種方法之間的相對差異應該具有可比性。從結果可以看出,Beautiful Soup 在爬取我們的示例網(wǎng)頁(yè)時(shí)比其他兩種方法慢 6 倍以上。事實(shí)上,這個(gè)結果是意料之中的,因為 lxml 和 regex 模塊是用 C 編寫(xiě)的,而 BeautifulSoup 是用純 Python 編寫(xiě)的。一個(gè)有趣的事實(shí)是 lxml 的行為與正則表達式一樣。由于 lxml 必須在搜索元素之前將輸入解析為內部格式,因此會(huì )產(chǎn)生額外的開(kāi)銷(xiāo)。當爬取同一個(gè)網(wǎng)頁(yè)的多個(gè)特征時(shí),這種初始解析的開(kāi)銷(xiāo)會(huì )減少,lxml會(huì )更有競爭力。多么神奇的模塊!

  2.2.5 個(gè)結論

  表2.1總結了每種爬取方式的優(yōu)缺點(diǎn)。

  

  如果您的爬蟲(chóng)的瓶頸是下載頁(yè)面,而不是提取數據,那么使用較慢的方法(如 Beautiful Soup)不是問(wèn)題。如果你只需要抓取少量數據并想避免額外的依賴(lài),那么正則表達式可能更合適。通常,lxml 是抓取數據的最佳選擇,因為它快速且健壯,而正則表達式和 Beautiful Soup 僅在某些場(chǎng)景下有用。

  2.2.6 添加鏈接爬蟲(chóng)的爬取回調

  我們已經(jīng)看到了如何抓取國家數據,我們需要將其集成到上一章的鏈接爬蟲(chóng)中。為了重用這個(gè)爬蟲(chóng)代碼去抓取其他網(wǎng)站,我們需要添加一個(gè)回調參數來(lái)處理抓取行為?;卣{是在某個(gè)事件發(fā)生后調用的函數(在這種情況下,在網(wǎng)頁(yè)下載完成后)。爬取回調函數收錄url和html兩個(gè)參數,可以返回要爬取的url列表。下面是它的實(shí)現代碼??梢?jiàn),用Python實(shí)現這個(gè)功能非常簡(jiǎn)單。

   def link_crawler(..., scrape_callback=None):

...

links = []

if scrape_callback:

links.extend(scrape_callback(url, html) or [])

...

  在上面的代碼片段中,我們將新添加的抓取回調函數代碼加粗。如果想獲取該版本鏈接爬蟲(chóng)的完整代碼,可以訪(fǎng)問(wèn)org/wswp/code/src/tip/chapter02/link_crawler.py。

  現在,我們只需要自定義傳入的scrape_callback函數,就可以使用爬蟲(chóng)抓取其他網(wǎng)站了。下面修改lxml抓取示例的代碼,以便在回調函數中使用。

   def scrape_callback(url, html):

if re.search('/view/', url):

tree = lxml.html.fromstring(html)

row = [tree.cssselect('table > tr#places_%s__row >

td.w2p_fw' % field)[0].text_content() for field in

FIELDS]

print url, row

  上面的回調函數會(huì )抓取國家數據并顯示出來(lái)。不過(guò)一般情況下,在爬取網(wǎng)站的時(shí)候,我們更希望能夠重用這些數據,所以讓我們擴展一下它的功能,把得到的數據保存在一個(gè)CSV表中。代碼如下。

   import csv

class ScrapeCallback:

def __init__(self):

self.writer = csv.writer(open('countries.csv', 'w'))

self.fields = ('area', 'population', 'iso', 'country',

'capital', 'continent', 'tld', 'currency_code',

'currency_name', 'phone', 'postal_code_format',

'postal_code_regex', 'languages',

'nei*敏*感*詞*ours')

self.writer.writerow(self.fields)

def __call__(self, url, html):

if re.search('/view/', url):

tree = lxml.html.fromstring(html)

row = []

for field in self.fields:

row.append(tree.cssselect('table >

tr#places_{}__row >

td.w2p_fw'.format(field))

[0].text_content())

self.writer.writerow(row)

  為了實(shí)現這個(gè)回調,我們使用回調類(lèi)而不是回調函數,以維護 csv 中 writer 屬性的狀態(tài)。在構造函數中實(shí)例化csv的writer屬性,然后在__call__方法中進(jìn)行多次寫(xiě)入。注意__call__是一個(gè)特殊的方法,當一個(gè)對象作為函數調用時(shí)會(huì )調用,這也是鏈接爬蟲(chóng)中cache_callback的調用方式。也就是說(shuō),scrape_callback(url, html) 等價(jià)于調用 scrape_callback.__call__(url, html)。如果想詳細了解Python的特殊類(lèi)方法,可以參考一下。

  以下是將回調傳遞給鏈接爬蟲(chóng)的代碼。

   link_crawler('http://example.webscraping.com/', '/(index|view)',

max_depth=-1, scrape_callback=ScrapeCallback())

  現在,當我們使用回調運行這個(gè)爬蟲(chóng)時(shí),程序會(huì )將結果寫(xiě)入 CSV 文件,我們可以使用 Excel 或 LibreOffice 等應用程序查看該文件,如圖 2.5 所示。

  

  成功!我們完成了我們的第一個(gè)工作數據抓取爬蟲(chóng)。

0 個(gè)評論

要回復文章請先登錄注冊


官方客服QQ群

微信人工客服

QQ人工客服


線(xiàn)

亚洲国产精品无码久久大片,亚洲AV无码乱码麻豆精品国产,亚洲品质自拍网站,少妇伦子伦精品无码STYLES,国产精久久久久久久