
本教程深入解析 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 的重试行为,从而构建更健壮、高效的爬虫。


