Scrapy 代理中间件完整配置指南(2026)

发布于 2026年6月4日 · 阅读约 11 分钟

2026 年,Scrapy 依然是生产级爬取的主力——也依然是最让人在代理配置上犯迷糊的框架,因为有四个不同的地方可以插入代理,而其中三个对大多数项目来说都是错的。这篇指南给你那个对的:一个小巧的自定义 middleware,带逐请求路由、sticky 会话、封禁检测,以及理智的重试行为。

如果你用的是纯 requests/httpx/aiohttp,请看 如何在 Python 里轮换代理。本文专讲 Scrapy。

30 秒版本

对于一个轮换住宅网关,最小可用配置就是每个请求一行——不需要 middleware:

def start_requests(self):
    for url in self.urls:
        yield scrapy.Request(
            url,
            meta={"proxy": "http://USERNAME:[email protected]:913"},
        )

Scrapy 内置的 HttpProxyMiddleware 会读取 request.meta["proxy"],并从 URL 里处理认证。网关会替你轮换出口 IP。如果你只需要这些,到此为止即可。这份指南剩下的部分,是为你需要控制力时准备的:sticky 会话、国家路由、感知封禁的轮换,以及并发调优。

一个生产级代理 Middleware

把这个放进 middlewares.py。它按域名分配 sticky 会话,遇到封禁就轮换,并给每个请求打标签,方便你调试是哪个会话抓了什么:

import random
import string

GATEWAY = "us.jibaoproxy.com:913"
USERNAME = "USERNAME"          # 真实项目里请移到 settings.py / 环境变量
PASSWORD = "PASSWORD"

def _new_session(n=8):
    return "".join(random.choices(string.ascii_lowercase + string.digits, k=n))

class JibaoProxyMiddleware:
    """每个域名一个 sticky 会话;被封时轮换会话。"""

    def __init__(self):
        self.sessions = {}          # 域名 -> 会话 id

    def _proxy_url(self, session_id):
        user = f"{USERNAME}-session-{session_id}"
        return f"http://{user}:{PASSWORD}@{GATEWAY}"

    def process_request(self, request, spider):
        domain = request.url.split("/")[2]
        session = self.sessions.setdefault(domain, _new_session())
        request.meta["proxy"] = self._proxy_url(session)
        request.meta["proxy_session"] = session

    def rotate(self, domain):
        """会话被烧掉时调用。"""
        self.sessions[domain] = _new_session()

再配一个下载器 middleware,它检测封禁并在新会话上重试:

from scrapy.downloadermiddlewares.retry import RetryMiddleware
from scrapy.utils.response import response_status_message

BAN_CODES = {403, 429}
BAN_MARKERS = (b"captcha", b"access denied", b"unusual traffic")

class BanAwareRetryMiddleware(RetryMiddleware):

    def process_response(self, request, response, spider):
        banned = (
            response.status in BAN_CODES
            or any(m in response.body[:2048].lower() for m in BAN_MARKERS)
        )
        if banned:
            domain = request.url.split("/")[2]
            proxy_mw = spider.crawler.engine.downloader.middleware.middlewares
            for mw in proxy_mw:
                if hasattr(mw, "rotate"):
                    mw.rotate(domain)        # 烧掉这个会话
            reason = response_status_message(response.status)
            return self._retry(request, reason, spider) or response
        return super().process_response(request, response, spider)

settings.py 里把两个都接上:

DOWNLOADER_MIDDLEWARES = {
    "myproject.middlewares.JibaoProxyMiddleware": 350,
    "scrapy.downloadermiddlewares.retry.RetryMiddleware": None,   # 替换掉自带的重试
    "myproject.middlewares.BanAwareRetryMiddleware": 550,
}
RETRY_TIMES = 2

优先级很重要:代理 middleware 必须跑在 Scrapy 的 HttpProxyMiddleware(750)之前,所以任何低于 750 的值都行;350 让它足够早、也足够可预测。

Sticky vs 轮换:哪个 spider 用哪种模式

爬取类型模式实现方式
无状态的页面采集轮换裸用户名,网关逐请求轮换
登录后在鉴权背后爬取每账号 sticky-session-{account_id},登录过程中绝不轮换
分页密集的列表页每域名 sticky,被封时轮换上面那个 middleware
按地区的定价轮换 + 国家锁定USERNAME-country-de 这类参数

对这一权衡更深入的探讨:Sticky 会话 vs 轮换代理会话

不会让你被封的并发设置

Scrapy 的默认值是为礼貌的单 IP 爬取调的。在一个轮换池背后,你可以推得猛得多——但每域名的限制依然重要,因为目标看到的是聚合行为:

# settings.py - 住宅池背后理智的起点
CONCURRENT_REQUESTS = 64
CONCURRENT_REQUESTS_PER_DOMAIN = 8     # 目标实际感受到的
DOWNLOAD_DELAY = 0.25                  # 每个槽位施加的抖动
RANDOMIZE_DOWNLOAD_DELAY = True        # 延迟的 0.5x-1.5x
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_TARGET_CONCURRENCY = 6.0
DOWNLOAD_TIMEOUT = 30
ROBOTSTXT_OBEY = True

只有在当前档位下盯了几千个请求的 403 率之后,才去调高 CONCURRENT_REQUESTS_PER_DOMAIN。因为"反正代理会轮换"就把它从 8 → 32,正是人们在重试上烧光 GB 的典型方式。

带宽纪律(住宅 GB 就是钱)

常见故障,以及它们真正的含义

407 Proxy Authentication Required

凭证没到达代理。把它们放进 meta["proxy"] 的 URL 里(http://user:pass@host:port)——Scrapy 会替你解析并设置 Proxy-Authorization。手动设置请求头同时又在 URL 里带凭证,会引发双重认证的怪问题;二选一。

TunnelError: Could not open CONNECT tunnel

几乎总是 host/port 打错了,或者 HTTPS 目标走了一个不允许在该端口上 CONNECT 的端点。先在 Scrapy 之外用 curl -x 验证一下。

Spider 跑了 10 分钟,然后全是 403

你的 sticky 会话活过了它该有的寿命,或者你的每域名速率太猛。上面那个感知封禁的 middleware 能处理第一种情况;第二种情况就调低 CONCURRENT_REQUESTS_PER_DOMAIN。如果目标是个检查 JA4 的站点,那可能是 Scrapy 的 TLS 栈本身露了馅——见 JA3/JA4 详解,了解为什么没有任何代理能修这个。

免费工具 · 无需注册

在爬取之前先验证你的代理清单

把端点粘进我们的 Proxy Checker:它会批量测试连通性、延迟、匿名级别和出口 IP 类型——在 Scrapy 把重试浪费在死掉或标错的代理上之前先抓出它们。

检查我的代理 →

厌倦了照看免费清单?一个住宅网关就能取代这一切——领取 500M免费流量 →

小结

让你的 Spider 指向一个真正的池子

一个网关上既有轮换也有 sticky 住宅会话。500M免费流量供你开爬。

免费试用

所有IP产品通用 · 海量节点随时可用

现在加入,立享最高20%充值返现

新用户注册即送500M免费流量,首次充值额外加赠,活动期间限时开放。