Puppeteer 验证码接口怎么接:从参数提取、Token 注入到目标站拒绝排查

2026-10-10 37

在 Puppeteer 自动化流程中集成验证码识别接口,核心是让外部 REST API 解算验证码,再由 Puppeteer 将结果注入页面完成提交。这不是简单的「调接口拿 Token」,而是要处理参数动态提取、异步轮询、DOM 注入、前端回调触发以及会话校验五个环节。工程中常见的问题是验证码 API 已返回成功,但目标站后端仍拒绝通过,原因可能是 Token 时效、IP 不一致、自动化指纹或前端事件未触发。

Puppeteer 与验证码 API 的标准集成架构

Puppeteer 本身不处理验证码识别,验证码 REST API 也不直接控制浏览器。两者的集成采用「带外识别 + 结果回填」模式:

  1. Puppeteer 提取参数:在页面加载后,用 page.evaluate() 或 page.$eval() 获取验证码组件的运行时参数,如 reCAPTCHA 的 data-sitekey、hCaptcha 的 data-sitekey、Turnstile 的 data-sitekey,以及当前页面 URL。
  2. 调用验证码 API:通过 HTTP 请求将这些参数发送到验证码识别服务(如 NexCaptcha 的 api.nexcaptcha.net/createTask 接口),创建异步任务并获得 taskId。
  3. 轮询任务结果:按固定间隔(通常 1-3 秒)请求 getTaskResult 接口,直到状态变为 ready 并返回 solution.gRecaptchaResponse 或对应字段的 Token 字符串。
  4. Puppeteer 注入 Token:用 page.evaluate() 将 Token 写入页面隐藏字段(如 #g-recaptcha-response),并执行页面注册的回调函数或派发 DOM 事件,让前端框架感知验证完成。
  5. 提交表单并校验:由 Puppeteer 触发表单提交或点击按钮,目标站后端校验 Token 与会话一致性。

Puppeteer 验证码接口集成五阶段流程:参数提取、任务创建、轮询结果、Token 注入、表单提交

这个流程中,「验证码 API 返回 Token」只是中间环节,最终成功取决于目标站后端是否接受该 Token 及会话状态。

标准接入代码示例

以下示例展示如何在 Puppeteer 中对接通用的验证码识别 REST API(以 reCAPTCHA v2 为例)。这里用标准 fetch 调用接口,不依赖特定服务商的 SDK。

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: false });
  const page = await browser.newPage();

  // 访问目标页面
  await page.goto('https://example.com/login', { waitUntil: 'networkidle2' });

  // 1. 提取验证码参数
  const sitekey = await page.$eval('.g-recaptcha', el => el.getAttribute('data-sitekey'));
  const pageurl = page.url();

  console.log('提取参数:', { sitekey, pageurl });

  // 2. 创建验证码任务
  const createTaskResponse = await fetch('https://api.nexcaptcha.net/createTask', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      clientKey: 'YOUR_API_KEY',  // 替换为测试密钥
      task: {
        type: 'RecaptchaV2TaskProxyless',
        websiteURL: pageurl,
        websiteKey: sitekey
      }
    })
  });

  const createTaskData = await createTaskResponse.json();
  if (createTaskData.errorId !== 0) {
    throw new Error(`任务创建失败: ${createTaskData.errorDescription}`);
  }

  const taskId = createTaskData.taskId;
  console.log('任务已创建:', taskId);

  // 3. 轮询任务结果
  let token = null;
  let attempts = 0;
  const maxAttempts = 60;  // 最多轮询 60 次(约 2 分钟)

  while (attempts < maxAttempts) {
    await new Promise(resolve => setTimeout(resolve, 2000));  // 等待 2 秒

    const getResultResponse = await fetch('https://api.nexcaptcha.net/getTaskResult', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        clientKey: 'YOUR_API_KEY',
        taskId: taskId
      })
    });

    const resultData = await getResultResponse.json();

    if (resultData.status === 'ready') {
      token = resultData.solution.gRecaptchaResponse;
      console.log('Token 已获取:', token.substring(0, 50) + '...');
      break;
    } else if (resultData.status === 'processing') {
      attempts++;
      console.log(`第 ${attempts} 次轮询,任务处理中...`);
    } else {
      throw new Error(`任务失败: ${resultData.errorDescription}`);
    }
  }

  if (!token) {
    throw new Error('轮询超时,未获取到 Token');
  }

  // 4. 注入 Token 到页面
  await page.evaluate((token) => {
    // 填充隐藏字段
    document.getElementById('g-recaptcha-response').value = token;
    
    // 触发前端回调(如果页面定义了 data-callback)
    const recaptchaElement = document.querySelector('.g-recaptcha');
    const callback = recaptchaElement?.getAttribute('data-callback');
    if (callback && typeof window[callback] === 'function') {
      window[callback](token);
    }
  }, token);

  console.log('Token 已注入页面');

  // 5. 提交表单
  await page.click('#submit-button');
  await page.waitForNavigation({ waitUntil: 'networkidle2' });

  console.log('表单已提交,当前 URL:', page.url());

  await browser.close();
})();

这段代码演示了标准流程,但真实工程中需要处理更多异常情况。

Token 注入不只是填字段

很多开发者在注入 Token 时只执行了这一步:

await page.evaluate((token) => {
  document.getElementById('g-recaptcha-response').value = token;
}, token);

结果点击提交后,前端仍提示「请先完成验证」。原因是现代 Web 应用(尤其使用 React、Vue 等框架)不会监听 DOM 的直接赋值操作,而是依赖事件或回调通知组件状态变化。

必须触发的三种机制

  1. 执行 data-callback:reCAPTCHA、hCaptcha、Turnstile 组件可以在 HTML 属性中指定回调函数,如 <div class="g-recaptcha" data-callback="onCaptchaSuccess"></div>。注入 Token 后需在 page.evaluate 中查找并执行该函数:
const callback = recaptchaElement?.getAttribute('data-callback');
if (callback && typeof window[callback] === 'function') {
  window[callback](token);
}
  1. 派发 DOM 事件:如果页面用 addEventListener 监听 input 或 change 事件,需要手动派发:
const textarea = document.getElementById('g-recaptcha-response');
textarea.value = token;
textarea.dispatchEvent(new Event('input', { bubbles: true }));
textarea.dispatchEvent(new Event('change', { bubbles: true }));
  1. 调用验证码 SDK 方法:某些站点在获取 Token 后会调用 grecaptcha.getResponse() 判断状态,此时可以重写该方法:
window.grecaptcha.getResponse = () => token;

如果不确定目标站用了哪种机制,可以在注入前先打开浏览器开发者工具,观察提交按钮的 disabled 属性何时被移除,或表单的 onsubmit 事件是否绑定了验证检查。

为什么 API 返回成功但目标站仍拒绝

即使验证码识别 API 返回了有效 Token,目标站后端校验仍可能失败。这是因为验证码厂商(如 Google、Cloudflare)在服务端校验时不只看 Token 本身,还会检查请求上下文。

验证码 Token 返回后目标站拒绝的四大原因排查流程图

1. Token 时效过期

reCAPTCHA、hCaptcha、Turnstile 的 Token 有效期通常为 60-120 秒。如果调用验证码 API 后,由于网络延迟、页面加载慢或业务逻辑等待,导致注入和提交之间间隔过长,Token 到达服务端时已过期,会被直接拒绝。

排查方法:在 Puppeteer 流程中记录时间戳,确认从 getTaskResult 返回 Token 到 page.click() 提交之间的耗时。如果超过 90 秒,考虑将验证码解算时机后移,或在提交前重新解算。

2. IP 地址不一致

部分验证码厂商会校验「解算 Token 时的 IP」与「提交表单时的 IP」是否一致。如果验证码 API 服务商的解算节点 IP 与 Puppeteer 实例使用的代理 IP 不同,后端会认为 Token 被盗用。

解决方案:

  • 使用支持自定义代理的验证码 API(任务类型名通常带 Proxy 关键字,需在请求中传入 proxyType、proxyAddress 等参数),让解算请求通过与 Puppeteer 相同的代理节点。
  • 或者在 Puppeteer 中不使用代理,直接用本地 IP 访问目标站,同时让验证码 API 也从相同 IP 解算。

3. 自动化指纹暴露

Puppeteer 默认会在浏览器环境中暴露 navigator.webdriver = true,许多站点会检测该属性并降低信任评分。对于 reCAPTCHA v3 这类返回评分(0.0-1.0)而非简单通过/失败的系统,自动化指纹会导致评分过低,业务逻辑拒绝通过。

缓解方法:

  • 启动 Puppeteer 时使用 puppeteer-extra 插件(如 puppeteer-extra-plugin-stealth)隐藏自动化特征:
const puppeteer = require('puppeteer-extra');
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
puppeteer.use(StealthPlugin());

const browser = await puppeteer.launch({ headless: false });
  • 或在页面加载前注入脚本覆盖属性:
await page.evaluateOnNewDocument(() => {
  Object.defineProperty(navigator, 'webdriver', { get: () => false });
});

4. 前端回调未触发

即使 Token 已正确填入 <textarea>,如果前端框架的状态管理未感知到变化,提交按钮可能仍处于 disabled 状态,或表单校验逻辑认为验证未完成。这种情况下,点击提交后页面会原地弹出提示,而不是向后端发送请求。

诊断步骤:在 page.click() 提交前,用 page.evaluate() 检查提交按钮状态:

const isDisabled = await page.$eval('#submit-button', btn => btn.disabled);
console.log('提交按钮是否禁用:', isDisabled);

如果仍为 true,说明前端未感知 Token 注入,需回到「触发回调或派发事件」环节补充代码。

隐式验证码的处理差异

reCAPTCHA v3 和部分 Turnstile 配置没有可见的复选框或挑战界面,而是在页面加载或用户交互时自动执行 grecaptcha.execute() 或 turnstile.render(),返回 Token 后静默提交。这类场景无法通过简单的「提取参数 → 解算 → 注入」流程处理。

针对 v3 的两种方案

  1. 拦截并替换执行结果:在页面加载前重写 grecaptcha.execute 方法,让它直接返回你从验证码 API 获取的 Token,而不真正向 Google 请求:
await page.evaluateOnNewDocument((presolvedToken) => {
  window.grecaptcha = {
    execute: () => Promise.resolve(presolvedToken)
  };
}, token);

这种方式需要在页面初始化前注入,且 Token 必须提前解算。

  1. 配合指纹伪装降低风险评分:如果目标站对 v3 评分要求不高(如仅拒绝 0.1 以下的请求),通过隐藏自动化特征和使用住宅代理,可以让 Puppeteer 正常触发 execute() 并获得足够高的评分,无需调用外部验证码 API。

对于 Cloudflare 托管挑战(UAM 或 Managed Challenge),Puppeteer 同样难以直接处理,通常需结合 undetected-chromedriver 或专门的 Cloudflare 绕过服务。

适合 Puppeteer 的验证码 API 选择

在选择验证码识别服务时,除了支持的验证码类型,还需考虑以下工程因素:

  • 任务类型完整性:是否同时提供 Proxyless 和 Proxy 任务类型,前者适合本地 IP 测试,后者适合大规模代理池场景。
  • 轮询接口稳定性:高频轮询时 API 是否会限流,返回结构是否一致。
  • Token 有效期保障:服务商解算延迟(P50/P95)是否能保证 Token 在注入前不过期。
  • 失败退款口径:按提交失败还是按任务创建计费,区别在于如果 Token 注入后目标站拒绝,是否会扣费。

NexCaptcha 提供统一的 REST API 处理 reCAPTCHA v2/v3、hCaptcha、Turnstile、GeeTest 等多种类型,任务模式采用标准的 createTask + getTaskResult 轮询或 webhook 回调,按成功识别的千次计费且失败退款。在需要同时支持多种验证码类型的 Puppeteer 自动化项目中,统一接口可以减少适配工作量。具体任务参数、延迟数据和价格可查看 NexCaptcha 首页。

实际工程中的注意事项

  1. 不要在 Token 解算后等待过久:轮询获得 Token 后应立即注入并提交,避免在页面其他操作(如填写表单、上传文件)上耗费时间。如果业务逻辑必须先完成其他步骤,将验证码解算放在最后一步执行。
  2. 错误处理要区分来源:createTask 返回 errorId 表示参数错误或余额不足,getTaskResult 返回 status: failed 表示解算失败(如验证码加载失败、目标站拒绝),提交后目标站返回错误表示 Token 校验失败。三者的重试策略不同。
  3. 避免并发共享 Page 实例:如果用 Puppeteer 批量处理任务,每个任务应使用独立的 page 实例,避免 Token 注入互相覆盖。
  4. 测试环境与生产隔离:在开发阶段使用验证码 API 的测试密钥,生产上线前切换为正式密钥并小流量验证。部分服务商的测试密钥只能解算特定的演示站点,不支持真实业务域名。

Puppeteer 与验证码识别 API 的集成本质上是异步任务协调,关键在于理解「识别成功」与「业务通过」的边界,以及在注入环节正确触发前端状态更新。按标准流程接入并逐层排查,可以覆盖大部分验证码场景。

相关文章

Cloudflare Turnstile识别实战:Siteverify强制校验、Pre-Clearance通行证与会话一致性架构
公开价格监控验证码处理实战:多平台比价采集的拦截分流、会话对齐与高并发调度
reCAPTCHA识别API实战:GCP全面收编后的Enterprise评估体系、v3低分诊断与高并发调度
2Captcha替代方案怎么选:从众包延迟到WebSocket推送与Adapter平滑迁移实战
Python 验证码识别 API 怎么接:异步 httpx 封装、WebSocket 推送与 Playwright 联调实战
验证码识别自建还是选打码平台?从开源 OCR、大模型调用到企业级 API 的架构选型实战