Scarnox · Blog

Node.js 使用 fetch 实现验证码求解器:可运行示例(2026)

在 Node.js 中,验证码求解就是一次 HTTP 请求。Node 18 及更新版本内置了 fetch,因此调用 API 无需安装任何包。发送 sitekey 和页面 URL,等待响应,然后把 token 放入表单。本指南展示了针对 PopularCaptcha 的完整流程,Scarnox 目前以无代理方式支持该类型的求解。

先获取免费试用额度,无需信用卡,并先在测试页面上运行下面的代码。

环境要求

求解器模块

下面的模块只包含一个函数。它携带 Bearer 请求头向 API 发送请求,185 秒后中止,并在 errorId 不为 0 时抛出错误:

// solver.mjs
const API_URL = "https://api.scarnox.com/api/tasks/create";

export async function solveCaptcha(sitekey, pageUrl, { invisible = false } = {}) {
  const body = {
    captcha_type: "PopularCaptchaTaskProxyless",
    site_key: sitekey,
    site_url: pageUrl,
  };
  if (invisible) body.is_invisible = true;

  const res = await fetch(API_URL, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SCARNOX_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(body),
    signal: AbortSignal.timeout(185_000), // 调用会在 token 准备好后返回
  });

  const data = await res.json().catch(() => null);
  if (!data) throw new Error(`HTTP ${res.status}: response was not JSON`);

  if (data.errorId !== 0) {
    throw new Error(`${data.errorCode}: ${data.errorDescription}`);
  }
  return data.solution.token;
}

这里没有轮询。请求会保持打开,直到 token 准备好。成功时 token 位于 solution.token 中。

重试

求解失败时,errorId 不为 0,且你不会因此被计费。对于临时性失败,简单的重试循环就足够了。请保持较少的尝试次数,以免 sitekey 或页面 URL 错误时白白浪费时间:

export async function solveWithRetry(sitekey, pageUrl, attempts = 3) {
  let lastError;
  for (let i = 1; i <= attempts; i++) {
    try {
      return await solveCaptcha(sitekey, pageUrl);
    } catch (err) {
      lastError = err;
      console.warn(`Attempt ${i} failed: ${err.message}`);
      await new Promise((r) => setTimeout(r, 1000 * i));
    }
  }
  throw lastError;
}

更完整的错误处理策略,请参阅验证码求解器错误处理。

提交 token

token 要放入网站期望的表单字段中。大多数 PopularCaptcha 页面会读取 h-captcha-response,许多页面也会读取 g-recaptcha-response。为了稳妥起见,请同时发送两者:

// submit.mjs
import { solveWithRetry } from "./solver.mjs";

const pageUrl = "https://example.com/signup";
const token = await solveWithRetry("YOUR-SITEKEY-UUID", pageUrl);

const form = new URLSearchParams({
  email: "you@example.com",
  "h-captcha-response": token,
  "g-recaptcha-response": token,
});

const res = await fetch(pageUrl, {
  method: "POST",
  body: form,
  redirect: "manual",
});
console.log("Status:", res.status);

运行方式:

SCARNOX_API_KEY=scarnox_YOURAPIKEY node submit.mjs

普通的 fetch 不会在请求之间保留 cookie。许多注册和搜索页面会将验证码与会话 cookie 绑定,因此没有对应 cookie 的 token 可能会被拒绝。有两种处理方式:

当表单比较简单、网站也不需要运行太多 JavaScript 时,选择第一种。对于更复杂的情况,选择第二种。

一个实用的检查方法:POST 之后,查看状态码和最终 URL。跳转到成功页面通常意味着 token 已被接受。如果页面再次显示组件,通常说明 token 被拒绝,或者 GET 请求中的 cookie 没有被带上。在最初几次运行中记录响应体,以便你弄清楚究竟是哪种情况。

时机

token 约在两分钟内过期。请在接近提交的时刻求解。常见的错误是在长时间任务开始时求解,然后在很久之后才提交。请求解后立即提交。验证码 token 过期对此有更详细的说明。

无感验证码

如果页面的组件带有 data-size="invisible",请传入 { invisible: true }。该函数随后会在请求中发送 is_invisible: true:

const token = await solveCaptcha("YOUR-SITEKEY-UUID", pageUrl, { invisible: true });

如果页面还为组件绑定了回调,表单可能需要调用该函数并传入 token。请阅读页面脚本以找到它。无感验证码指南涵盖了相关模式。

结合求解器进行抓取

有时验证码出现在你抓取的页面上,而不是你要提交的表单中。同样的模式依然适用:在 HTML 中检测组件,求解它,然后将其提交到组件所指向的 URL。请保持较低的请求频率,并先检查网站的条款。负责任地抓取:遵守 robots.txt,避免收集不需要的个人数据,也不要对服务器施加过大压力。

价格

Scarnox 按每次成功的求解计费,失败的求解不收费。当前费率请查看定价页面。要开始使用,请先获取免费试用额度,无需信用卡。

下一步