服务器端做登录自动化或数据采集时,验证码常常是唯一的阻塞点,但解决它不需要启动 Chrome。用 Axios 直接调用 CaptchaAI 的 HTTP 接口,reCAPTCHA、Turnstile 和图片验证码都能在几秒到几十秒内拿到 token,不用 Puppeteer,也不用 Playwright——无头浏览器每实例占 200–500MB 内存,纯 HTTP 只要几 MB,阿里云函数计算、Vercel 这类内存受限的环境里差距尤其明显。函数计算按内存和执行时长计费,多出的几百 MB 直接换算成账单;换成纯 HTTP 请求后,冷启动也会明显变快,因为运行时不用再拉起一个完整的 Chromium 进程。
环境准备
| 组件 | 版本 / 说明 |
|---|---|
| Node.js | 16+ |
| Axios | 1.x |
| CaptchaAI API Key | 注册后即可获取 |
npm install axios
国内网络访问 npm 官方源较慢时,可临时切到镜像:npm install axios --registry=https://registry.npmmirror.com。整套方案只依赖 axios 一个包,不需要额外装浏览器驱动或系统级依赖,这也是它比浏览器自动化方案部署更简单的原因之一。
封装 CaptchaAI 客户端
封装提交、轮询、查余额,后面示例直接复用:
const axios = require("axios");
class CaptchaAI {
constructor(apiKey) {
this.apiKey = apiKey;
this.baseUrl = "https://ocr.captchaai.com";
}
async submit(params) {
params.key = this.apiKey;
const resp = await axios.get(`${this.baseUrl}/in.php`, { params });
const text = resp.data;
if (!String(text).startsWith("OK|")) {
throw new Error(`Submit failed: ${text}`);
}
return String(text).split("|")[1];
}
async poll(taskId, timeoutMs = 300000) {
const deadline = Date.now() + timeoutMs;
const params = { key: this.apiKey, action: "get", id: taskId };
while (Date.now() < deadline) {
await new Promise((r) => setTimeout(r, 5000));
const resp = await axios.get(`${this.baseUrl}/res.php`, { params });
const text = String(resp.data);
if (text === "CAPCHA_NOT_READY") continue;
if (text.startsWith("OK|")) return text.split("|").slice(1).join("|");
throw new Error(`Solve failed: ${text}`);
}
throw new Error(`Timeout after ${timeoutMs}ms for task ${taskId}`);
}
async solve(params, timeoutMs = 300000) {
const taskId = await this.submit(params);
return this.poll(taskId, timeoutMs);
}
async getBalance() {
const resp = await axios.get(`${this.baseUrl}/res.php`, {
params: { key: this.apiKey, action: "getbalance" },
});
return parseFloat(resp.data);
}
}
module.exports = CaptchaAI;
无浏览器识别 reCAPTCHA v2
接入登录或表单流程,全程不打开浏览器。googlekey 就是页面上 data-sitekey 属性的值,pageurl 必须和验证码实际渲染的页面地址完全一致,两者填错都会导致识别失败:
const CaptchaAI = require("./captchaai");
async function main() {
const solver = new CaptchaAI(process.env.CAPTCHAAI_API_KEY);
// Solve the CAPTCHA without opening any browser
const token = await solver.solve({
method: "userrecaptcha",
googlekey: "6Le-wvkS...",
pageurl: "https://staging.example.com/qa-login",
});
// Submit form with the token using Axios
const resp = await axios.post("https://staging.example.com/qa-login", {
username: "user",
password: "pass",
"g-recaptcha-response": token,
});
console.log(`Login response: ${resp.status}`);
}
main().catch(console.error);
无浏览器识别 Turnstile
和 reCAPTCHA 类似,method 换成 turnstile,参数换成 sitekey。Turnstile 通常 <10 秒即可拿到 token,比 reCAPTCHA v2 的 <60 秒上限快得多,客户端封装里的轮询逻辑不用改,等待时间会明显更短:
const token = await solver.solve({
method: "turnstile",
sitekey: "0x4AAAAA...",
pageurl: "https://example.com",
});
// Submit with Turnstile token
const resp = await axios.post("https://example.com/api/verify", {
"cf-turnstile-response": token,
data: "payload",
});
识别图片验证码
OCR 类图片验证码,把图片转成 base64 提交即可,不需要额外声明验证码类型,CaptchaAI 会自动做字符识别:
const fs = require("fs");
const imageBuffer = fs.readFileSync("captcha.png");
const imageB64 = imageBuffer.toString("base64");
const text = await solver.solve({
method: "base64",
body: imageB64,
});
console.log(`CAPTCHA text: ${text}`);
// Submit form with solved text
const resp = await axios.post("https://example.com/verify", {
captcha: text,
other_data: "value",
});
完整抓取流程:从取页面到提交表单
串联取页面、提取 sitekey、识别、提交表单,这套流程和你平时用 axios + cheerio 写的普通抓取脚本几乎一样,只是中间多了一步向 CaptchaAI 提交并等待 token:
const CaptchaAI = require("./captchaai");
const axios = require("axios");
const cheerio = require("cheerio");
async function scrapeProtectedPage(url) {
const solver = new CaptchaAI(process.env.CAPTCHAAI_API_KEY);
// Step 1: Fetch the page
const page = await axios.get(url);
const $ = cheerio.load(page.data);
// Step 2: Extract the reCAPTCHA site key
const siteKey = $(".g-recaptcha").attr("data-sitekey");
if (!siteKey) {
console.log("No CAPTCHA found, returning page content");
return page.data;
}
// Step 3: Solve the CAPTCHA
console.log(`Solving CAPTCHA for ${url}...`);
const token = await solver.solve({
method: "userrecaptcha",
googlekey: siteKey,
pageurl: url,
});
// Step 4: Submit form with token
const formAction = $("form").attr("action") || url;
const formData = {};
$("form input").each((_, el) => {
const name = $(el).attr("name");
const value = $(el).attr("value") || "";
if (name) formData[name] = value;
});
formData["g-recaptcha-response"] = token;
const result = await axios.post(formAction, new URLSearchParams(formData), {
headers: { "Content-Type": "application/x-www-form-urlencoded" },
});
return result.data;
}
scrapeProtectedPage("https://example.com/data")
.then((data) => console.log("Success:", typeof data))
.catch(console.error);
国内站点常见 GeeTest(极验)滑块,method 换 geetest 即可复用。抓取前先确认目标数据在授权范围内,涉及个人信息时留意《数据安全法》与 PIPL 的合规边界,并遵守目标站点的 robots 协议。
并发批量识别
用 Promise.all 并发提交,无需排队等待。CaptchaAI 按线程数结算并发,账户开了几个线程就能同时跑几个 solve 调用:
async function solveBatch(urls, siteKey) {
const solver = new CaptchaAI(process.env.CAPTCHAAI_API_KEY);
const promises = urls.map(async (url) => {
try {
const token = await solver.solve({
method: "userrecaptcha",
googlekey: siteKey,
pageurl: url,
});
return { url, token, error: null };
} catch (error) {
return { url, token: null, error: error.message };
}
});
const results = await Promise.all(promises);
const solved = results.filter((r) => r.token);
console.log(`Solved ${solved.length}/${urls.length}`);
return results;
}
常见故障排查
出问题时先按下表定位,大部分都不用改代码,只是配置或余额问题:
| 报错信息 | 原因 | 处理方式 |
|---|---|---|
AxiosError: getaddrinfo ENOTFOUND |
DNS 解析失败 | 检查网络连接,确认 ocr.captchaai.com 可访问 |
Submit failed: ERROR_WRONG_USER_KEY |
API Key 错误 | 登录控制台核对 API Key |
Submit failed: ERROR_ZERO_BALANCE |
账户余额不足 | 前往控制台充值 |
| token 被目标站点拒绝 | token 已过期 | 拿到 token 后 60 秒内提交,超时需重新识别 |
常见问题
为什么服务器端更适合用 API 而不是浏览器自动化?
无头浏览器占 200–500MB 内存且有启动开销,纯 HTTP 只需几 MB,直接决定并发上限。在阿里云函数计算、Vercel 这类按内存计费的环境里,这个差距还会直接体现在账单上。
token 提交后多久会过期?
通常需 60 秒内提交给目标网站,超时会被拒绝,拿到就应立即提交,不要先缓存起来批量处理。
CaptchaAI 支持哪些验证码类型?
reCAPTCHA v2/v3、Turnstile、GeeTest v3、图片/OCR 都支持,CaptchaFox、Friendly Captcha、Lemin 为测试版,不支持 hCaptcha、FunCaptcha。
遇到 ERROR_ZERO_BALANCE 要怎么处理?
账户余额不足,去控制台充值即可继续提交,无需改代码。建议在 getBalance() 里加一个阈值告警,余额低于某个数值时提前提醒。
相关阅读
同样是无浏览器方案,下面几篇可以按你用的语言或工具继续深入: