Puppeteer tự nó không giải được CAPTCHA — nó chỉ điều khiển Chrome/Chromium qua DevTools Protocol. Khi trang bạn cần scraping hoặc kiểm thử hiển thị reCAPTCHA v2 hay Cloudflare Turnstile, cách xử lý thực tế là tách phần giải ra khỏi trình duyệt: script Puppeteer lấy sitekey và URL trang từ DOM, gửi sang API của CaptchaAI, chờ CaptchaAI giải ở phía server, rồi tiêm token nhận được ngược lại vào form trước khi submit. Không có plugin nào "tự động vượt CAPTCHA" ngay trong Puppeteer — phần giải luôn nằm ở một dịch vụ bên ngoài.
Bài này viết cho dev đã quen Puppeteer và cần một luồng chạy được ngay trong Node.js, không phải bài tổng quan lý thuyết về CAPTCHA. Nhiều đội QA và scraping tại các công ty outsource ở TP.HCM, Hà Nội dùng đúng mô hình gửi → nhận ID task → polling → tiêm token này để kiểm thử luồng đăng nhập hoặc theo dõi giá trên các sàn thương mại điện tử mà họ được phép truy cập.
Cần gì trước khi bắt đầu
Cài các gói sau trước khi chạy ví dụ trong bài:
| Yêu cầu | Chi tiết |
|---|---|
| Node.js 16+ | Kèm npm |
| Puppeteer | npm install puppeteer |
| axios | npm install axios |
| API key CaptchaAI | Lấy tại captchaai.com |
Luồng xử lý: 4 bước từ sitekey đến token
CaptchaAI xử lý CAPTCHA hoàn toàn ở phía server, Puppeteer chỉ đóng vai trò lấy dữ liệu và tiêm kết quả trở lại trang:
- Puppeteer mở trang có CAPTCHA
- Script trích sitekey (và đôi khi pageurl) từ DOM
- CaptchaAI giải thử thách phía server, trả về token
- Script tiêm token vào form (hoặc gọi callback JavaScript) rồi submit
Bước 1: Viết module giải CAPTCHA
Tách logic gọi CaptchaAI ra một file riêng để dùng lại cho cả reCAPTCHA v2 và Turnstile. Module dưới đây gửi task tới in.php, sau đó polling res.php mỗi 5 giây cho tới khi có token hoặc hết 60 lần thử:
// solver.js
const axios = require("axios");
const API_KEY = "YOUR_API_KEY";
const POLL_INTERVAL = 5000;
const MAX_ATTEMPTS = 60;
async function solveRecaptchaV2(siteKey, pageUrl) {
// Submit task
const submitResp = await axios.get("https://ocr.captchaai.com/in.php", {
params: {
key: API_KEY,
method: "userrecaptcha",
googlekey: siteKey,
pageurl: pageUrl,
},
});
if (!submitResp.data.startsWith("OK|")) {
throw new Error(`Submit failed: ${submitResp.data}`);
}
const taskId = submitResp.data.split("|")[1];
console.log(`Task submitted: ${taskId}`);
// Poll for result
for (let i = 0; i < MAX_ATTEMPTS; i++) {
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
const result = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "get", id: taskId },
});
if (result.data === "CAPCHA_NOT_READY") continue;
if (result.data.startsWith("OK|")) {
return result.data.split("|")[1];
}
throw new Error(`Solve failed: ${result.data}`);
}
throw new Error("Solve timed out");
}
async function solveTurnstile(siteKey, pageUrl) {
const submitResp = await axios.get("https://ocr.captchaai.com/in.php", {
params: {
key: API_KEY,
method: "turnstile",
sitekey: siteKey,
pageurl: pageUrl,
},
});
if (!submitResp.data.startsWith("OK|")) {
throw new Error(`Submit failed: ${submitResp.data}`);
}
const taskId = submitResp.data.split("|")[1];
for (let i = 0; i < MAX_ATTEMPTS; i++) {
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
const result = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "get", id: taskId },
});
if (result.data === "CAPCHA_NOT_READY") continue;
if (result.data.startsWith("OK|")) return result.data.split("|")[1];
throw new Error(`Solve failed: ${result.data}`);
}
throw new Error("Solve timed out");
}
module.exports = { solveRecaptchaV2, solveTurnstile };
Bước 2: Khởi tạo Puppeteer với cấu hình kiểm thử chuẩn
Trước khi điều hướng tới trang cần giải CAPTCHA, khởi tạo trình duyệt với User-Agent thật và các cờ khởi chạy phù hợp cho môi trường kiểm thử — tránh trường hợp trang chặn ngay từ bước tải, khiến CaptchaAI còn chưa kịp giải gì:
const puppeteer = require("puppeteer");
async function createBrowser() {
const browser = await puppeteer.launch({
headless: "new",
args: [
"--no-sandbox",
"--disable-setuid-sandbox",
"",
],
});
const page = await browser.newPage();
await page.setUserAgent(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
);
// Hide automation indicators
await page.evaluateOnNewDocument(() => {
Object.defineProperty(navigator, "webdriver", { get: () => false });
});
return { browser, page };
}
Bước 3: Giải reCAPTCHA v2 trên trang đích
Ghép hai phần trên lại: mở trang, trích sitekey, gọi solveRecaptchaV2, tiêm token vào ô ẩn #g-recaptcha-response, rồi submit form:
const { solveRecaptchaV2 } = require("./solver");
async function scrapeWithCaptcha(url) {
const { browser, page } = await createBrowser();
try {
await page.goto(url, { waitUntil: "networkidle2" });
// Extract site key
const siteKey = await page.$eval(
".g-recaptcha",
(el) => el.getAttribute("data-sitekey")
);
console.log("Site key:", siteKey);
// Solve with CaptchaAI
const token = await solveRecaptchaV2(siteKey, url);
console.log("Token received:", token.substring(0, 50));
// Inject token
await page.evaluate((token) => {
document.getElementById("g-recaptcha-response").innerHTML = token;
document.getElementById("g-recaptcha-response").style.display = "";
}, token);
// Submit the form
await page.click('button[type="submit"]');
await page.waitForNavigation({ waitUntil: "networkidle2" });
// Scrape the content
const content = await page.content();
console.log("Page loaded successfully");
return content;
} finally {
await browser.close();
}
}
Bước 4: Xử lý callback khi trang không submit form
Một số trang không submit form theo cách thông thường mà gọi callback JavaScript ngay khi reCAPTCHA có token. Trường hợp này, tìm và gọi trực tiếp hàm callback trong ___grecaptcha_cfg thay vì click nút submit:
// Trigger the reCAPTCHA callback
await page.evaluate((token) => {
// Method 1: Direct callback
if (typeof ___grecaptcha_cfg !== "undefined") {
const clients = ___grecaptcha_cfg.clients;
Object.keys(clients).forEach((key) => {
const client = clients[key];
// Find the callback function
const findCallback = (obj) => {
for (const prop in obj) {
if (typeof obj[prop] === "function") {
obj[prop](token);
return true;
}
if (typeof obj[prop] === "object" && obj[prop] !== null) {
if (findCallback(obj[prop])) return true;
}
}
return false;
};
findCallback(client);
});
}
}, token);
Ví dụ chạy được từ đầu đến cuối
Gộp toàn bộ luồng — gửi task, polling, tiêm token, submit — vào một script Node.js chạy độc lập. Đổi staging.example.com/qa-login thành trang bạn được phép kiểm thử:
const puppeteer = require("puppeteer");
const axios = require("axios");
const API_KEY = "YOUR_API_KEY";
async function solveCaptcha(siteKey, pageUrl) {
const submit = await axios.get("https://ocr.captchaai.com/in.php", {
params: {
key: API_KEY,
method: "userrecaptcha",
googlekey: siteKey,
pageurl: pageUrl,
},
});
const taskId = submit.data.split("|")[1];
while (true) {
await new Promise((r) => setTimeout(r, 5000));
const result = await axios.get("https://ocr.captchaai.com/res.php", {
params: { key: API_KEY, action: "get", id: taskId },
});
if (result.data === "CAPCHA_NOT_READY") continue;
if (result.data.startsWith("OK|")) return result.data.split("|")[1];
throw new Error(result.data);
}
}
(async () => {
const browser = await puppeteer.launch({
headless: "new",
args: [""],
});
const page = await browser.newPage();
try {
await page.goto("https://staging.example.com/qa-login", {
waitUntil: "networkidle2",
});
// Get the site key
const siteKey = await page.$eval(".g-recaptcha", (el) =>
el.getAttribute("data-sitekey")
);
// Solve
const token = await solveCaptcha(siteKey, page.url());
// Inject and submit
await page.evaluate((t) => {
document.getElementById("g-recaptcha-response").innerHTML = t;
}, token);
await page.click("#submit-btn");
await page.waitForNavigation();
console.log("Done:", page.url());
} finally {
await browser.close();
}
})();
Các lỗi thường gặp khi tích hợp
Bảng dưới liệt kê các lỗi hay gặp nhất khi ghép Puppeteer với CaptchaAI và cách xử lý nhanh:
| Vấn đề | Nguyên nhân | Cách xử lý |
|---|---|---|
page.$eval báo lỗi |
CAPTCHA chỉ tải sau khi trang render xong | Dùng page.waitForSelector('.g-recaptcha') trước khi trích sitekey |
| Token không hoạt động | Token hết hạn trước khi kịp submit | Tiêm token và submit ngay sau khi nhận, không polling thêm |
| Trang phát hiện Puppeteer | User-Agent hoặc cờ khởi chạy chưa khớp môi trường thật | Rà lại cấu hình ở Bước 2, test trước bằng trình duyệt có giao diện |
Navigation timeout |
Trang không điều hướng sau khi submit | Kiểm tra trang có submit bằng AJAX thay vì form POST hay không |
Câu hỏi thường gặp
Puppeteer giải CAPTCHA bằng CaptchaAI tốn bao nhiêu?
CaptchaAI tính phí theo thread chứ không theo từng lần giải. Gói BASIC ($15/tháng, 5 thread) đủ cho hầu hết script Puppeteer chạy tuần tự; nếu chạy song song nhiều tab cùng lúc, cân nhắc STANDARD ($30/tháng, 15 thread) trở lên.
Nên chạy Puppeteer ở chế độ headless hay có giao diện?
Headless chạy tốt vì CaptchaAI giải ở phía server, không cần trình duyệt hiển thị gì cả. Chỉ bật chế độ có giao diện khi cần xem trực tiếp lúc gỡ lỗi.
Puppeteer dùng với CaptchaAI có khác gì Playwright?
Về cách gọi API thì giống hệt nhau — cả hai đều gửi sitekey và pageurl tới in.php rồi polling res.php. Khác biệt chỉ nằm ở cách Puppeteer và Playwright thao tác DOM và trích thuộc tính data-sitekey.
Làm sao giải nhiều CAPTCHA cùng lúc trên một trang?
Trích từng sitekey riêng rồi gọi CaptchaAI song song bằng Promise.all(). Tính đủ số thread trong gói đang dùng để các task không phải xếp hàng chờ nhau.
CaptchaAI có giải được hCaptcha khi dùng với Puppeteer không?
Chưa. CaptchaAI hiện hỗ trợ reCAPTCHA v2/v3, Cloudflare Turnstile, Cloudflare Challenge, GeeTest v3, CAPTCHA ảnh/OCR và BLS; hCaptcha và FunCaptcha chưa được hỗ trợ.
Hướng dẫn liên quan
- Xử lý CAPTCHA bằng Selenium trong Python
- Giải CAPTCHA với Playwright
- Scraping có CAPTCHA bằng Node.js