用Python寫網(wǎng)絡(luò)爬蟲二

在上一篇中 敲街, 我們構(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)頁部分如圖所示

firebug抓取

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)

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

性能比較
結(jié)果:由于硬件條件的區(qū)別 , 不同 電腦的執(zhí)行結(jié)果也會存在一定差異膝蜈。 不過锅移, 每
種方法之間 的相對差異應(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é)論
結(jié)論.jpg
如果你的爬蟲瓶頸是下載網(wǎng)頁他挎, 而不是抽取數(shù)據(jù)的話, 那么使用較慢的方
法 ( 如 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ù)抓取爬蟲!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末龄章,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子乞封,更是在濱河造成了極大的恐慌做裙,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肃晚,死亡現(xiàn)場離奇詭異锚贱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)关串,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門拧廊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人晋修,你說我怎么就攤上這事吧碾。” “怎么了墓卦?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵倦春,是天一觀的道長。 經(jīng)常有香客問我,道長溅漾,這世上最難降的妖魔是什么山叮? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮添履,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘脑又。我一直安慰自己暮胧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布问麸。 她就那樣靜靜地躺著往衷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪严卖。 梳的紋絲不亂的頭發(fā)上席舍,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音哮笆,去河邊找鬼来颤。 笑死,一個胖子當(dāng)著我的面吹牛稠肘,可吹牛的內(nèi)容都是我干的福铅。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼项阴,長吁一口氣:“原來是場噩夢啊……” “哼滑黔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起环揽,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤略荡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后歉胶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體汛兜,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年跨扮,在試婚紗的時候發(fā)現(xiàn)自己被綠了序无。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡衡创,死狀恐怖帝嗡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情璃氢,我是刑警寧澤哟玷,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響巢寡,放射性物質(zhì)發(fā)生泄漏喉脖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一抑月、第九天 我趴在偏房一處隱蔽的房頂上張望树叽。 院中可真熱鬧,春花似錦谦絮、人聲如沸题诵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽性锭。三九已至,卻和暖如春叫胖,著一層夾襖步出監(jiān)牢的瞬間草冈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工瓮增, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留怎棱,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓钉赁,卻偏偏與公主長得像蹄殃,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子你踩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

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