在上一篇中 敲街, 我們構(gòu)建了一個爬蟲, 可以通過跟蹤鏈接的方式下載我們所
需的網(wǎng)頁。 但是爬蟲在下載網(wǎng)頁之后又將 結(jié)果丟棄掉了 疼燥。 現(xiàn)在, 我們需要讓這個爬蟲從每個網(wǎng)頁中抽取一些數(shù)據(jù)蚁堤,然后實現(xiàn)某些事情醉者, 這種做法也被稱為抓取(scraping) 披诗。
1.分析網(wǎng)頁
在抓取之前我們首先應(yīng)該了解網(wǎng)頁的結(jié)構(gòu)如何撬即,可以用瀏覽器打開你要抓取的網(wǎng)頁然后有點(diǎn)單機(jī)選擇查看頁面源代碼,
推薦使用火狐瀏覽器 可以使用firebug工具進(jìn)行查看 安裝firebug http://jingyan.baidu.com/article/fdffd1f832b032f3e98ca1b2.html
可以右鍵單機(jī)我們在抓取中感興趣的網(wǎng)頁部分如圖所示
2呈队,三種網(wǎng)頁的抓取方法
- 正則表達(dá)式
- BeautifulSoup模塊
- lxml
2.1正則表達(dá)式
import requests
import re
def download(url, user_agent='jians', num_retries=2):
headers = {'User-agent': user_agent}
response = requests.get(url, headers=headers)
if num_retries > 0:
if 500 <= response.status_code < 600:
return download(url, num_retries - 1)
return response.text
def get_page():
url = 'http://example.webscraping.com/view/Unitled-Kingdom-239'
html = download(url)
# 通過嘗試匹配<td>元素中的內(nèi)容
html_str = re.findall('<td class="w2p_fw">(.*?)</td>', html)
print html_str
# 分離出面積熟悉剥槐,拿到第二個元素(area)的信息
area_str = re.findall('<td class="w2p_fw">(.*?)</td>', html)[1]
print area_str
# 通過指定tr的id去查詢area,可以有效的防止網(wǎng)頁發(fā)生變化
html_places = re.findall('<tr id="places_area__row">.*?'
'<td class="w2p_fw">(.*?)</td>', html)
print html_places
get_page()
正則表達(dá)式為我們提供了抓取數(shù)據(jù)的快捷方式, 但是該方法過于脆弱 宪摧, 容易在網(wǎng)頁更新后出現(xiàn)問題
2.2 Beautiful Soup
Beautiful Soup 是一個非常流行的 Python 模塊粒竖。 該模塊可以解析網(wǎng)頁, 并
提供定位 內(nèi) 容的便捷接 口
安裝beautifulsoup
pip install beautifulsoup4
beanuifulsoup4文檔
https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html
簡單使用:
from bs4 import BeautifulSoup #導(dǎo)入beautifulsoup需要這樣導(dǎo)入
import requests
def get_area():
'''使用該方法抽取示例 國家面積數(shù)據(jù)的完整代碼几于。'''
url = 'http://example.webscraping.com/view/Unitled-Kingdom-239'
html = requests.get(url).text
soup = BeautifulSoup(html,'html.parser')
tr = soup.find(attrs={'id': 'places_area__row'})
td = tr.find(attrs={'class': 'w2p_fw'})
area = td.text
print area
get_area()
2.3Lxml
Lxml 是基于 libxml2 這一 XML 解析庫的 Python 封裝蕊苗。 該模塊使用 C
語言編寫 , 解析速度 比 Beautiful Soup 更快.
安裝模塊
pip install lxml
ping install cssselect #需要css選擇器模塊
簡單使用:
import lxml.html
import requests
def get_area():
'''將有可能不合法的HTML 解析為統(tǒng)一格式'''
broken_html = '<ul class = country> <li>Area<li>Population</ul>'
tree = lxml.html.fromstring(broken_html)
fixed_html= lxml.html.tostring(tree, pretty_print=True)
print fixed_html
def get_area1():
'''使用CSS選擇器抽取面積數(shù)據(jù)的代碼'''
url = 'http://example.webscraping.com/view/Unitled-Kingdom-239'
html = requests.get(url).text
tree = lxml.html.fromstring(html)
td = tree.cssselect('tr#places_area__row > td.w2p_fw')[0]
area = td.text_content()
print area
get_area()
get_area1()
2.4三種抓取方法的對比
用三種方式分別抓取1000次http://example.webscraping.com/view/Unitled-Kingdom-239 網(wǎng)頁中的國家數(shù)據(jù)沿彭,并打印時間
代碼:
#!/usr/bin/env python
# -*-coding:utf-8 -*-
import re
from bs4 import BeautifulSoup
import lxml.html
from lxml.cssselect import CSSSelector
import requests
import time
FIELDS = ('area', 'population', 'iso', 'country', 'capital', 'continent', 'tld', 'currency_code',
'currency_name', 'phone', 'postal_code_format', 'postal_code_regex', 'languages', 'neighbours' )
def download(url, user_agent='jians', num_retries=2):
headers = {'User-agent': user_agent}
response = requests.get(url, headers=headers)
if num_retries > 0:
if 500 <= response.status_code < 600:
return download(url, num_retries - 1)
return response.text
def re_scarper(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 results
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
def lx_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
def get_counter():
NUM_ITERATIONS = 1000
html = download('http://example.webscraping.com/view/Unitled-Kingdom-239')
for name, scrapter in [('REGULAR expression', re_scarper),
('beautifulsoup', bs_scraper),
('lxml', lx_scraper)]:
start = time.time()
for i in range(NUM_ITERATIONS):
if scrapter == re_scarper:
re.purge()
result = scrapter(html)
assert(result['area'] == '244,820 square kilometres')
end = time.time()
print '%s:%.2f seconds' % (name, end-start)
注意代碼格式朽砰,方法之間空兩行
種方法之間 的相對差異應(yīng)當(dāng)是相 當(dāng) 的 。 從結(jié)果中可以看出 饱搏, 在抓取我們的示
例 網(wǎng)頁時非剃, Beautiful Soup 比其他兩種方法慢了超過 6 倍之多 。 實際上這一結(jié)
果是符合預(yù)期的 推沸, 因 為 lxml 和正則表達(dá)式模塊都是 C 語言編寫 的 备绽, 而
BeautifulSoup 則是純 Python 編寫的 。 一個有趣的事實是鬓催, lxml 表現(xiàn)得
和正則表達(dá)式差不多好肺素。 由于 lxml 在搜索元素之前, 必須將輸入解析為 內(nèi)
部格式宇驾, 因此會產(chǎn)生額外的開銷 倍靡。 而當(dāng)抓取同一網(wǎng)頁的多個特征時, 這種初
始化解析產(chǎn)生的開銷就會降低课舍, lxml 也就更具競爭力 塌西。
結(jié)論
法 ( 如 Beautiful Soup) 也不成問題捡需。 如果只需抓取少量數(shù)據(jù) 办桨, 并且想要避免
額外依賴的話, 那么正則表達(dá)式可能更加適合站辉。 不過呢撞, 通常情況下, lxml 是
抓取數(shù)據(jù) 的最好選擇 饰剥, 這是 因 為 該方法既快速又健壯 殊霞, 而正則表達(dá)式和
Beautiful Soup 只在某些特定場景下有用。
為鏈接爬蟲添加抓取回調(diào)
前面我們已經(jīng)了解了 如何抓取國家數(shù)據(jù),接下來我們需要將其集成到上
一篇的鏈接爬蟲當(dāng)中
獲取該版本鏈接爬蟲的完整代碼捐川, 可以訪問
https : //bitbucket .org/wswp/code/src/tip/chapter02 /link crawler . py脓鹃。
對傳入的 scrape callback 函數(shù)定制化處理, 就能使
用該爬蟲抓取其他網(wǎng)站了
import re
import lxml.html
from link_craw import link_crawler
FIELDS = ('area', 'population', 'iso', 'country', 'capital', 'continent', 'tld', 'currency_code', 'currency_name', 'phone', 'postal_code_format', 'postal_code_regex', 'languages', 'neighbours')
def scrape_callback(url, html):
'''使用lxml抓取'''
if re.search('/view/', url):
tree = lxml.html.fromstring(html)
row = [tree.cssselect('table > tr#places_{}__row > td.w2p_fw'.format(field))[0].text_content() for field in FIELDS]
print url, row
if __name__ == '__main__':
link_crawler('http://example.webscraping.com/', '/(index|view)', scrape_callback=scrape_callback)
在抓取網(wǎng)站時古沥, 我們更希望能夠復(fù)用這些數(shù)據(jù) 瘸右, 因此下面我們
對其功能進(jìn)行擴(kuò)展, 把得到的結(jié)果數(shù)據(jù)保存到 csv 表格中 岩齿, 其代碼如下所示太颤。
import csv
import re
import lxml.html
from link_craw import link_crawler
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', 'neighbours')
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)
if __name__ == '__main__':
link_crawler('http://example.webscraping.com/', '/(index|view)', scrape_callback=ScrapeCallback())
程序就會將結(jié)果寫入一個 csv 文件中 , 我們可以使用類似 Excel 或者 LibreOffice 的應(yīng)用查看該文件盹沈,
我們完成了第一個可以工作的數(shù)據(jù)抓取爬蟲!