Scarnox · Blog

Giải PopularCaptcha trong Playwright và Puppeteer (Node.js)

Tự động hóa trình duyệt dừng lại ngay khi một PopularCaptcha xuất hiện. Click qua một lưới hình ảnh từ script vừa chậm vừa dễ vỡ. Lấy một token từ API giải và đưa vào trang chỉ tốn một request, và phần còn lại của script tiếp tục như bình thường.

Hướng dẫn này bao quát Playwright và Puppeteer với Node.js. Luồng giống nhau ở cả hai, và các ví dụ dùng lần giải proxyless, mà Scarnox chạy trên mạng riêng của mình. PopularCaptcha là loại Scarnox giải hiện nay. Các loại captcha khác sắp ra mắt, nên code bên dưới chỉ dành cho PopularCaptcha.

Luồng

  1. Đọc sitekey từ trang.
  2. Yêu cầu Scarnox một token bằng một POST request chặn.
  3. Đặt token vào trường phản hồi, hoặc chuyển cho callback của trang, rồi gửi form.

Thiết lập khóa

Tạo tài khoản và bắt đầu với tín dụng dùng thử miễn phí, không cần thẻ. Giữ khóa trong biến môi trường, không bao giờ trong code kiểm thử:

export SCARNOX_KEY="scarnox_YOURAPIKEY"

Node 18 trở lên đã có sẵn fetch, nên lệnh gọi API không cần gói thêm.

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());

// Đặt token vào cả hai trường phản hồi mà trang có thể đọc.
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]");

Lưu ý site_url lấy từ page.url(). Đó là trang chứa widget, đúng thứ solver cần. Lần giải diễn ra sau khi trang đã tải, nên sitekey đã có trong DOM.

Puppeteer

Puppeteer chỉ khác ở API selector. Tái sử dụng hàm solve ở trên và thay các lệnh gọi trang:

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);

Khi trang dùng callback

Một số trang không đọc trường ẩn. Thay vào đó, họ chờ callback JavaScript của widget. Tìm hàm được đặt tên trong thuộc tính data-callback của widget và gọi nó với token:

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

Nếu trang dùng cả hai, hãy đặt trường và gọi callback. Làm cả hai không gây hại gì.

Widget vô hình

Widget vô hình không có ô đánh dấu. Trang khởi động nó từ mã của chính trang, thường khi người truy cập nhấn gửi. Gửi cùng request với trường tùy chọn is_invisible đặt là true:

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

Hướng dẫn captcha vô hình giải thích cách nhận ra các widget này và cách trả token lại.

Mẹo về thời gian

Kiểm thử luồng của bạn

Chạy ví dụ trên một form bạn sở hữu trước khi áp dụng vào một thứ lớn hơn. Kiểm tra ba điều: token được chấp nhận, form được gửi, và trang tiếp theo xuất hiện. Khi luồng hoạt động từ đầu đến cuối, hãy giữ CI xanh với một lần giải thật cho mỗi lần chạy.

Dùng thử miễn phí

Tạo tài khoản và bắt đầu với tín dụng dùng thử miễn phí, không cần thẻ. Như vậy là đủ để cấu hình và kiểm tra toàn bộ luồng. Để biết chi phí khi giải ở khối lượng lớn, xem giá hiện tại.

Gỡ lỗi một token bị từ chối

Khi trang từ chối token, hãy tái tạo luồng bằng một request và một lần tải trang trước khi thay đổi bất cứ thứ gì khác. Kiểm tra theo thứ tự:

Lưu HTML trang tại thời điểm lỗi bằng await page.content(). So sánh HTML đó với một lần chạy thành công thường cho thấy khác biệt ngay: một sitekey khác, một trường bị thiếu, hoặc một widget được render ở chế độ khác.

Bước tiếp theo