用Python寫(xiě)網(wǎng)絡(luò)爬蟲(chóng)二

在上一篇中 , 我們構(gòu)建了一個(gè)爬蟲(chóng), 可以通過(guò)跟蹤鏈接的方式下載我們所
需的網(wǎng)頁(yè)。 但是爬蟲(chóng)在下載網(wǎng)頁(yè)之后又將 結(jié)果丟棄掉了 。 現(xiàn)在, 我們需要讓這個(gè)爬蟲(chóng)從每個(gè)網(wǎng)頁(yè)中抽取一些數(shù)據(jù),然后實(shí)現(xiàn)某些事情, 這種做法也被稱(chēng)為抓取(scraping) 。

1.分析網(wǎng)頁(yè)

在抓取之前我們首先應(yīng)該了解網(wǎng)頁(yè)的結(jié)構(gòu)如何,可以用瀏覽器打開(kāi)你要抓取的網(wǎng)頁(yè)然后有點(diǎn)單機(jī)選擇查看頁(yè)面源代碼
推薦使用火狐瀏覽器 可以使用firebug工具進(jìn)行查看 安裝firebug http://jingyan.baidu.com/article/fdffd1f832b032f3e98ca1b2.html
可以右鍵單機(jī)我們?cè)谧ト≈懈信d趣的網(wǎng)頁(yè)部分如圖所示

firebug抓取

2,三種網(wǎng)頁(yè)的抓取方法

  • 正則表達(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)
    # 通過(guò)嘗試匹配<td>元素中的內(nèi)容
    html_str = re.findall('<td class="w2p_fw">(.*?)</td>', html)
    print html_str
    # 分離出面積熟悉,拿到第二個(gè)元素(area)的信息
    area_str = re.findall('<td class="w2p_fw">(.*?)</td>', html)[1]
    print area_str

    # 通過(guò)指定tr的id去查詢(xún)area,可以有效的防止網(wǎng)頁(yè)發(fā)生變化
    html_places = re.findall('<tr id="places_area__row">.*?'
                             '<td class="w2p_fw">(.*?)</td>', html)
    print html_places
get_page()

正則表達(dá)式為我們提供了抓取數(shù)據(jù)的快捷方式, 但是該方法過(guò)于脆弱 , 容易在網(wǎng)頁(yè)更新后出現(xiàn)問(wèn)題

2.2 Beautiful Soup

Beautiful Soup 是一個(gè)非常流行的 Python 模塊。 該模塊可以解析網(wǎng)頁(yè), 并
提供定位 內(nèi) 容的便捷接 口
安裝beautifulsoup
pip install beautifulsoup4
beanuifulsoup4文檔
https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html
簡(jiǎn)單使用:

from bs4 import BeautifulSoup #導(dǎo)入beautifulsoup需要這樣導(dǎo)入
import requests
def get_area():
    '''使用該方法抽取示例 國(guó)家面積數(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 解析庫(kù)的 Python 封裝。 該模塊使用 C
語(yǔ)言編寫(xiě) , 解析速度 比 Beautiful Soup 更快.

安裝模塊
pip install lxml
ping install cssselect #需要css選擇器模塊

簡(jiǎn)單使用:

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三種抓取方法的對(duì)比

用三種方式分別抓取1000次http://example.webscraping.com/view/Unitled-Kingdom-239 網(wǎng)頁(yè)中的國(guó)家數(shù)據(jù),并打印時(shí)間
代碼:

#!/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)

注意代碼格式,方法之間空兩行

性能比較
結(jié)果:由于硬件條件的區(qū)別 , 不同 電腦的執(zhí)行結(jié)果也會(huì)存在一定差異。 不過(guò), 每
種方法之間 的相對(duì)差異應(yīng)當(dāng)是相 當(dāng) 的 。 從結(jié)果中可以看出 , 在抓取我們的示
例 網(wǎng)頁(yè)時(shí), Beautiful Soup 比其他兩種方法慢了超過(guò) 6 倍之多 。 實(shí)際上這一結(jié)
果是符合預(yù)期的 , 因 為 lxml 和正則表達(dá)式模塊都是 C 語(yǔ)言編寫(xiě) 的 , 而
BeautifulSoup 則是純 Python 編寫(xiě)的 。 一個(gè)有趣的事實(shí)是, lxml 表現(xiàn)得
和正則表達(dá)式差不多好。 由于 lxml 在搜索元素之前, 必須將輸入解析為 內(nèi)
部格式, 因此會(huì)產(chǎn)生額外的開(kāi)銷(xiāo) 。 而當(dāng)抓取同一網(wǎng)頁(yè)的多個(gè)特征時(shí), 這種初
始化解析產(chǎn)生的開(kāi)銷(xiāo)就會(huì)降低, lxml 也就更具競(jìng)爭(zhēng)力 。
結(jié)論
結(jié)論.jpg
如果你的爬蟲(chóng)瓶頸是下載網(wǎng)頁(yè), 而不是抽取數(shù)據(jù)的話(huà), 那么使用較慢的方
法 ( 如 Beautiful Soup) 也不成問(wèn)題。 如果只需抓取少量數(shù)據(jù) , 并且想要避免
額外依賴(lài)的話(huà), 那么正則表達(dá)式可能更加適合。 不過(guò), 通常情況下, lxml 是
抓取數(shù)據(jù) 的最好選擇 , 這是 因 為 該方法既快速又健壯 , 而正則表達(dá)式和
Beautiful Soup 只在某些特定場(chǎng)景下有用。

為鏈接爬蟲(chóng)添加抓取回調(diào)

前面我們已經(jīng)了解了 如何抓取國(guó)家數(shù)據(jù),接下來(lái)我們需要將其集成到上
一篇的鏈接爬蟲(chóng)當(dāng)中
獲取該版本鏈接爬蟲(chóng)的完整代碼, 可以訪問(wèn)
https : //bitbucket .org/wswp/code/src/tip/chapter02 /link crawler . py。
對(duì)傳入的 scrape callback 函數(shù)定制化處理, 就能使
用該爬蟲(chóng)抓取其他網(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)站時(shí), 我們更希望能夠復(fù)用這些數(shù)據(jù) , 因此下面我們
對(duì)其功能進(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())

程序就會(huì)將結(jié)果寫(xiě)入一個(gè) csv 文件中 , 我們可以使用類(lèi)似 Excel 或者 LibreOffice 的應(yīng)用查看該文件,
我們完成了第一個(gè)可以工作的數(shù)據(jù)抓取爬蟲(chóng)!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容