Geonode logo
Geonode Team

Geonode Team

Mis à jour : 7 septembre 2026

Publié : 2 septembre 2026

ChromeDP : guide de démarrage

chromedp pilote Chrome depuis Go via le DevTools Protocol, sans dépendances externes — pas de binaire driver, pas de Java, pas de processus serveur séparé. Cette conception rend le déploiement agréable et l'apprentissage un peu inhabituel, parce que tout s'accroche au paquet `context` de Go de façons qui ne sont pas évidentes au début. Ce guide couvre le modèle de context, les actions que vous utiliserez vraiment, le fait d'attendre correctement, et la configuration de déploiement et de proxy qui fait trébucher.

Notre enjeu : nous sommes Geonode et nous vendons des proxies, et l'automatisation de navigateur est la chose la plus gourmande en bande passante que vous puissiez faire à travers un. Un navigateur headless récupère chaque image, police, script et préchargement vidéo, donc faire tourner chromedp sur du trafic mesuré coûte à peu près un ordre de grandeur de plus que des requêtes HTTP brutes pour les mêmes pages. Il y a une section pour couper ça, et la technique y économisera plus que de choisir un fournisseur moins cher. La configuration proxy tient en trois lignes et a un vrai piège, également couvert.

Ce qu'est chromedp

Le projet se décrit comme « a faster, simpler way to drive browsers supporting the Chrome DevTools Protocol in Go without external dependencies ».

Cette dernière clause est l'argument de vente. Selenium a besoin d'un binaire driver assorti à la version du navigateur ; Playwright livre son propre runtime. chromedp parle le DevTools Protocol directement sur un websocket, donc un binaire Go plus une installation Chrome, c'est tout le déploiement.

Licence MIT et maintenance active — la version 0.15.1 est sortie en avril 2026, avec des commits jusqu'en juillet, vérifiés en septembre 2026.

L'installation n'a rien de remarquable :

go get -u github.com/chromedp/chromedp

Les bindings de protocole générés vivent dans un paquet compagnon, github.com/chromedp/cdproto, que vous allez chercher quand vous avez besoin de quelque chose que l'API de haut niveau n'enveloppe pas.

Le modèle de context

La partie à comprendre d'abord, parce que tout le reste en découle.

chromedp utilise context.Context pour deux jobs à la fois : l'annulation, comme Go le fait toujours, et le transport des handles du navigateur et de l'onglet. Ce double usage explique pourquoi la mise en place a cette forme.

ctx, cancel := chromedp.NewContext(context.Background())
defer cancel()

var title string
err := chromedp.Run(ctx,
    chromedp.Navigate("https://example.com"),
    chromedp.Text("h1", &title, chromedp.NodeVisible),
)

Le premier NewContext alloue un navigateur. Les contexts suivants dérivés créent de nouveaux onglets dans le même navigateur, ce qui permet de faire tourner plusieurs pages sans payer le démarrage du navigateur à répétition :

browserCtx, cancelBrowser := chromedp.NewContext(context.Background())
defer cancelBrowser()

tabCtx, cancelTab := chromedp.NewContext(browserCtx)
defer cancelTab()

Annuler ferme des choses. Annuler un context d'onglet ferme l'onglet ; annuler celui du navigateur ferme le navigateur. Le defer cancel() n'est pas une comptabilité optionnelle — l'omettre fuit un processus Chrome.

Les timeouts se composent à la manière ordinaire de Go :

ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()

Deux erreurs de la FAQ du projet valent d'être connues à l'avance.

« Executing an action without Run results in 'invalid context'. » La FAQ explique que « by default, a chromedp context does not have an executor, however one can be specified manually if necessary ». Les actions ne s'exécutent pas toutes seules — ce sont des valeurs que Run réalise.

« I'm seeing 'context canceled' errors. » La FAQ l'attribue à la perte de connexion : « when the connection to the browser is lost, chromedp cancels the context, and it may result in this error. This occurs, for example, if the browser is closed manually, or if the browser process has been killed or otherwise terminated. » Donc une erreur context canceled signifie souvent que Chrome est mort plutôt que le timeout a tiré — à distinguer avant de monter le timeout.

Actions

Run prend une séquence d'actions et les exécute dans l'ordre. Les courantes couvrent la plupart du travail.

err := chromedp.Run(ctx,
    chromedp.Navigate("https://example.com/search"),
    chromedp.WaitVisible(`input[name="q"]`),
    chromedp.SendKeys(`input[name="q"]`, "golang"),
    chromedp.Click(`button[type="submit"]`, chromedp.NodeVisible),
    chromedp.WaitVisible(`.results`),
    chromedp.Text(`.results`, &results, chromedp.NodeVisible),
)

Extraire de plusieurs éléments utilise Nodes ou Evaluate :

var links []string
err := chromedp.Run(ctx,
    chromedp.Navigate(url),
    chromedp.Evaluate(`[...document.querySelectorAll('a')].map(a => a.href)`, &links),
)

Evaluate exécute du JavaScript dans la page et désérialise le résultat en valeur Go, souvent le chemin le plus court pour tout ce qui implique plusieurs éléments à la fois. La valeur doit être sérialisable en JSON.

Pour les actions qui renvoient plusieurs valeurs, la FAQ donne le wrapper :

chromedp.Run(ctx, chromedp.ActionFunc(func(ctx context.Context) error {
    _, err := domain.SomeAction().Do(ctx)
    return err
}))

ActionFunc est aussi la façon de descendre vers des appels cdproto bruts pour ce que l'API de haut niveau ne couvre pas — poser des cookies, intercepter des requêtes réseau, émuler un appareil. Cette trappe est disponible pour tout le DevTools Protocol, une grande surface.

Attendre correctement

La différence entre un scraper fiable et un instable, et l'erreur est toujours la même.

Ne dormez pas. chromedp.Sleep(3*time.Second) existe, tente, et est soit trop court — des échecs intermittents un jour lent — soit trop long, gaspillant du temps à chaque exécution. En général les deux, selon la machine.

Attendez la chose qui vous importe :

chromedp.WaitVisible(`.results`, chromedp.ByQuery)
chromedp.WaitNotVisible(`.spinner`)
chromedp.WaitReady(`#content`)

WaitVisible attend que l'élément existe et soit visible ; WaitReady attend qu'il existe dans le DOM. Pour du contenu chargé après une interaction, visible est en général la bonne condition.

Pour une condition qu'aucun sélecteur n'exprime, faites un poll dans la page :

chromedp.Poll(`document.querySelectorAll('.item').length >= 20`, nil)

C'est la réponse à « attendre que la liste ait fini de charger », qu'aucune attente basée sur un élément ne peut exprimer.

Bornez toujours l'attente avec un timeout de context. Un WaitVisible sur un sélecteur qui ne matchera jamais bloque jusqu'à l'expiration du context, et sans timeout c'est pour toujours.

Exécuter en headless, et dans Docker

Chrome tourne en headless par défaut. La FAQ répond à la première question : « By default, Chrome is run in headless mode. See DefaultExecAllocatorOptions, and an example to override the default options. »

Pour le regarder travailler en développement :

opts := append(chromedp.DefaultExecAllocatorOptions[:],
    chromedp.Flag("headless", false),
)
allocCtx, cancelAlloc := chromedp.NewExecAllocator(context.Background(), opts...)
defer cancelAlloc()

ctx, cancel := chromedp.NewContext(allocCtx)
defer cancel()

NewExecAllocator est l'endroit où vous posez les flags en ligne de commande de Chrome, et c'est la couche au-dessus du context navigateur.

Pour les conteneurs, la recommandation du projet est précise : « The simplest way is to run the Go program that uses chromedp inside the chromedp/headless-shell image. That image contains headless-shell, a smaller headless build of Chrome, which chromedp is able to find out of the box. »

Ça vaut d'être suivi. Assembler un Chrome qui marche dans un conteneur à la main, c'est courir après des bibliothèques partagées et des paquets de polices manquants, et le résultat est plus gros que l'image conçue pour ça.

Un comportement spécifique à Linux dans la FAQ, qui surprend ceux qui lancent Chrome à part : « On Linux, chromedp is configured to avoid leaking resources by force-killing any started Chrome child processes. If you need to launch a long-running Chrome instance, manually start Chrome and connect using RemoteAllocator. »

RemoteAllocator se connecte à un navigateur déjà lancé via son endpoint websocket, le schéma d'un pool partagé ou d'un navigateur dans un conteneur séparé.

Utiliser un proxy

Trois lignes, et un piège.

opts := append(chromedp.DefaultExecAllocatorOptions[:],
    chromedp.ProxyServer("http://proxy.example.com:9000"),
)
allocCtx, cancelAlloc := chromedp.NewExecAllocator(context.Background(), opts...)
defer cancelAlloc()

ctx, cancel := chromedp.NewContext(allocCtx)
defer cancel()

ProxyServer pose le flag --proxy-server de Chrome.

Le piège est l'authentification. Le flag --proxy-server de Chrome n'accepte pas d'identifiants — une URL avec nom d'utilisateur et mot de passe n'authentifie pas. Chrome répond à un défi proxy en montrant un dialogue, et un navigateur headless n'a personne pour le remplir.

Deux contournements, par ordre de préférence.

Utilisez une allowlist d'IP. Si l'adresse source est stable, enregistrez-la chez le fournisseur et abandonnez les identifiants. C'est la réponse la plus propre et ça retire le problème plutôt que de le contourner.

Traitez l'événement d'authentification. chromedp peut répondre à la requête d'authentification du DevTools Protocol via fetch.Enable avec handleAuthRequests, en fournissant les identifiants par programme. C'est plus de code, et c'est la voie quand l'adresse n'est pas fixe.

Puis vérifiez que ça a marché, parce qu'un proxy mal configuré dans un navigateur est silencieux :

var ip string
err := chromedp.Run(ctx,
    chromedp.Navigate("https://api.ipify.org"),
    chromedp.Text("body", &ip, chromedp.NodeVisible),
)

Exécutez avec et sans l'option proxy. Si l'adresse ne change pas, Chrome ne l'utilise pas — et rien ne vous l'aura dit. Pour un travail géo-ciblé, allez plus loin et confirmez que du contenu régionalement distinct diffère vraiment, car l'adresse est la partie facile. C'est le schéma d'échec silencieux décrit dans pourquoi tester les proxies compte.

Alignez la locale sur le pays de sortie tant que vous y êtes. Une sortie allemande avec un en-tête de langue en-US et un fuseau de Londres est une combinaison qu'aucun visiteur réel ne produit, et beaucoup de sites utilisent la locale indépendamment de l'adresse :

chromedp.Flag("lang", "de-DE"),

Réduire la bande passante

La section qui économise le plus d'argent, et elle s'applique à toute automatisation de navigateur.

Une page de 200 Ko de HTML peut faire 4 Mo une fois chaque image, police, script de tracking et préchargement vidéo récupérés. Aux tarifs résidentiels de 0,79 $/Go — nos chiffres, vérifiés sur la page tarifaire en septembre 2026 — cette différence est tout le budget.

Bloquez les types de ressources dont vous n'avez pas besoin. Avec fetch.Enable et un listener request-paused, interrompez les requêtes d'images, de médias et de polices :

chromedp.ListenTarget(ctx, func(ev interface{}) {
    if e, ok := ev.(*fetch.EventRequestPaused); ok {
        go func() {
            c := chromedp.FromContext(ctx)
            execCtx := cdp.WithExecutor(ctx, c.Target)
            switch e.ResourceType {
            case network.ResourceTypeImage, network.ResourceTypeMedia, network.ResourceTypeFont:
                _ = fetch.FailRequest(e.RequestID, network.ErrorReasonBlockedByClient).Do(execCtx)
            default:
                _ = fetch.ContinueRequest(e.RequestID).Do(execCtx)
            }
        }()
    }
})

Cela coupe couramment la majeure partie du trafic, et accélère les exécutions en effet secondaire.

Réutilisez le navigateur, créez des onglets. Démarrer le navigateur est cher ; un onglet est bon marché. Pour un passage sur beaucoup de pages, allouez une fois et dérivez des contexts d'onglet.

Et demandez-vous si vous avez besoin d'un navigateur. Si le contenu est dans le HTML initial, une requête HTTP simple coûte une fraction et tourne bien plus vite. Vérifiez le code source de la page avant de recourir à l'automatisation — le réflexe de tout rendre est la source la plus courante de coût inutile dans ce domaine.

Déboguer quand rien ne marche

L'automatisation de navigateur échoue de façon opaque — un sélecteur qui ne matche jamais et une page qui ne charge jamais produisent le même timeout. Une séquence fixe résout la plupart.

Allumez le navigateur et regardez. Le diagnostic le plus rapide, et celui que les gens laissent pour la fin :

opts := append(chromedp.DefaultExecAllocatorOptions[:],
    chromedp.Flag("headless", false),
)

La moitié du temps la réponse est visible tout de suite — une bannière cookies qui recouvre le bouton, une redirection vers une page de connexion, un écran de défi, ou une mise en page différente de celle testée.

Capturez la page quand une exécution échoue. En environnement headless, cela remplace le regard :

var buf []byte
_ = chromedp.Run(ctx, chromedp.FullScreenshot(&buf, 90))
_ = os.WriteFile("failure.png", buf, 0644)

Associez-le au HTML, car une capture montre ce qui a été rendu et la source ce qui est arrivé :

var html string
_ = chromedp.Run(ctx, chromedp.OuterHTML("html", &html, chromedp.ByQuery))

Vérifiez que le sélecteur se résout avant de supposer un problème de timing :

var count int
_ = chromedp.Run(ctx, chromedp.Evaluate(`document.querySelectorAll('.item').length`, &count))

Zéro signifie un problème de sélecteur, et aucune attente ne le corrige.

Activez les logs du navigateur quand vous soupçonnez que la page elle-même erreur :

opts := append(chromedp.DefaultExecAllocatorOptions[:],
    chromedp.Flag("enable-logging", true),
    chromedp.Flag("v", "1"),
)

Écoutez les messages console et les requêtes en échec, qui expliquent souvent une page qui rend vide :

chromedp.ListenTarget(ctx, func(ev interface{}) {
    switch e := ev.(type) {
    case *runtime.EventConsoleAPICalled:
        log.Printf("console.%s", e.Type)
    case *network.EventLoadingFailed:
        log.Printf("failed: %s %s", e.Type, e.ErrorText)
    }
})

Une page dont tous les appels API échouent ressemble à une page dont les sélecteurs ont changé, et seuls les événements réseau les distinguent.

Et utilisez chromedp-proxy en dernier recours. Il s'intercale entre votre programme et le navigateur et journalise le trafic DevTools Protocol dans les deux sens. Quand le comportement n'a aucun sens, voir l'échange réel de protocole l'explique souvent en une lecture.

Quand chromedp est le bon choix

Utilisez-le quand vous écrivez déjà du Go et voulez un unique binaire statique sans driver à distribuer, ou quand vous avez besoin d'un accès direct au DevTools Protocol pour quelque chose que les outils de plus haut niveau n'exposent pas.

Envisagez Playwright quand vous voulez le multi-navigateur, l'attente automatique dans chaque action, le tracing et les captures à l'échec, ou un plus gros corpus de documentation et d'exemples. Le port Go existe mais l'écosystème est centré sur JavaScript et Python.

Envisagez du HTTP simple quand le contenu est dans la réponse initiale. Plus rapide, moins cher, plus simple, et plus du web sert du HTML utile que le discours ne le suggère.

La liste de ressources de la FAQ est une bonne carte pour la suite : le dépôt examples pour les actions complexes et les captures pleine page, la référence cdproto pour l'API de protocole générée, et chromedp-proxy — un proxy de logs CDP — pour voir exactement ce que votre programme et le navigateur se disent, l'outil de débogage de dernier recours et un vraiment bon.

Questions fréquentes

Qu'est-ce que chromedp ?

Un paquet Go qui pilote des navigateurs parlant le Chrome DevTools Protocol, sans dépendances externes. Contrairement à Selenium il n'a pas besoin de binaire driver, et contrairement à Playwright il ne livre pas de runtime — un binaire Go plus une installation Chrome, c'est tout le déploiement.

Pourquoi j'ai « invalid context » dans chromedp ?

Parce que vous avez exécuté une action sans Run. La FAQ explique qu'un context chromedp n'a pas d'exécuteur par défaut. Les actions sont des valeurs que chromedp.Run réalise ; en appeler une directement n'a rien pour l'exécuter.

Que signifie « context canceled » dans chromedp ?

En général que la connexion au navigateur a été perdue. La FAQ l'attribue à une fermeture manuelle ou à un processus tué. Ça vaut d'être distingué d'un timeout, car le correctif est différent — un Chrome planté ne se règle pas en attendant plus longtemps.

Comment lancer chromedp avec un navigateur visible ?

Chrome tourne en headless par défaut. Ajoutez chromedp.Flag("headless", false) à DefaultExecAllocatorOptions et passez-les à NewExecAllocator, puis dérivez votre context de cet allocator.

Comment utiliser un proxy avec chromedp ?

Ajoutez chromedp.ProxyServer("http://host:port") aux options de l'allocator. Les identifiants dans l'URL ne marchent pas, car le flag --proxy-server de Chrome ne les accepte pas — utilisez une allowlist d'IP si l'adresse est stable, ou traitez la requête d'authentification via le DevTools Protocol.

Comment exécuter chromedp dans Docker ?

Lancez le programme Go dans l'image chromedp/headless-shell, que le projet recommande explicitement. Elle contient un build headless plus petit de Chrome que chromedp trouve sans configuration, et évite d'assembler un environnement de navigateur à la main.

Comment attendre un élément dans chromedp ?

Utilisez WaitVisible, WaitReady ou WaitNotVisible avec un sélecteur, ou Poll avec une expression JavaScript pour les conditions qu'aucun sélecteur n'exprime. Évitez Sleep — trop court et instable ou trop long et gaspilleur, en général les deux selon la machine.

Comment réduire la bande passante avec chromedp ?

Bloquez images, polices et médias en activant l'interception de requêtes et en faisant échouer ces types de ressources, ce qui retire typiquement la majeure partie du trafic. Réutilisez un navigateur et créez des onglets plutôt que d'allouer à répétition. Et vérifiez si le contenu est dans le HTML initial, auquel cas sautez le navigateur entièrement.

Pour conclure

La courbe d'apprentissage de chromedp est presque entièrement le modèle de context. Une fois internalisé qu'un context porte le navigateur ou l'onglet, qu'en annuler un le ferme, et que les actions ne font rien jusqu'à ce que Run les réalise, le reste de l'API est simple.

Les habitudes qui comptent sont les mêmes que dans toute automatisation de navigateur. Attendez des conditions plutôt que de dormir, bornez chaque attente avec un timeout de context, et réutilisez un navigateur sur beaucoup d'onglets plutôt que de payer le démarrage à répétition.

Deux points spécifiques à Go à retenir. defer cancel() sur chaque context, ou vous fuyez des processus Chrome — et sous Linux, chromedp tue de force les enfants Chrome qu'il a démarrés, donc un navigateur de longue durée doit être lancé à part et atteint avec RemoteAllocator.

Et si vous passez par un proxy mesuré, bloquez les types de ressources dont vous n'avez pas besoin avant toute autre chose. Une page rendue coûte un ordre de grandeur de plus que le HTML qu'elle contient, et la plupart de ça, ce sont des images que vous n'alliez jamais regarder.