Reference

从 EndCaptcha 迁移到 CaptchaAI:API 映射指南

如果你还在用 EndCaptcha,迁移到 CaptchaAI 其实只改三处:

  • 鉴权:用户名 + 密码 → 一个 API Key
  • 端点:SOAP/WSDL → in.php/res.php
  • 解析:XML → 读 status 和 request 两个字段

提交、轮询的业务逻辑完全不用动。

迁移一共分几步

国内团队常见做法:先用清华 TUNA 镜像装好 requests,两家并行跑一段,稳定后再切流量。

步骤 状态
注册 CaptchaAI 账号、拿到 API Key ☐
把所有 EndCaptcha 调用映射到 CaptchaAI 等价写法 ☐
替换鉴权(用户名/密码 → API Key) ☐
更新提交端点(/Captcha/Upload → /in.php) ☐
更新轮询端点(/Captcha/GetText → /res.php) ☐
更新响应解析逻辑 ☐
两家服务并行跑一段时间做对比 ☐
切换生产流量 ☐
删除 EndCaptcha 凭据 ☐

两套 API 的结构差异

EndCaptcha 是 SOAP/XML 风格、方法名各异;CaptchaAI 走标准 REST,只有两个端点。

方面 EndCaptcha CaptchaAI
协议 SOAP/XML 或 HTTP POST HTTP POST/GET(REST)
提交 /Captcha/Upload 或 WSDL https://ocr.captchaai.com/in.php
取结果 /Captcha/GetText 或 WSDL https://ocr.captchaai.com/res.php
鉴权 用户名 + 密码 API Key
响应 XML / 自定义格式 JSON(json=1)或纯文本

参数与验证码类型对照

字段基本一一对应,只是命名和大小写不同:

EndCaptcha 参数 CaptchaAI 参数 说明
username key CaptchaAI 只用一个 API Key
password — 不再需要,鉴权由 API Key 覆盖
captchaData(base64) body(base64) base64 图片数据完全相同
captchaType method 类型标识符不同
siteKey googlekey 用于 reCAPTCHA 系列
pageUrl pageurl 概念相同,大小写不同
captchaId id 用于轮询的任务 ID

CaptchaAI 用 method 区分类型。注意 hCaptcha 暂不支持,无法迁移:

验证码类型 CaptchaAI method 关键参数
图片验证码 method=base64 body={base64_image}
reCAPTCHA v2 method=userrecaptcha googlekey、pageurl
Cloudflare Turnstile method=turnstile sitekey、pageurl
GeeTest v3 method=geetest gt、challenge、pageurl

迁移时最容易忽略的差异

还有几处细节容易踩坑:

环节 EndCaptcha CaptchaAI
鉴权 用户名 + 密码 单个 API Key
错误格式 自定义 JSON 的 error 字段 标准 request 字段返回错误码
轮询方式 POST 到独立端点 GET 请求 res.php,参数走 query
查余额 独立的 SOAP 方法 res.php?action=getbalance&key=KEY
上报错误识别 独立方法调用 res.php?action=reportbad&id=ID&key=KEY

代码迁移:改造前后对比

Python:迁移前(EndCaptcha)

import requests

USERNAME = "your_endcaptcha_user"
PASSWORD = "your_endcaptcha_pass"

def solve_image_endcaptcha(image_base64):
    # EndCaptcha image solve
    resp = requests.post("https://api.endcaptcha.com/Captcha/Upload", data={
        "username": USERNAME,
        "password": PASSWORD,
        "captchaData": image_base64,
        "captchaType": "1"
    })
    result = resp.json()
    captcha_id = result.get("captchaId")

    import time
    for _ in range(30):
        time.sleep(5)
        poll = requests.post("https://api.endcaptcha.com/Captcha/GetText", data={
            "username": USERNAME,
            "password": PASSWORD,
            "captchaId": captcha_id
        })
        poll_result = poll.json()
        if poll_result.get("text"):
            return {"solution": poll_result["text"]}
        if poll_result.get("error"):
            return {"error": poll_result["error"]}

    return {"error": "TIMEOUT"}

Python:迁移后(CaptchaAI)

import os
import time
import requests

API_KEY = os.environ["CAPTCHAAI_API_KEY"]

def solve_image_captchaai(image_base64):
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "base64",
        "body": image_base64,
        "json": 1
    })
    data = resp.json()
    if data.get("status") != 1:
        return {"error": data.get("request")}

    captcha_id = data["request"]

    for _ in range(30):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY,
            "action": "get",
            "id": captcha_id,
            "json": 1
        }).json()
        if result.get("status") == 1:
            return {"solution": result["request"]}
        if result.get("request") != "CAPCHA_NOT_READY":
            return {"error": result.get("request")}

    return {"error": "TIMEOUT"}

Python:reCAPTCHA v2(CaptchaAI)

换个 method、把 body 换成 googlekey + pageurl 即可。token 类耗时更长,轮询放宽到 60 次:

def solve_recaptcha_v2(sitekey, pageurl):
    resp = requests.post("https://ocr.captchaai.com/in.php", data={
        "key": API_KEY,
        "method": "userrecaptcha",
        "googlekey": sitekey,
        "pageurl": pageurl,
        "json": 1
    })
    data = resp.json()
    if data.get("status") != 1:
        return {"error": data.get("request")}

    captcha_id = data["request"]

    for _ in range(60):
        time.sleep(5)
        result = requests.get("https://ocr.captchaai.com/res.php", params={
            "key": API_KEY, "action": "get",
            "id": captcha_id, "json": 1
        }).json()
        if result.get("status") == 1:
            return {"solution": result["request"]}
        if result.get("request") != "CAPCHA_NOT_READY":
            return {"error": result.get("request")}

    return {"error": "TIMEOUT"}

JavaScript:迁移前(EndCaptcha)

const axios = require("axios");

const USERNAME = "your_endcaptcha_user";
const PASSWORD = "your_endcaptcha_pass";

async function solveImageEndCaptcha(imageBase64) {
  const submit = await axios.post("https://api.endcaptcha.com/Captcha/Upload", {
    username: USERNAME,
    password: PASSWORD,
    captchaData: imageBase64,
    captchaType: "1",
  });
  const captchaId = submit.data.captchaId;

  for (let i = 0; i < 30; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    const poll = await axios.post("https://api.endcaptcha.com/Captcha/GetText", {
      username: USERNAME,
      password: PASSWORD,
      captchaId,
    });
    if (poll.data.text) return { solution: poll.data.text };
    if (poll.data.error) return { error: poll.data.error };
  }
  return { error: "TIMEOUT" };
}

JavaScript:迁移后(CaptchaAI)

const axios = require("axios");
const API_KEY = process.env.CAPTCHAAI_API_KEY;

async function solveImageCaptchaAI(imageBase64) {
  const submit = await axios.post("https://ocr.captchaai.com/in.php", null, {
    params: { key: API_KEY, method: "base64", body: imageBase64, json: 1 },
  });
  if (submit.data.status !== 1) return { error: submit.data.request };

  const captchaId = submit.data.request;

  for (let i = 0; i < 30; i++) {
    await new Promise((r) => setTimeout(r, 5000));
    const poll = await axios.get("https://ocr.captchaai.com/res.php", {
      params: { key: API_KEY, action: "get", id: captchaId, json: 1 },
    });
    if (poll.data.status === 1) return { solution: poll.data.request };
    if (poll.data.request !== "CAPCHA_NOT_READY") return { error: poll.data.request };
  }
  return { error: "TIMEOUT" };
}

常见报错与排查

问题 原因 处理方式
ERROR_KEY_DOES_NOT_EXIST 误把 EndCaptcha 用户名当成了 API Key 改用控制台里的 CaptchaAI API Key
响应解析失败 JSON 结构不同 改成读取 status 和 request 字段
缺少 method 参数 EndCaptcha 用的是 captchaType 数字编号 映射到 CaptchaAI 的 method 名(base64、userrecaptcha 等)
reCAPTCHA 轮询超时 默认超时设置不同 token 类验证码把轮询放宽到 60 次 × 5 秒

常见问题

迁移时需要停服吗?

不需要。两套代码并行跑一段,稳定后整体切流量,再删掉 EndCaptcha 凭据。

CaptchaAI 按什么计费?

按并发线程(thread)计费,套餐内识别不限量。BASIC 为 $15/月、5 个线程,STANDARD 为 $30/月、15 个线程(按美元计价)。

原来的代理配置能直接复用吗?

可以。CaptchaAI 支持 proxy=user:pass@host:port 和 proxytype=HTTP|SOCKS5,直接传原来的代理串即可。

CaptchaAI 能识别 EndCaptcha 覆盖不到的验证码吗?

可以。除图片/OCR 外,还支持 reCAPTCHA v2/v3、Cloudflare Turnstile 与 Challenge、GeeTest v3、九宫格等;CaptchaFox、Friendly Captcha、Lemin 为测试版。hCaptcha 与 FunCaptcha 暂不支持。

相关文章

下一步

用 CaptchaAI 的 REST API 简化验证码识别——领取你的 API Key,今天就迁移。

相关指南:

该文章已禁用评论。