← Tous les articles

Pourquoi Votre Navigateur Playwright Reçoit des Challenges et Pas Votre Vrai Chrome

Vous lancez Playwright sur une page et tombez sur un écran “Just a moment…”. Il tourne en boucle. Parfois une case à cocher apparaît. Parfois la page se recharge et revient au même challenge. Vous finissez par abandonner, vous ouvrez exactement la même URL dans le Chrome que vous utilisez tous les jours, sur le même portable et le même Wi-Fi, et la page s’affiche en une seconde. Aucune case à cocher.

Même IP. Même machine. Même URL. Seul le navigateur a changé. C’est là l’indice : la plupart du temps, le système anti-bot ne réagit pas à votre réseau, mais au navigateur que votre outil d’automatisation a lancé.

À Quoi Ressemble le Problème

Ce problème suit un schéma reconnaissable :

  • Une page de challenge qui ne se résout jamais d’elle-même dans le navigateur automatisé, mais qui passe de façon passive (ou n’apparaît même pas) dans votre Chrome normal.
  • Un challenge avec case à cocher qui, une fois cliqué par votre script, se transforme en un autre challenge ou en une page “access denied” définitive.
  • Un scraper qui fonctionne en mode fenêtré sur votre portable et échoue en headless sur un serveur.
  • Une configuration qui a marché pendant des mois et s’est mise à échouer après une mise à jour du framework, sans aucune modification de code de votre côté.

Si votre Chrome normal est lui aussi bloqué depuis le même réseau, vous pouvez arrêter votre lecture ici : votre problème vient de la réputation de l’IP ou du rythme des requêtes, pas du navigateur. La suite de cet article traite du cas où c’est le navigateur qui fait la différence.

Votre Navigateur d’Automatisation N’est Pas le Chrome Que Vous Utilisez

Quand vous lancez pip install playwright puis playwright install, vous n’obtenez pas le Google Chrome de votre bureau. Vous obtenez un navigateur conçu pour l’automatisation :

  • Playwright 1.57 et versions ultérieures tourne sur des builds Chrome for Testing plutôt que sur Chromium open source. Les exécutions fenêtrées utilisent Chrome for Testing, les exécutions headless utilisent chrome-headless-shell par défaut.
  • Puppeteer v20 et versions ultérieures télécharge Chrome for Testing, et depuis la v21.6 il télécharge aussi chrome-headless-shell pour les exécutions headless.

Google décrit Chrome for Testing comme une variante “créée uniquement pour l’automatisation du navigateur et les tests”. Elle est figée sur une version et ne se met pas à jour toute seule, exactement ce dont une suite de tests a besoin.

chrome-headless-shell, c’est une autre histoire. Il s’agit de l’ancienne implémentation headless, que la documentation de Chrome décrit comme “une enveloppe légère autour du module //content de Chromium”, et qui s’identifie historiquement avec HeadlessChrome dans son user agent. Elle est rapide et parfaite pour les captures d’écran. Ce n’est pas le navigateur complet.

Votre Chrome de bureau est le build officiel de marque qu’utilise une très grande partie des vrais utilisateurs, et il se tient à jour automatiquement. Pour un système anti-bot qui évalue à quel point un visiteur paraît normal, l’écart entre “le navigateur que tout le monde utilise” et “un build fait pour l’automatisation” est un signal qui mérite d’être lu.

Pourquoi les Systèmes Anti-Bot Voient la Différence

Les challenges modernes ne sont pas des énigmes. Cloudflare explique que Turnstile exécute “une série de petits challenges JavaScript non interactifs pour recueillir des signaux sur le visiteur ou l’environnement du navigateur”, notamment le “sondage d’API web” et des challenges “pour détecter les particularités du navigateur et le comportement humain”. D’autres éditeurs procèdent de manière similaire. Voici où les builds d’automatisation se font remarquer :

Les flags d’automatisation

D’après MDN, Chrome passe navigator.webdriver à true lorsqu’il est lancé avec --enable-automation, --headless ou --remote-debugging-port=0. Les frameworks d’automatisation lancent Chrome avec un ensemble de switches par défaut, et --enable-automation en est un grand classique. C’est aussi lui qui affiche la barre “Chrome est contrôlé par un logiciel de test automatisé”.

Les différences de build et de fonctionnalités

La documentation de Playwright indique elle-même que “Chromium ne dispose pas de tous les codecs qu’intègrent Google Chrome ou Microsoft Edge”. Il manque à un headless shell des pans encore plus larges du vrai navigateur. Toute vérification qui demande “que sait réellement faire ce navigateur ?” et compare la réponse à “que prétend-il être ?” peut trouver ce genre d’écarts.

La dérive de version

Un build figé a la même version tous les jours, alors que la population réelle de Chrome avance toutes les quelques semaines. Quelques mois plus tard, votre scraper annonce une version de navigateur que de moins en moins de vrais visiteurs utilisent encore.

Le canal de contrôle

Les frameworks pilotent Chrome via le DevTools Protocol, et certaines des commandes qu’ils utilisent par défaut ont des effets de bord qu’un script de la page peut remarquer. Des outils comme Patchright existent précisément pour éviter les plus connus, comme la fuite Runtime.enable.

L’environnement autour du navigateur

Un profil neuf sans historique, un viewport fixe par défaut, des polices de serveur et un rendu logiciel dans un conteneur sans GPU finissent par s’additionner. Aucun de ces éléments n’est décisif à lui seul. Ensemble, ils dessinent un tableau qui ne ressemble pas à une personne devant son portable.

Pourquoi cliquer sur la case ne sert à rien

Quand la case apparaît, le système a déjà jugé votre environnement douteux. Un clic scripté n’est qu’une donnée de plus, mesurée dans ce même environnement suspect. C’est pourquoi cliquer mène souvent à un challenge plus dur ou à un blocage plutôt qu’à un passage. Si le challenge ne se résout pas de façon passive, corrigez d’abord l’environnement.

Comment le Diagnostiquer en Local

Ne devinez pas. Exécutez la même sonde dans chaque configuration de navigateur et comparez. Ce script lance le build fourni et votre Chrome installé, en mode fenêtré et en headless, et affiche ce qu’un script de la page peut voir :

from playwright.sync_api import sync_playwright

PROBE = """() => {
  const gl = document.createElement('canvas').getContext('webgl');
  const dbg = gl && gl.getExtension('WEBGL_debug_renderer_info');
  return {
    userAgent: navigator.userAgent,
    webdriver: navigator.webdriver,
    brands: navigator.userAgentData
      ? navigator.userAgentData.brands.map(b => `${b.brand} ${b.version}`)
      : null,
    h264: window.MediaSource
      ? MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E"')
      : null,
    webglRenderer: dbg ? gl.getParameter(dbg.UNMASKED_RENDERER_WEBGL) : null,
    viewport: `${innerWidth}x${innerHeight}`,
  };
}"""

def probe(channel, headless):
    with sync_playwright() as p:
        browser = p.chromium.launch(channel=channel, headless=headless)
        page = browser.new_page()
        page.goto("https://example.com")
        result = page.evaluate(PROBE)
        browser.close()
        return result

for channel in (None, "chrome"):
    for headless in (True, False):
        label = f"{channel or 'bundled'} / {'headless' if headless else 'headed'}"
        print(label)
        for key, value in probe(channel, headless).items():
            print(f"  {key}: {value}")

Chaque ligne qui diffère entre le build fourni et votre vrai Chrome est une information qu’un site peut lire lui aussi. Passez ensuite au vrai test : chargez la page protégée avec chacune des quatre configurations, depuis le même réseau. La combinaison qui passe vous indique quelle couche corriger. Si aucune ne passe, le navigateur n’est pas votre goulot d’étranglement.

Comment le Corriger

1. Utilisez le Chrome de marque

Les deux frameworks savent piloter le Chrome déjà installé sur votre machine :

browser = p.chromium.launch(channel="chrome", headless=False)
const browser = await puppeteer.launch({ channel: 'chrome', headless: false });

Sur une machine sans Chrome, playwright install chrome installe Google Chrome à l’emplacement par défaut du système (il remplace une installation existante, faites-le donc sur des serveurs, pas sur votre portable). En bonus, le Chrome de marque se met à jour tout seul sur les postes de travail, vous ne prenez donc plus de retard sur la population réelle.

2. Lancez-le en mode fenêtré, ou au moins avec le vrai headless

Le mode fenêtré reste l’option la plus authentique. Sur un serveur Linux, faites-le tourner dans un écran virtuel avec xvfb-run python scraper.py. Si vous devez rester en headless, utilisez channel="chrome" avec headless=True : depuis Playwright 1.49, le canal Chrome utilise le nouveau mode headless de Chrome, que la documentation qualifie de “vrai navigateur Chrome”, au lieu du headless shell.

3. Supprimez les flags d’automatisation les plus visibles

Retirez --enable-automation et désactivez la fonctionnalité Blink AutomationControlled. Cela élimine les vérifications les moins coûteuses. Cela ne vous rend pas invisible.

4. Gardez un profil persistant

Un profil neuf et vide à chaque exécution ressemble à un visiteur qui arrive pour la première fois, encore et encore. Un profil persistant conserve les cookies, le cache et le stockage du site d’une exécution à l’autre, comme votre propre navigateur. Associez-le aux idées de réutilisation des cookies de notre article sur la persistance de session.

5. Ne simulez pas ce que vous ne pouvez pas assumer

Définir un user agent personnalisé par-dessus un autre build de navigateur crée une incohérence entre ce que le navigateur affirme et ce qu’il sait faire. Laissez le vrai Chrome se présenter lui-même.

6. Laissez le challenge se résoudre tout seul

Attendez que le challenge disparaisse au lieu de cliquer dessus. Le tout assemblé :

from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeout

URL = "https://example.com/protected-page"

with sync_playwright() as p:
    context = p.chromium.launch_persistent_context(
        user_data_dir="./chrome-profile",
        channel="chrome",
        headless=False,
        no_viewport=True,
        ignore_default_args=["--enable-automation"],
        args=["--disable-blink-features=AutomationControlled"],
    )
    page = context.pages[0] if context.pages else context.new_page()
    page.goto(URL, wait_until="domcontentloaded")

    try:
        # Adaptez cette vérification au challenge que vous voyez réellement.
        page.wait_for_function(
            "() => !document.title.toLowerCase().includes('just a moment')",
            timeout=30_000,
        )
        html = page.content()
    except PlaywrightTimeout:
        html = None
        print("Challenge did not clear: check the environment, IP and request rate")

    context.close()

7. Envisagez un driver patché

Si vous voulez ces corrections déjà packagées, Patchright remplace directement Playwright et corrige des fuites connues comme Runtime.enable ainsi que les flags d’automatisation. Sa configuration recommandée est la même que ci-dessus : un contexte persistant, channel="chrome", le mode fenêtré, pas de viewport fixe et pas de user agent personnalisé. nodriver, le successeur d’undetected-chromedriver, suit une approche similaire en pilotant votre Chrome installé sans WebDriver.

Le Navigateur N’est Qu’une Couche

Corriger le navigateur supprime une raison de vous challenger. Pas les autres. Un vrai Chrome parfait qui envoie des milliers de requêtes par heure depuis une seule IP finira quand même par griller la réputation de cette IP, et un client HTTP brut garde le problème d’empreinte TLS quel que soit le navigateur utilisé pour obtenir les cookies. Pour la checklist complète sur l’une des protections les plus courantes, consultez comment scraper un site protégé par Cloudflare sans recevoir de 403. Et vérifiez toujours le contenu, pas le code de statut : une page de challenge peut arriver sous forme de réponse 200.

Respectez les conditions du site et gardez un rythme de requêtes raisonnable. La politesse reste aussi le moyen le moins cher de ne pas être bloqué.

FAQ

Chrome for Testing est-il identique à Google Chrome ?

Il est produit par le même processus de publication et se veut “aussi proche que possible du Chrome classique”, selon Google, mais c’est une variante à part, destinée uniquement à l’automatisation et aux tests. Il ne se met pas à jour tout seul et ce n’est pas le build qu’utilisent les visiteurs ordinaires.

Mettre navigator.webdriver à false rend-il Playwright indétectable ?

Non. Cela supprime une vérification peu coûteuse. Les différences de build, le canal de contrôle DevTools, l’environnement, votre IP et votre comportement restent visibles. Voyez-le comme une mesure d’hygiène, pas comme une solution.

Mon scraper doit-il cliquer automatiquement sur la case Turnstile ?

En général, pas en premier recours. La case apparaît parce que l’environnement semble déjà douteux, et un clic scripté dans ce même environnement change rarement le verdict. Ajustez la configuration du navigateur jusqu’à ce que le challenge se résolve de façon passive.

Puis-je faire tourner le Chrome de marque dans Docker ?

Oui. Installez Google Chrome dans l’image (playwright install chrome peut s’en charger) et lancez-le en mode fenêtré sous Xvfb, ou en headless avec channel="chrome" pour obtenir le nouveau mode headless plutôt que le headless shell.

Oubliez la Maintenance du Navigateur

Garder les builds de navigateur à jour, les profils actifs et les flags propres demande un vrai travail continu, et tout casse à chaque mise à jour d’un framework ou d’un système anti-bot. Si vous préférez consacrer ce temps aux données, ScrapeUnblocker rend la page dans un vrai navigateur pour vous et renvoie le HTML en une seule requête API. La documentation vous montre comment faire votre premier appel en quelques minutes.

Essayez ScrapeUnblocker gratuitement

Taux de réussite de plus de 95 % · à partir de 0,55 € pour 1 000 appels · 500 requêtes gratuites à l'inscription.

Essayer gratuitement → Voir les tarifs