Python中Scrapy的RetryMiddleware不生效,如何解决?
按照惯例,我先贴出来代码
class CustomerRetryMiddleware(RetryMiddleware):
"""
去掉了一些判断,增加了 log
"""
def process_response(self, request, response, spider):
if response.status in self.retry_http_codes:
retries = request.meta.get('retry_times', 0) + 1
reason = response_status_message(response.status)
spider.logger.warn('下载中收到错误 http code:{0},丢入重试,第{1}次重试'.format(reason, retries))
return self._retry(request, reason, spider) or response
else:
return response
"""
去掉了 EXCEPTIONS_TO_RETRY 判断,无脑重试
"""
def process_exception(self, request, exception, spider):
retries = request.meta.get('retry_times', 0) + 1
spider.logger.warn('下载中收到:{0},丢入重试,第{1}次重试'.format(exception.reasons, retries))
return self._retry(request, 'CustomerRetryError', spider)
def _retry(self, request, reason, spider):
retries = request.meta.get('retry_times', 0) + 1
retry_times = self.max_retry_times
if 'max_retry_times' in request.meta:
retry_times = request.meta['max_retry_times']
stats = spider.crawler.stats
if retries <= retry_times:
spider.logger.debug("Retrying %(request)s (failed %(retries)d times): %(reason)s",
{'request': request, 'retries': retries, 'reason': reason},
extra={'spider': spider})
retryreq = request.copy()
retryreq.meta['retry_times'] = retries
retryreq.dont_filter = True
retryreq.priority = request.priority + self.priority_adjust
if isinstance(reason, Exception):
reason = global_object_name(reason.__class__)
stats.inc_value('retry/count')
stats.inc_value('retry/reason_count/%s' % reason)
return retryreq
else:
stats.inc_value('retry/max_reached')
spider.logger.warn('超过重试次数,停止重试')
process_response 方法主要是为了加一点 log process_exception 方法是为了出现任何 Exception 都能重试 同样,重写 _retry 方法也是为了加点 log
我遇到的问题是,如果我 settings.py 中设定了 5 次重试,那么,log 中一定只打印第 6 次重试的 log,前 5 次的 log 并没有打出来,我很疑惑,求解答
另外,我为了去除次数的问题,我还改配置改到 100 次,然而 log 中只打印第 101 次重试,然后就是 _retry 方法中的 else 语句的 log "超过重试次数,停止重试"
Python中Scrapy的RetryMiddleware不生效,如何解决?
遇到Scrapy的RetryMiddleware不生效的问题,通常是因为配置或自定义逻辑覆盖了默认行为。下面是一个完整的排查和解决方案。
首先,确保你在 settings.py 中正确启用了重试中间件并设置了相关参数:
# settings.py
RETRY_ENABLED = True # 默认就是True,但检查一下没坏处
RETRY_TIMES = 2 # 除第一次请求外,重试次数
RETRY_HTTP_CODES = [500, 502, 503, 504, 408] # 需要重试的HTTP状态码
DOWNLOADER_MIDDLEWARES = {
'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550,
}
如果已经配置了但还是不生效,最常见的原因是你的spider或自定义中间件返回了 None 或抛出了异常,阻止了重试逻辑的执行。检查你的spider代码:
import scrapy
from scrapy.downloadermiddlewares.retry import get_retry_request
class MySpider(scrapy.Spider):
name = 'example'
def start_requests(self):
yield scrapy.Request('http://example.com', callback=self.parse, errback=self.errback)
def parse(self, response):
# 如果在这里直接返回None或item,错误处理链会中断
if response.status == 404:
self.logger.warning('Page not found')
# 不要直接return,让异常传播或返回Request对象
return
# ... 正常解析逻辑
def errback(self, failure):
# 自定义errback可能会覆盖默认重试逻辑
# 如果你在这里处理了failure但没有重新yield request,重试就不会发生
self.logger.error(f'Request failed: {failure.value}')
# 若要保留重试,可以调用原始重试逻辑:
# return get_retry_request(failure.request, failure.value, spider=self)
另一个常见坑点是自定义了 DOWNLOADER_MIDDLEWARES 但顺序不对,或者你的中间件返回了 Response/Request 对象。中间件顺序很重要,RetryMiddleware通常应该在550左右。检查你的自定义中间件:
# middlewares.py
from scrapy.downloadermiddlewares.retry import RetryMiddleware
class CustomRetryMiddleware(RetryMiddleware):
def process_response(self, request, response, spider):
# 如果你的逻辑在这里直接返回了response,原始RetryMiddleware就不会执行
if response.status in [403, 429]:
# 特殊状态码,你可能想自定义重试逻辑
reason = f'Status {response.status}'
return self._retry(request, reason, spider) or response
# 确保调用父类方法以保留默认重试行为
return super().process_response(request, response, spider)
def process_exception(self, request, exception, spider):
# 同样,确保异常处理调用父类
return super().process_exception(request, exception, spider)
然后在settings中正确配置中间件顺序:
DOWNLOADER_MIDDLEWARES = {
'myproject.middlewares.CustomRetryMiddleware': 540, # 比默认RetryMiddleware优先级高
'scrapy.downloadermiddlewares.retry.RetryMiddleware': 550,
}
最后,用这个简单的测试spider验证重试是否工作:
import scrapy
from scrapy.utils.test import get_crawler
class TestRetrySpider(scrapy.Spider):
name = 'test_retry'
start_urls = ['http://httpbin.org/status/500'] # 模拟服务器错误
custom_settings = {
'RETRY_TIMES': 2,
'RETRY_HTTP_CODES': [500],
'LOG_LEVEL': 'DEBUG', # 查看重试日志
}
def parse(self, response):
self.logger.info(f'最终成功: {response.url}')
运行后查看日志,你应该看到类似 Retrying <GET http://...> (failed 1 times): 500 Internal Server Error 的消息。
总结一下:检查设置、中间件顺序和自定义回调是否干扰了重试链。
还有一个你 if 里面 logger 是 debug,else 里面是 warn,如果 settings 里面改了 log_level 的话可能会看不到。
if 里边的是 Scrapy 的代码,我没有改,但是经过你的提醒,我终于发现我确实看不到 log,因为我配置的 loglevel 是 warn,多谢
我发现,我爬取了一批视频,总是有几个下载报 403,然而我手动打开这个视频是正常的。 我有 headers 中 random UA 和 X-Forwarded-For,不知道是不是因为下载太多被封了
403 一般来说是触发反爬了
我重新试了一下,我上边这段代码确实打印不出来 if retries <= retry_times: 判断里边的 log
重新看了一下,发现 <br>INFO: Enabled downloader middlewares:<br>['scrapy.downloadermiddlewares.httpauth.HttpAuthMiddleware',<br> 'New91Crawler.middlewares.CustomerRetryMiddleware',<br> 'scrapy.downloadermiddlewares.downloadtimeout.DownloadTimeoutMiddleware',<br> 'scrapy.downloadermiddlewares.defaultheaders.DefaultHeadersMiddleware',<br> 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware',<br> 'scrapy.downloadermiddlewares.retry.RetryMiddleware',<br> 'scrapy.downloadermiddlewares.redirect.MetaRefreshMiddleware',<br> 'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware',<br> 'scrapy.downloadermiddlewares.redirect.RedirectMiddleware',<br> 'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware',<br> 'scrapy.downloadermiddlewares.stats.DownloaderStats']<br><br>
我自定义的 retry 和 Scrapy 自带的 retry 都加载了,理论上不应该用我的覆盖系统的吗?
你可以看看 settings 里面的优先级,记得是数值越低的越先执行,系统自带的 retry 优先级好像是 550。
我只知道如何让自己的先执行,如何覆盖掉确实不知道了。
我自己的是 200,但是感觉还是先走的系统的,重试了之后才走的我的,奇怪


