Scrapy 没有按照配置 CLOSESPIDER_ITEMCOUNT, CLOSESPIDER_TIMEOUT 来终止爬虫的问题
在 settings 中配置了关闭爬虫的相关扩展:
CLOSESPIDER_TIMEOUT
CLOSESPIDER_ITEMCOUNT
CLOSESPIDER_PAGECOUNT
CLOSESPIDER_ERRORCOUNT
或者是在命令行运行 spider 时,用命令行参数进行了相关设置:
scrapy crawl xxxSpider -s CLOSESPIDER_ITEMCOUNT=10
scrapy crawl xxxSpider -s CLOSESPIDER_TIMEOUT=10
你会发现,爬虫并不会在爬取10个 item 之后或者是在 10秒之后停止,可以查到数据库中,可能有20几个 item 或者说设置为10秒,但直到几十秒才停止。
这是什么原因呢?
当触发 spider 关闭时,CLOSESPIDER_ITEMCOUNT 正好被定义,但由于 scrapy 的异步特性,它将完成对当前正在处理的所有 item 的爬取。因此,如果你想解析 2 个 item,但其实已经有 20 个 item 正在队列中等待处理了,那么爬虫也将完成所有 item的解析。
(参考: https://stackoverflow.com/questions/44006672/scrapy-closespider-itemcount-setting-not-working-as-expected?r=SearchResults)
如果一定要在确定的 n 个 item 之后关闭爬虫,可以偿试下面三种方案:
方案1:将下载并行数量改为1个(CONCURRENT_REQUESTS=1),可以在爬取n个之后,比较准确地把爬虫关闭在n个item的位置,但可能还有多出1个item,因为关闭spider时,可能还有一个item正在处理。
方案2:在 pipeline 中的 process_item() 方法中,将还未处理的 item 抛出 DropItem 异常处理掉。这种方法就是最准确地,只收集了 个item后其他 DropItem 丢弃。
if self.maxItemCount != 0 and self.itemCount >= self.maxItemCount:
raise DropItem("ClOSESPIDER_ITEMCOUNT limit reached - " + str(self.maxItemCount))
else:
pass
方案3:判断当前爬到的 item数量是不是达到了 CLOSESPIDER_ITEMCOUNT 配置的数目,如果达到配置的数量,就 raise CloseSpider() 异常来关闭爬虫。(这种方法也无法准备地达到只收取 n 个 item 的目的。CloseSpider() 异常抛出后,爬虫还是会继续处理完正在等待的业务。)
(方案3参考:https://stackoverflow.com/questions/44566184/scrapy-spider-not-terminating-with-use-of-closespider-extension/44585423?r=SearchResults#44585423 )
(CloseSpider()异常文档链接: https://doc.scrapy.org/en/latest/topics/exceptions.html#closespider)
(DropItem() 异常文档链接: https://doc.scrapy.org/en/latest/topics/exceptions.html#DropItem)
方案3 示例代码如下:
import scrapy
from scrapy.spiders import XMLFeedSpider
from scrapy.exceptions import CloseSpider
from myspiders.items import MySpiderItem
class MySpiderSpider(XMLFeedSpider):
name = "myspiders"
allowed_domains = {"www.mysource.com"}
start_urls = [
"https://www.mysource.com/source.xml"
]
iterator = 'iternodes'
itertag = 'item'
item_count = 0
@classmethod
def from_crawler(cls, crawler):
settings = crawler.settings
return cls(settings)
def __init__(self, settings):
self.settings = settings
def parse_node(self, response, node):
if(self.settings['CLOSESPIDER_ITEMCOUNT'] and int(self.settings['CLOSESPIDER_ITEMCOUNT']) == self.item_count):
raise CloseSpider('CLOSESPIDER_ITEMCOUNT limit reached - ' + str(self.settings['CLOSESPIDER_ITEMCOUNT']))
else:
self.item_count += 1
id = node.xpath('id/text()').extract()
title = node.xpath('title/text()').extract()
item = MySpiderItem()
item['id'] = id
item['title'] = title
return item