Scrapy自定义下载中间件触发robotstxt递归深度超限错误如何解决
问题根因
核心问题出在你自定义中间件process_request方法的返回值不符合Scrapy规范:
Scrapy的下载中间件process_request方法如果要让请求继续向下执行后续中间件,必须返回None,而你在else分支返回了request对象。当Scrapy接收到process_request返回的Request对象时,会将该请求重新送入整个下载中间件链从头开始处理,直接导致无限循环:
- 爬虫启动后自动发起
robots.txt请求 - 请求过中间件到你的自定义逻辑时,URL不在已访问集合,返回request
- 请求被重新送入中间件链,再次走到你的自定义逻辑,再次返回request
- 循环往复直到达到Python递归深度上限,抛出你看到的报错。
解决思路
1. 修复返回值(必改)
直接修改process_request方法的else分支,把return request改为return None即可,代码修改后如下:
def process_request(self, request, spider): if request.url in self.urls_visited: logging.info('url already scraped: %s', request.url) raise IgnoreRequest() # 返回None即可让后续中间件继续处理请求 return None
2. 可选优化
- 把存储已访问URL的容器改为
set类型,in查询时间复杂度从O(n)降到O(1),如果你的已访问URL量很大,性能提升会非常明显 - 当前你的已访问URL只在中间件初始化时从数据库拉取一次,同一次爬虫运行中新抓取的URL不会被加入到校验集合,会导致同一次运行中的重复URL无法被过滤,建议在
process_response中判断请求成功后,把URL加入self.urls_visited同时写入数据库 - 可以把自定义中间件的顺序调到
RobotsTxtMiddleware之前,已抓取的URL可以直接被拦截,不需要再走robots校验、超时判断等逻辑,节省资源 - 可以增加robots.txt请求的豁免逻辑,避免对Scrapy自动发起的系统请求做不必要的校验:
def process_request(self, request, spider): # 豁免robots.txt系统请求 if request.url.endswith('robots.txt'): return None if request.url in self.urls_visited: logging.info('url already scraped: %s', request.url) raise IgnoreRequest() return None
内容的提问来源于stack exchange,提问作者CyberPunk
相关产品推荐
相关产品推荐

