Resolvendo um PopularCaptcha no Playwright e no Puppeteer (Node.js)
A automação de navegador para no momento em que um PopularCaptcha aparece. Clicar em uma grade de imagens a partir de um script é lento e frágil. Obter um token de uma API de resolução e colocá-lo na página leva uma única requisição, e o restante do seu script segue normalmente.
Este guia cobre o Playwright e o Puppeteer com Node.js. O fluxo é o mesmo nos dois, e os exemplos usam resoluções proxyless, que a Scarnox executa na própria rede. O PopularCaptcha é o tipo que a Scarnox resolve hoje. Outros tipos de captcha estão em breve, então o código abaixo serve apenas para o PopularCaptcha.
O fluxo
- Leia o sitekey da página.
- Peça um token à Scarnox com uma única requisição POST bloqueante.
- Coloque o token no campo de resposta, ou entregue-o ao callback do site, e envie o formulário.
Configure a chave
Crie uma conta e comece com crédito de teste grátis, sem cartão. Guarde a chave em uma variável de ambiente, nunca no código de teste:
export SCARNOX_KEY="scarnox_YOURAPIKEY"
O Node 18 e versões posteriores incluem fetch, então a chamada à API não precisa de pacotes extras.
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());
// Coloque o token nos dois campos de resposta que o site pode ler.
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]");
Observe que o site_url vem de page.url(). Essa é a página em que o widget está, que é o que o solver precisa. A resolução acontece depois que a página carregou, então o sitekey já está no DOM.
Puppeteer
O Puppeteer difere apenas na API de seletores. Reutilize a função solve acima e substitua as chamadas de página:
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);
Quando o site usa um callback
Alguns sites não leem o campo oculto. Eles aguardam o callback em JavaScript do widget. Encontre a função nomeada no atributo data-callback do widget e chame-a com o token:
await page.evaluate((t) => {
const name = document.querySelector("[data-sitekey]")?.dataset.callback;
if (name && typeof window[name] === "function") window[name](t);
}, token);
Se o site usa os dois, defina o campo e chame o callback. Fazer as duas coisas não causa problema.
Widgets invisíveis
Um widget invisível não tem caixa de marcação. O site o inicia pelo próprio código, normalmente quando o visitante pressiona enviar. Envie a mesma requisição com o campo opcional is_invisible definido como true:
body: JSON.stringify({
captcha_type: "PopularCaptchaTaskProxyless",
site_key: sitekey,
site_url: url,
is_invisible: true,
}),
O guia de captcha invisível explica como identificar esses widgets e como devolver o token.
Dicas de tempo
- Busque o token logo antes de enviar. Os tokens expiram em cerca de dois minutos.
- Dê à requisição de resolução um timeout longo, de cerca de 185 segundos. Ela bloqueia até o token estar pronto.
- Resoluções que falham são gratuitas, então uma nova tentativa custa apenas tempo. Tente de novo falhas transitórias com backoff, como descrito em tratamento de erros e backoff.
- Rodando muitos navegadores ao mesmo tempo? Limite o número de resoluções simultâneas para que um lote lento não trave o restante da execução.
- Feche os navegadores em um bloco
finally. Um teste que cai e deixa o Chromium rodando vai deixar lentas todas as resoluções seguintes na mesma máquina.
Testando o seu próprio fluxo
Rode o exemplo em um formulário seu antes de apontá-lo para algo maior. Confira três coisas: o token é aceito, o formulário é enviado e a página seguinte aparece. Quando o fluxo funcionar de ponta a ponta, mantenha-o verde no CI com uma única resolução real por execução.
Teste grátis
Crie uma conta e comece com crédito de teste grátis, sem cartão. Isso basta para montar e verificar o fluxo completo. Para saber quanto custa a resolução em volume, veja a página de preços.
Depurando um token rejeitado
Quando o site rejeita um token, reproduza o fluxo com uma requisição e um carregamento de página antes de mudar qualquer outra coisa. Verifique nesta ordem:
- O sitekey que você enviou corresponde ao widget da página que você carregou.
- O
site_urlé a página em que o widget está, e não um destino de redirecionamento. - O token foi para o campo que a página lê, ou o callback foi chamado.
- O envio aconteceu em até cerca de dois minutos após a resolução.
Salve o HTML da página no momento da falha com await page.content(). Comparar esse HTML com uma execução que funcionou costuma mostrar a diferença de imediato: um sitekey diferente, um campo ausente ou um widget renderizado em outro modo.