
本教程深入解析scrapy爬虫在遭遇5xx等http错误时,即使设置了`handle_httpstatus_all`仍可能触发重试或停止爬取的原因。核心在于下载器中间件`retrymiddleware`先于爬虫中间件`httpErrormiddleware`处理响应。文章将详细阐述两大中间件的工作机制及其交互,并提供多种配置策略,助你有效控制错误处理与请求重试行为。
Scrapy HTTP错误处理机制概览
在Scrapy爬取网页时,我们有时会遇到500 internal Server Error这类HTTP错误。尽管在scrapy.Request的meta参数中设置了”handle_httpstatus_all”: True,期望所有HTTP状态码的响应都能进入parse方法进行处理,但爬虫可能仍然在多次重试后因500错误而停止。这通常是由于对Scrapy中间件的工作机制存在误解。理解Scrapy处理请求和响应的内部流程,特别是下载器中间件和爬虫中间件的协同作用,是解决此类问题的关键。
Scrapy中间件架构:下载器与爬虫中间件
Scrapy的请求和响应处理流程通过一系列中间件进行,它们分为两大类,并以特定的顺序执行:
- 
下载器中间件 (Downloader Middleware) 下载器中间件位于Scrapy引擎和下载器之间。它们在请求发送到目标网站之前和从目标网站接收到响应之后进行处理。例如,它们可以处理User-Agent、cookie、代理、限速以及请求重试等。当一个响应从下载器返回时,它首先会经过下载器中间件链。 
- 
爬虫中间件 (Spider Middleware) 爬虫中间件位于Scrapy引擎和Spider之间。它们在响应被传递给Spider的parse方法之前以及Spider生成的Request对象或Item对象被传递回引擎之前进行处理。这类中间件通常用于错误处理、过滤、处理Spider的输出等。 
理解这两类中间件的关键在于它们的处理顺序:一个请求从引擎发出,首先经过下载器中间件处理后发送;响应返回后,首先经过下载器中间件处理,然后才传递给爬虫中间件,最后才到达Spider的parse方法。
HttpErrorMiddleware:控制错误响应进入Spider
HttpErrorMiddleware属于爬虫中间件。它的主要作用是决定哪些HTTP状态码的响应会被传递给Spider的parse方法。默认情况下,Scrapy只会将状态码为200-300范围内的响应传递给Spider,而其他状态码(如4xx或5xx)则会被过滤掉,除非通过特定设置进行允许。
当你设置”handle_httpstatus_all”: True时,你实际上是在告诉HttpErrorMiddleware:无论响应的HTTP状态码是什么,都应该将其传递给Spider的parse方法。
示例代码:
import scrapy class MySpider(scrapy.Spider): name = 'my_spider' start_urls = ['https://www.something.net'] # 假设这个URL可能返回500错误 def start_requests(self): # 设置 handle_httpstatus_all 为 True,允许所有状态码进入 parse 方法 yield scrapy.Request( url=self.start_urls[0], callback=self.parse, meta={'handle_httpstatus_all': True} ) def parse(self, response): if response.status != 200: self.logger.warning(f"接收到非200状态码 {response.status} for {response.url}") # 在这里可以根据不同的状态码进行逻辑处理 # 即使是500错误,如果最终抵达这里,也可以被处理 item = {} # 构建你的 Item # ... 根据 response 内容填充 item yield item
RetryMiddleware:自动重试临时性错误
RetryMiddleware则属于下载器中间件。它的核心功能是识别并自动重试那些被认为是临时性错误的HTTP响应。常见的临时性错误包括500 Internal Server Error、503 Service Unavailable、408 Request Timeout等。RetryMiddleware会在这些错误发生时,按照预设的次数自动重新发送请求,以期在后续尝试中获得成功的响应。
由于RetryMiddleware是下载器中间件,它在HttpErrorMiddleware之前执行。这意味着,当Scrapy收到一个500错误响应时,RetryMiddleware会首先拦截它,并根据配置尝试重试请求。只有当所有的重试尝试都失败后,这个“最终”的错误响应才会被传递给爬虫中间件链,进而由HttpErrorMiddleware处理。
这就是为什么即使设置了”handle_httpstatus_all”: True,500错误仍然可能导致爬虫在多次重试后停止的原因:handle_httpstatus_all只在响应最终到达HttpErrorMiddleware时才发挥作用,而在此之前,RetryMiddleware已经进行了多次重试。
深入理解两者交互
总结来说,Scrapy处理HTTP错误响应的流程如下:
- 请求发送: Spider发出Request,经过下载器中间件处理后发送。
- 响应接收: 下载器收到响应。
-  下载器中间件处理: 响应首先进入下载器中间件链。
- 如果RetryMiddleware检测到可重试的错误状态码(如500),它会拦截该响应,并根据配置尝试重新发送请求。
- 如果重试成功,则将成功响应传递下去。
- 如果重试次数达到上限,或者请求被明确标记为不重试,则将该最终的错误响应传递给爬虫中间件。
 
-  爬虫中间件处理: 响应进入爬虫中间件链。
- HttpErrorMiddleware根据handle_httpstatus_all的设置,决定是否将此响应(无论是成功还是失败)传递给Spider。
 
- Spider处理: 响应最终到达Spider的parse方法。
因此,”handle_httpstatus_all”: True的作用是确保“最终”的响应(无论是重试后的成功响应,还是重试失败后的错误响应)能够进入Spider,而不是阻止RetryMiddleware进行初期的重试尝试。
精准控制Scrapy的重试行为
为了更灵活地处理HTTP错误和重试,Scrapy提供了多种配置选项:
- 
禁用特定请求的重试:dont_retry 在Request的meta中设置’dont_retry’: True,可以禁用该特定请求的重试行为。这意味着即使遇到可重试的HTTP错误码,RetryMiddleware也不会对其进行重试,而是直接将响应传递给后续中间件。 yield scrapy.Request( url='https://www.example.com/no_retry', callback=self.parse, meta={'dont_retry': True, 'handle_httpstatus_all': True} )
- 
自定义特定请求的重试次数:max_retry_times 通过在Request的meta中设置’max_retry_times’,你可以为单个请求指定不同的最大重试次数。 yield scrapy.Request( url='https://www.example.com/custom_retry', callback=self.parse, meta={'max_retry_times': 1, 'handle_httpstatus_all': True} # 只重试1次 )
- 
全局禁用重试中间件:RETRY_ENABLED 在项目的settings.py文件中设置RETRY_ENABLED = False,可以完全禁用RetryMiddleware。这将导致所有请求在遇到可重试错误时都不会被自动重试。 # settings.py RETRY_ENABLED = False 注意: 除非你已实现了自己的重试逻辑,否则不建议全局禁用,因为这可能导致大量临时性错误直接失败。 
- 
自定义重试的HTTP状态码:RETRY_HTTP_CODES 在settings.py中,你可以修改RETRY_HTTP_CODES列表,以自定义哪些HTTP状态码会被RetryMiddleware视为可重试的错误。 # settings.py # 默认值示例 # RETRY_HTTP_CODES = [500, 502, 503, 504, 408, 429] # 示例:移除500,让其直接进入 parse 方法而不重试 RETRY_HTTP_CODES = [502, 503, 504, 408, 429] 通过从列表中移除500,可以使得500错误不再被RetryMiddleware重试,而是直接传递给HttpErrorMiddleware,然后根据handle_httpstatus_all的设置进入Spider。 
注意事项与最佳实践
- 理解业务需求: 在配置重试策略时,首先要明确哪些错误应该重试(通常是临时性的服务器错误),哪些应该立即处理(例如404 Not Found或403 Forbidden,这些通常不是临时性错误)。
- 资源消耗: 过多的重试会增加爬虫的运行时间,消耗更多的网络带宽和服务器资源,并可能对目标网站造成不必要的负担。合理设置max_retry_times至关重要。
- 避免死循环: 如果你完全禁用了RetryMiddleware,请确保你的Spider或自定义中间件中有适当的错误处理逻辑,以防止请求陷入无限重试或未处理的错误导致爬虫崩溃。
- 日志记录: 详细的日志记录对于调试HTTP错误和重试行为至关重要。确保Scrapy的日志级别设置得当,以便能够追踪请求的重试过程和最终状态。
总结
Scrapy的中间件机制为请求和响应处理提供了强大的灵活性。当遇到5xx这类HTTP错误时,理解HttpErrorMiddleware和RetryMiddleware的各自职责及其执行顺序是解决问题的关键。RetryMiddleware作为下载器中间件,负责在响应到达Spider之前处理并重试临时性错误。而HttpErrorMiddleware作为爬虫中间件,则根据handle_httpstatus_all的设置决定“最终”的响应是否进入Spider。通过灵活运用dont_retry、max_retry_times、RETRY_ENABLED和RETRY_HTTP_CODES等配置,开发者可以根据实际需求精准控制Scrapy的重试行为,从而构建更健壮、高效的爬虫。


