Scarnox · Blog

在 Playwright 和 Puppeteer 中识别 PopularCaptcha(Node.js)

一旦出现 PopularCaptcha,浏览器自动化就会卡住。从脚本里点击图片网格既慢又脆弱。从识别 API 获取 token 并把它放入页面,只需一次请求,脚本的其余部分就可以照常继续。

本指南涵盖使用 Node.js 的 Playwright 和 Puppeteer。两者的流程相同,示例使用无代理识别,由 Scarnox 在自己的网络上完成。PopularCaptcha 是 Scarnox 目前识别的类型。其他验证码类型即将支持,因此下面的代码仅适用于 PopularCaptcha。

流程

  1. 从页面中读取 sitekey。
  2. 通过一次阻塞的 POST 请求向 Scarnox 获取 token。
  3. 把 token 填入响应字段,或交给网站的 JavaScript 回调,然后提交表单。

设置密钥

注册账户,免费试用额度,无需信用卡。请把密钥保存在环境变量中,绝不要写在测试代码里:

export SCARNOX_KEY="scarnox_YOURAPIKEY"

Node 18 及更高版本内置了 fetch,因此调用 API 无需额外的包。

Playwright

import { chromium } from "playwright";

const API_KEY = process.env.SCARNOX_KEY;

async function solve(sitekey, url) {
  const r = await fetch("https://api.scarnox.com/api/tasks/create", {
    method: "POST",
    headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({ captcha_type: "PopularCaptchaTaskProxyless", site_key: sitekey, site_url: url }),
  });
  const data = await r.json();
  if (data.errorId !== 0) throw new Error(`${data.errorCode}: ${data.errorDescription}`);
  return data.solution.token;
}

const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto("https://example.com/signup");

const sitekey = await page.getAttribute("[data-sitekey]", "data-sitekey");
const token = await solve(sitekey, page.url());

// 把 token 写入网站可能读取的两个响应字段。
await page.evaluate((t) => {
  for (const name of ["h-captcha-response", "g-recaptcha-response"]) {
    document.querySelectorAll(`[name="${name}"]`).forEach((el) => { el.value = t; });
  }
}, token);

await page.fill("#email", "you@example.com");
await page.click("button[type=submit]");

注意 site_url 来自 page.url()。那是控件所在的页面,也是识别器需要的 URL。识别是在页面加载完成之后进行的,所以 sitekey 已经在 DOM 中了。

Puppeteer

Puppeteer 与 Playwright 的区别仅在于选择器 API。复用上面的 solve 函数,并替换页面调用:

const sitekey = await page.$eval("[data-sitekey]", (el) => el.dataset.sitekey);
const token = await solve(sitekey, page.url());

await page.evaluate((t) => {
  document.querySelectorAll('[name="h-captcha-response"],[name="g-recaptcha-response"]')
    .forEach((el) => { el.value = t; });
}, token);

当网站使用回调时

有些网站不读取隐藏字段,而是等待控件的 JavaScript 回调。找到控件 data-callback 属性中指定的函数,并用 token 调用它:

await page.evaluate((t) => {
  const name = document.querySelector("[data-sitekey]")?.dataset.callback;
  if (name && typeof window[name] === "function") window[name](t);
}, token);

如果网站两种方式都用,那就同时设置字段并调用回调。两者都做没有坏处。

隐形控件

隐形控件没有复选框。网站从自己的代码中启动它,通常是在访客点击提交时。发送同样的请求,并将可选字段 is_invisible 设为 true:

body: JSON.stringify({
  captcha_type: "PopularCaptchaTaskProxyless",
  site_key: sitekey,
  site_url: url,
  is_invisible: true,
}),

隐形验证码指南说明了如何识别这类控件,以及如何把 token 回传给页面。

时机技巧

测试你自己的流程

在对更大的目标运行示例之前,先在你自己的表单上运行。检查三件事:token 被接受、表单成功提交,并且下一个页面出现。当流程端到端跑通之后,可以在每次运行中只做一次真实识别,并让 CI 保持通过。

免费试用

注册账户,免费试用额度,无需信用卡。这足以搭建并验证整个流程。大规模识别的费用,请查看当前定价。

调试被拒绝的 token

当网站拒绝 token 时,请只用一次请求和一次页面加载来复现流程,然后再做其他改动。请按以下顺序检查:

在失败的时刻,用 await page.content() 保存页面 HTML。将它与一次成功的运行做比较,通常能一眼看出差异:sitekey 不同、字段缺失,或者控件以另一种模式渲染。

下一步