Scraping avec nodriver : Guide Complet Proxy & Anti-Détection

Apprenez à remplacer undetected-chromedriver par nodriver, le successeur async basé sur CDP, et à intégrer des proxies résidentiels ProxyHat pour contourner Cloudflare et Imperva sans vous faire bloquer.

Scraping With nodriver: A Practical Guide to Undetected Async Browser Automation
Dans cet article

Si vous arrivez d'undetected-chromedriver et que vos scripts se font bloquer par Cloudflare ou Imperva malgré tous vos efforts, scraping avec nodriver est la suite logique. nodriver (anciennement undetected_chromedriver v2) supprime entièrement la couche Selenium/WebDriver et communique directement avec Chrome via le Chrome DevTools Protocol (CDP) sur websockets. Résultat : plus de navigator.webdriver = true, plus de fuites CDP détectables, et un navigateur qui ressemble à un utilisateur réel.

Rappel légal : Le scraping doit se limiter à des données publiques que vous êtes autorisé à collecter. Respectez le robots.txt, les conditions d'utilisation des sites, et tenez compte du CFAA (Computer Fraud and Abuse Act) aux États-Unis et du RGPD en Europe. Cet article ne couvre que des techniques pour accéder à des données publiquement visibles dans le cadre d'une automatisation autorisée.

Pourquoi le Scraping avec nodriver Change la Donne

Le problème fondamental avec Selenium et WebDriver est que le protocole W3C WebDriver injecte des marqueurs visibles : la propriété navigator.webdriver est fixée à true, des variables CDP spécifiques sont exposées, et les en-têtes HTTP contiennent parfois des indices comme HeadlessChrome. Les systèmes anti-bot modernes comme Cloudflare Bot Management et Imperva Incapsula vérifient ces empreintes en quelques millisecondes. Selon les spécifications du Chrome DevTools Protocol, chaque commande CDP passe par une connexion WebSocket directe, sans intermédiaire WebDriver — c'est exactement ce que nodriver exploite.

nodriver, créé par ultrafunkamsterdam (le même auteur qu'undetected-chromedriver), supprime cette couche d'émulation. Le navigateur lancé par nodriver est un Chrome réel, piloté en asynchrone via asyncio et le CDP. Il n'y a aucun driver binaire tiers à maintenir, aucune version Selenium à synchroniser avec votre version Chrome. Le navigateur est patché à la volée pour retirer les marqueurs les plus évidents.

Différences Clés avec undetected-chromedriver

Critère undetected-chromedriver nodriver
Protocole WebDriver (W3C) CDP over WebSocket
Modèle d'exécution Synchrone (threading) Asynchrone (asyncio)
navigator.webdriver Patché après démarrage Supprimé à la source
Fuites CDP Runtime Présentes (Runtime.enable détectable) Minimisées
Dépendance externe ChromeDriver + Selenium Aucune (Chrome natif)
Concurrence Difficile (1 thread = 1 browser) Naturelle (N tabs async)

L'API Idiomatique de nodriver

nodriver est entièrement async. Toute l'API repose sur asyncio — pas de time.sleep(), pas de WebDriverWait. Vous utilisez await partout, et les événements CDP remplacent les polling loops.

Démarrage et Objets de Base

Le point d'entrée est nodriver.start(), qui retourne un objet Browser. À partir du browser, vous récupérez un Tab — l'objet principal pour interagir avec une page. Contrairement à Selenium où vous manipulez un WebDriver unique, nodriver vous donne un browser qui peut gérer plusieurs tabs concurrents.

import nodriver as uc
import asyncio

async def main():
    browser = await uc.start(
        headless=False,
        browser_args=["--disable-blink-features=AutomationControlled"]
    )
    tab = await browser.get("https://example.com")
    # select() attend qu'un sélecteur apparaît — pas de WebDriverWait
    element = await tab.select("h1", timeout=10)
    print(await element.get_attribute("textContent"))
    browser.stop()

if __name__ == "__main__":
    asyncio.run(main())

Événements CDP au Lieu de Waits

Au lieu de poller le DOM, nodriver expose des hooks d'événements CDP. Vous pouvez écouter Network.requestWillBeSent, Page.frameNavigated, ou Runtime.executionContextCreated pour réagir au moment exact où quelque chose se produit — sans délai artificiel.

async def on_request(event):
    if event.request.url.endswith("/api/data"):
        print(f"Intercepted: {event.request.url}")

tab.add_handler(uc.cdp.network.RequestWillBeSent, on_request)
await tab.get("https://target.com")

C'est une différence fondamentale par rapport à l'approche Selenium : vous ne devinez pas quand la page est prête, vous écoutez les signaux du navigateur.

Configurer un Proxy nodriver avec ProxyHat

nodriver n'a pas de système de rotation de proxy intégré. Vous passez le proxy via l'argument Chrome --proxy-server= au démarrage. C'est idiomatic et transparent : nodriver ne fait qu'exposer les arguments du navigateur, à vous de construire l'URL du proxy.

Le défi avec Chrome est que --proxy-server ne supporte pas l'authentification par nom d'utilisateur/mot de passe directement. Vous devez soit utiliser un proxy sans authentification, soit injecter un handler CDP Fetch.authRequired pour fournir les credentials. nodriver rend cela naturel via son système de handlers.

Proxy Résidentiel : Pourquoi c'est Essentiel

Un navigateur headful non détectable ne sert à rien si votre IP est un datacenter connu (AWS, OVH, DigitalOcean). Les WAF croisent l'empreinte du navigateur avec la réputation de l'IP. Une IP résidentielle attribue à votre trafic une légitimité que les IP datacenter ne peuvent pas offrir. ProxyHat propose des proxies résidenti avec rotation par requête ou sessions sticky — voir notre page de tarification pour les détails.

Construction de l'URL Proxy

ProxyHat utilise un format où le pays et la session sont encodés dans le nom d'utilisateur :

def build_proxyhat_url(country="US", session=None, socks5=False):
    """Construit l'URL proxy ProxyHat pour nodriver."""
    user_parts = [f"country-{country}"]
    if session:
        user_parts.append(f"session-{session}")
    username = "user-" + "-".join(user_parts)
    password = "YOUR_PASSWORD"
    host = "gate.proxyhat.com"
    if socks5:
        return f"socks5://{username}:{password}@{host}:1080"
    return f"http://{username}:{password}@{host}:8080"

# Exemple : session sticky US
proxy_url = build_proxyhat_url(country="US", session="abc123")
# → http://user-country-US-session-abc123:YOUR_PASSWORD@gate.proxyhat.com:8080

Exemple Complet : Scraper une Page Protégée

Voici un script complet qui lance nodriver via un endpoint résidentiel ProxyHat US avec session sticky, gère l'authentification proxy via CDP, navigue sur une page protégée par Cloudflare, et extrait du JSON depuis un endpoint API intercepté.

import nodriver as uc
import asyncio
import json

PROXY_HOST = "gate.proxyhat.com"
PROXY_PORT = 8080
PROXY_USER = "user-country-US-session-abc123"
PROXY_PASS = "YOUR_PASSWORD"

async def main():
    # Lancement avec proxy via --proxy-server
    browser = await uc.start(
        headless=False,
        browser_args=[
            f"--proxy-server=http://{PROXY_HOST}:{PROXY_PORT}",
            "--disable-blink-features=AutomationControlled",
            "--no-sandbox",
        ],
    )
    tab = await browser.get("https://example.com")

    # Handler CDP pour l'authentification proxy
    async def auth_handler(event):
        await tab.send(uc.cdp.fetch.continueWithAuth(
            request_id=event.request_id,
            auth_challenge_response=uc.cdp.fetch.AuthChallengeResponse(
                response="ProvideCredentials",
                username=PROXY_USER,
                password=PROXY_PASS,
            )
        ))

    # Activer le domaine Fetch pour intercepter l'auth proxy
    await tab.send(uc.cdp.fetch.enable(
        handle_auth_requests=True,
        patterns=[{"requestStage": "Request"}]
    ))
    tab.add_handler(uc.cdp.fetch.AuthRequired, auth_handler)

    # Intercepter les réponses API JSON
    api_responses = []
    async def on_response(event):
        url = event.response.url
        if "/api/" in url and event.response.mime_type == "application/json":
            body = await tab.send(uc.cdp.network.getResponseBody(
                request_id=event.request_id
            ))
            api_responses.append(json.loads(body[0]))

    tab.add_handler(uc.cdp.network.ResponseReceived, on_response)

    # Naviguer vers la page cible
    await tab.get("https://target-protected-site.com")
    # Attendre que le contenu se charge
    content = await tab.select(".data-container", timeout=30)
    text = await content.get_attribute("textContent")
    print(f"Contenu extrait : {text[:200]}")
    print(f"Réponses API interceptées : {len(api_responses)}")

    browser.stop()

if __name__ == "__main__":
    asyncio.run(main())

Ce pattern — handler d'auth proxy + interception de réponses réseau + select() pour le DOM — est l'approche idiomatic pour le nodriver web scraping. Vous pouvez consulter la documentation ProxyHat pour plus de détails sur les paramètres de geo-targeting disponibles.

Scaling : Flotte Headless et Concurrence

nodriver brille particulièrement en concurrence grâce à asyncio. Vous pouvez lancer plusieurs tabs dans un seul browser, ou plusieurs browsers avec chacun leur proxy. Voici les deux patterns principaux.

Pattern 1 : Multi-Tabs dans un Browser Unique

Idéal pour scraper plusieurs pages du même site avec la même IP (session sticky). Un seul browser, N tabs concurrents.

async def scrape_page(browser, url, session_id):
    tab = await browser.get(url)
    element = await tab.select(".result", timeout=15)
    data = await element.get_attribute("textContent")
    await tab.close()
    return {"url": url, "data": data, "session": session_id}

async def main():
    browser = await uc.start(
        headless=True,
        browser_args=[
            f"--proxy-server=http://gate.proxyhat.com:8080",
            "--disable-blink-features=AutomationControlled",
        ]
    )
    # ... configurer le handler d'auth comme ci-dessus ...

    urls = ["https://example.com/page1", "https://example.com/page2", "https://example.com/page3"]
    tasks = [scrape_page(browser, url, "abc123") for url in urls]
    results = await asyncio.gather(*tasks, return_exceptions=True)
    browser.stop()

Pattern 2 : Multi-Browsers avec Proxies Différents

Pour répartir la charge sur plusieurs IP, lancez un browser par proxy. Chaque browser a son propre contexte, son IP, et sa session. C'est le pattern recommandé pour le nodriver async à grande échelle.

async def create_browser_with_proxy(country, session_id):
    """Crée un browser nodriver avec un proxy ProxyHat dédié."""
    proxy_user = f"user-country-{country}-session-{session_id}"
    browser = await uc.start(
        headless=True,
        browser_args=[
            f"--proxy-server=http://gate.proxyhat.com:8080",
            "--disable-blink-features=AutomationControlled",
            "--no-sandbox",
            "--disable-gpu",
        ],
    )
    # Handler d'auth proxy pour ce browser
    tab = await browser.get("about:blank")
    async def auth_handler(event):
        await tab.send(uc.cdp.fetch.continueWithAuth(
            request_id=event.request_id,
            auth_challenge_response=uc.cdp.fetch.AuthChallengeResponse(
                response="ProvideCredentials",
                username=proxy_user,
                password="YOUR_PASSWORD",
            )
        ))
    await tab.send(uc.cdp.fetch.enable(handle_auth_requests=True))
    tab.add_handler(uc.cdp.fetch.AuthRequired, auth_handler)
    return browser

async def main():
    configs = [
        ("US", "sess-001"),
        ("DE", "sess-002"),
        ("GB", "sess-003"),
    ]
    browsers = await asyncio.gather(
        *[create_browser_with_proxy(c, s) for c, s in configs]
    )
    # Chaque browser a sa propre IP résidentielle
    # Lancer des tâches sur chaque browser en parallèle
    browser.stop()  # arrêt propre

Conteneurisation Docker

Pour déployer une flotte headless, Docker est le standard. Utilisez une image Chrome headless avec --no-sandbox et --disable-gpu. Chaque conteneur exécute un worker nodriver avec son propre proxy ProxyHat. Vous pouvez scaler horizontalement avec Docker Compose ou Kubernetes.

Avec ProxyHat, vous pouvez attribuer un proxy différent par conteneur via les sessions sticky. Consultez nos locations disponibles pour cibler les bons pays. Pour des workloads de SERP tracking à grande échelle, voir notre cas d'usage SERP tracking.

Arrêt Propre et Gestion des Erreurs

Toujours gérer l'arrêt propre pour éviter les processus Chrome zombies :

async def main():
    browser = None
    try:
        browser = await uc.start(headless=True, browser_args=[...])
        # ... scraping logic ...
    except Exception as e:
        print(f"Erreur : {e}")
    finally:
        if browser:
            browser.stop()  # tue les processus Chrome enfants

if __name__ == "__main__":
    asyncio.run(main())

Quand NE PAS Utiliser un Navigateur Complet

nodriver est puissant, mais un navigateur headless consomme ~300-500 MB de RAM par instance et ajoute 200-500ms de latence par requête. Si votre cible n'a pas de protection anti-bot avancée (pas de Cloudflare, pas de challenge JS), un simple client HTTP avec curl_cffi et un proxy résidentiel ProxyHat est 10x plus rapide et 50x moins gourmand en ressources.

Approche RAM par instance Latence par requête Contourne Cloudflare Coût
HTTP + curl_cffi + proxy ~20 MB ~50ms Partiel (TLS fingerprint) Bas
nodriver headless + proxy ~300-500 MB ~200-500ms Oui (JS + fingerprint) Moyen
nodriver headful + proxy ~500-800 MB ~300-700ms Oui (max stealth) Élevé

La règle : commencez simple. Essayez d'abord curl_cffi avec un proxy résidentiel ProxyHat. Si vous êtes bloqué par un challenge JS ou un CAPTCHA, passez à nodriver. Pour des cas d'usage de web scraping à grande échelle, consultez notre guide web scraping.

Erreurs Courantes et Edge Cases

1. Oublier le Handler d'Authentification Proxy

Chrome ne supporte pas l'authentification proxy via --proxy-server. Sans le handler Fetch.authRequired, chaque requête échouera avec une 407 Proxy Authentication Required. C'est l'erreur la plus fréquente chez les nouveaux utilisateurs de nodriver proxy.

2. Utiliser time.sleep() au Lieu d'await

nodriver est async. time.sleep(5) bloque l'event loop entier — aucun tab ne progresse pendant ce temps. Utilisez await asyncio.sleep(5) ou, mieux, await tab.select(selector, timeout=10) qui attend activement l'élément.

3. Ne Pas Gérer les Déconnexions WebSocket

La connexion CDP peut se rompre si le navigateur crash ou si le proxy coupe. Encapsulez vos appels CDP dans des try/except et prévoyez un retry avec un nouveau browser si nécessaire.

4. Trop de Tabs Concurrents dans un Browser

Au-delà de 5-8 tabs concurrents, un seul browser Chrome devient instable. Préférez plusieurs browsers avec peu de tabs chacun plutôt qu'un browser avec 20 tabs.

Points Clés à Retenir

  • nodriver supprime WebDriver : fini Selenium, fini ChromeDriver — communication CDP directe en async.
  • Proxy via --proxy-server + handler CDP Fetch.authRequired pour l'authentification ProxyHat.
  • IP résidentielle indispensable : un navigateur non détectable avec une IP datacenter est un échec garanti contre Cloudflare.
  • Sessions sticky via user-country-US-session-abc123 pour maintenir la même IP à travers plusieurs requêtes.
  • Scaling async natif : multi-browsers avec asyncio.gather(), un proxy par browser.
  • Commencez par HTTP simple : ne sortez nodriver que si les challenges JS l'exigent.

FAQ

Qu'est-ce que le scraping avec nodriver ?

nodriver est le successeur async d'undetected-chromedriver, qui pilote Chrome via le Chrome DevTools Protocol (CDP) sur websockets sans aucune dépendance Selenium. Le scraping avec nodriver consiste à utiliser cette API pour accéder à des pages protégées par des anti-bot comme Cloudflare, en profitant d'un navigateur réel sans marqueurs WebDriver détectables.

Pourquoi nodriver importe-t-il pour les utilisateurs de proxy ?

Un proxy résidentiel masque votre IP, mais si le navigateur laisse des traces détectables (navigator.webdriver, fuites CDP), le WAF peut quand même bloquer la requête. nodriver élimine ces traces à la source, ce qui rend la combinaison avec un proxy résidentiel ProxyHat particulièrement efficace pour accéder à des sites fortement protégés.

Quel type de proxy fonctionne le mieux avec nodriver ?

Les proxies résidentiels sont le meilleur choix, car ils attribuent une IP d'un vrai FAI qui ne figure pas dans les listes de blocage datacenter. ProxyHat propose des endpoints résidentiels avec geo-targeting et sessions sticky via le format user-country-US-session-abc123. Les proxies datacenter peuvent fonctionner sur des sites peu protégés, mais échouent face aux WAF avancés.

Comment éviter les blocages avec nodriver ?

Combinez trois éléments : (1) un navigateur nodriver non détectable sans marqueurs WebDriver, (2) un proxy résidentiel ProxyHat avec rotation ou session sticky, et (3) des délais aléatoires entre requêtes pour simuler un comportement humain. Évitez les concurrences excessives (max 5-8 tabs par browser) et gérez les retries avec de nouvelles sessions proxy en cas d'échec.

nodriver peut-il fonctionner en headless ?

Oui, nodriver supporte le mode headless via headless=True dans uc.start(). Cependant, certains WAF détectent le mode headless via des différences de rendu. Pour une discrétion maximale, le mode headful (avec Xvfb en conteneur) offre une meilleure couverture anti-détection, au prix d'une consommation de ressources plus élevée.

Questions fréquentes

Qu'est-ce que le scraping avec nodriver ?

nodriver est le successeur async d'undetected-chromedriver, qui pilote Chrome via le Chrome DevTools Protocol (CDP) sur websockets sans aucune dépendance Selenium. Le scraping avec nodriver consiste à utiliser cette API pour accéder à des pages protégées par des anti-bot comme Cloudflare, en profitant d'un navigateur réel sans marqueurs WebDriver détectables.

Pourquoi nodriver importe-t-il pour les utilisateurs de proxy ?

Un proxy résidentiel masque votre IP, mais si le navigateur laisse des traces détectables (navigator.webdriver, fuites CDP), le WAF peut quand même bloquer la requête. nodriver élimine ces traces à la source, ce qui rend la combinaison avec un proxy résidentiel ProxyHat particulièrement efficace pour accéder à des sites fortement protégés.

Quel type de proxy fonctionne le mieux avec nodriver ?

Les proxies résidentiels sont le meilleur choix, car ils attribuent une IP d'un vrai FAI qui ne figure pas dans les listes de blocage datacenter. ProxyHat propose des endpoints résidentiels avec geo-targeting et sessions sticky via le format user-country-US-session-abc123. Les proxies datacenter peuvent fonctionner sur des sites peu protégés, mais échouent face aux WAF avancés.

Comment éviter les blocages avec nodriver ?

Combinez trois éléments : (1) un navigateur nodriver non détectable sans marqueurs WebDriver, (2) un proxy résidentiel ProxyHat avec rotation ou session sticky, et (3) des délais aléatoires entre requêtes pour simuler un comportement humain. Évitez les concurrences excessives (max 5-8 tabs par browser) et gérez les retries avec de nouvelles sessions proxy en cas d'échec.

nodriver peut-il fonctionner en headless ?

Oui, nodriver supporte le mode headless via headless=True dans uc.start(). Cependant, certains WAF détectent le mode headless via des différences de rendu. Pour une discrétion maximale, le mode headful (avec Xvfb en conteneur) offre une meilleure couverture anti-détection, au prix d'une consommation de ressources plus élevée.

Prêt à commencer ?

Proxys résidentiels, ISP et mobiles dans plus de 148 pays. Créez un compte gratuit.

Créer un compte gratuit
← Retour au Blog