Come funziona davvero il rilevamento di Puppeteer: 12 segnali che tradiscono i bot
I framework di automazione ereditano il fingerprint di un browser reale ma lo modificano in decine di modi osservabili. Una guida sul campo ai 12 segnali JavaScript, di rete e comportamentali che tradiscono Puppeteer e Playwright.
I framework di automazione controllano browser reali, il che significa che ne ereditano il fingerprint. Un Chrome guidato da Puppeteer ha lo stesso user agent, la stessa versione di Chrome e la stessa build Chromium del Chrome di un essere umano. In superficie, sembrano identici.
Ma i framework di automazione modificano il browser in decine di modi sottili — la maggior parte dei quali osservabile da JavaScript. Il rilevamento non è magia. È una checklist.
1. Il flag navigator.webdriver
Il segnale più semplice e più famoso. Quando Chrome gira sotto un framework di automazione, navigator.webdriver restituisce true. Fa parte della specifica W3C WebDriver e Chromium lo implementa di default.
Ogni progetto di automazione serio lo corregge in pochi secondi. Puppeteer-extra-stealth sovrascrive il getter. Gli utenti di Playwright iniettano script Object.defineProperty sui nuovi documenti. Il flag è banale da nascondere.
Ma il fatto stesso che sia banale da nascondere è un segnale. Un Chrome legittimo non ha bisogno di nascondere nulla. Quando una pagina controlla Object.getOwnPropertyDescriptor(Navigator.prototype, 'webdriver') e il descrittore appare diverso da quello di un browser nativo — l'automazione viene scoperta anche dopo che il flag è stato corretto.
2. L'anomalia dell'oggetto chrome
Il Chrome legittimo espone un oggetto globale window.chrome con una struttura consistente — chrome.runtime, chrome.loadTimes, chrome.csi. Chrome headless e le configurazioni Puppeteer più vecchie omettono del tutto questo oggetto o ne espongono una versione ridotta.
I plugin stealth ricreano l'oggetto, ma la ricostruzione è imperfetta. chrome.runtime.onConnect potrebbe essere presente senza chrome.runtime.PlatformOs. Le firme delle funzioni potrebbero restituire oggetti invece di undefined. Ogni discrepanza è un segnale positivo.
3. Incoerenze della Permissions API
I browser reali restituiscono risultati coerenti quando interrogati tramite la Permissions API. navigator.permissions.query({name: 'notifications'}) dovrebbe restituire 'default' se l'utente non ha esplicitamente concesso o negato il permesso.
Chrome headless restituisce 'denied' di default, perché non c'è una UI in cui mostrare il prompt di autorizzazione. I framework di automazione lo correggono — ma spesso in modo sbagliato. Un indizio comune: interrogare un permesso insolito come 'clipboard-read' restituisce un risultato che non corrisponde a ciò che fa il vero Chrome nella versione corrente.
4. Gli array di plugin e tipi MIME
navigator.plugins in un browser reale restituisce un PluginArray con voci come il visualizzatore PDF e il plugin PDF di Chromium. In modalità headless, questo array è vuoto.
I plugin stealth aggiungono voci finte, ma le voci hanno spesso proprietà sbagliate — length mancante, campi di descrizione errati, oppure oggetti plugin che non si comportano come istanze di Plugin quando testati con instanceof.
5. Discrepanze di lingua e locale
navigator.language e navigator.languages dovrebbero corrispondere all'header HTTP Accept-Language. Dovrebbero anche essere coerenti con il fuso orario riportato da Intl.DateTimeFormat().resolvedOptions().timeZone.
Un bot che dichiara di trovarsi a Kyiv (fuso orario Europe/Kyiv) ma invia Accept-Language: en-US,en;q=0.9 e riporta navigator.language === 'en-US' è possibile ma statisticamente insolito. Combinato con un IP di un datacenter di Francoforte, il quadro diventa chiaro.
6. Le stringhe del renderer WebGL
WebGL2RenderingContext.getParameter(WebGLDebugRendererInfo.UNMASKED_RENDERER_WEBGL) restituisce vendor e modello della GPU. Su una macchina reale, potrebbe essere:
ANGLE (Intel, Intel(R) UHD Graphics 620 Direct3D11 vs_5_0 ps_5_0)
Chrome headless in esecuzione in un container restituisce spesso:
ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero)), SwiftShader driver)
Qualsiasi stringa GPU contenente SwiftShader o Subzero è un forte segnale di bot.
7. Anomalie di schermo e viewport
Una finestra di browser reale ha window.outerWidth e window.outerHeight che tengono conto del chrome del browser — barra degli indirizzi, schede, barra dei preferiti. La differenza tra dimensioni esterne e interne è tipicamente di 80–140 pixel in verticale.
I browser headless hanno spesso outerHeight === innerHeight perché non c'è alcun chrome da mostrare. Puppeteer con headless: false lo corregge, ma molte configurazioni di automazione girano ancora con dimensioni uguali.
8. Enumerazione dei font
Gli utenti reali hanno centinaia di font installati, che variano per OS, lingua e applicazioni installate. Windows 11 con Office installato ha 500+ font. I container di automazione con Alpine Linux ne hanno 30.
L'enumerazione dei font tramite document.fonts.check() o il rendering su canvas fuori schermo lo rivela immediatamente. Un browser che si dichiara Chrome su Windows 11 ma espone solo 30 font è quasi certamente automazione.
9. Entropia dei movimenti del mouse
Gli utenti reali muovono il mouse con jitter. Le curve sono non lineari. La velocità varia. Tra due clic ci sono tipicamente centinaia di eventi mousemove.
I framework di automazione sintetizzano il movimento — con linee rette verso le coordinate, oppure con curve programmate prive dei microtremori di una mano umana. Anche le simulazioni con curve di Bézier tendono ad avere profili di accelerazione sospettosamente lisci.
I sistemi di rilevamento dei bot raccolgono le tracce di movimento e le valutano rispetto a un modello del movimento umano. Le tracce con velocità troppo costante, curve troppo pulite o microeventi mancanti vengono segnalate.
10. Firme temporali
I framework di automazione eseguono JavaScript con caratteristiche temporali diverse dai browser guidati dall'utente. Le tempistiche di performance.now() tra gli eventi, in particolare tra pointerdown e pointerup, seguono una distribuzione diversa.
I tempi reali di pressione di un clic sono di 50–150ms con alta varianza. Il tempo di pressione predefinito di Puppeteer è un valore fisso — spesso 30ms o 100ms — con varianza quasi nulla. Cento clic con tempi di pressione identici sono automazione conclamata.
11. Discrepanza del fingerprint TLS
Questo segnale bypassa completamente JavaScript. Quando Puppeteer si connette, usa lo stack TLS di Chromium — lo stesso di un vero Chrome. Ma gli scraper basati su undici o axios che fingono di essere Chrome tramite spoofing dello user agent usano lo stack TLS di Node.js, che ha una firma Client Hello (hash JA4) nettamente diversa.
Una richiesta che dichiara User-Agent: Chrome/124.0.6367.60 ma presenta un fingerprint TLS di Node.js è la conferma immediata di un client non-browser.
12. Leakage del protocollo CDP
Puppeteer e Playwright comunicano con Chrome tramite il Chrome DevTools Protocol. In alcune configurazioni, questo protocollo lascia artefatti rilevabili — proprietà extra sull'oggetto Runtime, formati di Error.stack alterati o specifici output di console.debug che compaiono solo quando è collegato un client CDP.
Il comando Runtime.enable, in particolare, cambia il modo in cui vengono renderizzate le stack trace. I bot che non provocano mai errori evitano questo segnale, ma qualsiasi flusso di automazione che incontra un'eccezione (e la maggior parte lo fa) lascia impronte.
Stratificare questi segnali
Nessun segnale da solo è la prova dell'automazione. Un utente reale con un driver GPU insolito potrebbe restituire una stringa WebGL strana. Un utente attento alla privacy potrebbe avere navigator.plugins modificato.
La forza del rilevamento moderno viene dalla combinazione dei segnali — 12 positivi deboli sono più forti di un positivo forte, perché un utente reale raramente fa scattare più di 2–3 anomalie contemporaneamente.
I sistemi di rilevamento assegnano a ogni segnale un peso e calcolano un punteggio. Sopra la soglia, il visitatore viene trattato come automazione. La soglia esatta e i pesi sono le parti più difficili da azzeccare — troppo severi, e gli utenti legittimi vengono bloccati; troppo permissivi, e i bot sofisticati passano.
I segnali in sé sono la parte facile. Quasi ogni framework di automazione lascia trapelare tutti e 12. La vera ingegneria sta nel decidere quali combinazioni contano, quali sono falsi positivi e come aggiornare il modello man mano che i framework di automazione si adattano.