用Python写网络爬虫二

在上一篇中 , 我们构建了一个爬虫, 可以通过跟踪链接的方式下载我们所
需的网页。 但是爬虫在下载网页之后又将 结果丢弃掉了 。 现在, 我们需要让这个爬虫从每个网页中抽取一些数据,然后实现某些事情, 这种做法也被称为抓取(scraping) 。

1.分析网页

在抓取之前我们首先应该了解网页的结构如何,可以用浏览器打开你要抓取的网页然后有点单机选择查看页面源代码
推荐使用火狐浏览器 可以使用firebug工具进行查看 安装firebug http://jingyan.baidu.com/article/fdffd1f832b032f3e98ca1b2.html
可以右键单机我们在抓取中感兴趣的网页部分如图所示

firebug抓取

2,三种网页的抓取方法

  • 正则表达式
  • BeautifulSoup模块
  • lxml

2.1正则表达式

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>元素中的内容
    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,可以有效的防止网页发生变化
    html_places = re.findall('<tr id="places_area__row">.*?'
                             '<td class="w2p_fw">(.*?)</td>', html)
    print html_places
get_page()

正则表达式为我们提供了抓取数据的快捷方式, 但是该方法过于脆弱 , 容易在网页更新后出现问题

2.2 Beautiful Soup

Beautiful Soup 是一个非常流行的 Python 模块。 该模块可以解析网页, 并
提供定位 内 容的便捷接 口
安装beautifulsoup
pip install beautifulsoup4
beanuifulsoup4文档
https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html
简单使用:

from bs4 import BeautifulSoup #导入beautifulsoup需要这样导入
import requests
def get_area():
    '''使用该方法抽取示例 国家面积数据的完整代码。'''
    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 解析为统一格式'''
    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选择器抽取面积数据的代码'''
    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 网页中的国家数据,并打印时间
代码:

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

注意代码格式,方法之间空两行

性能比较
结果:由于硬件条件的区别 , 不同 电脑的执行结果也会存在一定差异。 不过, 每
种方法之间 的相对差异应当是相 当 的 。 从结果中可以看出 , 在抓取我们的示
例 网页时, Beautiful Soup 比其他两种方法慢了超过 6 倍之多 。 实际上这一结
果是符合预期的 , 因 为 lxml 和正则表达式模块都是 C 语言编写 的 , 而
BeautifulSoup 则是纯 Python 编写的 。 一个有趣的事实是, lxml 表现得
和正则表达式差不多好。 由于 lxml 在搜索元素之前, 必须将输入解析为 内
部格式, 因此会产生额外的开销 。 而当抓取同一网页的多个特征时, 这种初
始化解析产生的开销就会降低, lxml 也就更具竞争力 。
结论
结论.jpg
如果你的爬虫瓶颈是下载网页, 而不是抽取数据的话, 那么使用较慢的方
法 ( 如 Beautiful Soup) 也不成问题。 如果只需抓取少量数据 , 并且想要避免
额外依赖的话, 那么正则表达式可能更加适合。 不过, 通常情况下, lxml 是
抓取数据 的最好选择 , 这是 因 为 该方法既快速又健壮 , 而正则表达式和
Beautiful Soup 只在某些特定场景下有用。

为链接爬虫添加抓取回调

前面我们已经了解了 如何抓取国家数据,接下来我们需要将其集成到上
一篇的链接爬虫当中
获取该版本链接爬虫的完整代码, 可以访问
https : //bitbucket .org/wswp/code/src/tip/chapter02 /link crawler . py。
对传入的 scrape callback 函数定制化处理, 就能使
用该爬虫抓取其他网站了

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)

在抓取网站时, 我们更希望能够复用这些数据 , 因此下面我们
对其功能进行扩展, 把得到的结果数据保存到 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())

程序就会将结果写入一个 csv 文件中 , 我们可以使用类似 Excel 或者 LibreOffice 的应用查看该文件,
我们完成了第一个可以工作的数据抓取爬虫!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容