5月31日 01:07

Scrapy 中间件有什么作用?适合哪些场景?

Scrapy 中间件可以理解为爬虫流程里的拦截层:请求发出去之前能改,响应回来之后能查,异常发生时也能兜底。它主要分为下载器中间件和爬虫中间件,前者夹在引擎和下载器之间,常用来处理请求头、代理、重试、Cookie、响应状态;后者夹在引擎和 Spider 之间,更适合处理输入给 Spider 的响应和 Spider 产出的请求。中间件的价值是把通用逻辑从 Spider 里抽走,但边界也明显:业务字段解析不要塞进中间件,否则后面排查时会分不清数据到底在哪一步被改掉。判断一个逻辑要不要做成中间件,可以看它是否能被多个 Spider 复用,以及是否只依赖请求、响应、异常这些通用对象。

追问

下载器中间件和爬虫中间件有什么区别?

下载器中间件关注“请求能不能顺利拿到响应”,所以常见场景是加请求头、切代理、处理 403、记录耗时、对异常做重试。爬虫中间件关注“响应和请求如何进出 Spider”,比如过滤某些响应、统一补充 meta、处理 Spider 抛出的异常。取舍上,大多数反爬和网络层问题放下载器中间件更自然,业务解析前后的流程控制才考虑爬虫中间件。踩坑点是把两者职责混在一起,比如在下载器中间件里解析商品价格,短期能跑,长期会让代码很难测试。

python
class TimingDownloaderMiddleware: def process_request(self, request, spider): request.meta["start_ts"] = time.time() def process_response(self, request, response, spider): cost = time.time() - request.meta.get("start_ts", time.time()) spider.logger.info("%s %s %.2fs", response.status, response.url, cost) return response

process_requestprocess_responseprocess_exception 分别怎么用?

process_request 在请求进入下载器前执行,适合补请求头、代理、Cookie 或直接返回缓存响应。process_response 在响应回到引擎后执行,适合检查状态码、替换响应、对异常页面重新发请求。process_exception 只处理下载阶段抛出的异常,比如超时、连接失败、DNS 错误。边界是返回值会改变流程:返回 Request 会重新调度,返回 Response 会跳过下载,返回 None 才是继续交给下一个中间件;不理解这个规则,很容易写出重复请求或响应丢失的问题。

python
class StatusRetryMiddleware: def process_response(self, request, response, spider): if response.status in {403, 429} and request.meta.get("retry_times", 0) < 2: new = request.copy() new.meta["retry_times"] = request.meta.get("retry_times", 0) + 1 new.dont_filter = True return new return response

中间件优先级应该怎么配置?

Scrapy 通过数字控制中间件顺序,下载器中间件的 process_request 按数字从小到大执行,process_response 则反过来。这个设计容易让人第一次配置时看反,尤其是多个中间件都在改代理、请求头和重试逻辑时。取舍上,通用基础逻辑可以靠前,例如设置默认 Header;依赖响应结果的统计、重试、清洗可以靠后。踩坑最多的是优先级和内置中间件冲突,比如自定义重试放错位置,导致 Scrapy 内置 RetryMiddleware 已经处理过一次,你又额外重试一次。

python
# settings.py DOWNLOADER_MIDDLEWARES = { "myproject.middlewares.RandomHeaderMiddleware": 400, "myproject.middlewares.ProxyMiddleware": 410, "myproject.middlewares.StatusRetryMiddleware": 550, }

哪些逻辑不适合写进中间件?

和具体页面结构强相关的字段解析不适合写进中间件,应该留在 Spider 或 Item Pipeline 里。中间件也不适合保存大量业务状态,例如把所有已抓商品、分类树、价格规则都塞进去,这会让它变成隐藏的业务中心。边界判断可以很简单:如果这个逻辑换一个 Spider 仍然有价值,它适合抽成中间件;如果只服务某个页面字段,就别放进去。实际项目里滥用中间件会让调试很痛苦,因为响应还没到 parse 方法就已经被改过,日志不全时很难还原现场。

如何设计一个可维护的代理中间件?

代理中间件不要只做随机选择,还应该记录代理的失败次数、最近使用时间、适用域名和是否需要隔离登录态。轻量任务可以从列表里轮询,成本低也容易排查;高并发任务则需要独立代理池服务,负责健康检查和下线坏代理。取舍上,本地简单实现开发快,但多 Spider 共用时容易重复踩同一个坏代理;中心化代理池更稳定,却需要额外维护。常见坑是失败后立刻无限重试同一个请求,最后把调度队列拖慢,应该设置最大重试次数并对状态码做区分。

python
class ProxyMiddleware: def process_request(self, request, spider): proxy = spider.proxy_pool.get(domain=request.url.split('/')[2]) if proxy: request.meta["proxy"] = proxy def process_exception(self, request, exception, spider): proxy = request.meta.get("proxy") if proxy: spider.proxy_pool.mark_bad(proxy)

结论

Scrapy 中间件适合承载跨 Spider 的通用流程逻辑,尤其是网络请求、反爬、重试、代理、日志和异常处理。写中间件时最重要的是职责边界和执行顺序:通用逻辑抽出来,业务解析留在业务层,返回值和优先级要明确。这样中间件才是扩展点,而不是另一个难排查的黑盒。生产环境里还要给关键中间件补日志和开关,遇到代理池抖动、目标站改规则或重试风暴时,可以快速关闭某一层,而不是停掉整套爬虫。

标签:Scrapy