Scrapy中CrawlSpider如何处理yield生成的Request对象
对应官方示例代码(方便对照)
def parse(self, response): request = scrapy.Request('http://www.example.com/index.html', callback=self.parse_page2, cb_kwargs=dict(main_url=response.url)) request.cb_kwargs['foo'] = 'bar' # 给回调函数补充传参 yield request def parse_page2(self, response, main_url, foo): yield dict( main_url=main_url, other_url=response.url, foo=foo, )
问题1解答
这个是Scrapy引擎层面内置的既定核心逻辑,不是CrawlSpider独有的能力,所有继承自基础Spider的爬虫类都遵循这套规则:
- 引擎在执行爬虫回调函数时,会自动接收函数产出的所有结果,逐一对结果类型做判断:如果是
Request对象,就丢给调度器入队,等待下载完成后调用该Request绑定的回调函数;如果是字典、Item这类结构化数据,就交给数据管道做后续存储、清洗处理。 CrawlSpider只是在基础Spider上封装了链接自动提取、爬取规则匹配的能力,处理回调产出对象的逻辑和基础Spider完全一致,不需要开发者额外做任何配置。
问题2解答
这个理解存在偏差,用yield而非return和“维持函数状态等待接收Response”没有关系:
- 核心原因是Scrapy支持回调返回可迭代对象,带yield的生成器函数属于可迭代对象的一种。你可以在一个回调里多次yield不同的Request、Item,引擎会遍历整个可迭代对象,把所有元素逐个按类型分发处理,不需要你提前把所有要发的请求、要存的数据攒到列表里最后一次性返回。
- 回调函数yield出Request后,当前函数不会驻留等待对应响应返回:Request被调度器接收后,当前回调会继续执行后续逻辑,把所有要产出的对象yield完就直接结束生命周期。后续Request下载完成得到的Response,是交给该Request绑定的目标回调(比如示例里的
parse_page2)处理,和当前的parse函数没有关联。
问题3解答
单URL抓取场景下,直接return Request和yield Request的运行效果完全一致:
- Scrapy引擎本身兼容两种回调返回形式:既支持直接返回单个Request/Item对象,也支持返回可迭代对象(生成器、列表、元组都算)。如果你的回调只需要产出一个对象,两种写法没有任何运行差异。
- 官方示例统一用yield只是为了写法的一致性:如果后续要扩展逻辑,比如在同一个回调里新增多个待抓取URL、多份待存储数据,不需要改动函数结构,直接加新的yield语句就行,开发效率更高。
实际开发提示:不管当前回调是不是只返回一个对象,都建议优先用yield写法,能避免后续加逻辑时漏改return导致只返回最后一个对象的低级bug。
内容的提问来源于stack exchange,提问作者John Jacob
相关产品推荐
相关产品推荐

