如何在 Python 和 Node 中设置验证码代理轮换

captcha proxy rotation - How to Set Up CAPTCHA Proxy Rotation in Python and Node

验证码代理轮换是你要在自己代码里实现的东西。识别工具每个任务只接受一个代理,并且只在该任务中使用它,所以代理池、轮换策略和重试逻辑全都在你这一侧。两条规则就能解决大部分问题:把一个 IP 固定给一个会话,而不是每次调用都换;并且只在失败之后才轮换。本指南会讲清各个 SDK 中代理参数的确切写法、其背后的原始 API 字段对,以及代理会被静默忽略的那一种验证码类型。

代理适用于三类验证码,而不是全部四类

先说限制,因为它能替你省下一个下午的排查时间。CapSkip 可以识别四类验证码,代理只对其中三类有效:reCAPTCHA、Turnstile 和极验(GeeTest)。图片验证码不接受代理。

reCAPTCHA 在这里只算一类。v2 复选框、Invisible、Enterprise 和 v3 都是同一个识别调用上的选项,而不是独立的类型,所以它们在代理方面的行为完全一致。

类型是否支持代理原因
reCAPTCHA v2 和 v3,包括 Enterprise识别过程中会通过代理向 Google 发出请求
Cloudflare Turnstile同上,只是面向 Cloudflare
极验 v3同上,只是面向极验的 API 服务器
图片或 OCR图片已经在手,因此不会发出任何对外请求

给图片任务传代理并不会报错。它会被接受然后忽略,这反而更糟,因为这里的轮换 bug 完全不会产生任何信号。如果你让 图片验证码 与其他所有类型走同一个封装层,那就在这条路径上跳过 proxy 参数。

各个 SDK 中的代理写法

每个 SDK 接受的都是同一个双字段对象:一个类型和一个 URI。URI 要么是主机加端口,要么是凭据后面跟主机和端口。

# pip install capskip
from capskip import CapSkip

solver = CapSkip(host="127.0.0.1", port=8080)

# The proxy applies to this one solve, nothing is remembered.
result = solver.recaptcha(
    sitekey="YOUR_SITEKEY",
    url="https://example.com/page-with-recaptcha",
    proxy={"type": "HTTPS", "uri": "user:[email protected]:3128"},
)

print(result["code"])   # token, solved through that IP

Node 把同样的对象作为 options 参数传入。

// npm install capskip
const { CapSkip } = require('capskip');

const solver = new CapSkip({ host: '127.0.0.1', port: 8080 });

const result = await solver.turnstile(
  'YOUR_SITEKEY',
  'https://example.com/page-with-turnstile',
  { proxy: { type: 'SOCKS5', uri: '1.2.3.4:1080' } },
);

console.log(result.code, result.userAgent);  // send both back

PHP 把它作为 proxy 键嵌套在 options 数组里,.NET 则传入一个由同样两个值构建的 Proxy 对象。四个允许的类型值在各处都一样:HTTP、HTTPS、SOCKS5 和 SOCKS5H。SOCKS5H 在代理端而不是本地解析 DNS,当目标站点按地区返回不同解析结果时,这正是你需要的。

按会话固定代理,而不是按请求

很多人的第一反应是每次识别都取一个新 IP。这是个错误的默认做法,也正是会话被打上标记的原因。

受保护的页面会把 token 与它签发时所处的上下文绑定。如果你的爬虫用一个 IP 加载页面,而 token 是在另一个 IP 上识别出来的,站点就会看到浏览会话与验证之间对不上。有些站点不在意,Cloudflare 和 reCAPTCHA Enterprise 却很在意,token 会被判定为低质量,或者直接验证失败。

所以轮换的单位是会话,而不是单次调用。同一个代理负责抓取页面、识别验证码并提交表单。下一个会话再换用下一个代理。

# pip install capskip
import itertools

# One entry per exit IP. Round-robin, not random:
# random picks repeat, and a repeat is what a rate limiter sees.
POOL = [
    {"type": "HTTPS", "uri": "user:[email protected]:3128"},
    {"type": "HTTPS", "uri": "user:[email protected]:3128"},
    {"type": "SOCKS5H", "uri": "user:[email protected]:1080"},
]

pool = itertools.cycle(POOL)

def new_session():
    """Hand the same proxy to the HTTP client and the solver."""
    return next(pool)

在这里,按顺序轮流取用(round-robin)胜过随机选择,原因只有一个:从小代理池里随机抽样,连续两次抽到同一个 IP 的概率高到不容忽视,而来自同一出口节点的连续请求,恰恰是限流器最关注的模式。

按失败轮换,而不是按定时轮换

第二条规则是何时换下一个。每 N 个请求就轮换一次,会丢掉本来好用的 IP,同时又让坏掉的 IP 继续被用上最多 N 次调用。应该依据证据来轮换。

# pip install capskip
from capskip import CapSkip
from capskip import ApiException, NetworkException, TimeoutException

solver = CapSkip(host="127.0.0.1", port=8080)

def solve_with_retry(sitekey, url, attempts=3):
    last = None
    for _ in range(attempts):
        proxy = new_session()
        try:
            return solver.recaptcha(sitekey=sitekey, url=url, proxy=proxy)
        except (ApiException, NetworkException, TimeoutException) as err:
            # Burn this IP for the run and take the next one.
            last = err
    raise last

对大多数代理池来说,三次尝试是合适的上限。如果同一个 sitekey 在三个不同的 IP 上都失败,问题就不在代理:要么 sitekey 已经过期,要么页面 URL 不对,要么站点换了挑战方式。第四次重试只是在耗费识别能力去确认这一点。

你捕获到哪种异常,就说明发生了什么。NetworkException 表示识别工具不可达,或者任务还没就绪就被轮询了。TimeoutException 表示轮询窗口已经超时。ApiException 会带上返回的错误代码,这一种值得连同所用代理一起记录下来,因为你正是靠它在四十个节点的代理池里找出那一个已经失效的出口节点。

原始 API:proxy 与 proxytype

不用 SDK 时,同样的事情就是在提交调用上多加两个表单字段。SDK 里的那个对象正好一一对应到它们。

字段默认值
proxyIP:PORTlogin:pass@IP:PORT
proxytypeHTTP、HTTPS、SOCKS5 或 SOCKS5HHTTP
# No install step. curl is already on your machine.
curl -X POST http://127.0.0.1:8080/in.php \
  -d "key=YOUR_API_KEY" \
  -d "method=userrecaptcha" \
  -d "googlekey=YOUR_SITEKEY" \
  -d "pageurl=https://example.com/page-with-recaptcha" \
  -d "proxy=user:[email protected]:3128" \
  -d "proxytype=HTTPS"

OK|2122988149   # submitted, and it will solve through that IP

proxytype 的默认值是 HTTP,因此不带该字段发送的 HTTPS 或 SOCKS5 代理会被当作普通 HTTP 去连接,进而失败。每次都把这个字段发出去,不要指望默认值刚好和你的代理池对得上。

换一个 host,同样的调用就能打到共享实例上。CapSkip 在 Local 模式下默认监听 127.0.0.1,而 连接设置 还提供 Server 模式:此时它监听你的内网或公网 IP,让其他机器也能访问这个 API。把它放到一台 VPS 上,一个识别工具就能为爬虫集群里的每个 worker 服务。代理的用法不变:代理依然按任务生效,代理池依然放在你自己的代码里。

常见错误及其含义

症状原因修复
识别成功,token 却被拒绝抓取页面所用的 IP 与识别时用的 IP 不是同一个在抓取、识别和提交这三步中固定使用同一个代理
ERROR_CAPTCHA_UNSOLVABLE 仅在某一个 IP 上出现该出口节点已被目标站点封禁把它移出代理池,换下一个重试
每个带代理的任务都超时proxytype 与实际代理不匹配显式发送该字段,不要依赖 HTTP 默认值
代理看起来毫无作用该任务是图片验证码这符合预期。代理适用于另外三类
本地正常,在容器里失败网络内部的 DNS 解析结果不同改用 SOCKS5H,让代理来解析主机名

每种类型的完整参数细节,请见 API 文档.

常见问题

识别验证码到底需不需要代理?

不需要。不用代理的识别方式在大多数站点上都能用,而且少一个活动部件。只有当目标站点有地域限制、按 IP 限流,或者识别明明正常却开始出现 token 被拒时,才加上代理。先不用代理开始,把它当作针对已观察到的问题的修复手段再加进来。

住宅代理还是数据中心代理?

数据中心 IP 更快也更便宜,在只做限流的站点上完全够用。当目标站点会对网络本身打分时,住宅 IP 才变得重要,这在受 Cloudflare 保护的页面和 reCAPTCHA Enterprise 上很常见。把两者混在同一个池里也可行:先试数据中心 IP,失败后再回退到住宅 IP。

SOCKS5 和 SOCKS5H 有什么区别?

SOCKS5 在你的机器上解析主机名,然后把得到的 IP 发给代理。SOCKS5H 则把主机名发过去,交给代理去解析。当站点按地区返回不同地址,或者你本地的解析器根本看不到目标时,就用 SOCKS5H。

代理会拖慢识别速度吗?

会慢一点,而且取决于出口节点,而不是识别工具本身。轮询本身不受影响:SDK 从 250 毫秒起步,逐步退避到 pollingInterval 上限,所以识别得快,结果依然返回得快。慢代理体现为首次成功轮询所需的时间变长,而不是额外的轮询开销。

简短版结论

每个会话固定使用一个代理;在识别失败时轮换,而不是按时间表轮换;始终发送 proxytype;图片任务则完全跳过代理。由于识别本身就跑在本地,是一个 无限量验证码识别工具,一次重试除了代理带宽之外不产生任何成本,这让失败即轮换的策略便宜到足以成为默认做法。至于它所处的更大流程,请参见 网络爬虫验证码识别,其中完整介绍了会话处理的全过程。而 Python 集成指南 介绍了客户端的设置方法。