API Tutorials

Resolva Grid Image CAPTCHA com Node.js e CaptchaAI

Se o seu script em Node.js travou diante de uma grade "selecione todos os quadrados com semáforos", a resposta curta é: o reconhecimento não precisa acontecer no seu código. Você tira um print da grade, envia a imagem com o texto da instrução para a API da CaptchaAI e recebe a lista de células a clicar. O clique continua sendo do seu navegador automatizado — a API devolve só os índices.

A grade não é um tipo separado de desafio: é a tela que o reCAPTCHA v2 mostra quando a caixa "Não sou um robô" não confia o suficiente na sessão. E ela costuma aparecer no pior momento — no meio de um teste de regressão noturno, quando ninguém está olhando. O fluxo abaixo usa Puppeteer no navegador e axios nas chamadas HTTP.


O fluxo completo em quatro etapas

Antes do código, o mapa do que será construído:

  1. Capturar — achar o iframe do desafio, ler a instrução e salvar um print só da área da grade.
  2. Enviar — POST multipart para in.php com a imagem, o grid_size e as instructions.
  3. Consultar — polling em res.php até a resposta sair, tratando CAPCHA_NOT_READY como "ainda processando".
  4. Clicar — mapear os números devolvidos para os blocos do DOM e confirmar na verificação.

O ponto que mais gera dúvida é o terceiro: a API é assíncrona. O envio devolve um ID de tarefa, não a resposta — quem tenta ler o resultado no mesmo instante recebe CAPCHA_NOT_READY e conclui, erradamente, que a integração está quebrada.


Pré-requisitos

Item Valor
Chave de API CaptchaAI Disponível em captchaai.com
Node.js 14+
Bibliotecas axios, puppeteer

Guarde a chave de API em uma variável de ambiente, nunca no script. Nos exemplos ela aparece como YOUR_API_KEY só por legibilidade.


Etapa 1: capturar a imagem da grade

O desafio visual vive em um iframe próprio, diferente do iframe da caixa de seleção. Filtre os frames por recaptcha/api2/bframe, leia a instrução (é ela que diz o que procurar) e recorte o print no elemento da grade — enviar a página inteira derruba a qualidade da leitura.

const puppeteer = require('puppeteer');
const fs = require('fs');

const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com/page-with-recaptcha');

// Switch to the reCAPTCHA challenge iframe
const frames = page.frames();
const challengeFrame = frames.find((f) => f.url().includes('recaptcha/api2/bframe'));

// Get the instruction text
const instruction = await challengeFrame.$eval(
  '.rc-imageselect-desc-no-canonical',
  (el) => el.textContent.trim()
);

// Screenshot the grid
const grid = await challengeFrame.$('.rc-imageselect-target');
await grid.screenshot({ path: 'grid.png' });

Se challengeFrame vier undefined, o desafio ainda não abriu: clique na caixa de verificação e aguarde o frame surgir.


Etapa 2: enviar a grade para a CaptchaAI

O envio é um POST multipart para in.php. Três campos merecem atenção: grid_size descreve o formato (3x3 ou 4x4), img_type identifica a origem do desafio e instructions carrega o texto lido na etapa anterior. Mantenha json=1 para receber uma resposta estruturada.

const axios = require('axios');
const FormData = require('form-data');

const API_KEY = 'YOUR_API_KEY';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

const form = new FormData();
form.append('key', API_KEY);
form.append('method', 'post');
form.append('grid_size', '3x3');
form.append('img_type', 'recaptcha');
form.append('instructions', instruction);
form.append('json', '1');
form.append('file', fs.createReadStream('grid.png'));

const { data: submitData } = await axios.post('https://ocr.captchaai.com/in.php', form, {
  headers: form.getHeaders(),
});

if (submitData.status !== 1) throw new Error(submitData.request);
const taskId = submitData.request;
console.log(`Task submitted: ${taskId}`);

status igual a 1 traz o ID da tarefa em request. Qualquer outro valor é código de erro — registre no log.


Etapa 3: consulte o resultado

Agora o polling. Espere alguns segundos antes da primeira consulta, use um intervalo fixo e defina um teto de tentativas para o loop nunca ficar preso. CAPCHA_NOT_READY significa "continue esperando"; qualquer outra string é erro real e interrompe a execução.

await sleep(5000);

let cellsToClick;
for (let i = 0; i < 30; i++) {
  const { data: pollData } = await axios.get('https://ocr.captchaai.com/res.php', {
    params: { key: API_KEY, action: 'get', id: taskId, json: 1 },
  });

  if (pollData.status === 1) {
    cellsToClick = JSON.parse(pollData.request);
    console.log('Click cells:', cellsToClick);
    break;
  }
  if (pollData.request !== 'CAPCHA_NOT_READY') {
    throw new Error(pollData.request);
  }
  await sleep(5000);
}

O retorno é um array de células, contadas da esquerda para a direita e de cima para baixo, a partir de 1.


Etapa 4: clique nos blocos corretos

Os índices devolvidos começam em 1; a lista de elementos do DOM começa em 0. Daí o - 1 no acesso ao array — o erro mais comum aqui. O intervalo curto entre os cliques evita que o widget descarte uma sequência disparada de uma vez.

const tiles = await challengeFrame.$$('.rc-imageselect-tile');

for (const cellNum of cellsToClick) {
  await tiles[cellNum - 1].click();
  await sleep(300);
}

// Click verify
await challengeFrame.click('#recaptcha-verify-button');
console.log(`Solved: clicked tiles ${JSON.stringify(cellsToClick)}`);
await browser.close();

Resultado esperado no console:

Click cells: [1, 3, 6, 9]
Solved: clicked tiles [1,3,6,9]

Erros comuns e o que fazer

Sintoma Causa provável Correção
ERROR_ZERO_BALANCE Saldo ou plano expirado Verifique o saldo no painel antes de rodar lotes grandes
Sempre CAPCHA_NOT_READY Primeira consulta cedo demais Aguarde 5 s antes da primeira consulta e mantenha o intervalo
Células erradas selecionadas Print com margens ou da página inteira Recorte apenas o elemento .rc-imageselect-target
Instrução vazia O seletor da instrução mudou Leia o texto do frame e falhe cedo se vier vazio
Verificação recusada A grade recarregou com novas imagens Capture e envie novamente na nova rodada

Quando a automação de navegador falha mas a chamada direta à API funciona, o problema está no navegador, não na resolução — isole os dois caminhos antes de mexer na integração.


Threads, volume e custo em um cenário real

Imagine uma equipe em São Paulo que roda a suíte de regressão do próprio checkout às 3h, com os workers em sa-east-1: 40 cenários por execução e um em cada quatro esbarra em uma grade — cerca de 10 desafios por noite, em série.

A cobrança da CaptchaAI é por thread simultânea, com resoluções ilimitadas no plano, e não por desafio. Para esse volume noturno, o BASIC (US$ 15/mês, 5 threads) sobra; um pipeline de CI com dezenas de execuções paralelas por hora se acomoda melhor no ADVANCE (US$ 90/mês, 50 threads). O que importa não é o total de desafios no mês, e sim quantos você resolve ao mesmo tempo no pico.

Vale ser exato sobre os tipos: grades de imagem, CAPTCHAs de imagem/OCR, reCAPTCHA v2 e v3, Cloudflare Turnstile e GeeTest v3 são suportados; CaptchaFox, Friendly Captcha e Lemin estão em beta; hCaptcha e FunCaptcha não são suportados, e o GeeTest v4 aparece apenas como "em breve".


Uso responsável e conformidade

Esse fluxo pertence a ambientes que você tem autorização para automatizar: staging próprio, QA de formulários internos, monitoramento contratado. Se a rotina registra dados pessoais, considere as obrigações da LGPD (RGPD em Portugal) — prints e HTML capturado retêm mais do que a equipe imagina. Guarde só o necessário e defina um prazo de expurgo.


Perguntas frequentes

Funciona com grades 4×4?

Sim. Basta enviar grid_size como 4x4. O resto não muda: o retorno continua sendo uma lista de células, agora de 1 a 16.

Qual a diferença entre a grade e um CAPTCHA de imagem comum?

O CAPTCHA de imagem clássico devolve texto — as letras distorcidas que você digita. A grade devolve posições: quais blocos clicar. Os dois usam o mesmo endpoint, mas instructions e grid_size só fazem sentido na grade.

Preciso mesmo de um navegador?

Para a grade, sim: o print vem do DOM real e o clique precisa acontecer no widget. É diferente do reCAPTCHA v2 padrão, em que basta a sitekey e a URL da página.

E as grades do hCaptcha?

Não temos suporte a hCaptcha. Este tutorial cobre a grade de imagens do reCAPTCHA v2; para outros tipos, confira antes a lista oficial de métodos suportados.

Quanto tempo costuma levar cada resolução?

Alguns segundos na maioria dos casos, mas trate o tempo como variável: mantenha o teto de tentativas, registre o tempo de resolução e use um timeout que não trave a suíte inteira.


Guias relacionados


Comece a resolver Grid Image CAPTCHAs com CaptchaAI →

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