写点什么

Scrapy 中 process_request 返回 request 和 None 的区别

用户头像
LLLibra146
关注
发布于: 2020 年 11 月 03 日

本文首发于个人博客,文章链接为:https://blog.d77.xyz/archives/67ddca4b.html

前言


上篇文章中遇到了一个问题,就是在 process_request 函数中返回 request 对象导致爬虫退出的问题,这篇文章来解释下。

环境搭建


为了弄清楚这个问题,首先搭建一个可以运行 scrapy 的环境,新建一个 scrapy 项目,scrapy startproject testscrapy,之后添加一个名为 testscrapy 的爬虫,代码为:


import scrapy as scrapyfrom scrapy import Request

class TestScrapy(scrapy.Spider): name = 'testscrapy'
def start_requests(self): # 使用http方便抓包 yield Request(url='http://httpbin.org', callback=self.parse)
def parse(self, response, **kwargs): print(response.status) print(response.xpath('//title/text()').get())
复制代码


并且在设置中开启下载中间件,运行下爬虫没有问题环境就搭建好了。


问题原因


首先以正常模式运行爬虫,没有问题,可以正常打印状态码和 title。



接着修改下载中间件,在请求头中随意添加一个键值对。



运行爬虫,抓包可以看到请求头被正常添加进去了。



此时如果将返回值修改为 request 对象的话,爬虫会直接退出。




并不会打印状态码和 title,这里有两条日志供我们分析。


  • 首先是递归错误,超过了最大递归深度(RecursionError: maximum recursion depth exceeded while calling a Python object)。


  • 其次是不再显示重复项(no more duplicates will be shown (see DUPEFILTER_DEBUG to show all duplicates))


看到第一条日志我猜测可能是出现了死循环,process_request 函数在不停的调用自己。


看到第二条日志我猜测可能是 request 被重复调度了,scrapy 发现返回的 request 对象已经被请求过了,所以不再调度它。由于 scrapy 默认开启了 URL 去重,所以 request 对象会被丢掉。


再来看日志顺序,重复调度日志在递归错误之后,这里可以猜测在递归错误报错后(因为死循环时是无法调度的)调度器才调度到了 request 对象,然后发现请求过了,之后打印日志,丢掉 request 请求对象。


为了证明 process_request 进入了死循环,加一个延迟,运行爬虫。



可以看到,一直在打印数字 1,没有打印重复调度的日志,直到递归错误,爬虫才会往下继续运行提示重复调度。


到这里原因就很明显了,如果 processrequest 方法返回一个 request 请求的话,scrapy 会马上将这个 request 对象添加到下载队列(不会进入到下载器),然后继续处理它,由于 processrequest 方法又会返回 request 对象,又会重复将 request 对象添加到下载队列,进入到了死循环。


当发生递归错误之后,死循环被打破,scrapy 提示重复调度,丢掉请求,爬虫退出。


解决方法


所以解决方案就是:不要默认返回 request 对象!


如果只是添加请求头,修改代理 IP 等需求,直接返回 None 就可以了,scrapy 会将修改后的 request 对象调度到下载器去下载。


如果需要将一个请求转到另一个请求的话,需要做好判断后返回 request 对象



例如:如果想要将 httpbin.org 的请求转发到 baidu,可以按照以上的代码来请求,可以看到,返回了正确的状态码和百度的 title。


顺便再来说下返回值为 response 对象的适用范围。


如果 process_request 方法返回一个 response 对象的话,说明不需要 scrapy 的下载器来下载响应,直接就可以将 response 对象当作下载好的响应返回。


例如:




可以看到状态码和 body 都是返回的 response 响应里面的内容,而不是 httpbin 网站的 title。


最常见的用途就是 selenium,它使用 selenium 来渲染网页,然后将获取到的源代码封装成 response 对象返回,巧妙地跳过了 scrapy 自带的下载器,因为它不能执行 JS 代码。


总结


通过以上的实验,可以得知 scrapy 下载中间件死循环的原因和解决方法,scrapy 的官方文档只有简单的几句话介绍,并没有更详细的解释,所以在遇到这种解释很简单的方法时,最好自己动手实验一下,可以加深自己对 scrapy 的理解。


发布于: 2020 年 11 月 03 日阅读数: 52
用户头像

LLLibra146

关注

还未添加个人签名 2018.09.17 加入

还未添加个人简介

评论

发布
暂无评论
Scrapy中process_request返回request和None的区别