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
- Đọc sitekey từ trang.
- Yêu cầu Scarnox một token bằng một POST request chặn.
- Đặ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
- Lấy token ngay trước khi gửi. Token hết hạn trong khoảng hai phút.
- Đặt timeout dài cho request giải, khoảng 185 giây. Nó chặn cho đến khi token sẵn sàng.
- Lần giải thất bại không bị tính phí, nên retry chỉ tốn thời gian. Retry các lỗi tạm thời với backoff, như mô tả trong xử lý lỗi và backoff.
- Chạy nhiều trình duyệt cùng lúc? Giới hạn số lần giải đồng thời để một lô chậm không làm đình trệ phần còn lại.
- Đóng trình duyệt trong khối
finally. Một bài kiểm thử bị crash để Chromium chạy nền sẽ làm chậm mọi lần giải sau đó trên cùng máy.
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ự:
- Sitekey bạn gửi khớp với widget trên trang bạn đã tải.
site_urllà trang chứa widget, không phải đích của một chuyển hướng.- Token được đưa vào trường mà trang đọc, hoặc callback đã được gọi.
- Việc gửi xảy ra trong vòng khoảng hai phút kể từ lúc giải.
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.