Fast jeder, der mit Scraping beginnt, erlebt denselben Moment: Produkte, Preise und Bewertungen stehen im Browser direkt vor Ihnen, doch wenn Sie dieselbe Adresse in Python mit requests abrufen, finden die Selektoren nichts. Im HTML der Seite sehen Sie statt einer Produktliste den Text „Wird geladen..." und einige <script>-Tags. Das Problem liegt nicht in Ihrem Code; die Seite ist dynamisch, und im Browser laufendes JavaScript holt den Inhalt nachträglich.
In diesem Artikel erklären wir den Unterschied zwischen statischen und dynamischen Seiten, wie Sie in wenigen Minuten erkennen, ob eine Seite dynamisch ist, und was ein Headless-Browser ist. Den Schwerpunkt legen wir darauf, wie Sie in den meisten Fällen ganz ohne Headless-Browser an die Daten kommen: indem Sie die API-Anfrage finden, die die Seite im Hintergrund stellt, und in das HTML eingebettetes JSON lesen. Ist ein Browser wirklich nötig, zeigen wir die richtigen Wartestrategien in Playwright und Wege, die Kosten zu senken. Die Beispiele haben wir auf einer lokalen Testseite ausgeführt, die ihren Inhalt mit JavaScript lädt.
Was ist eine statische Seite?
Bei einer statischen Seite steht alles, was der Browser anzeigt, im HTML der ersten Serverantwort. Der Server kann dieses HTML aus einer vorbereiteten Datei lesen oder bei jeder Anfrage aus einer Datenbank erzeugen; für Scraping zählt, dass der Inhalt bereits im HTML steht, wenn er beim Browser ankommt.
Um Daten von einer statischen Seite zu erhalten, brauchen Sie keinen Browser:
import requests
from bs4 import BeautifulSoup
html = requests.get("https://example.com/deals", timeout=20).text
cards = BeautifulSoup(html, "html.parser").select("div.product")
print("Gefundene Produktkarten:", len(cards))Nachrichtenseiten, Blogs, viele Unternehmensseiten und serverseitig gerenderte Onlineshops gehören zu dieser Gruppe.
Was ist eine dynamische (per JavaScript geladene) Seite?
Bei einer dynamischen Seite ist die erste Serverantwort ein Gerüst: das Seitenlayout, ein leerer Listencontainer und JavaScript-Dateien. Der Inhalt kommt nachträglich in diesen Schritten:
- Der Browser erhält das HTML-Gerüst und zeichnet einen Platzhalter wie „Wird geladen..." auf den Bildschirm.
- JavaScript-Dateien werden heruntergeladen und ausgeführt. Oft ein Framework wie React oder Vue.
- JavaScript sendet eine API-Anfrage. Zum Beispiel per
fetchan/api/products?category=headphones&page=1. - Der Server liefert die Daten als JSON. Produktnamen, Preise, Bestandsinformationen.
- JavaScript baut aus diesem JSON HTML und fügt es in die Seite ein. Die Produktkarten erscheinen erst in diesem Schritt.
- Scrollen oder Klicken löst neue Anfragen aus. Unendliches Scrollen, eine Schaltfläche „Mehr anzeigen", Filter.
Ein HTTP-Client wie requests in Python erledigt nur den ersten Schritt; er führt kein JavaScript aus. Deshalb enthält das empfangene HTML keine Produktkarten. Auf unserer lokalen Testseite fand der requests-Code oben null Karten; dieselbe Seite zeigte im Browser zwei Produkte.
Daneben gibt es eine Mischform: Frameworks wie Next.js und Nuxt rendern den ersten Zustand der Seite auf dem Server und betten dieselben Daten zusätzlich als JSON-Block in das HTML ein, damit JavaScript sie nutzen kann. Auf solchen Seiten stehen die Daten ohne JavaScript im HTML, manchmal aber nicht in den sichtbaren Karten, sondern in einem <script>-Tag.
Wie erkennen Sie, ob eine Seite dynamisch ist?
Eine Diagnose von wenigen Minuten vor der Einrichtung eines Headless-Browsers hilft, den richtigen Weg zu wählen:
- Quelltext anzeigen.
Strg + Uauf der Seite (Cmd + Option + Uunter macOS) zeigt das rohe HTML, das der Server gesendet hat. Suchen Sie dort nach einem Produktnamen oder Preis von der Seite. Finden Sie ihn, ist die Seite wahrscheinlich statisch. - Mit dem Elements-Panel vergleichen. Das Elements-Panel der Entwicklertools zeigt die Seite nach der Ausführung von JavaScript. Steht der Text in Elements, aber nicht im Quelltext, kam der Inhalt per JavaScript.
- JavaScript abschalten und neu laden. Öffnen Sie in den Chrome-Entwicklertools mit
Strg + Umschalt + Pdas Befehlsmenü, geben Sie „Disable JavaScript" ein und laden Sie die Seite neu. Verschwindet der Inhalt, ist die Seite dynamisch. - Mit Code prüfen. Suchen Sie im per
requestserhaltenen HTML nach einem erwarteten Text. Fehlt er und ist das HTML sehr kurz, haben Sie ein Gerüst erhalten. - Den Fetch/XHR-Filter im Network-Panel ansehen. Sehen Sie nach dem Neuladen unter diesem Filter JSON-Antworten, liegen die Daten wahrscheinlich in einer dieser Anfragen.
Schritt vier hat eine Falle: Fehlt der Inhalt im HTML, ist nicht immer JavaScript der Grund. Die Website hat womöglich eine Prüfseite oder anderen Inhalt geliefert. Prüfen Sie Statuscode und Inhalt der Antwort; diese Diagnose erklären wir ausführlich in Web Scraping ohne Sperren.
Was ist ein Headless-Browser?
Ein Headless-Browser ist ein echter Browser, der ohne Fenster auf dem Bildschirm läuft. Die Engine von Chromium, Firefox oder WebKit lädt die Seite wie ein normaler Browser, führt JavaScript aus, sendet API-Anfragen und baut die Seite auf; Sie lesen diese Seite per Code. Verbreitete Werkzeuge sind Playwright, Puppeteer und Selenium.
Ein Headless-Browser kann mehr als ein HTTP-Client, kostet aber auch mehr:
- CPU und Speicher. Jeder Browser-Tab verbraucht ein Vielfaches der Ressourcen einer HTTP-Anfrage. Wie viele Seiten gleichzeitig auf einer Maschine laufen, begrenzen Kerne und Speicher, nicht das Netz.
- Zeit. Das Herunterladen und Ausführen des gesamten JavaScripts einer Seite kann Sekunden dauern.
- Anfragezahl und Bandbreite. Eine einzige Seite erzeugt Dutzende zusätzliche Anfragen für Bilder, Schriften, Stylesheets, Analyse-Skripte und Werbung. Das erhöht Ihren Verkehr und die Last der Zielseite.
- Wartung. Browserversionen, Treiber und Wartelogik erhöhen die Komplexität.
Deshalb sollte ein Headless-Browser das letzte Mittel sein, nicht die erste Wahl. Einen Vergleich der Werkzeuge finden Sie in Web Scraping: JavaScript oder Python?, die Einrichtung von Selenium in Proxys mit Selenium verwenden.
Zuerst die API/XHR-Anfrage finden
Die Daten einer dynamischen Seite kommen irgendwoher als JSON beim Browser an. Finden Sie diese Anfrage, erhalten Sie die Daten direkt, ohne die Seite überhaupt zu rendern. Die Referenz zum Network-Panel von Chrome erklärt Filter und Kopieroptionen ausführlich; der kurze Weg:
- Öffnen Sie die Entwicklertools (F12) und wechseln Sie zum Tab Network.
- Wählen Sie in der Filterleiste Fetch/XHR.
- Laden Sie die Seite neu oder führen Sie die Aktion aus, die die Daten lädt (Seite scrollen, Filter wählen, zur nächsten Seite wechseln).
- Klicken Sie auf Anfragen, deren Name Wörter wie
api,graphql,searchoderproductsenthält, und sehen Sie sich das JSON im Tab Response an. - Haben Sie die Anfrage mit den benötigten Daten gefunden, notieren Sie Adresse, Parameter und nötige Header aus dem Tab Headers. Mit Rechtsklick auf die Anfrage und Copy > Copy as cURL können Sie sie auf der Befehlszeile ausprobieren.
Die gefundene Anfrage in Python zu wiederholen, braucht meist nur wenige Zeilen:
import requests
session = requests.Session()
session.headers.update({
"User-Agent": "ExamplePriceBot/1.0 (+https://example.com/about-our-bot)",
"Accept": "application/json",
})
response = session.get(
"https://example.com/api/products",
params={"category": "headphones", "page": 1},
timeout=20,
)
response.raise_for_status()
for product in response.json()["products"]:
print(product["name"], product["price"])Die Vorteile dieses Wegs sind groß: Die Daten kommen strukturiert, Sie brauchen keine HTML-Selektoren, Ihr Code bricht bei Designänderungen nicht, und eine einzelne Anfrage ist ein Bruchteil der Last einer ganzen Seite. Geblättert wird meist mit einem Parameter page, offset oder cursor.
Worauf Sie achten sollten:
- Authentifizierung und Tokens. Manche API-Anfragen verlangen Cookies, Sitzungstokens oder kurzlebige Signaturen. Diese von Hand zu kopieren, funktioniert kurz und bricht dann.
- Bedingungen der Website. Eine interne API, die die Website für ihr eigenes Frontend nutzt, ist keine öffentliche API. Wenden Sie Nutzungsbedingungen und robots.txt-Regeln der Website auch auf diese Anfragen an; gibt es eine offizielle API, bevorzugen Sie diese. Den rechtlichen Rahmen finden Sie in Ist Web Scraping legal?.
- Geschwindigkeit. Weil API-Anfragen leicht sind, lassen sie sich sehr schnell senden, und damit überschreiten Sie die Limits einer Website auch leichter. Wenden Sie dieselben Geschwindigkeitsregeln an.
In das HTML eingebettetes JSON lesen
Bei Websites mit Next.js, Nuxt und ähnlichen Frameworks liegen die Daten oft fertig in einem <script>-Tag im HTML. Zeigen Sie den Quelltext an und suchen Sie nach __NEXT_DATA__, __NUXT__, __INITIAL_STATE__ oder application/ld+json.
import json
import requests
from bs4 import BeautifulSoup
html = requests.get("https://example.com/deals", timeout=20).text
soup = BeautifulSoup(html, "html.parser")
data = json.loads(soup.select_one("script#__NEXT_DATA__").string)
for product in data["props"]["pageProps"]["products"]:
print(product["name"], product["price"])application/ld+json-Blöcke enthalten strukturierte Daten wie Produktname, Preis, Bestand und Bewertung im schema.org-Format und sind, weil sie für Suchmaschinen vorbereitet werden, meist stabil. Wie Selektoren robust gegenüber Designänderungen werden, behandeln wir in CSS-Selektor oder XPath.
Wartestrategien in Playwright
Liegen die Daten weder in einer API noch in eingebettetem JSON, oder braucht es für den Inhalt Interaktionen wie Klicken, Scrollen und Ausfüllen von Formularen, kommt ein Headless-Browser zum Einsatz. Der häufigste Fehler ist dann der Zeitpunkt des Lesens: Lesen Sie sofort nach dem Öffnen, ist der Inhalt noch nicht da; warten Sie eine feste Zeit, verlieren Sie entweder Zeit oder erhalten bei einer langsamen Antwort trotzdem ein leeres Ergebnis.
Die richtigen Wartewerkzeuge in Playwright:
- Automatisches Warten von Locators. Operationen wie
page.locator("div.product h2").inner_text()warten bis zum Standard-Timeout, bis das Element auf der Seite erscheint. Die Dokumentation zu Aktionsprüfungen von Playwright listet, worauf gewartet wird. - Auf ein bestimmtes Element warten.
page.locator("div.product").first.wait_for()wartet, bis die erste Produktkarte erscheint. - Auf eine bestimmte Antwort warten.
page.expect_response(...)wartet auf die Rückkehr der Datenanfrage der Seite und gibt Ihnen direkt das JSON. - Keine festen Wartezeiten.
page.wait_for_timeout(5000)ist in Tests nützlich, in der Produktion unzuverlässig.
Die Playwright-Dokumentation rät vom Ladezustand networkidle ab, also vom Warten, bis der Netzwerkverkehr eine Weile ruht; auf Seiten, deren Analyse- und Werbeskripte ständig Anfragen senden, tritt dieser Zustand womöglich nie ein.
Das folgende Beispiel öffnet die Seite über einen Proxy, blockiert Bilder und Schriften zur Lastsenkung, fängt die Antwort der Datenanfrage ab und liest dann die gerenderten Karten:
from playwright.sync_api import sync_playwright
PROXY = {"server": "http://pr.proxynet.io:8000", "username": "user", "password": "pass"}
with sync_playwright() as p:
browser = p.chromium.launch(proxy=PROXY)
page = browser.new_page()
# Bilder und Schriften nicht laden, um Zeit und Traffic zu sparen
page.route("**/*.{png,jpg,jpeg,webp,gif,woff2}", lambda route: route.abort())
with page.expect_response(lambda r: "/api/products" in r.url and r.ok) as response:
page.goto("https://example.com/deals")
api_data = response.value.json() # JSON direkt erhalten
page.locator("div.product").first.wait_for() # oder auf die gerenderten Karten warten
names = page.locator("div.product h2").all_inner_texts()
print(len(api_data["products"]), names)
browser.close()In diesem Beispiel genügt das mit expect_response abgefangene JSON oft allein; die Karten müssen gar nicht gelesen werden. Den Browser nutzen Sie dann nur, damit die Seite diese Anfrage mit den richtigen Parametern und Cookies sendet.
In Playwright werden Proxy-Daten beim Start des Browsers angegeben, Benutzername und Passwort in eigenen Feldern. Für Abläufe, bei denen die IP über die Sitzung gleich bleiben muss, eignet sich eine feste Ausgangsadresse; für viele unabhängige Seiten ein Rotierender Proxy, der bei jeder Verbindung eine andere IP liefert. Warum Prüfbildschirme bei der Browserautomatisierung erscheinen, erklären wir in Puppeteer und CAPTCHA.
Kosten: Wann ist ein Headless-Browser unnötig?
| Ansatz | Wann er funktioniert | Geschwindigkeit und Ressourcen | Anfälligkeit | Worauf zu achten ist |
|---|---|---|---|---|
| HTTP-Client + HTML | Die Seite ist statisch, der Inhalt steht im Quelltext | Sehr schnell, sehr leicht | Empfindlich bei Designänderungen | Selektoren an stabile Attribute binden |
| In HTML eingebettetes JSON | Mischseiten wie Next.js, Nuxt | Sehr schnell, leicht | Die Datenstruktur kann sich ändern | Fehlerbehandlung, die den JSON-Pfad prüft |
| API-Anfrage im Hintergrund | Der Inhalt kommt als JSON | Sehr schnell, am leichtesten | Unabhängig vom Design, die API kann sich ändern | Bedingungen der Website, Tokens, Rate-Limits |
| Headless-Browser | Interaktion nötig, Daten sonst nicht erreichbar | Langsam, schwer | Empfindlich bei der Wartelogik | Bilder blockieren, Nebenläufigkeit niedrig halten |
Typische Fälle, in denen ein Headless-Browser unnötig ist:
- Der Inhalt steht bereits im Quelltext der Seite.
- Die Daten kommen aus einer JSON-Anfrage, die sich mit einfachen Parametern wiederholen lässt.
- Die Daten liegen in einem
__NEXT_DATA__- oder JSON-LD-Block. - JavaScript wird nur für die Navigation zwischen Seiten genutzt, der Inhalt kommt auf jeder Seite vom Server.
Fälle, in denen ein Headless-Browser wirklich nötig ist:
- Der Inhalt lädt nur durch Scrollen, Klicken oder Formularinteraktion, und die zugehörige API-Anfrage lässt sich nicht wiederholen.
- Anfragen brauchen kurzlebige Signaturen oder Tokens, die das JavaScript der Seite erzeugt.
- Die visuelle Ausgabe der Seite (ein Screenshot, eine Layoutprüfung) ist selbst die Aufgabe.
Nutzen Sie einen Headless-Browser, stellen Sie die Zahl gleichzeitiger Seiten nach den Maschinenressourcen ein; das behandeln wir in Concurrency und Parallelism.
Anwendungsfälle
- Preisbeobachtung im E-Commerce: Die Kategorieseite ist dynamisch, doch die Produktliste kommt aus einer einzigen Anfrage
/api/products. Die API-Anfrage statt eines Headless-Browsers; um Preise in verschiedenen Ländern zu sehen, ein Residential-Proxy, der über echte Privatanschlüsse ins Netz geht. Den Aufbau beschreibt unsere Seite zur E-Commerce-Proxy-Lösung. - Eine mit Next.js gebaute Anzeigenseite: Die Daten liegen in
__NEXT_DATA__. HTTP-Client und JSON-Parsing genügen. - Eine Bewertungsliste mit unendlichem Scrollen: Die beim Scrollen gesendete Blätteranfrage wird gefunden und mit ihrem
cursor-Parameter in einer Schleife abgearbeitet. - Ein Kontopanel mit Interaktion (Ihr eigenes Konto): Anmeldung, Filterauswahl und Berichtsdownload mit einem Headless-Browser; dieselbe IP für die Sitzung. Den allgemeinen Aufbau zeigt unsere Seite zur Datenerfassung.
Häufige Fehler
- Zuerst zu einem Headless-Browser wechseln. Die JSON-Anfrage im Network-Panel liefert dieselben Daten oft viel günstiger.
- Eine feste Zeit warten.
time.sleep(5)verschwendet bei schnellen Antworten Zeit und liefert bei langsamen trotzdem leere Ergebnisse. - Auf
networkidlewarten. Auf Seiten, die ständig Anfragen senden, tritt der Zustand womöglich nie ein. - Alle Ressourcen herunterladen. Bilder, Schriften und Videos braucht es für Daten nicht; sie zu blockieren, spart Zeit und Traffic.
- Ein leeres Ergebnis für eine dynamische Seite halten. Auch eine Prüfseite oder Sperre liefert ein leeres Ergebnis; prüfen Sie Statuscode und Inhalt.
- Eine interne API wie eine öffentliche nutzen. Bedingungen und Rate-Limits der Website gelten auch für diese Anfragen.
- Zu Werkzeugen zur Umgehung der Erkennung greifen. Plugins, die den Browser „menschlich" wirken lassen sollen, verdecken die Ursache; Geschwindigkeit, Umfang und Erlaubnis zu überprüfen, ist der solidere Weg. Warum solche Werkzeuge problematisch sind, diskutieren wir in Undetected ChromeDriver für Web Scraping.
Entscheidungshilfe
| Diagnoseergebnis | Empfehlung |
|---|---|
| Der Text steht im Quelltext | HTTP-Client + HTML-Parsing |
Der Quelltext enthält __NEXT_DATA__ oder JSON-LD | HTTP-Client + JSON-Parsing |
| Das Network-Panel zeigt eine JSON-Anfrage mit den Daten | Die Anfrage direkt wiederholen |
| Die JSON-Anfrage braucht eine kurzlebige Signatur | Headless-Browser + expect_response |
| Der Inhalt kommt nur durch Interaktion | Headless-Browser + Warten mit Locators |
| Der Headless-Browser ist langsam und schwer | Bilder blockieren, Nebenläufigkeit senken |
| Kein Inhalt im HTML und Statuscode 403/429 | Nicht dynamisch, sondern eine Sperre; diagnostizieren |
Häufig gestellte Fragen
Warum liefert requests nicht den Inhalt, den ich im Browser sehe?
Weil requests nur das erste HTML des Servers holt und kein JavaScript ausführt. Wird der Seiteninhalt später per JavaScript geladen, enthält dieses HTML nur das Gerüst. Suchen Sie im Network-Panel die Anfrage, aus der die Daten kommen, oder nach in das HTML eingebettetem JSON.
Was ist der Unterschied zwischen Headless- und normalem Browser?
Die Engine ist dieselbe; im Headless-Modus wird nur kein Fenster auf den Bildschirm gezeichnet. Laden der Seite, Ausführen von JavaScript und Netzwerkanfragen funktionieren wie in einem normalen Browser. Manche Websites können Headless-Browser an kleinen Unterschieden erkennen.
Playwright oder Selenium?
Beide führen dynamische Seiten aus. Playwright bietet für Scraping hilfreiche Funktionen wie automatisches Warten, Abfangen von Netzwerkantworten und Blockieren von Anfragen in einer API; Selenium ist älter, mehrsprachig und hat ein breites Ökosystem. Mit dem Werkzeug Ihres bestehenden Projekts weiterzuarbeiten, ist meist am praktischsten.
Ist die Nutzung einer versteckten API-Anfrage legal?
Dass eine Anfrage technisch öffentlich ist, macht ihre Nutzung nicht uneingeschränkt zulässig. Berücksichtigen Sie Nutzungsbedingungen, robots.txt-Regeln und Datenschutzgesetze; gibt es eine offizielle API, bevorzugen Sie diese. Sind Sie unsicher, holen Sie rechtlichen Rat ein.
Wie nutze ich einen Headless-Browser mit Proxy?
In Playwright wird der Proxy beim Start des Browsers mit dem Parameter proxy angegeben; Serveradresse, Benutzername und Passwort sind eigene Felder. In Puppeteer und Selenium wird er als Startargument an den Browser übergeben, und die Authentifizierung wird gesondert behandelt.
Wie kommen Sie an Daten von Seiten mit unendlichem Scrollen?
Suchen Sie zuerst im Network-Panel die Blätteranfrage, die beim Scrollen gesendet wird. Sie trägt meist eine Seitennummer oder einen cursor-Wert und lässt sich in einer Schleife wiederholen. Lässt sie sich nicht wiederholen, scrollen Sie die Seite im Headless-Browser schrittweise und warten bei jedem Schritt auf neue Karten.
Fazit
Bei einer statischen Seite steht der Inhalt fertig im HTML, und eine einfache HTTP-Anfrage genügt; bei einer dynamischen Seite kommt der Inhalt später per JavaScript, und eine HTTP-Anfrage liefert ein Gerüst. Bevor Sie zu einem Headless-Browser wechseln, sehen Sie sich den Quelltext an, suchen im Network-Panel die JSON-Anfrage mit den Daten und prüfen auf eingebettete Daten im HTML. Ist ein Browser wirklich nötig, nutzen Sie statt fester Wartezeiten das Warten mit Locators und expect_response, blockieren Bilder und halten die Nebenläufigkeit niedrig. Proxy-Typen für Ihre Datenerfassung finden Sie in unseren Proxy-Diensten.




