Reference

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

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

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

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

迁移一共分几步

国内团队常见做法:先用清华 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 googlekeypageurl
Cloudflare Turnstile method=turnstile sitekeypageurl
GeeTest v3 method=geetest gtchallengepageurl

迁移时最容易忽略的差异

还有几处细节容易踩坑:

环节 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 结构不同 改成读取 statusrequest 字段
缺少 method 参数 EndCaptcha 用的是 captchaType 数字编号 映射到 CaptchaAI 的 method 名(base64userrecaptcha 等)
reCAPTCHA 轮询超时 默认超时设置不同 token 类验证码把轮询放宽到 60 次 × 5 秒

常见问题

迁移时需要停服吗?

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

CaptchaAI 按什么计费?

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

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

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

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

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

相关文章

下一步

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

相关指南:

该文章已禁用评论。