Como a detecção de Puppeteer realmente funciona: 12 sinais que entregam os bots
Frameworks de automação herdam o fingerprint de um navegador real, mas o alteram de dezenas de formas observáveis. Um guia de campo dos 12 sinais de JavaScript, rede e comportamento que entregam Puppeteer e Playwright.
Frameworks de automação controlam navegadores reais, o que significa que herdam o fingerprint do navegador. Um Chrome conduzido pelo Puppeteer tem o mesmo user agent, a mesma versão do Chrome e o mesmo build de Chromium que o Chrome de um humano. Superficialmente, parecem idênticos.
Mas os frameworks de automação alteram o navegador de dezenas de formas sutis — a maioria delas observável a partir do JavaScript. A detecção não é mágica. É um checklist.
1. A flag navigator.webdriver
O sinal mais simples e mais famoso. Quando o Chrome roda sob um framework de automação, navigator.webdriver retorna true. Faz parte da especificação WebDriver do W3C, e o Chromium a implementa por padrão.
Todo projeto sério de automação corrige isso em segundos. O puppeteer-extra-stealth sobrescreve o getter. Usuários de Playwright injetam scripts de Object.defineProperty em novos documentos. A flag é trivial de esconder.
Mas o fato de que ela pode ser trivialmente escondida é, por si só, um sinal. Um Chrome legítimo não precisa esconder nada. Quando uma página verifica Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver') e o descriptor parece diferente do de um navegador nativo — a automação é flagrada mesmo depois de a flag ter sido corrigida.
2. A anomalia do objeto chrome
O Chrome legítimo expõe um objeto global window.chrome com estrutura substancial — chrome.runtime, chrome.loadTimes, chrome.csi. O Chrome headless e configurações mais antigas de Puppeteer ou omitem esse objeto por completo ou expõem uma versão reduzida.
Os plugins stealth recriam o objeto, mas a recriação é imperfeita. chrome.runtime.onConnect pode estar presente sem chrome.runtime.PlatformOs. Assinaturas de função podem retornar objetos em vez de undefined. Cada divergência é um sinal positivo.
3. Inconsistências na Permissions API
Navegadores reais retornam resultados coerentes quando consultados pela Permissions API. navigator.permissions.query({name: 'notifications'}) deve retornar 'default' se o usuário não concedeu nem negou a permissão explicitamente.
O Chrome headless retorna 'denied' por padrão, porque não há UI para exibir um prompt de permissão. Os frameworks de automação corrigem isso — mas frequentemente de forma incorreta. Um indício comum: consultar uma permissão incomum como 'clipboard-read' retorna um resultado que não corresponde ao que o Chrome real faz na versão atual.
4. Arrays de plugins e de tipos MIME
navigator.plugins em um navegador real retorna um PluginArray com entradas como o visualizador de PDF e o plugin de PDF do Chromium. No modo headless, esse array está vazio.
Os plugins stealth adicionam entradas falsas, mas as entradas frequentemente têm propriedades erradas — length ausente, campos de descrição incorretos ou objetos de plugin que não se comportam como instâncias de Plugin quando testados com instanceof.
5. Divergências de idioma e localidade
navigator.language e navigator.languages devem corresponder ao header HTTP Accept-Language. Também devem ser consistentes com o fuso horário reportado por Intl.DateTimeFormat().resolvedOptions().timeZone.
Um bot que afirma estar em Kyiv (fuso Europe/Kyiv), mas envia Accept-Language: en-US,en;q=0.9 e reporta navigator.language === 'en-US', é possível, mas estatisticamente incomum. Combinado com um IP de um datacenter em Frankfurt, o quadro fica claro.
6. Strings do renderer de WebGL
WebGL2RenderingContext.getParameter(WebGLDebugRendererInfo.UNMASKED_RENDERER_WEBGL) retorna o fabricante e o modelo da GPU. Em uma máquina real, isso pode ser:
ANGLE (Intel, Intel(R) UHD Graphics 620 Direct3D11 vs_5_0 ps_5_0)
O Chrome headless rodando em um contêiner frequentemente retorna:
ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero)), SwiftShader driver)
Qualquer string de GPU contendo SwiftShader ou Subzero é um forte sinal de bot.
7. Anomalias de tela e viewport
Uma janela de navegador real tem window.outerWidth e window.outerHeight que incluem a interface do navegador — barra de endereço, abas, barra de favoritos. A diferença entre as dimensões externas e internas é tipicamente de 80–140 pixels na vertical.
Navegadores headless frequentemente têm outerHeight === innerHeight, porque não há interface a exibir. O Puppeteer com headless: false corrige isso, mas muitas configurações de automação ainda rodam com dimensões iguais.
8. Enumeração de fontes
Usuários reais têm centenas de fontes instaladas, variando conforme o SO, o idioma e os aplicativos instalados. Um Windows 11 com Office instalado tem mais de 500 fontes. Contêineres de automação rodando Alpine Linux têm 30.
A enumeração de fontes via document.fonts.check() ou renderização em canvas fora da tela revela isso imediatamente. Um navegador que se apresenta como Chrome no Windows 11, mas expõe apenas 30 fontes, é quase certamente automação.
9. Entropia do movimento do mouse
Usuários reais movem o mouse com tremores. As curvas são não lineares. A velocidade varia. Entre dois cliques, há tipicamente centenas de eventos mousemove.
Os frameworks de automação sintetizam o movimento — ou com linhas retas até as coordenadas, ou com curvas programadas que carecem dos microtremores de uma mão humana. Até as simulações com curvas de Bézier tendem a ter perfis de aceleração suspeitosamente suaves.
Os sistemas de detecção de bots coletam os traçados de movimento e os pontuam contra um modelo de movimento humano. Traçados com velocidade consistente demais, curvas limpas demais ou microeventos ausentes são sinalizados.
10. Assinaturas de temporização
Os frameworks de automação executam JavaScript com características de temporização diferentes das de navegadores conduzidos por usuários. A temporização de performance.now() entre eventos, especialmente entre pointerdown e pointerup, segue outra distribuição.
Os tempos reais de clique pressionado são de 50–150ms, com alta variância. O tempo de clique padrão do Puppeteer é um valor fixo — geralmente 30ms ou 100ms — com quase nenhuma variância. Cem cliques com tempos de pressão idênticos é automação definitiva.
11. Divergência da impressão digital de TLS
Este contorna o JavaScript por completo. Quando o Puppeteer se conecta, ele usa a pilha TLS do Chromium — a mesma de um Chrome real. Mas scrapers baseados em undici ou axios que fingem ser o Chrome via falsificação de user agent usam a pilha TLS do Node.js, que tem uma assinatura de Client Hello (hash JA4) claramente diferente.
Uma requisição que alega User-Agent: Chrome/124.0.6367.60 mas apresenta uma impressão digital de TLS do Node.js é confirmação instantânea de um cliente que não é navegador.
12. Vazamento do protocolo CDP
Puppeteer e Playwright se comunicam com o Chrome via Chrome DevTools Protocol. Em algumas configurações, esse protocolo deixa artefatos detectáveis — propriedades extras no objeto Runtime, formatos alterados de Error.stack ou saídas específicas de console.debug que só aparecem quando um cliente CDP está anexado.
O comando Runtime.enable, em particular, muda como os stack traces são renderizados. Bots que nunca disparam erros evitam esse sinal, mas qualquer fluxo de automação que encontre uma exceção (e a maioria encontra) deixa fingerprints.
Combinando esses sinais em camadas
Nenhum sinal isolado é prova de automação. Um usuário real com um driver de GPU incomum pode retornar uma string de WebGL estranha. Um usuário focado em privacidade pode ter modificado navigator.plugins.
A força da detecção moderna vem da combinação de sinais — 12 positivos fracos são mais fortes que um positivo forte, porque um usuário real raramente dispara mais de 2–3 anomalias simultaneamente.
Os sistemas de detecção atribuem um peso a cada sinal e calculam um score. Acima do limiar, o visitante é tratado como automação. O limiar exato e os pesos são as partes mais difíceis de acertar — rígido demais e usuários legítimos são bloqueados; permissivo demais e bots sofisticados escapam.
Os sinais em si são a parte fácil. Praticamente todo framework de automação vaza os 12. A engenharia de verdade está em decidir quais combinações importam, quais são falsos positivos e como atualizar o modelo à medida que os frameworks de automação se adaptam.