当前位置:嗨网首页>书籍在线阅读

13-为链接爬虫添加抓取回调

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

[toc]

2.7.2 为链接爬虫添加抓取回调

前面我们已经了解了如何抓取国家(或地区)数据,接下来我们需要将其集成到第1章的链接爬虫当中。要想复用这段爬虫代码抓取其他网站,我们需要添加一个 callback 参数处理抓取行为。 callback 是一个函数,在发生某个特定事件之后会调用该函数(在本例中,会在网页下载完成后调用)。这里的抓取 callback 函数包含 urlhtml 两个参数,并且可以返回一个待爬取的URL列表。下面是其实现代码,可以看出在Python中实现该功能非常简单。

def link_crawler(..., scrape_callback=None):
    ...
    data = []
    if scrape_callback:
        data.extend(scrape_callback(url, html) or [])
        ...

在上面的代码片段中,我们显示了新增加的抓取 callback 函数代码。如果想要获取该版本链接爬虫的完整代码,可以访问异步社区下载本书源码,从中找到该文件,其名为 advanced_link_crawler.py

现在,我们只需对传入的 scrape_callback 函数进行定制化处理,就能使用该爬虫抓取其他网站了。下面对 lxml 抓取示例的代码进行了修改,使其能够在 callback 函数中使用。

def scrape_callback(url, html):
    fields = ('area', 'population', 'iso', 'country_or_district', 'capital',
              'continent', 'tld', 'currency_code', 'currency_name',
              'phone', 'postal_code_format', 'postal_code_regex',
              'languages', 'neighbours')
    if re.search('/view/', url):
        tree = fromstring(html)
        all_rows = [
            tree.xpath('//tr[@id="places_%s__row"]/td[@class="w2p_fw"]' %
field)[0].text_content()
            for field in fields]
        print(url, all_rows)

上面这个 callback 函数会去抓取国家(或地区)数据,然后将其显示出来。我们可以通过导入这两个函数,并使用我们的正则表达式及URL调用它们,来进行测试。

>>> from chp2.advanced_link_crawler import link_crawler, scrape_callback
>>> link_crawler('http://example.python-scraping.com', '/(index|view)/',
scrape_callback=scrape_callback)

你现在应该能够看到页面下载的输出显示,以及一些显示了URL和被抓取数据的行,如下所示。

Downloading: http://example.python-scraping.com/view/Botswana-30
http://example.webscraping.com/view/Botswana-30 ['600,370 square
kilometres', '2,029,307', 'BW', 'Botswana', 'Gaborone', 'AF', '.bw', 'BWP',
'Pula', '267', '', '', 'en-BW,tn-BW', 'ZW ZA NA ']

通常,当抓取网站时,我们更希望复用得到的数据,而不仅仅是打印出来,因此我们将对该示例进行扩展,将结果保存到CSV电子表格当中,如下所示。

import csv
import re
from lxml.html import fromstring
class CsvCallback:
    def __init__(self):
        self.writer = csv.writer(open('../data/countries_or_districts.csv','w'))
        self.fields = ('area', 'population', 'iso', 'country_or_district',
                       '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 = fromstring(html)
            all_rows = [
                tree.xpath(
                  '//tr[@id="places_%s__row"]/td[@class="w2p_fw"]' %
field)[0].text_content()
                for field in self.fields]
             self.writer.writerow(all_rows)

为了实现该 callback ,我们使用了回调类,而不再是回调函数,以便保持 csvwriter 属性的状态。 csvwriter 属性在构造方法中进行了实例化处理,然后在 __call__ 方法中执行了多次写操作。请注意, __call__ 是一个特殊方法,在对象作为函数被调用时会调用该方法,这也是链接爬虫中 cache_callback 的调用方法。也就是说, scrape_callback(url, html) 和调用 scrape_callback.__call__(url,html) 是等价的。如果想要了解更多有关Python特殊类方法的知识,可以参考 https://docs. python.org/3/reference/datamodel.html#special-method-names

下面是向链接爬虫传入回调的代码写法。

>>> from chp2.advanced_link_crawler import link_crawler
>>> from chp2.csv_callback import CsvCallback
>>> link_crawler('http://example.python-scraping.com/', '/(index|view)',
max_depth=-1, scrape_callback=CsvCallback())

请注意, CsvCallback 期望在与你运行代码的父目录同一层中包含一个 data 目录。这一要求同样可以修改,不过我们建议你遵循良好的编码实践,保持代码与数据分离——让你的代码在版本控制之下,而 data 目录在 .gitignore 文件中。下面是示例的目录结构。

wswp/
|-- code/
|    |-- chp1/
|    |    + (code files from chp 1)
|    +-- chp2/
|        + (code files from chp 2)
|-- data/
|    + (generated data files)
|-- README.md
+-- .gitignore

现在,当我们运行这个使用了 scrape_callback 的爬虫时,程序就会将结果写入到一个CSV文件中,我们可以使用类似Excel或者LibreOffice的应用查看该文件。此时可能会比第一次运行时花费更多时间,因为它正在忙碌地收集信息。当爬虫退出时,你应该就可以查看包含所有数据的CSV文件了,如图2.8所示。

26.jpg

图2.8

成功了!我们完成了第一个可以工作的数据抓取爬虫。