Use Cases

CAPTCHA no Puppeteer: como resolver com Node.js e a CaptchaAI

Quer parar de travar em CAPTCHAs durante scripts Puppeteer? O caminho é simples: extraia a sitekey do DOM, envie para a API da CaptchaAI, receba o token e injete no formulário — sem intervenção manual. Este guia mostra o módulo solver, a configuração do Puppeteer e o fluxo completo para reCAPTCHA v2 e Cloudflare Turnstile.

Pré-requisitos

  • Node.js 16 ou mais recente, com npm instalado.
  • Puppeteer — instale com npm install puppeteer.
  • Axios — instale com npm install axios para chamar a API da CaptchaAI.
  • Chave de API da CaptchaAI — crie uma conta em captchaai.com e copie a chave no painel.

Como funciona o fluxo de resolução

  1. O Puppeteer navega até a página que exibe o CAPTCHA
  2. Seu script extrai a sitekey do CAPTCHA a partir do DOM
  3. A CaptchaAI resolve o desafio do lado do servidor
  4. Seu script injeta o token na página e envia o formulário

Etapa 1: crie o módulo solver

O módulo abaixo concentra toda a comunicação com a API da CaptchaAI: envia a tarefa, faz o polling do resultado e devolve o token pronto para uso. Ele serve tanto para reCAPTCHA v2 quanto para Cloudflare Turnstile — a única diferença entre as duas funções é o parâmetro method.

// 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 };

Etapa 2: configure o Puppeteer para QA de navegador

Antes de resolver qualquer CAPTCHA, vale configurar o navegador com um perfil de QA comum — isso evita que o site bloqueie a página por sinais óbvios de automação antes mesmo do desafio aparecer, deixando o solver da CaptchaAI cuidar do resto.

const puppeteer = require("puppeteer");

async function createBrowser() {
  const browser = await puppeteer.launch({
    headless: "new",
    args: [
      "--no-sandbox",
      "--disable-setuid-sandbox",
      "--disable-blink-features=AutomationControlled",
    ],
  });

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

Etapa 3: resolva o reCAPTCHA na página

Com o solver e o navegador prontos, o resto do script segue direto: navegue até a página, extraia a sitekey, aguarde o token da CaptchaAI e injete-o no campo de resposta antes de enviar o formulário.

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

Etapa 4: trate callbacks JavaScript do CAPTCHA

Alguns sites disparam a validação por callback do JavaScript em vez de um envio de formulário tradicional:

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

Exemplo completo e funcional

Reunindo as etapas anteriores em um único script: este exemplo resolve o reCAPTCHA de uma página de QA e segue a automação normalmente depois do envio do formulário.

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: ["--disable-blink-features=AutomationControlled"],
  });
  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();
  }
})();

Rodando isso em produção no Brasil

Três pontos costumam pegar equipes brasileiras de surpresa quando esse fluxo vai para produção:

  • Latência. Workers rodando na região sa-east-1 (São Paulo) da AWS tendem a receber o token mais rápido do que workers na Virgínia ou na Europa — vale medir o RTT real no seu ambiente antes de fixar o POLL_INTERVAL.
  • Conformidade com a LGPD. Se o formulário coleta dados pessoais, revise as obrigações da LGPD antes de armazenar o que o Puppeteer captura: registre só o necessário e evite logar CPFs, e-mails ou tokens de sessão em texto puro.
  • Limite de requisições. Rodar várias instâncias do Puppeteer em paralelo multiplica as chamadas a in.php/res.php; distribua o polling entre workers para não estourar o limite de requisições do seu plano.

Solução de problemas comuns

Problema Causa Correção
page.$eval falha O CAPTCHA carrega depois da renderização inicial Use page.waitForSelector('.g-recaptcha')
O token não funciona Expirou antes do envio Injete o token imediatamente após recebê-lo
O site detecta o Puppeteer Faltou configuração de QA de navegador Use a configuração padrão mostrada na Etapa 2
Navigation timeout A página não navegou após o envio Verifique se o site usa AJAX em vez de postagem de formulário
Erro de conexão ao chamar in.php/res.php Instabilidade de rede entre o worker e a API Adicione retentativa com backoff exponencial antes de lançar o erro

Perguntas frequentes

Quanto tempo leva para a CaptchaAI devolver o token no Puppeteer?

Geralmente poucos segundos, variando com o tipo de CAPTCHA e a fila do momento. O POLL_INTERVAL de 5 s e o limite de 60 tentativas do exemplo cobrem os cenários normais sem travar o script.

Dá para resolver Cloudflare Turnstile com Puppeteer usando a CaptchaAI?

Sim. Extraia o data-sitekey da div .cf-turnstile e chame solveTurnstile, que usa method=turnstile na API. O resto do fluxo é igual ao do reCAPTCHA.

Por que o token do reCAPTCHA para de funcionar antes do envio do formulário?

Tokens têm validade curta. Se o script busca o token e só injeta minutos depois, ele expira — injete assim que ele chegar e envie o formulário na sequência.

Preciso combinar proxy com a CaptchaAI no Puppeteer?

Não necessariamente. A CaptchaAI resolve o desafio via API, independente de como sua requisição HTTP chega ao site-alvo; proxy é uma decisão separada de infraestrutura.

Como tratar mais de um CAPTCHA na mesma página com Puppeteer?

Extraia cada sitekey separadamente e resolva-as em paralelo:

  • capture a sitekey de cada elemento .g-recaptcha ou .cf-turnstile na página;
  • chame solveRecaptchaV2 ou solveTurnstile conforme o tipo de cada CAPTCHA;
  • aguarde todas as promises com Promise.all() antes de injetar os tokens e enviar o formulário.

Guias relacionados

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