Trường Hợp Sử Dụng

Puppeteer giải CAPTCHA bằng Node.js và CaptchaAI

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:

  1. Puppeteer mở trang có CAPTCHA
  2. Script trích sitekey (và đôi khi pageurl) từ DOM
  3. CaptchaAI giải thử thách phía server, trả về token
  4. 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

Os comentários estão desativados para este artigo.