Scrapy 接入动态 IP 代理池:会话、重试与成本控制的完整实现

先说结论

采集项目真正卡住的地方,很少是"不会写爬虫",而是代理层没设计好:请求成功率忽高忽低、重试把 IP 池打穿、账单月底才发现超了一倍。

这篇把代理池接入 Scrapy 的四个关键环节拆开讲:中间件怎么写、会话怎么保、失败怎么退避、成本怎么算。代码可以直接跑。


一、把代理抽象成"可切换的资源",别硬编码

最差的做法是在 settings.py 里写死一个代理地址。稍好一点是每次请求随机取一个。都不对——代理是有状态、有配额、会失效的资源,要当成连接池来管。

# proxy_pool.py
import random, time, threading

class ProxyPool:
    def __init__(self, fetcher):
        self.fetcher = fetcher          # 拉取代理列表的函数
        self.lock = threading.Lock()
        self.pool = []                  # [{"addr": "ip:port", "fail": 0, "last_ok": 0}]
        self.refresh()

    def refresh(self):
        items = self.fetcher()          # 返回 ["1.2.3.4:8080", ...]
        with self.lock:
            self.pool = [{"addr": a, "fail": 0, "last_ok": 0} for a in items]

    def get(self):
        with self.lock:
            alive = [p for p in self.pool if p["fail"] < 3]
            if not alive:
                self.refresh()
                alive = self.pool
            return random.choice(alive)["addr"]

    def report(self, addr, ok):
        with self.lock:
            for p in self.pool:
                if p["addr"] == addr:
                    if ok:
                        p["fail"] = 0
                        p["last_ok"] = time.time()
                    else:
                        p["fail"] += 1

要点:给每个代理记失败次数。失败三次就暂时踢出池子,比"全局重试"有效得多——否则一个坏代理会被反复抽到,把整个任务拖垮。


二、中间件:只做两件事,别写复杂逻辑

# middlewares.py
from scrapy import signals
import logging

class ProxyMiddleware:
    def __init__(self, pool):
        self.pool = pool

    @classmethod
    def from_crawler(cls, crawler):
        mw = cls(crawler.settings.get("PROXY_POOL"))
        crawler.signals.connect(mw.spider_closed, signal=signals.spider_closed)
        return mw

    def process_request(self, request, spider):
        if request.meta.get("no_proxy"):
            return None
        addr = request.meta.get("proxy") or self.pool.get()
        request.meta["_proxy_addr"] = addr
        request.meta["proxy"] = "http://" + addr
        return None

    def process_response(self, request, response, spider):
        addr = request.meta.get("_proxy_addr")
        if addr:
            self.pool.report(addr, response.status in (200, 301, 302))
        if response.status in (403, 429):
            retry = request.copy()
            retry.meta["proxy"] = None
            retry.dont_filter = True
            return retry
        return response

三个细节:

  1. 403 和 429 要分开处理。403 是规则拦截,换出口可能有用;429 是频率超限,换出口往往没用,该降速。不要全部重试。
  2. 重试必须换出口。用同一个代理重试被拒的请求,只会把失败次数刷高。
  3. dont_filter=True 别忘,否则 dupefilter 会把重试请求挡掉。

三、会话保持:什么时候必须用同一个出口

这是最容易出问题的地方。很多人的池子"成功率"很高,但登录态一直掉,原因就是每个请求都换了 IP。

需要保持会话的场景只有三类:

  • 要登录的页面(Cookie 和 IP 绑定)
  • 分页翻取(服务端按 IP 记录翻页位置)
  • 需要连续操作的流程(加购、提交表单)
def start_requests(self):
    session_proxy = self.pool.get()      # 一个会话用一个出口
    for url in self.start_urls:
        yield scrapy.Request(
            url,
            meta={"proxy": session_proxy, "session_id": "task-001"},
            callback=self.parse
        )

反过来,不需要会话的场景就别硬保:纯列表页采集、静态页面抓取,每请求一换反而分散压力。

判断标准一句话:这个请求失败后能不能独立重来?能,就不用保会话。


四、退避与预算:别让重试吃掉成本

重试是有成本的。一条线路上跑三次失败重试,等于三次计费。所以退避策略要和预算绑在一起算。

class Backoff:
    def __init__(self, base=1.0, cap=60.0, retries=3):
        self.base, self.cap, self.retries = base, cap, retries

    def wait(self, attempt):
        if attempt >= self.retries:
            return None
        delay = min(self.cap, self.base * (2 ** attempt)) * (0.5 + random.random())
        time.sleep(delay)
        return delay

配合并发参数一起调:

场景 并发 延迟 重试上限
资讯类静态页 16–32 0.2s 2
电商列表页 8–16 0.5s 3
需要登录的流程 2–4 1–2s 2
接口型数据 4–8 1s 3

先降速,再换 IP。 很多"被封"其实是并发超了目标站承受能力,把并发砍一半、加随机间隔,往往不用换线路就恢复了。


五、怎么判断代理层真的在工作

上线后别只看"任务跑完了",看四个数:

成功率     = 200 响应数 / 总请求数      目标 > 92%
平均延迟   = 所有请求响应时间均值        目标 < 800ms
失败分布   = 按状态码分组              403/429 各占多少
单 IP 请求 = 总请求 / 独立 IP 数        是否超出承载

这些数字不监控,出问题就只能凭感觉猜。


六、几个踩过的坑

  • DNS 还在走本地:代理只转发 TCP,域名解析仍可能由本机完成,导致地区对不上。解决方式是让代理端做远端解析(SOCKS5 用 socks5h://)。
  • IPv6 绕过代理:部分代理只处理 IPv4,目标站支持双栈时可能优先走 IPv6 直连。
  • 短效 IP 打断长任务:需要连续会话的任务用短效池,必然反复掉线,这类场景要选长效或独享线路。
  • 不考虑 robots 和站点规则:技术上能做到,和业务上该做是两件事。稳定的数据来源是授权接口和合作,不是无限换 IP。

代理选型的判断标准(成功率、延迟、会话、地区、协议五项)单独写过一份清单,放在采集场景方案里;需要现场测自己出口质量,可以用IP 地址查询工具对比切换前后的归属与运营商。

posted @ 2026-11-08 12:53  58无极  阅读(3)  评论(0)    收藏  举报