13-为链接爬虫添加抓取回调
[toc]
2.7.2 为链接爬虫添加抓取回调
前面我们已经了解了如何抓取国家(或地区)数据,接下来我们需要将其集成到第1章的链接爬虫当中。要想复用这段爬虫代码抓取其他网站,我们需要添加一个 callback 参数处理抓取行为。 callback 是一个函数,在发生某个特定事件之后会调用该函数(在本例中,会在网页下载完成后调用)。这里的抓取 callback 函数包含 url 和 html 两个参数,并且可以返回一个待爬取的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 ,我们使用了回调类,而不再是回调函数,以便保持 csv 中 writer 属性的状态。 csv 的 writer 属性在构造方法中进行了实例化处理,然后在 __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所示。

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