Scrapy中间件实战:自定义请求头和代理池实现

Scrapy 作为 Python 生态中最强大的爬虫框架之一,其高扩展性很大程度上依赖于中间件(Middleware)------ 中间件介于 Scrapy 引擎和其他组件之间,能够拦截、修改、增强爬虫的请求(Request)和响应(Response),是实现反爬规避、请求优化的核心利器。本文将聚焦两个实战场景:自定义请求头和代理池实现,通过完整的代码示例和步骤讲解,帮助你掌握 Scrapy 中间件的核心用法。

一、前置准备

  1. 已安装 Scrapy 框架:pip install scrapy

  2. 新建 Scrapy 项目(用于实战演示): bash

    运行

    复制代码
    # 1. 创建项目
    scrapy startproject middleware_demo
    # 2. 进入项目目录
    cd middleware_demo
    # 3. 新建爬虫(以爬取测试站点为例)
    scrapy genspider test_spider httpbin.org
  3. 核心概念铺垫:

    • 本文重点使用下载中间件(Downloader Middleware) ,对应项目中的middlewares.py文件
    • 下载中间件的核心方法:process_request(self, request, spider)(请求发送前拦截处理)
    • 所有自定义中间件必须在settings.py中启用才能生效

二、实战一:自定义请求头(User-Agent 池)

爬虫请求中,固定的User-Agent极易被目标网站识别并封禁,通过中间件实现User-Agent池随机切换,是最基础的反爬手段之一。

步骤 1:编写自定义请求头中间件

打开项目中的middlewares.py,新增RandomUserAgentMiddleware类,实现User-Agent随机切换:

python

运行

复制代码
import random
from scrapy import signals

class MiddlewareDemoDownloaderMiddleware:
    # 项目默认中间件,保留基础结构即可
    @classmethod
    def from_crawler(cls, crawler):
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    def process_request(self, request, spider):
        return None

    def process_response(self, request, response, spider):
        return response

    def process_exception(self, request, exception, spider):
        pass

    def spider_opened(self, spider):
        spider.logger.info("Spider opened: %s" % spider.name)

# 新增:自定义User-Agent池中间件
class RandomUserAgentMiddleware:
    # 1. 定义User-Agent池(涵盖主流浏览器、设备)
    USER_AGENTS = [
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/120.0.0.0 Safari/537.36",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15",
        "Mozilla/5.0 (Linux; Android 13; SM-G998B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36",
        "Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1"
    ]

    def process_request(self, request, spider):
        # 2. 随机选择一个User-Agent
        random_ua = random.choice(self.USER_AGENTS)
        # 3. 为请求设置自定义请求头(替换默认User-Agent)
        request.headers.setdefault('User-Agent', random_ua)
        # 可选:添加其他自定义请求头(如Referer、Accept等)
        request.headers.setdefault('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8')
        request.headers.setdefault('Referer', 'https://www.baidu.com/')
        
        spider.logger.info(f"已设置请求头:User-Agent -> {random_ua[:50]}...")
        return None

步骤 2:在 settings.py 中启用中间件

打开settings.py,找到DOWNLOADER_MIDDLEWARES配置项(默认被注释),取消注释并添加自定义中间件(优先级数值越小,执行顺序越靠前,通常设置为 100-900 之间):

python

运行

复制代码
# 下载中间件配置
DOWNLOADER_MIDDLEWARES = {
    # 启用自定义User-Agent中间件
    "middleware_demo.middlewares.RandomUserAgentMiddleware": 543,
    # 禁用Scrapy默认的User-Agent中间件(避免冲突)
    "scrapy.downloadermiddlewares.useragent.UserAgentMiddleware": None,
}

步骤 3:测试自定义请求头

修改test_spider.py,爬取httpbin.org/headers(该接口会返回请求头信息,用于验证):

python

运行

复制代码
import scrapy

class TestSpiderSpider(scrapy.Spider):
    name = "test_spider"
    allowed_domains = ["httpbin.org"]
    start_urls = ["https://httpbin.org/headers"]

    def parse(self, response):
        # 打印响应结果(查看请求头是否生效)
        self.logger.info("响应结果:\n" + response.text)
        yield None

步骤 4:运行爬虫验证效果

执行以下命令运行爬虫:

bash

运行

复制代码
scrapy crawl test_spider

运行成功后,可在日志中看到:

  1. 爬虫打印出已设置的随机User-Agent
  2. 响应结果中headers部分的User-Agent与设置的一致,且每次运行都会随机切换

三、实战二:自定义代理池实现

当爬虫频繁请求目标网站时,固定 IP 极易被封禁,通过中间件实现代理池随机切换,能够有效隐藏真实 IP,规避 IP 封禁风险。

步骤 1:准备代理 IP 池

代理 IP 可通过免费代理站点(如西刺代理、快代理)或付费代理服务获取,格式为http://ip:端口https://ip:端口(注意:免费代理稳定性较差,生产环境建议使用付费代理)。

步骤 2:编写自定义代理池中间件

继续在middlewares.py中,新增RandomProxyMiddleware类:

python

运行

复制代码
# 新增:自定义代理池中间件
class RandomProxyMiddleware:
    # 1. 定义代理IP池(格式:http/https + ip + 端口,可添加带账号密码的代理:http://user:pass@ip:port)
    PROXIES = [
        "http://123.124.135.136:8080",
        "http://117.121.10.11:9090",
        "https://220.225.8.9:8888",
        # 更多代理IP...
    ]

    def process_request(self, request, spider):
        # 2. 随机选择一个代理IP
        random_proxy = random.choice(self.PROXIES)
        try:
            # 3. 为请求设置代理
            request.meta['proxy'] = random_proxy
            
            # 可选:处理带账号密码的代理(如需认证)
            # import base64
            # proxy_user_pass = "username:password"
            # encoded_user_pass = base64.b64encode(proxy_user_pass.encode()).decode()
            # request.headers['Proxy-Authorization'] = f'Basic {encoded_user_pass}'
            
            spider.logger.info(f"已设置代理:{random_proxy}")
        except Exception as e:
            spider.logger.error(f"代理设置失败:{str(e)}")
        
        return None

    # 可选:处理代理异常(如代理不可用,重新请求)
    def process_exception(self, request, exception, spider):
        spider.logger.error(f"代理 {request.meta.get('proxy')} 不可用,触发异常:{str(exception)}")
        # 重新生成请求(不添加dont_filter=True会被Scrapy去重过滤)
        new_request = request.copy()
        new_request.dont_filter = True
        return new_request

步骤 3:在 settings.py 中启用代理池中间件

更新DOWNLOADER_MIDDLEWARES配置,添加代理池中间件:

python

运行

复制代码
DOWNLOADER_MIDDLEWARES = {
    # 启用自定义User-Agent中间件
    "middleware_demo.middlewares.RandomUserAgentMiddleware": 543,
    # 启用自定义代理池中间件(优先级略低于User-Agent中间件)
    "middleware_demo.middlewares.RandomProxyMiddleware": 600,
    # 禁用Scrapy默认的User-Agent中间件
    "scrapy.downloadermiddlewares.useragent.UserAgentMiddleware": None,
}

步骤 4:优化代理池(可选,生产环境必备)

上述代理池为固定静态列表,实际使用中存在代理失效、数量不足等问题,可进行以下优化:

  1. 动态获取代理:编写接口从代理服务提供商获取实时有效代理(如通过 API 接口拉取)

  2. 代理有效性检测:在中间件中添加代理连通性检测,剔除失效代理

  3. 代理权重分配:根据代理的响应速度、成功率分配权重,优先使用优质代理

  4. 添加代理超时配置 :在settings.py中设置代理请求超时时间

    python

    运行

    复制代码
    # 下载超时时间(单位:秒)
    DOWNLOAD_TIMEOUT = 10

步骤 5:测试代理池效果

重新运行爬虫:

bash

运行

复制代码
scrapy crawl test_spider

在日志中可看到随机切换的代理 IP,同时爬取httpbin.org/ip(修改start_urls["https://httpbin.org/ip"])可查看当前出口 IP,验证代理是否生效。

四、核心注意事项与最佳实践

  1. 中间件优先级DOWNLOADER_MIDDLEWARES中的优先级数值决定执行顺序,建议遵循 Scrapy 默认规范:请求头设置(500 左右)→ 代理设置(600 左右)→ 其他处理(700+)

  2. 禁用冲突中间件:启用自定义请求头 / 代理中间件时,需禁用 Scrapy 默认的对应中间件,避免配置冲突

  3. 日志记录:在中间件中添加详细日志,便于排查请求失败、代理失效等问题

  4. 代理合法性:使用代理时需遵守目标网站的 robots 协议和相关法律法规,禁止用于恶意爬取

  5. 请求去重 :处理代理异常重新请求时,需设置dont_filter=True,避免被 Scrapy 的去重机制过滤

  6. 生产环境优化

    • 请求头和代理池建议存储在配置文件(如config.json)中,便于维护
    • 添加代理池自动更新机制,定期剔除失效代理
    • 结合DOWNLOAD_DELAY设置请求延迟,降低反爬风险

    python

    运行

    复制代码
    # settings.py中设置请求延迟(单位:秒)
    DOWNLOAD_DELAY = 2

五、总结

  1. Scrapy 下载中间件是实现请求定制化的核心,通过process_request方法可拦截并修改请求头、代理等关键配置
  2. 自定义请求头(User-Agent池)和代理池是规避反爬的基础手段,核心步骤为「编写中间件→启用中间件→测试验证」
  3. 静态池(请求头 / 代理)适合快速验证,生产环境需实现动态更新、有效性检测等高级功能,提升爬虫的稳定性和可用性

通过本文的实战操作,你不仅掌握了两种核心中间件的实现,更理解了 Scrapy 中间件的工作逻辑,后续可基于此扩展更多功能(如 Cookie 池、请求重试、反反爬解密等),打造更加强大的爬虫系统。

相关推荐
WJSKad12352 小时前
果园树干识别与定位_faster-rcnn_x101-32x4d_fpn_1x_coco改进实践
python
hui函数2 小时前
Python系列Bug修复|如何解决 pip install 安装报错 invalid command ‘bdist_wheel’(缺少 wheel)问题
python·bug·pip
hui函数2 小时前
Python系列Bug修复|如何解决 pip install -r requirements.txt 私有索引未设为 trusted-host 导致拒绝 问题
python·bug·pip
子午2 小时前
【2026原创】动物识别系统~Python+深度学习+人工智能+模型训练+图像识别
人工智能·python·深度学习
o_insist2 小时前
LangChain1.0 实现 PDF 文档向量检索全流程
人工智能·python·langchain
脑洞AI食验员2 小时前
智能体来了:用异常与文件处理守住代码底线
人工智能·python
曲幽2 小时前
FastAPI登录验证:用OAuth2与JWT构筑你的API安全防线
python·fastapi·web·jwt·token·oauth2
计算机小手2 小时前
Docker 部署 weserv-images:打造非侵入式图片处理中间件
图像处理·经验分享·docker·中间件
幻云20102 小时前
Next.js指南:从入门到精通
开发语言·javascript·人工智能·python·架构