news 2026/4/3 5:42:38

Python 爬虫实战:Scrapy 中间件自定义开发

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python 爬虫实战:Scrapy 中间件自定义开发

前言

Scrapy 框架的高扩展性核心体现在其模块化的组件设计,而中间件(Middleware)是连接引擎(Engine)与其他核心组件(下载器、爬虫、响应处理)的关键桥梁。无论是应对反爬机制(如 UA 伪装、IP 代理、Cookie 池),还是实现请求 / 响应的个性化处理(如数据加密 / 解密、请求重试),自定义中间件都是最优解决方案。本文将从中间件的核心原理入手,系统讲解 Scrapy 各类中间件的开发规范,结合实战案例实现 UA 随机切换、代理池集成、请求重试等高频需求,帮助开发者掌握中间件的定制化开发能力,解决爬虫开发中的各类个性化与反爬问题。

摘要

本文聚焦 Scrapy 中间件的自定义开发实战,首先剖析 Scrapy 中间件的分类(下载器中间件、爬虫中间件)及执行流程,明确不同中间件的作用域与优先级规则;其次通过多个实战案例(目标站点:豆瓣图书 Top100),分别实现自定义 User-Agent 中间件、IP 代理中间件、请求重试中间件、响应数据清洗中间件;最后讲解中间件的调试方法与优先级调优策略。通过本文,读者可掌握 Scrapy 中间件的开发逻辑,灵活应对各类爬虫场景的个性化需求,提升爬虫的稳定性与抗反爬能力。

一、Scrapy 中间件核心原理

1.1 中间件分类与作用

Scrapy 中间件分为两大类,核心作用与执行阶段如下表所示:

中间件类型作用域核心作用
下载器中间件引擎 ↔ 下载器处理请求(如修改 UA、添加代理、加密参数)、处理响应(如解密、数据清洗)、请求异常重试
爬虫中间件引擎 ↔ 爬虫处理爬虫产出的 Item、调整请求优先级、过滤无效请求 / 响应

1.2 中间件执行流程

  1. 下载器中间件执行顺序
    • 请求方向(引擎→下载器):按settings.pyDOWNLOADER_MIDDLEWARES配置的优先级(数字越小越先执行)依次执行process_request方法;
    • 响应方向(下载器→引擎):按优先级(数字越大越先执行)依次执行process_response方法;
    • 异常处理:请求失败时执行process_exception方法。
  2. 爬虫中间件执行顺序
    • 请求方向(爬虫→引擎):执行process_spider_output方法;
    • 响应方向(引擎→爬虫):执行process_spider_input方法;
    • 异常处理:执行process_spider_exception方法。

1.3 核心方法说明

方法名所属中间件触发时机返回值规则
process_request下载器引擎将请求发送至下载器前返回 None:继续执行后续中间件;返回 Response:直接返回响应;返回 Request:重新调度请求
process_response下载器下载器返回响应至引擎前返回 Response:继续执行后续中间件;返回 Request:重新调度请求
process_exception下载器请求抛出异常时返回 None:继续抛出异常;返回 Response:替代异常结果;返回 Request:重新调度请求
process_spider_input爬虫响应发送至爬虫前返回 None:正常执行;抛出异常:触发异常处理
process_spider_output爬虫爬虫产出 Item/Request 后返回迭代器:包含 Item/Request 对象

二、环境搭建

2.1 基础环境要求

软件 / 库版本要求作用
Python≥3.8基础开发环境
Scrapy≥2.6爬虫框架
fake-useragent≥1.1.1生成随机 User-Agent
requests≥2.28代理池接口请求(可选)

2.2 环境安装

bash

运行

pip install scrapy==2.6.2 fake-useragent==1.1.1 requests==2.28.2

三、自定义中间件实战开发

3.1 创建基础爬虫项目

bash

运行

# 创建项目 scrapy startproject douban_book_middleware # 进入项目目录 cd douban_book_middleware # 创建爬虫文件 scrapy genspider douban_book_top100 book.douban.com

3.2 实战 1:自定义 User-Agent 中间件(反基础反爬)

3.2.1 开发思路

目标网站常通过固定 User-Agent 识别爬虫,需在请求头中随机切换 UA。通过下载器中间件的process_request方法修改请求头,利用fake-useragent生成随机 UA。

3.2.2 中间件实现(middlewares.py)

python

运行

from fake_useragent import UserAgent class RandomUserAgentMiddleware: """随机切换 User-Agent 中间件""" def __init__(self): # 初始化 UA 生成器 self.ua = UserAgent() def process_request(self, request, spider): """修改请求头中的 User-Agent""" # 随机选择一个 UA(可选指定浏览器类型) random_ua = self.ua.random request.headers['User-Agent'] = random_ua spider.logger.info(f"当前使用 User-Agent:{random_ua}") # 返回 None,继续执行后续中间件 return None
3.2.3 启用中间件(settings.py)

python

运行

# 启用自定义 UA 中间件,优先级设置为 543(Scrapy 默认中间件优先级范围 0-1000) DOWNLOADER_MIDDLEWARES = { 'douban_book_middleware.middlewares.RandomUserAgentMiddleware': 543, # 关闭 Scrapy 默认的 UserAgent 中间件 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, }
3.2.4 测试验证

修改爬虫文件(douban_book_top100.py):

python

运行

import scrapy class DoubanBookTop100Spider(scrapy.Spider): name = 'douban_book_top100' allowed_domains = ['book.douban.com'] start_urls = ['https://book.douban.com/top250'] def parse(self, response): # 仅打印响应状态码,验证 UA 中间件生效 self.logger.info(f"响应状态码:{response.status}") yield {'url': response.url, 'status': response.status}

启动爬虫:

bash

运行

scrapy crawl douban_book_top100
3.2.5 输出结果与原理

输出日志示例

plaintext

2025-12-18 10:00:00 [douban_book_top100] INFO: 当前使用 User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 2025-12-18 10:00:01 [douban_book_top100] INFO: 响应状态码:200

核心原理

  • process_request方法在请求发送至下载器前被调用,修改request.headers即可替换 UA;
  • 优先级 543 处于 Scrapy 默认中间件的中间区间,确保自定义 UA 覆盖默认值;
  • fake-useragent内置主流浏览器的 UA 池,可随机生成不同类型的 UA,降低被识别为爬虫的概率。

3.3 实战 2:自定义 IP 代理中间件(反 IP 封禁)

3.3.1 开发思路

当单 IP 访问频率过高时,目标网站会封禁 IP,需通过代理池动态切换 IP。本案例实现从本地代理池接口获取可用代理,在请求中添加代理配置。

3.3.2 中间件实现(middlewares.py)

python

运行

import requests import random class RandomProxyMiddleware: """随机切换 IP 代理中间件""" def __init__(self): # 代理池接口(需自行搭建代理池,如 scrapy-proxypool、ProxyPool) self.proxy_pool_url = "http://127.0.0.1:5010/get/" # 无效代理列表 self.invalid_proxies = [] def get_random_proxy(self): """从代理池获取随机可用代理""" try: response = requests.get(self.proxy_pool_url, timeout=5) if response.status_code == 200: proxy = response.text.strip() if proxy and proxy not in self.invalid_proxies: return f"http://{proxy}" except Exception as e: self.logger.error(f"获取代理失败:{e}") return None def process_request(self, request, spider): """为请求添加代理""" proxy = self.get_random_proxy() if proxy: request.meta['proxy'] = proxy spider.logger.info(f"当前使用代理:{proxy}") return None def process_exception(self, request, exception, spider): """请求异常时,标记代理无效并重新请求""" if 'proxy' in request.meta: invalid_proxy = request.meta['proxy'].replace('http://', '') self.invalid_proxies.append(invalid_proxy) spider.logger.warning(f"代理 {invalid_proxy} 无效,已加入黑名单") # 重新生成请求,不使用该代理 new_request = request.copy() new_request.dont_filter = True # 避免被去重过滤 return new_request
3.3.3 启用中间件(settings.py)

python

运行

DOWNLOADER_MIDDLEWARES = { 'douban_book_middleware.middlewares.RandomUserAgentMiddleware': 543, 'douban_book_middleware.middlewares.RandomProxyMiddleware': 542, # 优先级高于 UA 中间件 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, } # 增加超时时间,适配代理请求 DOWNLOAD_TIMEOUT = 10
3.3.4 输出结果与原理

输出日志示例

plaintext

2025-12-18 10:05:00 [douban_book_top100] INFO: 当前使用代理:http://123.125.71.3:8080 2025-12-18 10:05:01 [douban_book_top100] INFO: 响应状态码:200 2025-12-18 10:06:00 [douban_book_top100] WARNING: 代理 192.168.1.1:8888 无效,已加入黑名单

核心原理

  • process_request为请求添加request.meta['proxy']配置,Scrapy 下载器会通过该代理发送请求;
  • process_exception捕获请求异常(如超时、连接失败),标记代理无效并重新生成请求;
  • 优先级 542 高于 UA 中间件,确保代理配置先于 UA 生效;
  • dont_filter = True避免重新生成的请求被去重组件过滤。

3.4 实战 3:自定义请求重试中间件(提升稳定性)

3.4.1 开发思路

网络波动或目标网站临时故障会导致请求失败,需自定义重试逻辑,针对特定状态码(如 403、500)或异常类型进行重试,并限制重试次数。

3.4.2 中间件实现(middlewares.py)

python

运行

from scrapy.downloadermiddlewares.retry import RetryMiddleware from scrapy.utils.response import response_status_message class CustomRetryMiddleware(RetryMiddleware): """自定义请求重试中间件""" def process_response(self, request, response, spider): """根据响应状态码决定是否重试""" # 如果请求设置了 dont_retry,则不重试 if request.meta.get('dont_retry', False): return response # 针对 403、500、502、503 状态码重试 if response.status in [403, 500, 502, 503]: reason = response_status_message(response.status) spider.logger.warning(f"响应状态码 {response.status},触发重试:{reason}") return self._retry(request, reason, spider) or response return response def process_exception(self, request, exception, spider): """针对特定异常重试""" # 捕获连接超时、连接拒绝异常 if isinstance(exception, (scrapy.core.downloader.handlers.http11.TunnelError, scrapy.exceptions.ConnectionTimeout)): reason = f"请求异常:{type(exception).__name__}" spider.logger.warning(reason) return self._retry(request, reason, spider) return super().process_exception(request, exception, spider)
3.4.3 启用中间件(settings.py)

python

运行

DOWNLOADER_MIDDLEWARES = { 'douban_book_middleware.middlewares.RandomUserAgentMiddleware': 543, 'douban_book_middleware.middlewares.RandomProxyMiddleware': 542, 'douban_book_middleware.middlewares.CustomRetryMiddleware': 541, 'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': None, # 关闭 Scrapy 默认的重试中间件 'scrapy.downloadermiddlewares.retry.RetryMiddleware': None, } # 重试配置 RETRY_TIMES = 3 # 最大重试次数 RETRY_HTTP_CODES = [403, 500, 502, 503] # 需重试的状态码
3.4.4 输出结果与原理

输出日志示例

plaintext

2025-12-18 10:10:00 [douban_book_top100] WARNING: 响应状态码 403,触发重试:Forbidden 2025-12-18 10:10:02 [douban_book_top100] WARNING: 响应状态码 403,触发重试:Forbidden 2025-12-18 10:10:04 [douban_book_top100] INFO: 响应状态码:200

核心原理

  • 继承 Scrapy 原生RetryMiddleware,重写process_responseprocess_exception方法,自定义重试规则;
  • _retry方法为内置方法,会自动增加重试次数并重新调度请求;
  • RETRY_TIMES限制最大重试次数,避免无限重试。

3.5 实战 4:自定义爬虫中间件(数据过滤)

3.5.1 开发思路

爬虫产出的 Item 可能包含无效数据(如空值、重复值),需通过爬虫中间件的process_spider_output方法过滤无效 Item。

3.5.2 中间件实现(middlewares.py)

python

运行

class ItemFilterMiddleware: """过滤无效 Item 的爬虫中间件""" def process_spider_output(self, response, result, spider): """处理爬虫输出的 Item/Request""" for item in result: # 仅处理 Item 对象,Request 对象直接放行 if isinstance(item, scrapy.Item): # 过滤空标题的 Item if not item.get('title') or item.get('title').strip() == '': spider.logger.warning(f"过滤无效 Item:标题为空") continue # 过滤评分低于 8.0 的 Item score = item.get('score', 0) try: if float(score) < 8.0: spider.logger.warning(f"过滤无效 Item:评分 {score} 低于 8.0") continue except ValueError: spider.logger.warning(f"过滤无效 Item:评分 {score} 格式错误") continue yield item
3.5.3 启用中间件(settings.py)

python

运行

# 启用爬虫中间件 SPIDER_MIDDLEWARES = { 'douban_book_middleware.middlewares.ItemFilterMiddleware': 543, } # 完善 Item 定义(items.py) import scrapy class DoubanBookMiddlewareItem(scrapy.Item): title = scrapy.Field() score = scrapy.Field() author = scrapy.Field()
3.5.4 测试验证

修改爬虫文件的parse方法:

python

运行

def parse(self, response): book_list = response.xpath('//tr[@class="item"]') for book in book_list: item = DoubanBookMiddlewareItem() item['title'] = book.xpath('.//a/@title').extract_first() item['score'] = book.xpath('.//span[@class="rating_nums"]/text()').extract_first() item['author'] = book.xpath('.//p[@class="pl"]/text()').extract_first() yield item

启动爬虫后,日志输出示例:

plaintext

2025-12-18 10:15:00 [douban_book_top100] WARNING: 过滤无效 Item:评分 7.9 低于 8.0 2025-12-18 10:15:00 [douban_book_top100] WARNING: 过滤无效 Item:标题为空

核心原理

  • process_spider_output接收爬虫产出的迭代器(包含 Item/Request),遍历并过滤无效 Item;
  • 仅放行符合条件的 Item,确保最终存储的数据有效性;
  • 爬虫中间件优先级规则与下载器中间件一致,数字越小越先执行。

四、中间件调试与优先级调优

4.1 中间件调试方法

调试方式适用场景操作方法
日志打印验证中间件是否执行、参数是否正确在中间件方法中添加spider.logger.info/.warning/error打印关键信息
Scrapy shell测试单个请求的中间件执行流程scrapy shell https://book.douban.com/top250,查看request.headers/meta
断点调试定位中间件逻辑错误在中间件方法中添加import pdb; pdb.set_trace(),启动爬虫后分步调试

4.2 优先级调优规则

优先级区间作用示例配置
0-500核心基础中间件(如代理、UA)代理中间件(542)、UA 中间件(543)
500-800业务逻辑中间件(如重试、解密)重试中间件(541)、数据解密中间件(600)
800-1000后置处理中间件(如数据清洗)响应数据清洗中间件(900)

核心原则

  • 依赖前置的中间件优先级更高(如代理配置需先于 UA 配置);
  • 数据处理类中间件后置执行(如先获取响应,再清洗数据);
  • 避免优先级冲突(相同优先级的中间件执行顺序不确定)。

五、常见问题与解决方案

问题现象原因分析解决方案
中间件未执行未在 settings.py 中启用 / 优先级配置错误检查DOWNLOADER_MIDDLEWARES/SPIDER_MIDDLEWARES配置,调整优先级
UA 未生效默认 UserAgent 中间件未关闭关闭scrapy.downloadermiddlewares.useragent.UserAgentMiddleware
代理配置后请求超时代理无效 / 代理池接口不可用验证代理可用性,增加代理池健康检查逻辑
重试中间件无限重试未设置RETRY_TIMES/ 重试条件过于宽松配置RETRY_TIMES,缩小重试状态码 / 异常范围
爬虫中间件过滤掉有效 Request逻辑错误,误过滤 Request 对象process_spider_output中判断对象类型,仅过滤 Item

六、总结

本文系统讲解了 Scrapy 中间件的自定义开发流程,从核心原理出发,通过 4 个实战案例覆盖了下载器中间件(UA 切换、代理池、请求重试)和爬虫中间件(数据过滤)的开发与配置,同时给出了调试方法与优先级调优策略。中间件作为 Scrapy 框架的扩展核心,能够灵活应对反爬机制、数据处理、请求稳定性等各类需求,是企业级爬虫开发的必备技能。

在实际开发中,可根据业务场景扩展更多个性化中间件:如 Cookie 池中间件、响应数据解密中间件、请求参数加密中间件等。掌握中间件的开发逻辑后,可大幅提升爬虫的适应性与稳定性,解决各类复杂的爬虫场景问题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/6 8:02:42

Python 爬虫实战:Pyppeteer 无头浏览器爬虫开发

前言 无头浏览器是动态页面爬虫开发的核心工具&#xff0c;相较于传统 Selenium&#xff0c;基于 Chrome DevTools Protocol&#xff08;CDP&#xff09;的无头浏览器具备更轻量、更高效的特性。Pyppeteer 作为 Google Puppeteer 的 Python 实现&#xff0c;无需额外配置浏览器…

作者头像 李华
网站建设 2026/4/1 8:17:15

Python 爬虫实战:Selenium 模拟浏览器操作

前言 随着前端技术的发展&#xff0c;越来越多的网站采用动态渲染&#xff08;如 JavaScript 异步加载、AJAX 请求&#xff09;方式呈现内容&#xff0c;传统的 Requests 库仅能获取静态 HTML 源码&#xff0c;无法解析动态加载的数据。Selenium 作为一款自动化测试工具&#…

作者头像 李华
网站建设 2026/3/31 10:01:37

Python 爬虫实战:Cookie 持久化与登录态保持

摘要 本文聚焦爬虫开发中 Cookie 持久化与登录态保持的核心技术&#xff0c;针对需要登录才能访问的网站数据爬取场景&#xff0c;系统讲解 Cookie 的工作原理、登录态维持机制、Cookie 持久化存储方案及异常处理策略。实战验证基于知乎登录页&#xff08;需登录访问的核心数据…

作者头像 李华
网站建设 2026/3/9 19:01:52

为什么说Kali学得好,牢饭吃到饱

前期提要&#xff1a;本篇文章不会提供任何Kali的命令行&#xff0c;感兴趣的小伙伴可以看B站视频。一&#xff0c;什么是Kali Kali Linux是一个基于Debian的Linux发行版&#xff0c;专为渗透测试、网络安全和数字取证而设计。它由Offensive Security公司开发、资助和维护&…

作者头像 李华
网站建设 2026/3/26 9:05:20

【网络安全】靶机pikachu之xss注入与代码分析

本文使用靶机pikachu&#xff0c;来练习一下工具XSStrike 常用命令 -u url–skip 跳过确认提示–skip-dom 跳过dom型扫描–data post型时的数据 反射型XSS(get) 输入kobe 正常 可以看到&#xff0c;是get型&#xff0c;页面返回正常 攻击 python xsstrike.py -u "ht…

作者头像 李华
网站建设 2026/3/31 14:46:42

Scanner类——Java输入交互的实用工具

在Java编程的世界里&#xff0c;实现程序与用户的交互是基础且关键的需求&#xff0c;而Scanner类正是完成这一任务的核心工具。作为java.util包下的输入处理类&#xff0c;Scanner类诞生于Java 5&#xff0c;它的出现彻底改变了此前通过BufferedReader等类处理输入的繁琐局面&…

作者头像 李华