如果你还在用 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,今天就迁移。
相关指南:
- 竞品接口端点对照参考
- 从 AZCaptcha 迁移到 CaptchaAI
- 迁移期的并行测试方法