Integrations

Smartproxy + CaptchaAI 集成:出口网关与验证码识别配置

先说结论:Smartproxy 决定请求从哪个出口 IP 发出,CaptchaAI 负责把 reCAPTCHA v2 或 Cloudflare Turnstile 换成可提交的 token。配错基本都出在把两件事混在一起。守住一条即可:加载页面和提交 token 走同一出口 IP。

安全范围: 本指南仅适用于你自有或经授权的 QA、staging 与预发布环境。内容覆盖针对你自己 CAPTCHA 集成的诊断、测试与可观测性模式 — 不涉及第三方站点或未授权流程。


先理清数据流:哪一步走网关

  1. 取页面:请求经网关发出,从 HTML 拿到 sitekey
  2. 拿 tokensitekeypageurl 提交给 in.php,再轮询 res.php,这一步不走网关
  3. 回传表单:带 token 提交回页面,出口 IP 与第 1 步一致。

第 2 步最容易配错:识别请求挂上网关只是多花流量,成功率不变——在意 IP 一致性的是目标站点。


Python 配置:取页面与验证码识别

依赖只有 requests,国内装包走镜像更稳:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple requests。下面这段把请求挂到网关,提交后每 5 秒轮询一次,最多 60 次。

import requests
import time

SMARTPROXY_USER = "spuser"
SMARTPROXY_PASS = "sppassword"
SMARTPROXY_HOST = "gate.smartproxy.com"
SMARTPROXY_PORT = 10001

CAPTCHAAI_KEY = "YOUR_API_KEY"
CAPTCHAAI_URL = "https://ocr.captchaai.com"

proxies = {
    "http": f"http://{SMARTPROXY_USER}:{SMARTPROXY_PASS}@{SMARTPROXY_HOST}:{SMARTPROXY_PORT}",
    "https": f"http://{SMARTPROXY_USER}:{SMARTPROXY_PASS}@{SMARTPROXY_HOST}:{SMARTPROXY_PORT}",
}


def fetch_page(url):
    return requests.get(url, proxies=proxies, timeout=30)


def solve_captcha(site_url, sitekey, captcha_type="recaptcha_v2"):
    submit_data = {
        "key": CAPTCHAAI_KEY,
        "pageurl": site_url,
        "json": 1,
    }

    if captcha_type == "turnstile":
        submit_data["method"] = "turnstile"
        submit_data["sitekey"] = sitekey
    else:
        submit_data["method"] = "userrecaptcha"
        submit_data["googlekey"] = sitekey

    resp = requests.post(f"{CAPTCHAAI_URL}/in.php", data=submit_data)
    data = resp.json()
    if data["status"] != 1:
        raise Exception(f"Submit failed: {data['request']}")

    task_id = data["request"]

    for _ in range(60):
        time.sleep(5)
        resp = requests.get(f"{CAPTCHAAI_URL}/res.php", params={
            "key": CAPTCHAAI_KEY,
            "action": "get",
            "id": task_id,
            "json": 1,
        })
        data = resp.json()
        if data["request"] == "CAPCHA_NOT_READY":
            continue
        if data["status"] == 1:
            return data["request"]
        raise Exception(f"Solve: {data['request']}")

    raise TimeoutError("Timeout")

两个坑:googlekey 只用于 reCAPTCHA v2,Turnstile 用 sitekeyCAPCHA_NOT_READY 少一个 T 是历史写法,不是笔误。

粘性会话:一次流程锁定同一个 IP

中途换 IP,站点通常直接判定 token 无效。Smartproxy 把会话写进用户名:-session-<id> 指定会话,-sessionduration-<分钟> 定时长。

import random
import string


def get_sticky_proxy(session_duration_minutes=10):
    """Create a sticky session proxy (same IP for duration)."""
    session_id = "".join(random.choices(string.ascii_lowercase + string.digits, k=8))

    proxy_url = (
        f"http://{SMARTPROXY_USER}"
        f"-session-{session_id}"
        f"-sessionduration-{session_duration_minutes}"
        f":{SMARTPROXY_PASS}@{SMARTPROXY_HOST}:{SMARTPROXY_PORT}"
    )

    return {"http": proxy_url, "https": proxy_url}


# Use same IP for entire CAPTCHA workflow
sticky = get_sticky_proxy(session_duration_minutes=10)

# Page load
resp = requests.get("https://staging.example.com/qa-form", proxies=sticky)

# Solve CAPTCHA
token = solve_captcha("https://staging.example.com/qa-form", "SITEKEY_HERE")

# Submit with same IP
resp = requests.post(
    "https://target.com/submit",
    data={"g-recaptcha-response": token},
    proxies=sticky,
)

10 分钟够覆盖“加载 → 识别 → 提交”,多步表单调到 30 分钟;会话 ID 按任务生成。

按国家/地区选择出口

同一页面在不同出口下常常不是同一份 HTML:货币、语种、是否弹验证码都会变。比如面向东南亚做价格监控 QA,要在新加坡和马来西亚出口下各验证一遍货币与 sitekey,把 country 设成 sgmy 各跑一遍。

# Smartproxy country targeting via username
def get_country_proxy(country_code):
    proxy_url = (
        f"http://{SMARTPROXY_USER}"
        f"-country-{country_code}"
        f":{SMARTPROXY_PASS}@{SMARTPROXY_HOST}:{SMARTPROXY_PORT}"
    )
    return {"http": proxy_url, "https": proxy_url}

# US proxy
us_proxy = get_country_proxy("us")

# UK proxy
uk_proxy = get_country_proxy("gb")

# Germany proxy
de_proxy = get_country_proxy("de")

另外,reCAPTCHA 加载的是 Google 托管脚本,内地网络访问不稳定,调试时先换出口再怀疑代码。


Selenium 接入

页面要跑 JavaScript 才渲染验证码时,浏览器也走同一出口。

from selenium import webdriver
from selenium.webdriver.common.by import By


def create_smartproxy_driver(country=None, sticky_session=None):
    proxy_user = SMARTPROXY_USER
    if country:
        proxy_user += f"-country-{country}"
    if sticky_session:
        proxy_user += f"-session-{sticky_session}"

    proxy_url = f"{proxy_user}:{SMARTPROXY_PASS}@{SMARTPROXY_HOST}:{SMARTPROXY_PORT}"

    options = webdriver.ChromeOptions()
    options.add_argument(f"--proxy-server=http://{SMARTPROXY_HOST}:{SMARTPROXY_PORT}")
    options.add_argument("--no-sandbox")
    options.add_argument("--window-size=1920,1080")

    # For authenticated proxies, use seleniumwire or extension
    return webdriver.Chrome(options=options)


def scrape_with_captcha(url, country="us"):
    session_id = "".join(random.choices(string.ascii_lowercase, k=8))
    driver = create_smartproxy_driver(country=country, sticky_session=session_id)

    try:
        driver.get(url)
        time.sleep(3)

        sitekey = driver.execute_script(
            "return document.querySelector('[data-sitekey]')?.getAttribute('data-sitekey')"
        )

        if sitekey:
            token = solve_captcha(url, sitekey)
            driver.execute_script(f"""
                document.querySelector('#g-recaptcha-response').value = '{token}';
            """)
            driver.find_element(By.CSS_SELECTOR, "form").submit()
            time.sleep(3)

        return driver.page_source

    finally:
        driver.quit()

Chrome 的 --proxy-server 不接受用户名密码:带认证的网关用 Selenium Wire 配置,或把凭据打包成浏览器扩展。


Node.js 接入

写法对应:用 https-proxy-agent 把出口挂到 axios,识别请求直连,不带 agent。

const axios = require("axios");
const HttpsProxyAgent = require("https-proxy-agent");

const CAPTCHAAI_KEY = "YOUR_API_KEY";

function getSmartproxyAgent(options = {}) {
  let user = "spuser";
  if (options.country) user += `-country-${options.country}`;
  if (options.session) user += `-session-${options.session}`;

  return new HttpsProxyAgent(
    `http://${user}:[email protected]:10001`
  );
}

async function scrapeWithCaptcha(url, sitekey) {
  const agent = getSmartproxyAgent({
    country: "us",
    session: `sess-${Date.now()}`,
  });

  // Fetch page through proxy
  const pageResp = await axios.get(url, { httpsAgent: agent });

  // Solve CAPTCHA via CaptchaAI (no proxy needed)
  const submitResp = await axios.post(
    "https://ocr.captchaai.com/in.php",
    null,
    {
      params: {
        key: CAPTCHAAI_KEY,
        method: "userrecaptcha",
        googlekey: sitekey,
        pageurl: url,
        json: 1,
      },
    }
  );

  const taskId = submitResp.data.request;

  // Poll for result
  for (let i = 0; i < 60; i++) {
    await new Promise((r) => setTimeout(r, 5000));

    const result = await axios.get("https://ocr.captchaai.com/res.php", {
      params: {
        key: CAPTCHAAI_KEY,
        action: "get",
        id: taskId,
        json: 1,
      },
    });

    if (result.data.request === "CAPCHA_NOT_READY") continue;
    if (result.data.status === 1) return result.data.request;
  }

  throw new Error("Timeout");
}

批量并发:max_workers 该设多少

并发上限由 CaptchaAI 的线程数决定,不是网关。它按并发线程计费、套餐内识别次数不限:BASIC($15/月,5 线程)、ADVANCE($90/月,50 线程),最高到 VIP-3($7,500/月,5,000 线程)。max_workers 别超过线程数,示例里的 5 正好对应 BASIC。

from concurrent.futures import ThreadPoolExecutor, as_completed


def process_url(url):
    session_id = "".join(random.choices(string.ascii_lowercase, k=8))
    proxy = get_sticky_proxy(10)

    try:
        resp = requests.get(url, proxies=proxy, timeout=30)

        # Check if CAPTCHA is present (simplified detection)
        if "data-sitekey" in resp.text:
            import re
            match = re.search(r'data-sitekey="([^"]+)"', resp.text)
            if match:
                sitekey = match.group(1)
                token = solve_captcha(url, sitekey)
                return {"url": url, "status": "solved", "token": token[:30]}

        return {"url": url, "status": "no_captcha"}

    except Exception as e:
        return {"url": url, "status": "error", "error": str(e)}


urls = [
    "https://site1.com/page",
    "https://site2.com/page",
    "https://site3.com/page",
]

with ThreadPoolExecutor(max_workers=5) as executor:
    futures = {executor.submit(process_url, u): u for u in urls}

    for future in as_completed(futures):
        result = future.result()
        print(f"[{result['status']}] {result['url']}")

Smartproxy 出口类型怎么选

挑选只看两点:验证码出现频率,会话能否保持。

出口类型 IP 规模 适用场景 验证码出现频率
家庭宽带出口 55M+ IP 常规采集
数据中心出口 100K+ IP 高速批量 中到高
移动网络出口 10M+ IP 移动端页面 很低
ISP 静态出口 静态 IP 长会话流程

规模数字取自 Smartproxy 官方资料,实际表现请自行实测。


排错清单

现象 原因 处理方式
407 认证失败 用户名密码格式写错 到控制台核对凭据
中途 IP 变了 没启用粘性会话 用户名加 -session-ID
每个请求都弹验证码 用了数据中心出口 换家庭宽带
连接变慢 该地区出口拥挤 换国家或城市
token 被目标站拒绝 两步之间换了 IP 延长会话时长

常见问题

可以把 Smartproxy 的出口 IP 传给 CaptchaAI 吗?

可以,提交接口支持 proxy 参数。多数场景用不上,只有站点把识别与页面 IP 绑定校验时才需要。

CaptchaAI 支持哪些验证码类型?

reCAPTCHA v2/v3(含 Enterprise 与隐形 reCAPTCHA)、Turnstile 与 Cloudflare Challenge、GeeTest v3、图片/OCR 与九宫格、BLS;CaptchaFox、Friendly Captcha、Lemin 为测试版。hCaptcha 与 FunCaptcha 暂不支持,GeeTest v4 即将支持。

国内常见的极验滑块适用这套流程吗?

部分适用。国内页面多用 GeeTest(极验)、网易易盾、腾讯防水墙,CaptchaAI 只覆盖 GeeTest v3,参数为 gtchallenge

采集数据时的合规底线是什么?

只采集你有权访问的数据,对齐《网络安全法》《数据安全法》《个人信息保护法》与 robots 协议。


相关阅读


网关负责出口,CaptchaAI 负责 token——领取 API Key

该文章已禁用评论。