Statische und dynamische Seiten beim Web Scraping

Veröffentlicht:

14 Min. Lesezeit

Acar Diveroli
Autor: Acar Diveroli
Ein Bot-Knoten zwischen einem statischen Fenster voller Codezeilen und einem dynamischen Fenster mit Platzhalter-Ladeelementen

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:

python
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:

  1. Der Browser erhält das HTML-Gerüst und zeichnet einen Platzhalter wie „Wird geladen..." auf den Bildschirm.
  2. JavaScript-Dateien werden heruntergeladen und ausgeführt. Oft ein Framework wie React oder Vue.
  3. JavaScript sendet eine API-Anfrage. Zum Beispiel per fetch an /api/products?category=headphones&page=1.
  4. Der Server liefert die Daten als JSON. Produktnamen, Preise, Bestandsinformationen.
  5. JavaScript baut aus diesem JSON HTML und fügt es in die Seite ein. Die Produktkarten erscheinen erst in diesem Schritt.
  6. 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:

  1. Quelltext anzeigen. Strg + U auf der Seite (Cmd + Option + U unter 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.
  2. 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.
  3. JavaScript abschalten und neu laden. Öffnen Sie in den Chrome-Entwicklertools mit Strg + Umschalt + P das Befehlsmenü, geben Sie „Disable JavaScript" ein und laden Sie die Seite neu. Verschwindet der Inhalt, ist die Seite dynamisch.
  4. Mit Code prüfen. Suchen Sie im per requests erhaltenen HTML nach einem erwarteten Text. Fehlt er und ist das HTML sehr kurz, haben Sie ein Gerüst erhalten.
  5. 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:

  1. Öffnen Sie die Entwicklertools (F12) und wechseln Sie zum Tab Network.
  2. Wählen Sie in der Filterleiste Fetch/XHR.
  3. 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).
  4. Klicken Sie auf Anfragen, deren Name Wörter wie api, graphql, search oder products enthält, und sehen Sie sich das JSON im Tab Response an.
  5. 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:

python
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.

python
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:

python
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?

AnsatzWann er funktioniertGeschwindigkeit und RessourcenAnfälligkeitWorauf zu achten ist
HTTP-Client + HTMLDie Seite ist statisch, der Inhalt steht im QuelltextSehr schnell, sehr leichtEmpfindlich bei DesignänderungenSelektoren an stabile Attribute binden
In HTML eingebettetes JSONMischseiten wie Next.js, NuxtSehr schnell, leichtDie Datenstruktur kann sich ändernFehlerbehandlung, die den JSON-Pfad prüft
API-Anfrage im HintergrundDer Inhalt kommt als JSONSehr schnell, am leichtestenUnabhängig vom Design, die API kann sich ändernBedingungen der Website, Tokens, Rate-Limits
Headless-BrowserInteraktion nötig, Daten sonst nicht erreichbarLangsam, schwerEmpfindlich bei der WartelogikBilder 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 networkidle warten. 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

DiagnoseergebnisEmpfehlung
Der Text steht im QuelltextHTTP-Client + HTML-Parsing
Der Quelltext enthält __NEXT_DATA__ oder JSON-LDHTTP-Client + JSON-Parsing
Das Network-Panel zeigt eine JSON-Anfrage mit den DatenDie Anfrage direkt wiederholen
Die JSON-Anfrage braucht eine kurzlebige SignaturHeadless-Browser + expect_response
Der Inhalt kommt nur durch InteraktionHeadless-Browser + Warten mit Locators
Der Headless-Browser ist langsam und schwerBilder blockieren, Nebenläufigkeit senken
Kein Inhalt im HTML und Statuscode 403/429Nicht 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.

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.

ChatGPT fragenClaude fragen