Se sua suíte de QA ou pipeline de coleta de dados roda em modo headless e passou a receber mais CAPTCHA, o problema quase nunca é a lógica do teste — é o navegador entregando sinais que reCAPTCHA, Cloudflare e DataDome reconhecem.
Este guia mostra quais sinais os antibots checam, como resolver o desafio via API sem trocar de framework e como reduzir a frequência de CAPTCHA sem depender de ajustes frágeis que quebram a cada atualização do Chrome.
Por que o modo headless recebe mais CAPTCHA
| Sinal verificado pelos sites | O que costuma indicar |
|---|---|
navigator.webdriver |
Retorna true em modo headless |
| Dimensões da janela | Headless geralmente abre em 800x600 por padrão |
| Renderizador WebGL | Headless retorna "SwiftShader" |
| Protocolo Chrome DevTools (CDP) | Porta CDP aberta |
| Plugins ausentes | Sem leitor de PDF, Flash etc. |
| API de permissões | Respostas diferentes em modo headless |
| String do user-agent | Substring "HeadlessChrome" |
Isolado, nenhum sinal costuma derrubar a sessão — o problema é a combinação, que reduz a pontuação de confiança do antibot e dispara o desafio.
Na prática, isso significa:
- Um teste que passa no Chrome com interface pode falhar no mesmo pipeline em modo headless, sem nenhuma mudança de código.
- Cada atualização do Chrome ou do driver pode alterar quais sinais aparecem — o comportamento muda sem aviso prévio.
- Corrigir um sinal isolado (só o
navigator.webdriver, por exemplo) raramente resolve sozinho; o antibot soma vários pontos de sinal antes de decidir. - Tentar imitar um navegador real o tempo todo não é sustentável a longo prazo — por isso resolver o CAPTCHA quando ele aparecer é a camada que não quebra a cada release.
Checklist rápido de diagnóstico
Antes de reescrever a suíte inteira, confirme qual sinal está disparando o desafio. O diagnóstico leva menos de cinco minutos:
- Abra o DevTools e rode
navigator.webdriverno console — se voltartrue, esse já é o principal suspeito. - Compare um screenshot da página em modo headless com um em modo com interface; diferenças de layout costumam indicar viewport 800x600.
- Verifique o renderizador WebGL (
about:gpuou equivalente) — "SwiftShader" é a assinatura clássica de headless. - Olhe os logs do pipeline de CI: se o CAPTCHA aparece só no primeiro request de cada execução, o padrão aponta mais para reputação de IP do que para sinais do navegador.
Erros comuns por navegador
- Chrome headless —
navigator.webdriver = true: use a flag--disable-blink-features. - Puppeteer — faltam plugins de navegador: use um pacote de compatibilidade (ex.:
puppeteer-extra-plugin-stealth). - Selenium — switch
enable-automationativo: definaexcludeSwitches: ["enable-automation"]. - Playwright — sinal de navegador do WebKit: use o canal Chromium com os mesmos ajustes de compatibilidade.
- Todos — viewport inconsistente: fixe em 1920x1080 ou randomize dentro de valores realistas.
Resolva o CAPTCHA pela API (funciona em qualquer navegador headless)
Em vez de evitar o CAPTCHA por completo, resolva-o quando ele aparecer. A CaptchaAI funciona com qualquer navegador headless porque a resolução acontece do lado do servidor.
Selenium (Python)
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import requests
import time
API_KEY = "YOUR_API_KEY"
options = Options()
options.add_argument("--headless=new")
options.add_argument("--disable-blink-features=AutomationControlled")
driver = webdriver.Chrome(options=options)
driver.get("https://staging.example.com/qa-login")
# Check for CAPTCHA
recaptcha = driver.find_elements("class name", "g-recaptcha")
if recaptcha:
site_key = recaptcha[0].get_attribute("data-sitekey")
# Solve via CaptchaAI
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY, "method": "userrecaptcha",
"googlekey": site_key, "pageurl": driver.current_url
})
task_id = resp.text.split("|")[1]
while True:
time.sleep(5)
result = requests.get("https://ocr.captchaai.com/res.php", params={
"key": API_KEY, "action": "get", "id": task_id
})
if result.text == "CAPCHA_NOT_READY": continue
token = result.text.split("|")[1]
break
# Inject token
driver.execute_script(
f"document.getElementById('g-recaptcha-response').innerHTML = '{token}';"
)
driver.find_element("css selector", "form").submit()
O mesmo padrão vale para Puppeteer — troque só a chamada HTTP pelo cliente do seu stack:
Puppeteer (Node.js)
const puppeteer = require("puppeteer");
const axios = require("axios");
const API_KEY = "YOUR_API_KEY";
const browser = await puppeteer.launch({ headless: "new" });
const page = await browser.newPage();
await page.goto("https://staging.example.com/qa-login");
// Check for CAPTCHA
const siteKey = await page
.$eval(".g-recaptcha", (el) => el.getAttribute("data-sitekey"))
.catch(() => null);
if (siteKey) {
const submit = await axios.get("https://ocr.captchaai.com/in.php", {
params: {
key: API_KEY,
method: "userrecaptcha",
googlekey: siteKey,
pageurl: page.url(),
},
});
const taskId = submit.data.split("|")[1];
let token;
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;
token = result.data.split("|")[1];
break;
}
await page.evaluate((t) => {
document.getElementById("g-recaptcha-response").innerHTML = t;
}, token);
await page.click('button[type="submit"]');
}
Cenário comum: um pipeline de QA com workers de Selenium em sa-east-1 (São Paulo) recebe CAPTCHA já no primeiro request de um staging novo, sem nenhum comportamento suspeito. A resolução via API acima resolve isso sem whitelist de IP — e usar dados fictícios no login mantém o teste alinhado à LGPD.
Diminua a frequência de CAPTCHA sem abrir mão da API
Resolver via API sempre funciona, mas reduzir a frequência do CAPTCHA economiza tempo e chamadas de API.
Corrija o sinal navigator.webdriver
# Selenium
options.add_argument("--disable-blink-features=AutomationControlled")
options.add_experimental_option("excludeSwitches", ["enable-automation"])
A segunda flag reforça a primeira: ela remove o switch enable-automation, que alguns antibots ainda checam separadamente do valor de navigator.webdriver.
// Puppeteer
await page.evaluateOnNewDocument(() => {
Object.defineProperty(navigator, "webdriver", { get: () => false });
});
Use um viewport realista
Janela em 800x600 é um dos sinais mais óbvios de automação. Fixe uma resolução comum antes de abrir a página:
# Selenium
driver.set_window_size(1920, 1080)
O equivalente em Puppeteer usa o mesmo valor:
// Puppeteer
await page.setViewport({ width: 1920, height: 1080 });
Adicione pacotes de compatibilidade do navegador
Bibliotecas de configuração padrão preenchem as lacunas do headless (plugins, navigator.webdriver, WebGL) sem alterar sua lógica de teste. Em Puppeteer, instale o pacote:
# Puppeteer
npm install puppeteer-extra puppeteer-extra-plugin-stealth
E registre o plugin antes de abrir o navegador:
const puppeteer = require("puppeteer-extra");
const QaPlugin = require("puppeteer-extra-plugin-stealth");
puppeteer.use(QaPlugin());
Em Selenium, o equivalente é outro driver, não um plugin:
# Selenium
pip install undetected-chromedriver
Troque o import e a inicialização do driver:
import undetected_chromedriver as uc
driver = uc.Chrome(headless=True)
Cloudflare Turnstile em staging: o token sozinho não basta
Páginas de desafio do Turnstile exigem mais que o token — você também precisa do cookie de validação. A CaptchaAI entrega os dois:
# CaptchaAI handles full Cloudflare Turnstile em stagings
resp = requests.get("https://ocr.captchaai.com/in.php", params={
"key": API_KEY,
"method": "turnstile_staging",
"pageurl": "https://example.com",
"proxy": "http://user:pass@proxy:port",
"proxytype": "HTTP"
})
task_id = resp.text.split("|")[1]
# Result includes cookie_qa_validacao cookie and user_agent
# Use both to make subsequent requests
Guarde cookie_qa_validacao junto com o token e reenvie os dois nas próximas requisições da mesma sessão — sem o cookie, o servidor de staging volta a tratar a sessão como não verificada.
Perguntas frequentes
Selenium, Puppeteer ou Playwright: qual dispara menos CAPTCHA?
Nenhum é imune por padrão — os três herdam os mesmos sinais do Chromium/WebKit. A diferença está na facilidade de aplicar as correções acima, não no framework.
Preciso reconfigurar tudo a cada atualização do Chrome?
Com frequência, sim: sinais mudam a cada release e um ajuste de compatibilidade pode parar de funcionar de uma hora para outra. Por isso resolver via API é a camada estável — não depende do sinal que o site checa hoje.
Vale a pena rodar com interface, só para reduzir CAPTCHA?
Reduz alguns sinais, mas exige servidor de exibição (Xvfb no Linux) e complica o CI. A API da CaptchaAI funciona igual com ou sem interface, o que a torna mais previsível para QA automatizado.
Dá para eliminar o CAPTCHA por completo em scraping ou QA headless?
Não de forma confiável. Mesmo um navegador bem configurado acaba recebendo CAPTCHA em algum volume, principalmente contra Cloudflare ou PerimeterX. Resolver via API quando ele aparece mantém a automação rodando em vez de travada.
Rodar os testes em um contêiner Docker muda alguma coisa?
Não na resolução em si — a chamada à API funciona igual dentro ou fora de contêiner. O que muda é a superfície de sinais: imagens Docker minimalistas costumam não ter fontes nem GPU, o que empurra ainda mais para o modo headless "puro" e pode aumentar a frequência de CAPTCHA. Resolver via API absorve essa diferença sem exigir uma imagem customizada.