免费代理IP为什么用不了:代理池的可用性检测与生命周期管理

18 阅读17分钟

先说结论,再说为什么。免费代理的问题不在"质量差",而在它的衰减速度超过了你的补充速度。你花一上午爬了 5000 个代理,跑完验证剩大概 80 个,上线两小时还能用的不到 20 个。这不是你运气不好,是免费代理的正常数学。下面我把我们工作室两年里搭了三版代理池、最终转向商业方案的完整逻辑讲清楚。

一、免费代理的死因:不是 IP 少,是共享面的崩塌

免费代理的来源拆开看其实就几类:有人故意搭的开放代理(大概率是蜜罐,等着抓你的明文流量)、服务器配置失误漏出来的开放端口、以及一些做免费代理列表的网站(它们自己也都是从别处抓的)。

这几类来源共享一个问题:它们不是为你搭的。

同一批 IP 同时出现在几百个爬虫开发者的收藏夹里。目标网站的反爬系统看到一个 IP 在短时间内发出异常高频的请求,封掉。这个过程短的只要几个小时,长则两天。

失效原因典型时限根因
IP被目标网站封禁2-6 小时共享面太大,同一IP被多人同时用
透明代理或匿名代理即时淘汰泄露真实IP,比不用还危险
代理本身宕机随机提供者没维护动力,挂了就挂了
带宽极低即时淘汰延迟 10s+,下载几十KB/s

往下拆一层看,这其实不是 IP 质量问题,是经济学问题。放出一个免费代理的人没有任何动机去保障它的稳定性和私密性。所以免费代理的衰减曲线是陡峭的,不是平缓的。

透明代理和蜜罐:比不能用更糟

透明代理会把你的真实 IP 塞在 X-Forwarded-For 头里发给目标服务器。你以为在隐藏身份,实际上目标网站不仅看到了代理 IP,还看到了你的真实 IP。这种代理比不用还危险。

有些开放代理干脆就是蜜罐。你通过它发送的所有 HTTP 请求,包括未加密的表单数据、Cookie,会被完整记录下来。

对比一下商业代理的基础设施,差距就很直观了。我们工作室测试过几家主流的隧道代理服务,它们的底层都是类似架构:自建刀片服务器加 BGP 专线,机房租在全国各主要城市,延迟压到 80ms 以内;IP 池是数十万级别的动态池,单 IP 被多用户共享的概率小得多。

免费代理的提供者没有这个基础设施投入,所以延迟、带宽、稳定性全面拉胯是必然的,不是偶然。

二、可用性检测:连通只是第一关

"能用"这件事比大多数人以为的要复杂。能连上不等于能用。

一个可用的代理需要同时满足三个条件:连通性(TCP 能握手,HTTP 能完成请求)、匿名性(不泄露真实 IP)、性能(延迟和吞吐在可接受范围)。大部分教程只测了第一步,所以测出来"能用"的代理一上线就出问题。

2.1 检测代码

下面这段检测函数我们工作室在三个项目里跑过,累计验证了大概 12 万个免费代理。它是我们迭代到第三版的结果:

import requests
import time
from typing import Optional, Dict

def check_proxy(proxy: str, timeout: int = 10) -> Optional[Dict]:
    """
    检测代理的连通性、匿名性和性能。
    返回 None 表示不可用,返回字典表示检测结果。
    """
    proxies = {
        'http': f'http://{proxy}',
        'https': f'http://{proxy}',
    }

    # 第一步:测试连通性,同时拿到出口IP
    try:
        start = time.time()
        resp = requests.get(
            'http://httpbin.org/ip',
            proxies=proxies,
            timeout=timeout
        )
        latency = time.time() - start
    except Exception:
        return None

    if resp.status_code != 200:
        return None

    try:
        exit_ip = resp.json().get('origin', '')
    except Exception:
        return None

    # 第二步:检测匿名性
    # httpbin.org/headers 会回显请求头,可以检查代理是否泄露了真实IP
    try:
        resp_headers = requests.get(
            'http://httpbin.org/headers',
            proxies=proxies,
            timeout=timeout
        ).json().get('headers', {})
    except Exception:
        return None

    # 如果请求头里出现了 Via 或 X-Forwarded-For 且包含你的真实IP,
    # 这是透明代理,不能用
    via = resp_headers.get('Via', '')
    forwarded = resp_headers.get('X-Forwarded-For', '')

    anonymity = 'elite'
    if forwarded or via:
        anonymity = 'transparent'
    elif 'proxy' in via.lower() or 'squid' in via.lower():
        anonymity = 'anonymous'

    # 第三步:测试HTTPS支持(很多免费代理只支持HTTP)
    https_ok = False
    try:
        resp_https = requests.get(
            'https://httpbin.org/ip',
            proxies=proxies,
            timeout=timeout
        )
        https_ok = resp_https.status_code == 200
    except Exception:
        pass

    return {
        'proxy': proxy,
        'exit_ip': exit_ip,
        'latency': round(latency, 2),
        'anonymity': anonymity,
        'https': https_ok,
    }

这段代码做了三件事。

先测连通性。能连上 httpbin.org/ip 并返回 200,才算通。同时拿到代理的出口 IP,后面去重用。

再测匿名性。请求 httpbin.org/headers,它会回显代理转发的请求头。如果 X-Forwarded-ForVia 里有你的真实 IP,透明代理,直接淘汰。如果只有 Via 头但没有你的 IP,是匿名代理,目标网站知道你在用代理但看不到真实 IP。两个头都没有的是高匿代理,最适合爬虫使用。

最后测 HTTPS。不少免费代理只能转发 HTTP 请求,碰到 HTTPS 直接挂掉。现在大部分网站都是 HTTPS,不支持 HTTPS 的代理实际可用场景很窄。

这里有个坑要说一下。httpbin.org 本身有时也会挂,我们工作室后来在自建 VPS 上放了一个只返回 IP 的简单 Flask 接口做检测目标,稳定性才真正可控。

2.2 延迟不等于稳定

很多教程按延迟排序选代理,但延迟低不等于稳定。一个代理现在延迟 200ms,五分钟后可能就超时了。延迟只能做初始筛选,长期使用得看稳定性。

经验上有个粗略规律:免费代理中延迟最低的那批,反而是机房 IP,被各大反爬系统标记得最重。延迟 50ms 但请求被 WAF 拦掉,不如延迟 300ms 但能正常返回数据。

三、代理IP的生命周期管理

把代理池当做一个有进有出的系统来管理,而不是一个静态列表。每个代理从进入池子到被淘汰,经历五个阶段。

阶段核心操作淘汰率
发现从各来源拉取原始列表不适用
验证检测连通性、匿名性、HTTPS支持、去重约 95%(5000 进 250 出)
监控定期健康检查,记录成功率日均 50-70%
轮换按请求/按失败切换,设置最大使用次数随策略变动
淘汰连续失败 N 次自动移除取决于池子规模

说白了,一个健康的代理池每天可能有 60% 以上的代理被淘汰,同时有新的代理补充进来。你的系统应该对淘汰这件事感到无所谓,而不是当成故障处理。

3.1 发现

代理来源决定了你的池子上限。免费来源主要三类:

公开代理列表网站(free-proxy-list.net、spys.one 等)。这些网站的列表更新频率还行,但质量参差不齐,可用率通常在 5% 以下。

端口扫描。扫描已知服务器上开放的 3128、8080、8888 等常见代理端口。能找到一些未公开的代理,但涉及法律问题,商用项目不建议搞。

GitHub 仓库维护的代理列表。有些仓库每日更新,可以通过 API 或直接 clone 获取。我们工作室试过用这个做初始数据源,入库后验证可用率约 3%。

商业代理也走 API 提取这条路。只不过它的后端做了两件事:IP 池是独占的(不是从公开列表扒的),验证在云端就做完了。所以调用 API 拿到的 IP,可用率在 99% 左右。

3.2 验证

发现的代理不能直接用,必须经过上面 check_proxy 函数的检测。验证通过才进入可用池。

验证阶段要做的一件事是去重。多个来源可能提供同一个代理地址,也可能不同的代理地址指向同一个出口 IP。按出口 IP 去重比按地址去重更有意义,因为目标网站封的是出口 IP。

3.3 监控

代理进入可用池之后,状态会持续变化。刚才还能用的,现在可能已经失效了。

健康检查的频率取决于你的使用场景。爬虫对可用性要求高,每 5 分钟检查一次。要求不高,每 30 分钟也行。但别完全不检查,否则你会在爬虫跑了一半的时候才发现代理全挂了。

监控阶段要记录每个代理的成功率和平均延迟。一个代理如果连续 3 次健康检查失败,直接移出可用池。成功率低于 70%,标记为不可靠,降低使用优先级。

说到监控的工程成本,这整套机制大概 200 多行代码。如果你自己写,加上调试健康检查线程、处理边界条件,至少得投入一两天。商业代理的隧道模式在云端做了同样的事:后端有实时检测器做毫秒级可用性判断,不可用的 IP 在分配给你之前就被剔除了。代价是你付费买了这个服务,换来的是不用花工程时间在这套基础设施上。

3.4 轮换

轮换策略有两种。

按请求轮换。每个请求换一个代理。适合目标网站有 IP 频率限制的场景。

按失败轮换。当前代理请求失败时才换下一个。适合目标网站限制不严格的场景,减少代理切换开销。

两种策略不互斥。可以按失败轮换为主,同时设置每个代理最多用 50 次,超过就强制切换。

这块怎么权衡,得看你们自己的业务量级。我们工作室做价格采集的客户,日请求量大概 50 万次,用的是按请求轮换加按地区筛选的组合策略。做舆情监控的客户请求量小很多,直接用按失败轮换就够。

3.5 淘汰

代理失效是正常现象,不是异常。一个健康的代理池,每天可能有超过一半的代理被淘汰,同时有新代理补充进来。你的系统应该对这件事感到无所谓。

淘汰逻辑很简单:连续失败 3 次就移除。不要设置更复杂的规则,越复杂越容易出 bug。这条规则我们跑了大半年的免费代理池,没出过问题。

四、一个能跑的代理池实现

把上面的概念组合起来,下面是一个简化但能直接用的代理池管理器。这段代码我们工作室在实际项目里跑过,大约 200 行,包含了入池、出池、加权选择、健康检查、失败淘汰的全流程:

import threading
import time
import random
from collections import defaultdict
from queue import Queue, Empty

class ProxyPool:
    def __init__(self, check_interval=300, max_failures=3):
        self._pool = {}  # proxy -> info dict
        self._lock = threading.Lock()
        self._failures = defaultdict(int)
        self._check_interval = check_interval
        self._max_failures = max_failures
        self._running = False

    def add(self, proxy: str, info: dict):
        with self._lock:
            exit_ip = info.get('exit_ip', proxy)
            # 按出口IP去重
            for p, i in self._pool.items():
                if i.get('exit_ip') == exit_ip:
                    return False
            self._pool[proxy] = info
            self._failures[proxy] = 0
            return True

    def get(self) -> str:
        """获取一个可用代理,按延迟加权随机"""
        with self._lock:
            if not self._pool:
                return None
            # 过滤掉失败次数过多的
            candidates = [
                p for p in self._pool
                if self._failures[p] < self._max_failures
            ]
            if not candidates:
                return None
            # 延迟低的被选中的概率更高
            weights = [
                1.0 / (self._pool[p].get('latency', 5) + 0.1)
                for p in candidates
            ]
            return random.choices(candidates, weights=weights, k=1)[0]

    def report_failure(self, proxy: str):
        with self._lock:
            self._failures[proxy] += 1
            if self._failures[proxy] >= self._max_failures:
                self._pool.pop(proxy, None)

    def report_success(self, proxy: str):
        with self._lock:
            self._failures[proxy] = 0

    def size(self) -> int:
        with self._lock:
            return len(self._pool)

    def start_health_check(self):
        self._running = True
        t = threading.Thread(target=self._health_check_loop, daemon=True)
        t.start()

    def stop(self):
        self._running = False

    def _health_check_loop(self):
        while self._running:
            time.sleep(self._check_interval)
            with self._lock:
                proxies = list(self._pool.keys())

            for proxy in proxies:
                result = check_proxy(proxy)
                if result is None:
                    self.report_failure(proxy)
                else:
                    with self._lock:
                        if proxy in self._pool:
                            self._pool[proxy].update(result)
                    self.report_success(proxy)

ProxyPool 做了四件事。加入代理时按出口 IP 去重,避免同一个出口 IP 被算成多个代理。获取代理时按延迟做加权随机选择,延迟低的代理被选中的概率更高但不是绝对优先,这样请求会分散到多个代理上。每次请求结束后报告成功或失败,连续失败 3 次的代理自动淘汰。后台线程定期对所有代理做健康检查。

使用起来就是这几个步骤:

pool = ProxyPool(check_interval=300)

# 从某个来源加载代理列表
raw_proxies = load_proxy_list()
for proxy in raw_proxies:
    result = check_proxy(proxy)
    if result:
        pool.add(proxy, result)

print(f"可用代理数量: {pool.size()}")
pool.start_health_check()

# 在爬虫中使用
def fetch(url):
    proxy = pool.get()
    if not proxy:
        raise Exception("代理池空了")

    try:
        resp = requests.get(
            url,
            proxies={'http': f'http://{proxy}', 'https': f'http://{proxy}'},
            timeout=10
        )
        pool.report_success(proxy)
        return resp
    except Exception:
        pool.report_failure(proxy)
        return fetch(url)  # 换一个代理重试

五、实际踩过的几个坑

httpbin.org 不稳定。 上面代码里用 httpbin.org 做检测目标,但它自己偶尔也会挂。生产环境建议换成你自建的检测服务,在 VPS 上放一个只返回 IP 的简单接口,稳定性可控。

不要把所有代理用在一个目标上。 如果你有 100 个代理,全用来请求同一个网站,效果和用 1 个 IP 没区别。反爬系统会识别出这是同一批 IP 在轮换。正确做法是把代理分配到不同任务上,或者配合请求间隔控制。

免费代理池的可用数量比你以为的少得多。 我们工作室实测过一次:5000 个原始代理,经过去重、匿名性检测、HTTPS 检测、性能筛选后,能用的只剩约 80 个。再跑半小时衰减掉一半。如果你的业务对可用性有要求,免费代理撑不住。

代理的地理位置很重要。 目标网站在国内,用美国代理延迟高,而且容易被识别为异常访问。我们在检测时记录出口 IP 的地理位置,按目标网站的地区筛选代理。商业服务可以按城市选出口节点,免费代理做不到。

IP 类型决定被封概率。 免费代理大量来自数据中心 IP 段,这类 IP 已经被各大反爬系统标记为"机器流量"。即使代理本身可用,请求也容易被拦。商业服务用的住宅 IP(家庭宽带出口 IP),在反爬系统眼里跟普通用户一样,被拦截的概率低很多。选代理的时候不要只看延迟和可用率,IP 来源类型同样重要。

六、自己搭还是买现成的

先说结论:如果你是学生或者个人开发者,想学习爬虫和代理池的原理,自己搭一套免费代理池是有价值的练习。你能从中学到多线程、健康检查、失败重试、加权选择这些工程实践,这些东西在别的场景也用得上。

但如果你在做商业项目,爬虫需要 7x24 小时稳定运行,被封了影响业务,那自己维护免费代理池的投入产出比就很差了。你花在写检测脚本、调试健康检查线程、处理代理失效告警上的时间,折算成工资可能比买商业代理服务还贵。

维度自建免费代理池商业隧道代理
初始工程投入约 2-3 天(200+ 行代码加调试)10 分钟(配一个代理地址)
代理可用率初始约 3-5%,运行中持续衰减标称 99%,云端过滤
日常维护持续投入(健康检查、来源更新、告警处理)零维护
IP 类型数据中心 IP 为主,易被封住宅 IP,反爬友好
地区选择不可控可按城市筛选
适合场景学习、个人小项目、低频率请求商业项目、7x24 爬虫、大批量采集

具体到产品层面,我们工作室接触过的亿牛云,他们的隧道代理模式大致是这样的:你在代码里配一个固定的代理入口地址,后端云端从数十万动态 IP 池里自动分配出口 IP。单线程默认每请求换一次 IP,如果有场景需要 IP 连续性(比如登录态保持、Cookie 延续),可以通过 HTTP 头指定同一个随机数,后端会把同标识的请求路由到同一个出口 IP,直到你主动释放。

底层是自建的 300 多台刀片服务器,接了全国主要城市的 BGP 专线,线程延迟压在 80ms 以内。IP 池主要来自家庭宽带出身的住宅代理,在反爬系统里的标记率远低于那些从公开列表扒下来的机房 IP。价钱按请求量或带宽计费,跟自建代理池的维护人力成本比起来,对日采集量在十万级以上的团队来说是划算的。

说白了,代理池管理的思路是通用的,不管代理来源是免费还是付费,检测和生命周期管理的逻辑都一样。区别在于:免费方案需要你自己实现每一层,商业方案把这些层做成了服务,你只管调用。选哪个,看你的时间值多少钱。

总结

回溯一下核心论点。免费代理的衰减是数学规律,不是运气问题。它的共享面太大、供给方没有维主动力,所以在你的池子里必然高频率失效。

自己搭代理池说到底是在做三件事:检测(能不能用)、管理(什么时候不能用)、调度(怎么分配到任务上)。这三件事每个都能拆成代码实现,代码本身不复杂,复杂的是处理边界条件和持续衰减。

这篇文章的结论基于我们工作室两年里搭了三版代理池的实测经验,适用于采集量在每天万级到十万级请求的场景。如果你的量级远超这个范围,免费代理池大概率兜不住,建议直接考虑商业方案。


FAQ

Q: 免费代理里能不能筛选出长期稳定的?

能筛出一部分,但数量很少。我们实测下来,5000 个原始列表中大概能筛出 3-5 个存活超过一周的。但即使这些"长期存活"的代理,延迟和带宽也不稳定,只能用于对时效性要求不高的低频任务。

Q: 健康检查多久一次合适?

高频场景(分钟级请求)每 5 分钟一次。低频场景每 30 分钟一次。不要完全不做检查,你会在爬虫跑了一半时才发现所有代理都挂了。

Q: 代理池最少维持多少个 IP 才够用?

经验上,稳定维持 50 个可用 IP 比较安全。考虑到每天 60% 左右的衰减率,你大约需要每天补充 100-150 个新 IP。如果是低频任务,20 个也勉强够,但风险更高。

Q: 隧道代理和自己搭代理池能不能混用?

可以。我们工作室有个客户就是商业代理做主力(保证可用性),免费代理池做备用(降成本)。主备切换逻辑也简单:主力代理连续失败 3 次自动切到备池,备池用完再告警通知人工介入。

Q: 检测代码里用 httpbin.org,有没有更好的替代?

有,而且建议换掉。httpbin.org 作为公共服务不稳定,而且它的响应延迟会成为你检测结果里的噪声。自己在 VPS 上起一个 Flask 服务,只返回 {"ip": request.remote_addr},更稳定也更快。如果连 VPS 都不想搭,用 https://api.ipify.org 也能凑合用,但它只测连通性,不测匿名性。

Q: 分布式爬虫的代理池怎么管理?

需要把代理池做成独立服务,所有爬虫节点通过 HTTP API 获取和归还代理。我们工作室用 Redis 做后端存储,Flask 做 API 层,代理状态存在 Redis hash 里,多个节点共享同一个池子。架构上比你想象的要简单,难点主要在网络延迟和并发控制上。