Vous êtes passé du HTTP brut à un vrai navigateur. Empreinte TLS : authentiquement celle de Chrome. IP : résidentielle. Et le site sait quand même. En 2026, la détection des navigateurs headless a largement dépassé navigator.webdriver — la génération actuelle de contrôles détecte le protocole d'automatisation lui-même, et la plupart des plugins « stealth » corrigent les signaux d'hier tout en exhibant bruyamment ceux d'aujourd'hui.
Cet article cartographie ce que les sites vérifient réellement aujourd'hui, à peu près dans l'ordre où ils le vérifient, et ce qui aide véritablement à chaque couche. Il complète la trilogie de la détection avec le fingerprinting TLS JA3/JA4 (couche réseau) et la réputation d'IP & ASN (couche adresse) — voici la couche navigateur.
navigator.webdriver — la spécification exige qu'il soit true sous automatisation. Les configurations stealth de Playwright/Puppeteer le masquent ; le --disable-blink-features=AutomationControlled de Chrome le gère nativement. Les sites le vérifient encore parce que c'est gratuit et que ça attrape les bots naïfs.HeadlessChrome/ dans le User-Agent. Corrigé par le nouveau mode headless (headless=new), qui utilise le vrai chemin du binaire Chrome.navigator.plugins, pas de chrome.runtime, outerWidth à 0×0. L'ancien mode headless échouait à tout cela ; le nouveau headless passe pour l'essentiel.Si une cible ne vérifie que le Niveau 1, un Playwright moderne avec headless=new passe tranquillement. Les cibles sérieuses sont passées à autre chose il y a des années.
Playwright et Puppeteer pilotent Chrome via le Chrome DevTools Protocol, et CDP laisse des effets de bord d'exécution que le JavaScript de la page peut observer :
// The classic CDP leak: serialization callbacks
const err = new Error();
Object.defineProperty(err, "stack", {
get() {
// This getter fires DURING console serialization -
// which only happens when DevTools/CDP is attached
window.__cdp_detected = true;
},
});
console.debug(err);
Les variantes de ceci — effets de bord de getter pendant la sérialisation console, anomalies de timing dans Runtime.evaluate, le comportement de toString() sur des fonctions natives patchées — sont dans chaque bundle anti-bot commercial. Le point crucial : ceci détecte le protocole, pas le mode headless. Un Chrome avec interface, patché stealth et d'apparence parfaitement humaine échoue quand même s'il est piloté via CDP.
Ce qui aide :
Au-delà des artefacts, les systèmes modernes vérifient si l'histoire de votre navigateur est cohérente en interne :
| Contrôle | Indice de bot |
|---|---|
| Hash de rendu Canvas / WebGL | rendu logiciel SwiftShader/llvmpipe = GPU serveur ; ou un hash partagé par 10 000 « utilisateurs différents » |
| Polices & codecs | jeu de polices de serveur Linux sous un User-Agent Windows |
| Fuseau horaire × locale × géo IP | Intl dit UTC, l'IP dit Texas, Accept-Language dit de-DE |
| Métriques d'écran | 1920×1080 sans aucune barre des tâches, devicePixelRatio exactement 1, fenêtre jamais redimensionnée |
| Concurrence matérielle / mémoire | 96 cœurs annoncés à une page prétendant être un téléphone |
| Micro-signaux comportementaux | aucune entropie de souris, remplissages de formulaire instantanés, défilements par pas identiques de 100px |
C'est là que le stealth fait-maison meurt : chaque patch que vous appliquez doit s'accorder avec tous les autres signaux. Un UA Windows usurpé sur un conteneur Linux contredit simultanément les polices, les chaînes GPU et les empreintes TCP. La cohérence l'emporte sur la perfection — un Chrome Linux avec interface honnête obtient un meilleur score qu'un « Windows » mal usurpé.
Rien de tout cela ne compte si la couche adresse est grillée : un navigateur impeccable sur un ASN de centre de données reste « un navigateur impeccable dans un centre de données » — et les seuils de détection headless sont adaptatifs à la réputation de l'IP. Le même navigateur reçoit davantage de défis JavaScript depuis une IP marquée. Les exits résidentiels abaissent effectivement la surveillance que la couche navigateur doit survivre :
browser = p.chromium.launch(
headless=False, # headed survives more checks
args=["--disable-blink-features=AutomationControlled"],
proxy={
"server": "us.jibaoproxy.com:913",
"username": "USERNAME", "password": "PASSWORD",
},
)
toString() non patché est lui-même un signal. Moins de patches, mais cohérents, l'emportent.Des IP résidentielles qui concordent avec l'histoire que raconte votre empreinte — 500 Mo de trafic gratuit, sans carte requise.
Démarrer l'essai gratuitLes nouveaux utilisateurs reçoivent 500 Mo à l'inscription, plus un bonus sur la première recharge. Offre à durée limitée.