21-使用爬虫实现双向爬取
3.4.1 使用爬虫实现双向爬取
你可能已经注意到了前面例子中的 `yield` 语句。 `yield` 与 `return` 在某种意义上来说有些相似,都是将返回值提供给调用者。不过,和 `return` 不同的是, `yield` 不会退出函数,而是继续执行 `for` 循环。从功能上来说,前面的例子与下面的代码大体相当: `yield` 是Python“魔法”的一部分,它可以使日常的高效编程工作更加轻松。
我们将之前的爬虫拷贝到一个新文件中,并命名为 manual.py
。
$ ls
properties scrapy.cfg
$ cp properties/spiders/basic.py properties/spiders/manual.py
next_requests = []
for url in...
next_requests.append(Request(...))
for url in...
next_requests.append(Request(...))
return next_requests
在 properties/spiders/manual.py
文件中,通过添加 from scrapy.http import Request
语句引入 Request
模块,将爬虫的 name
参数改为 'manual'
,修改 start_urls
以使用第一个索引页,并将 parse()
方法重命名为 parse_item()
。好了!现在开始编写一个新的 parse()
方法,来实现水平和垂直两种抓取方式。
def parse(self, response):
# Get the next index URLs and yield Requests
next_selector = response.xpath('//*[contains(@class,'
'"next")]//@href')
for url in next_selector.extract():
yield Request(urlparse.urljoin(response.url, url))
# Get item URLs and yield Requests
item_selector = response.xpath('//*[@itemprop="url"]/@href')
for url in item_selector.extract():
yield Request(urlparse.urljoin(response.url, url),
callback=self.parse_item)
我们现在已经准备好运行该爬虫了。不过如果让该爬虫以当前的方式运行的话,则会抓取网站完整的5万个页面。为了避免运行时间过长,可以通过命令行参数: -s CLOSESPIDER_ITEMCOUNT=90
,告知爬虫在爬取指定数量(如90个)的Item后停止运行(更多细节参见第7章)。现在,我们可以运行了。
$ scrapy crawl manual -s CLOSESPIDER_ITEMCOUNT=90
INFO: Scrapy 1.0.3 started (bot: properties)
...
DEBUG: Crawled (200) <...index_00000.html> (referer: None)
DEBUG: Crawled (200) <...property_000029.html> (referer: ...index_00000.
html)
DEBUG: Scraped from <200 ...property_000029.html>
{'address': [u'Clapham, London'],
'date': [datetime.datetime(2015, 10, 4, 21, 25, 22, 801098)],
'description': [u'situated camden facilities corner'],
'image_urls': [u'http://web:9312/images/i10.jpg'],
'price': [223.88],
'project': ['properties'],
'server': ['scrapyserver1'],
'spider': ['manual'],
'title': [u'Portered Mile'],
'url': ['http://.../property_000029.html']}
DEBUG: Crawled (200) <...property_000028.html> (referer: ...index_00000.
html)
...
DEBUG: Crawled (200) <...index_00001.html> (referer: ...)
DEBUG: Crawled (200) <...property_000059.html> (referer: ...)
...
INFO: Dumping Scrapy stats: ...
'downloader/request_count': 94, ...
'item_scraped_count': 90,
如果仔细查看前面的输出,就会发现我们同时获得了水平抓取和垂直抓取的结果。第一个 index_00000.html
读取后,派生出了许多请求。当它们执行时,调试信息通过 referer
URL指出是谁发起的请求。比如,可以看到, property_000029.html
、 property_000028.html……
及 index_00001.html
都有相同的 referer(index_00000.html)
。而 property_000059.html
及其他请求则是以 index_00001.html
为 referer
的,并且该过程还在持续。
从该示例中还可以观察到,Scrapy在处理请求时使用的是 后入先出(LIFO) 策略(即深度优先爬取)。用户提交的最后一个请求会被首先处理。在大多数情况下,这种默认的方式非常方便。比如,我们想要在移动到下一个索引页之前处理每一个房源页时。否则,我们将会填充一个包含待爬取房源页URL的巨大队列,无谓地消耗内存。另外,在许多情况中,你可能需要辅助的请求来完成单个请求,我们将会在后面的章节中遇到这种情况。你需要这些辅助的请求能够尽快完成,以腾出资源,并且让被抓取的Item能够稳定流动。
我们可以通过设置 Request()
的优先级参数修改默认顺序,大于0表示高于默认的优先级,小于0表示低于默认的优先级。通常来说,Scrapy的调度器会首先执行高优先级的请求,不过不要花费太多时间来考虑具体的哪个请求应该被首先执行。很可能在你的应用中,不会使用超过1个或2个请求优先级。此外还需要注意的是,URL还会被执行去重操作,这在大部分时候也是我们想要的功能。不过如果我们需要多次执行同一个URL的请求,可以设置 dont_filter_Request()
参数为 true
。