chromedp-Test: Ein echter Browser braucht trotzdem die richtige Bereitschaft

Zuletzt aktualisiert am August 17, 2026
chromedp-Test: Ein echter Browser braucht trotzdem die richtige Bereitschaft
KI-Zusammenfassung

chromedp ist eine reine Go-Bibliothek mit MIT-Lizenz, die einen echten Chrome über das Chrome DevTools Protocol steuert. Sie liest das DOM erst aus, nachdem das JavaScript der Seite ausgeführt wurde, direkt aus einem Go-Programm heraus, ohne separates WebDriver- oder Node-Setup. Das Go-Modul wird in die Anwendung eingebaut, aber zur Laufzeit braucht das System weiterhin ein externes Chrome-Programm, dessen Lebensdauer über Go-Contexts gesteuert wird. Die Installation bestand aus einem einzigen go get plus einem Chrome, den ich selbst bereitstellen musste: Allocator erstellen, Context ableiten, Run eine Aktionsliste übergeben.

chromedp ist eine reine Go-Bibliothek mit MIT-Lizenz, die über das Chrome DevTools Protocol einen echten Chrome steuert. Sie liest das DOM erst aus, nachdem das JavaScript der Seite ausgeführt wurde — direkt aus einem Go-Programm heraus, ohne separates WebDriver-Setup oder Node-Runtime. Das Go-Modul wird in die Anwendung eingebaut, aber zur Laufzeit braucht das System weiterhin ein externes Chrome-Programm, dessen Lebensdauer über Go-Contexts gesteuert wird.

Die Installation bestand aus einem einzigen go get plus einem Chrome, den ich selbst bereitstellen musste: Allocator erstellen, Context ableiten, Run eine Aktionsliste übergeben. Auf diesem macOS-arm64-Host mit einem bereits warmen Headless-Shell auf der Festplatte lag die Medianzeit vom frischen Prozess bis zum ersten Script-Ergebnis bei 102 ms. Das ist ein lokaler Ausgangswert, keine allgemeine Aussage darüber, dass Startup nie zum Flaschenhals wird. In einem Testfall, der einen Link 800 Millisekunden nach dem Laden einfügt, lieferten zwei von vier Lese-Strategien das Ergebnis zurück, bevor der Link überhaupt existierte.

Der Browser war echt, der Inhalt war da, und der Code hat einfach nicht darauf gewartet. Genau diese Lücke war für mich die wichtigste Erkenntnis aus chromedp, und sie ist kein Bug — sie zeigt den Unterschied zwischen „Ich habe die Seite gerendert“ und „Ich habe auf das gewartet, was ich wirklich wollte“. Diese Unterscheidung wird in den üblichen Headless-Browser-Geschichten oft glattgebügelt. Nicht der Browser entscheidet, sondern die Wait-Strategie, ob Sie die Daten bekommen. Alle Zahlen hier stammen aus einem lokalen Test-Setup unter meiner Kontrolle, bei dem die Ground Truth vor jedem Lauf feststand; die Rohzusammenfassungen liegen im chromedp-Ordner unseres Benchmark-Repos.

Was chromedp eigentlich ist

Das chromedp-Stack ist absichtlich schlank gehalten. Kein Selenium-Server. Kein WebDriver-Wrapper. Keine versteckte Node-Runtime. Ihr Go-Programm öffnet eine WebSocket-Verbindung zu einer Chrome-Instanz und spricht direkt CDP damit — im Grunde dasselbe Wire-Protokoll, das auch Puppeteer verwendet, nur eben ohne JavaScript.

Als ich am 27. Juli 2026 nachgesehen habe, hatte das Repo 13.212 Sterne und 178 offene Issues, unter einer MIT-Lizenz. Die Version, die ich getestet habe, ist v0.16.0 — also das neueste Tag im Repo. Wichtig, damit es nicht verwirrt: Auf der GitHub-Releases-Seite wird weiterhin v0.15.1 (veröffentlicht am 01.04.2026) als neuestes Release-Objekt angezeigt, während go get github.com/chromedp/chromedp@latest auf v0.16.0 auflöst. Go-Module und GitHub-Release-Objekte sind hier auseinander gelaufen. Nicht kaputt, aber nervig, wenn man herausfinden will, was man wirklich ausführt.

Das mentale Modell sind durchgehend Go-Contexts. Sie erzeugen einen Allocator-Context (der weiß, wie Chrome gestartet wird), leiten davon einen Browser-Context ab und rufen dann chromedp.Run(ctx, actions...) mit einer Liste von Actions auf. Ein Kind-Context eines Browser-Contexts ist ein neuer Tab. Wenn Sie einen Context abbrechen, verschwindet das, was er repräsentiert. Wer schon mit Go-Concurrency gearbeitet hat, fühlt sich damit sofort zu Hause; wer nicht, findet in unserem Leitfaden für den Einstieg ins Web Scraping mit Go einen deutlich sanfteren Einstieg als in chromedps Godoc.

Eine Grenze sollte man früh ziehen: chromedp liefert Ihnen ein gerendertes DOM. Es liefert Ihnen keine strukturierten Daten. Alles, was Sie aus diesem DOM herausziehen — Felder, Tabellen, Preise — ist Code, den Sie selbst schreiben und pflegen. Es ist ein Treiber, kein Scraper-Framework.

Die Technik unter der Haube

Alles in chromedp ist eine Action, und Run führt eine solche Liste der Reihe nach gegen ein Ziel aus. Navigate, Click, Evaluate, OuterHTML, WaitVisible — alle folgen demselben Interface, sind kombinierbar und sind letztlich nur CDP-Befehle in Go-Typen gekleidet. Diese Einheitlichkeit ist die beste Designentscheidung der Bibliothek, weil dadurch Komfort-API und Rohprotokoll auf derselben Ebene liegen.

Das ist wichtig, weil die Komfortschicht bewusst dünn ist. chromedp baut auf cdproto auf, einem generierten Satz typisierter Go-Bindings für die gesamte DevTools-Protocol-Oberfläche, und das chromedp-Godoc dokumentiert beide Schichten nebeneinander. Wenn die bequeme Action nicht existiert, wechseln Sie im selben Run auf den Domain-Call — network.Enable(), page.CaptureScreenshot(), runtime.Evaluate() — ganz ohne Bruch zwischen „schöner API“ und „echter API“. Das ist nicht bei jedem Browser-Treiber so.

Die Wait-Actions sind der Punkt, an dem man im Alltag Entscheidungen treffen muss, und es gibt mehr davon, als die meisten nutzen:

WarteaktionWorauf sie blockiert
WaitReady(sel)bis der Knoten an das DOM angehängt ist
WaitVisible(sel)bis der Knoten tatsächlich sichtbar ist
WaitNotPresent(sel) / WaitNotVisible(sel)das Gegenteil, nützlich für Ladespinner
Poll(js, res)führt in Intervallen ein JavaScript-Prädikat aus, bis es wahr ist

Auch die Prozessverwaltung ist ein wichtiger Teil der Mechanik, denn sie entscheidet, ob Ihr Programm einen Browser zurücklässt. chromedp startet Chrome über Go exec.CommandContext. Wenn Sie diesen Context abbrechen, wird der Prozess beendet. Dieses eine Implementierungsdetail erklärt sowohl das gute Verhalten als auch die scharfe Kante, auf die ich im Test gestoßen bin.

Das Setup ist ein Go-Binary plus ein Chrome, den Sie selbst liefern müssen

go get github.com/chromedp/chromedp löste sauber auf v0.16.0 auf, ohne Drama, und der Dependency-Tree enthält keine cgo-Imports. Daher ist die oft wiederholte Aussage „reines Go, keine externen Abhängigkeiten“ korrekt — bezogen auf das Go-Modul.

Zur Laufzeit stimmt das nicht. chromedp steuert ein externes Chrome, und ohne Chrome auf dem Rechner schlägt ein Lauf sofort fehl. Für jede Messung habe ich das konkrete Executable über chromedp.ExecPath übergeben, und zwar auf ein Chrome for Testing Headless Shell 151.0.7922.10. Das ist kein Vorwurf — wer einen Browser steuern will, braucht einen Browser — aber „keine externen Abhängigkeiten“ und „Sie müssen ein 155-MB-Chrome zusammen mit Ihrem Binary ausliefern“ sind zwei sehr unterschiedliche Deploymentszenarien, und nur eines davon steht im README.

Eine zweite Falle bei der Einrichtung hat mich Zeit gekostet und ist wichtig zu wissen, bevor Sie Code schreiben. chromedp-Issue #1591 beschreibt, dass der Go-1.25+-go test-Runner NewExecAllocator mitten im Startvorgang abbricht; derselbe Code läuft als kompiliertes Binary problemlos. Ich habe für jede Messung ein Probe-Binary mit go build erstellt und dieses ausgeführt, statt irgendetwas über go test zu treiben. Meine Go-Version war 1.26.5 auf macOS arm64. Wenn Ihr erster chromedp-Kontakt eine Testdatei ist, die beim Chrome-Start stirbt, lesen Sie zuerst dieses Issue, bevor Sie Ihren eigenen Code verdächtigen.

Praxis: Vier Wege, dieselbe Seite zu lesen — zwei bleiben leer

Gemessenes Diagramm: Welche Lesestrategie sah welchen Link?

Der Fixture ist ein lokaler Server auf 127.0.0.1, der drei Arten von Inhalten liefert, die sich nur darin unterscheiden, wann sie ins DOM gelangen: ein statischer <a> in den ausgelieferten Bytes, ein <a>, das durch ein Inline-<script> während des initialen Parsens erzeugt wird, und ein <a>, der per setTimeout eine konfigurierbare Anzahl Millisekunden nach dem Load-Event erzeugt wird. Die Marker und hrefs für die beiden durch Skript erzeugten Links werden in JavaScript aus String-Fragmenten zusammengesetzt, sodass im ausgelieferten HTML kein zusammenhängendes Literal existiert. Ein „gefunden“ belegt also, dass Chrome JavaScript ausgeführt hat — nicht, dass jemand HTML gelesen hat.

Der Recall wird in Python gegen vorregistrierte Ground-Truth-Marker berechnet, nicht im Go-Probe selbst, damit der Probe sich die Antwort nicht „erleichtern“ kann. Jede Strategie lief drei Mal; die gefundenen Mengen waren in allen drei Läufen identisch.

LesestrategieStatischer HTML-LinkBeim Parsen eingefügt800 ms nach dem Laden eingefügtDauer
Navigate + lesen, ohne Wartezeitgefundengefundenverpasst317 ms
WaitReady("body")gefundengefundenverpasst107 ms
WaitVisible("#delayed-injected")gefundengefundengefunden912 ms
Polling bis der Marker erscheintgefundengefundengefunden972 ms

Zwei Zeilen bringen zwei von drei Links zurück. Der naive Leseweg verpasst den dritten Link, weil Navigate beim Load-Event zurückkehrt und der Link dann noch nicht existiert. WaitReady("body") scheitert aus einem subtileren, in der Praxis sogar gefährlicheren Grund: body ist beim Laden bereits angehängt, also ist die Wartebedingung sofort erfüllt, und man hat das Gefühl, alles Richtige getan zu haben. In 107 ms kam die Antwort sogar schneller als der Weg ohne Wartezeit — und lieferte dieselbe unvollständige Seite.

Um den Mechanismus zu bestätigen, statt ihn nur anzunehmen, habe ich die Einfügeverzögerung variiert und beide Extreme erneut ausgeführt (recall-summary.json):

Einfügeverzögerung nach dem LadenLesen ohne Wartezeit sieht esWaitVisible sieht esWaitVisible Dauer
0 msja (Race)ja109 ms
100 msneinja208 ms
400 msneinja519 ms
800 msneinja911 ms
1500 msneinja1625 ms

Die Laufzeit von WaitVisible folgt in diesem Fixture der Einfügeverzögerung — 100 auf 208, 400 auf 519, 800 auf 911, 1500 auf 1625 — und zeigt damit, dass die Aktion blockiert hat, bis der Knoten erschienen ist, statt zu früh zu lesen. Die 0-ms-Zeile ist die Grenze: setTimeout(…, 0) kann vor dem sofortigen Lesezugriff feuern, also kann der Weg ohne Wartezeit ihn noch erwischen. Ab 100 ms und darüber hat der No-Wait-Pfad ihn in diesem Sweep in keinem Lauf mehr gesehen.

In der Produktion kann derselbe Timing-Fehler zu gültigem HTML mit null extrahierten Zeilen und Exit 0 führen, sofern die Pipeline nicht die Ausgabemenge prüft. Das ist ein plausibler Fehlmodus, gestützt durch das Verhalten des Fixtures — kein hier direkt gemessener Vorfall. Rendering ist nur die halbe Anforderung; das Lesen muss auf eine applikationsbezogene Bedingung warten, die an die gewünschten Daten gekoppelt ist.

WaitReady und WaitVisible sind nicht gegeneinander zu bewerten — sie beantworten unterschiedliche Fragen

Oft liest man, WaitVisible sei „zuverlässiger“ als WaitReady. Das ist ungenau genug, um schädlich zu sein. Auf einer Seite mit einem Knoten, der zwar im DOM hängt, aber per display: none versteckt ist, trennen sich die beiden klar (waitsem-summary.json, drei identische Läufe):

ZielknotenAktionErgebnisZeit
angehängt, display:noneWaitReadykehrt zurück~6 ms
angehängt, display:noneWaitVisibleTimeout, context deadline exceeded4000 ms
sichtbarer KnotenWaitVisible, Standard-Querykehrt zurück4–12 ms
sichtbarer KnotenWaitVisible, ByIDkehrt zurück1–2 ms
sichtbarer KnotenWaitVisible, ByQuerykehrt zurück1 ms

WaitReady bedeutet: angehängt. WaitVisible bedeutet: sichtbar. Wenn Sie die falsche Aktion wählen, laufen Sie entweder an Inhalt vorbei, der nie gerendert wurde, oder blockieren Ihre gesamte Timeout-Dauer auf einen Knoten, der ohnehin nie sichtbar sein wird. Das Deadline-Verhalten ist sauber — ein korrektes context deadline exceeded nach exakt 4 s, kein Hängenbleiben, kein Zombie-Zustand — und damit besser als bei manchem anderen Treiber.

Eine gemeldete Falle ließ sich nicht reproduzieren. Issue #440 berichtet, dass WaitVisible("#id") mit der Standard-Query hängt; das ließ sich in v0.16.0 nicht nachstellen — Standard-Query, ByID und ByQuery kehrten bei dem sichtbaren Knoten in jedem Lauf zurück. Nicht reproduziert ist nicht dasselbe wie behoben: Das ist nur eine Selector-Form auf einer Seite, und damit ist das Issue nicht erledigt.

Das defer cancel(), das Sie weggelassen haben, hält das Dach

Nicht Rückgabewerte, sondern Prozesszahlen zählen. Jeder Lifecycle-Lauf verwendete ein eindeutiges --user-data-dir und zählte echte Chrome-Browser-Prozesse mit pgrep, wobei Renderer-Kinder herausgefiltert wurden. Jeder Pfad wurde drei Mal ausgeführt (lifecycle-summary.json).

Exit-Pfad (macOS, je 3 Läufe)Was mit dem gestarteten chrome-headless-shell geschahZeit
Context und Allocator abbrechenverschwunden13, 13 und 12 Millisekunden
Go-Prozess ohne Abbruch beendenüberlebt Ihr Programm — vor dem Exit null Browserprozesse, nach dem Verlassen des Probes einer; in drei von drei Läufen verwaist

Abbrechen ist sauber, schnell und genau das, was exec.CommandContext verspricht. (Jeder verwaiste Prozess wurde danach vom Harness zwangsweise beendet; der Host war anschließend wieder sauber.)

Das ist bekanntes, dokumentiertes, plattformspezifisches Verhalten — die Messung stammt von mir, die Entdeckung nicht. chromedps Tracker behandelt das aus mehreren Blickwinkeln: #774 beschreibt dasselbe Nicht-Beenden auf FreeBSD, #752 meldet hängende Chromium-Prozesse auf macOS, und #562 sowie #1566 erklären den Mechanismus. Was ich ergänzt habe, sind die Prozesszählung und die Zeiten auf beiden Seiten des Vergleichs — genau diese qualitativen Berichte liefern das nicht.

Der Mechanismus selbst hängt an Build-Tags und ist deshalb erwähnenswert. Im Quellcode von v0.16.0 setzt allocate_linux.go für den Kindprozess Pdeathsig = SIGKILL, sodass Linux ein Kernel-basiertes Parent-Death-Signal bekommt. allocate_other.go, das auf macOS kompiliert wird, macht daraus einen No-Op. Auf darwin gibt es kein entsprechendes Signal, also wird Chrome beim Beenden Ihres Programms nicht getötet. Gleichzeitig liest sich der Godoc-Text wie ein allgemeines Versprechen — der Standardbefehl „sendet SIGKILL an alle offenen Browser, wenn das Go-Programm endet“ — während die Linux-Einschränkung nur im Build-Tag-geschützten Quellcode steht, den man extra lesen müsste. Von einer überzogenen Dokumentation zu sprechen, ist fair; von einem chromedp-Bug zu sprechen, nicht.

Die praktische Folge bleibt trotzdem dieselbe: Auf macOS ist defer cancel() tragend. Lassen Sie es weg, und jeder Lauf hinterlässt einen Browserprozess. Ich habe Linux nicht getestet, deshalb verallgemeinere ich dieses Orphaning-Ergebnis nicht dorthin — der Quellcode legt nahe, dass sich Linux anders verhält, und eine Vermutung ist keine Messung.

Cold Start, Parallelität und die langweiligen Dinge, die Ihren Deployment-Plan bestimmen

Gemessenes Diagramm: Cold Start und zwei Parallelitätsformen

102 ms war der Median vom frischen Prozess über Allocator, Context, localhost-Navigation bis zum ersten Evaluate über fünf Prozesse hinweg, mit einer Spannweite von 98 bis 111 ms (coldstart-summary.json). Auf diesem macOS-arm64-Fixture mit einem warmen Headless-Shell auf der Platte war der Startaufwand klein im Vergleich zum Warten auf verzögerten Inhalt. Container, kalte Dateisysteme, CI, serverlose Umgebungen und produktive Navigation wurden nicht gemessen.

Bei Parallelität bietet chromedp zwei Formen — ein Browser mit mehreren Kind-Contexts (Tabs) oder mehrere unabhängige Browser. Vier Navigationsläufe, je drei Durchgänge (concurrency-summary.json):

ModusWall-Clock-Zeit (p50)BereichMaximale Chrome-Browserprozesse
Gemeinsamer Browser, 4 Kind-Contexts214 ms209–2191
4 getrennte Browser264 ms261–2784

Das gemessene Ergebnis ist die Prozesszahl: ein Chrome-Browserprozess statt vier für diese vier trivialen lokalen Navigationsschritte. Die Zeitbereiche überlappten nicht, bleiben aber eher richtungsweisend als ein echter Durchsatz-Benchmark. RSS und PSS wurden nicht gemessen, daher belegt dieser Test keine Speichereinsparung.

Der kleine Fehlerpfad-Test ist hier nicht detailliert genug, um eine Robustheitsaussage zu stützen: Der Entwurf nennt nicht, ob HTTP-Status, Navigationsfehler, Ereignis oder Harness-Logik die jeweilige Bedingung sichtbar machte. Behandeln Sie 500-/Dead-Link-Handling daher als nicht berichtet, bis das genaue API-Ergebnis und das Rohartefakt veröffentlicht sind.

Nicht getestet und damit außerhalb dessen, was diese Zahlen abdecken: Lifecycle-Verhalten unter Linux, Parallelität über N=4 hinaus oder mit echter Seitenarbeit, Speicher-Deltas (ich habe Prozesse gezählt, nicht RSS), Network Interception und Request-Capture sowie die offenen WaitReady-Timeout-Berichte in #168 und #1593 — dort geht es um intermittierende Timeouts, während ich hier die Wait-Semantik gemessen habe, also eine andere Frage. Ein Rechner, ein Chrome-Build.

chromedp ist nicht der einzige Go-CDP-Treiber auf diesem Tisch: rod wurde im selben Durchgang mit exakt demselben Fixture, Harness, Host und Chrome-Build getestet und bekommt einen eigenen Bericht.

Vor- und Nachteile

Vorteile:

  • Echter CDP-Zugriff — die Komfort-Actions und die rohen cdproto-Domain-Calls lassen sich im selben Run kombinieren, sodass Sie nie an eine API-Grenze stoßen.
  • Lokaler Cold-Cycle-Baseline von 102 ms p50 mit einer Spannweite von 98–111 ms auf dem getesteten macOS-Fixture.
  • Ein cancel räumt Chrome in rund 13 ms zuverlässig weg, bei jedem Lauf.
  • Kind-Contexts teilen sich einen Browserprozess für N Tabs (1 Prozess statt 4 bei getrennten Browsern).
  • Deterministisch im Test — Recall-Mengen, Wait-Semantik und Lifecycle-Ergebnisse waren bei drei Wiederholungen jeweils identisch.
  • Saubere Deadline-Behandlung: WaitVisible auf eine unerreichbare Bedingung lieferte exakt nach 4 s ein korrektes context deadline exceeded statt zu hängen.
  • Reines Go-Modul (kein cgo), MIT-lizenziert; zur Laufzeit wird trotzdem ein externes Chrome-Programm benötigt.

Nachteile:

  • Erfordert zur Laufzeit ein externes Chrome; der Ruf „keine Abhängigkeiten“ bezieht sich nur auf das Go-Modul.
  • WaitReady("body") ist eine Falle, die sich richtig anfühlt und nach dem Laden eingefügten Inhalt stillschweigend verpasst — in meinem Test kam es nach 107 ms mit unvollständiger Seite zurück.
  • Der naive Pfad Navigate + lesen verpasst alles, was ≥ ~100 ms nach dem Laden eingefügt wird, deterministisch und ohne Fehler.
  • Auf macOS hinterlässt ein Beenden ohne cancel() einen verwaisten Browser (3/3 Läufe). Bekannt und auf die Plattform beschränkt, aber leicht zu übersehen.
  • Die Godoc-Formulierung zu SIGKILL beim Beenden liest sich universell, obwohl der Mechanismus nur im Linux-spezifischen Build-Tag-Quellcode steckt.
  • go test kann unter Go 1.25+ den Allocator-Start abbrechen (#1591); bauen Sie stattdessen ein Binary.
  • Es liefert ein DOM, keine strukturierten Daten — jedes Feld ist Parsing-Code, den Sie selbst besitzen und pflegen.
  • Das neueste Tag (v0.16.0) liegt vor dem neuesten GitHub-Release-Objekt (v0.15.1), was die Versionsprüfung kurzzeitig verwirrend macht.

Für wen chromedp gedacht ist — und wer besser verzichten sollte

Wenn Ihr Service bereits in Go geschrieben ist und Sie darin einen echten Browser brauchen, ist chromedp ziemlich die naheliegende Wahl. Kein Node-Prozess, den Sie überwachen müssen, kein WebDriver-Server, den Sie am Leben halten müssen, ein kompiliertes Binary plus ein Chrome, das Sie ausliefern oder installieren. Das Context-Modell passt so direkt zu Go-Concurrency-Prinzipien, dass Browser-Lebenszyklen am Ende der gleichen defer-Disziplin folgen wie der Rest Ihrer Codebasis. Und wenn Sie etwas brauchen, das die Komfort-API nicht abdeckt — CDP-Netzwerkereignisse, präzise Page-Lifecycle-Hooks, Protokoll-Tricks — wechseln Sie einfach zu cdproto, ohne die Bibliothek zu verlassen.

Es ist auch eine gute Wahl, wenn Sie explizite Kontrolle über das Warten wollen. Die Wait-Actions sind Primitive, keine Heuristiken; sie tun genau das, was sie sagen. Das ist ein Vorteil, sobald man akzeptiert, dass die richtige Wahl jetzt Ihre Aufgabe ist.

Verzichten Sie darauf, wenn Ihr Team nicht in Go schreibt — die Sprachbindung ist der eigentliche Preis, nicht die Bibliothek. Verzichten Sie darauf, wenn Sie Auto-Waiting-Ergonomie wollen, die für Sie möglichst oft richtig rät, denn chromedp rät nicht; es tut exakt, was Sie gesagt haben, und gibt den Zustand der Seite zu diesem Moment zurück. Verzichten Sie darauf — oder planen Sie es sehr ernsthaft ein — wenn Sie eigentlich strukturierte Datensätze statt eines DOM brauchen: Jedes Feld ist ein Selector, den Sie selbst schreiben, testen und bei Seitenänderungen reparieren. Und wenn „Hol mir einfach die Daten von diesen 500 URLs“ die ganze Anforderung ist, dann ist Browser-Orchestrierung in Go für diese Aufgabe ziemlich viel Mechanik. Unser Leitfaden zur Browser-Automatisierung erklärt, wann sich diese Mechanik lohnt und wann nicht.

Alternativen und wo unser eigener Stack hineinpasst

Innerhalb der Kategorie Browser-Treiber ist rod ein weiterer Go-CDP-Treiber, während Playwright und Puppeteer Node-basierte Optionen sind, die wir in unserem Vergleich Playwright vs. Puppeteer behandeln. Ihre Waiting-Verträge sind nicht austauschbar. Playwright wartet für viele Aktionen automatisch auf Actionability; das sagt ihm aber immer noch nicht, wann die Anwendungsdaten nach der Aktion wirklich angekommen sind. chromedp gibt Ihnen niedrigere Wait-Primitive und überlässt sowohl die Actionability als auch die Bereitschaft auf Applikationsebene dem Aufrufer. Wer sich den breiteren Markt ansieht, findet in unserem Überblick über getestete Open-Source-Scraper statische Crawler und Extraktionsbibliotheken auf der anderen Seite dieser Linie.

Verwandter Testbericht: Browserless-Test.

Ein verwalteter Extraktionsdienst ist eine andere Kategorie. Er tauscht Browser- und Selector-Kontrolle gegen ausgelagertes Rendering und Schema-Formung. Das kann nützlich sein, wenn das Ergebnis strukturierte Datensätze statt eines DOMs sein sollen, während chromedp besser passt, wenn der Browser unter der Kontrolle Ihres Go-Services bleiben muss. Wir bauen Thunderbit, einen solchen Dienst, haben ihn aber nicht gegen dieses Fixture getestet; daher erlaubt dieser Test keinen Gleichwertigkeits-, Latenz-, Extraktionsqualitäts- oder Kostenvergleich mit chromedp.

Thunderbit für Web-Datenextraktion testen

Fazit

Sollten Sie chromedp verwenden? Ja, wenn Sie in Go entwickeln und einen echten Browser unter Ihrer Kontrolle wollen. In diesem lokalen Fixture erreichte es das erste Script-Ergebnis in 102 ms p50, räumte Chrome nach dem Abbruch in etwa 13 ms ab und nutzte bei vier parallelen Tabs nur einen Browserprozess. Das sind begrenzte Beobachtungen, keine universellen Performanceversprechen; der dauerhafte Reiz liegt im direkten CDP-Zugriff aus Go, wenn die Komfortschicht an ihre Grenze stößt.

Setzen Sie die Versprechen aber richtig an, denn der Ruf verkauft zwei Dinge zu gut. „Reines Go, keine Abhängigkeiten“ beschreibt das Modul; zur Laufzeit liefern und verwalten Sie trotzdem ein Chrome-Binary. Und „ein Headless-Browser holt die dynamischen Inhalte schon“ stimmt nur, wenn Ihr Wait an den gewünschten Knoten gekoppelt ist — ein naives Lesen und WaitReady("body") gaben mir beide eine Seite zurück, der Inhalte fehlten, die 800 ms nach dem Laden eingefügt wurden, stillschweigend, jedes Mal. Auf macOS ist defer cancel() keine Stilfrage; lassen Sie es weg, und Sie leaken pro Lauf einen Browser — bekanntes Plattformverhalten, aber trotzdem Ihr Problem. Wenn Sie diese drei Punkte richtig machen, ist chromedp einer der berechenbareren Browser-Treiber, die ich gemessen habe. Wenn Sie sie falsch machen, scheitert es still — und genau das ist für einen Scraper die schlechteste Art zu scheitern.

Thunderbit für Web-Datenextraktion testen Get Started Free

FAQs

Kann chromedp wirklich JavaScript-gerenderte Inhalte sehen? Ja — aber nur mit einem Wait, der auf den gewünschten Knoten ausgerichtet ist. In einem Fixture, in dem ein Link 800 ms nach dem Load-Event eingefügt wurde, verpassten Navigate plus Lesen ihn und auch WaitReady("body") verpasste ihn, während WaitVisible auf diesem Knoten und ein JavaScript-Poll ihn jeweils in drei von drei Läufen erfassten. Beim Durchsweepen der Verzögerung verpasste der Weg ohne Wartezeit den Knoten ab 100 ms in jedem Setting. Rendering ist nötig; korrektes Warten macht es erst ausreichend.

Was ist der Unterschied zwischen WaitReady und WaitVisible? WaitReady blockiert, bis der Knoten an das DOM angehängt ist. WaitVisible blockiert, bis er wirklich sichtbar ist. Bei einem Knoten, der angehängt, aber per display: none versteckt ist, kam WaitReady nach etwa 6 ms zurück, während WaitVisible bis zum 4-Sekunden-Context-Deadline blockierte und sauber mit context deadline exceeded endete. Keines von beiden ist „zuverlässiger“ — sie beantworten unterschiedliche Fragen, und die falsche Wahl ist die eigentliche Falle.

Brauche ich mit chromedp wirklich defer cancel()? Auf macOS: ja. Wenn Context und Allocator abgebrochen wurden, war der gestartete Chrome in allen Läufen nach 12–13 ms weg; wenn das Go-Programm ohne Abbruch beendet wurde, blieb in allen drei Läufen ein Browserprozess zurück. Das ist bekanntes, plattformspezifisches Verhalten — chromedps Tracker dokumentiert dasselbe Nicht-Beenden auch auf anderen Nicht-Linux-Systemen, und das Parent-Death-Killing dafür steckt im Linux-spezifischen Build-Tag-Quellcode. Ich habe Linux nicht getestet, also behandeln Sie das Orphan-Ergebnis als macOS-beschränkt.

Braucht chromedp eine separat installierte Chrome-Version? Ja. Das Go-Modul selbst ist reines Go ohne cgo, aber es steuert einen externen Browser und scheitert sofort ohne einen solchen. Ich habe ein Chrome for Testing Headless Shell 151.0.7922.10 explizit über chromedp.ExecPath bereitgestellt. Der Vorteil: Die Startkosten sind klein — ein kompletter Cold Cycle bis zum ersten Script-Ergebnis lag bei einem Median von 102 ms über fünf frische Prozesse, mit 98–111 ms Spannweite.

Soll ich einen Browser für mehrere Tabs teilen oder getrennte Browser starten? Wenn es darum geht, die Anzahl der Browserprozesse klein zu halten, sind Kind-Contexts die bessere Wahl. Vier lokale Navigationsläufe über einen Browser nutzten 1 Chrome-Browserprozess; vier separate Browser nutzten 4. Auch die Wall-Clock-Zeit sprach leicht für das geteilte Setup (214 ms statt 264 ms Median), aber vier triviale lokale Seiten sind kein Durchsatz-Benchmark. Speicher wurde nicht gemessen. Separate Browser können trotzdem richtig sein, wenn Sie stärkere Session-Isolation, unterschiedliche Proxies oder einen kleineren Ausfallradius brauchen; diese Abwägungen waren nicht Teil des Tests.

Ke
Ke
CTO bei Thunderbit | Senior Data Scientist & ML-Experte Mit fast zehn Jahren Erfahrung in Machine Learning und Data Science ist Ke Shen Absolvent der Columbia University und ehemaliger Senior Data Scientist bei Walmart Labs. Mit tiefgreifender, von Fachkollegen anerkannter Expertise in Python, R, Java und Statistik teilt er praxiserprobte Einblicke dazu, wie sich komplexe KI-Algorithmen von der Theorie in eine produktionsreife Architektur überführen lassen.
Inhaltsverzeichnis
Von der Webseite zur Tabelle
Beschreibe, was du brauchst — Thunderbits KI-Agent erfasst es und exportiert es nach Excel, Google Sheets, Airtable oder Notion. Kostenlos loslegen.
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week