Viele der lohnenswerten Daten im modernen Web erscheinen nie im Seitenquelltext. Ein Produktraster, das sich beim Scrollen füllt, eine Tabelle, die sich bei einer Filteränderung aktualisiert, ein Dashboard, das seine Zahlen kurz nach dem Layout lädt: All diese verwenden AJAX (Asynchronous JavaScript and XML), um Inhalte im Hintergrund abzurufen und in eine bereits geladene Seite einzufügen. Das sorgt für ein flüssiges Erlebnis und bricht dabei stillschweigend die einfachsten Scraper.

Diese Anleitung zeigt Ihnen, wie Sie mit Python Daten von AJAX-gesteuerten Websites scrapen. Sie erstellen einen kleinen, lauffähigen Scraper, der die Seite über die Crawling API rendert, auf das Eintreffen des asynchronen Inhalts wartet, die über XHR geladenen Daten erfasst, sie analysiert und sauberes JSON und CSV exportiert. Die Anleitung verwendet einen neutralen Platzhalter-Angebote-Bereich, damit Sie die Mechanik nachvollziehen und denselben Ablauf dann auf Ihr eigenes Ziel anwenden können.

Was Sie erstellen werden

Ein Python-Skript, das eine AJAX-Seite über Crawlbase abruft, die asynchron geladenen Daten liest und jeden Artikel in einen strukturierten Datensatz umwandelt. Das laufende Beispiel ist ein generisches öffentliches Angebot, bei dem jede Karte einen Namen, einen Preis und eine Kategorie enthält. Wir lesen diese Felder pro Artikel:

  • Name der Titel, der auf jeder Angebotskarte angezeigt wird.
  • Price der für den Artikel gerenderte numerische Preis.
  • Category die Gruppe oder das Tag, zu dem der Artikel gehört.
  • Link die URL der Detailseite des Artikels.

Sie sehen zwei Wege zu denselben Daten: den AJAX-Aufruf direkt replizieren und die vollständige Seite rendern, wobei beide im gleichen Exportschritt enden.

Warum eine einfache Anfrage bei AJAX-Seiten scheitert

Fragen Sie eine AJAX-gesteuerte URL mit einem einfachen HTTP-Client an und Sie erhalten Status 200 zurück, aber kaum einen der gewünschten Daten. Der Grund ist das Timing. Der Server sendet eine dünne HTML-Hülle, der Browser führt das JavaScript der Seite aus, und erst dann feuert das Skript die Hintergrundanfragen (die AJAX-Aufrufe), die den echten Inhalt zurückgeben und ihn in das DOM einfügen. Ein einfaches requests.get stoppt bei der Hülle: Es führt niemals das JavaScript aus, löst daher nie die Folgeaufrufe aus, und der von Ihnen geparste Body ist größtenteils ein leeres Layout.

Es gibt zwei ehrliche Wege darum herum. Der erste ist, den AJAX-Endpunkt zu finden, den die Seite im Hintergrund aufruft, und ihn direkt anzufragen, was schnell ist, da das Rendering übersprungen wird. Der zweite ist, die gesamte Seite in einem echten Browser zu rendern, sodass der asynchrone Inhalt geladen wird, und dann das fertige HTML zu parsen. Das ist der Weg, den Sie möchten, wenn der Endpunkt signiert oder schwer zu reproduzieren ist. Die Anleitung JavaScript-Websites crawlen behandelt die Rendering-Seite ausführlich.

Normales Token vs. JS-Token

Crawlbase bietet zwei Token-Typen. Das normale Token ruft statisches HTML ab; das JavaScript (JS)-Token rendert die Seite zuerst in einem echten Browser. Da AJAX-Inhalte erst nach der Skriptausführung erscheinen, verwenden Sie hier das JS-Token, kombiniert mit ajax_wait und page_wait, damit die API auf das Ende der Hintergrundaufrufe wartet, bevor sie die Seite aufzeichnet.

Voraussetzungen

Bevor Sie mit dem Schreiben von Code beginnen, müssen einige Dinge bereitstehen.

Python-Grundkenntnisse. Sie sollten in der Lage sein, ein Python-Skript zu schreiben, auszuführen und Pakete mit pip zu installieren. Falls das Parsen neu für Sie ist, ergänzt die BeautifulSoup-Anleitung dieses Tutorial gut.

Python 3.8 oder neuer. Prüfen Sie Ihre Version mit python --version. Falls nicht vorhanden, installieren Sie es von python.org und stellen Sie sicher, dass Python in Ihrem PATH enthalten ist.

Ein Crawlbase-Konto und JS-Token. Registrieren Sie sich, öffnen Sie Ihr Dashboard und kopieren Sie Ihr JavaScript (JS)-Token. Crawlbase enthält bis zu 20.000 kostenlose Anfragen zum Start, was für diese Anleitung mehr als ausreichend ist. Behandeln Sie das Token wie ein Passwort und halten Sie es aus der Versionsverwaltung heraus.

Das Projekt einrichten

Erstellen Sie eine virtuelle Umgebung, damit die Projektabhängigkeiten isoliert bleiben, und installieren Sie dann die Bibliotheken, die der Scraper benötigt.

bash
python --version

python -m venv ajax_env
source ajax_env/bin/activate

pip install crawlbase beautifulsoup4

Unter Windows aktivieren Sie die Umgebung mit ajax_env\Scripts\activate anstelle der source-Zeile. Zwei Abhängigkeiten erledigen die Arbeit: crawlbase ist der offizielle Client für die Crawling API, und beautifulsoup4 analysiert das zurückgegebene HTML beim Weg über die gerenderte Seite. Sowohl json als auch csv sind in der Standardbibliothek enthalten, sodass für den Exportschritt nichts weiter zu installieren ist.

Schritt 1: Die AJAX-Anfrage identifizieren

Bevor Sie Code schreiben, finden Sie den Hintergrundaufruf, den die Seite macht. Öffnen Sie das Ziel in Chrome, klicken Sie mit der rechten Maustaste und wählen Sie Untersuchen (oder drücken Sie Strg+Umschalt+I) und wechseln Sie zum Tab Netzwerk. Filtern Sie nach XHR, um die XMLHttpRequest- und Fetch-Aufrufe von Bildern und Stylesheets zu trennen, und laden Sie dann die Seite neu. Wenn sich der Inhalt füllt, erscheint die Anfrage, die ihn transportiert hat. Klicken Sie darauf, um die Anfrage-URL, ihre Abfrageparameter und das zurückgegebene JSON zu lesen.

Für den hier verwendeten Platzhalter lädt die Seite ihre Artikel von einem JSON-Endpunkt, der so aussieht:

bash
https://example.com/api/items?page=1&limit=20

Dieser Endpunkt gibt dieselben Daten zurück, die die Seite anzeigt, nur als sauberes JSON anstelle von gerendertem HTML. Wenn ein solcher Aufruf existiert und erreichbar ist, ist die direkte Anfrage der einfachste Weg. Wenn er signiert, an eine Sitzung gebunden oder anderweitig schwer zu reproduzieren ist, rendern Sie stattdessen die Seite. Beide Wege folgen unten.

Schritt 2: Den AJAX-Endpunkt über Crawlbase abrufen

Auch ein sauberer JSON-Endpunkt kann automatisierten Traffic von einer Datacenter-IP Rate-limiten oder blockieren. Die Anfrage über Crawlbase zu leiten gibt Ihnen eine vertrauenswürdige IP und integrierte Rotation, sodass die Anfrage wie ein echter Besucher aussieht. Importieren Sie die Klasse CrawlingAPI, initialisieren Sie sie mit Ihrem Token und fordern Sie den Endpunkt an. Die Überprüfung von cb_status (legacy pc_status) vor dem Parsen sorgt dafür, dass Fehler auffällig und nicht still auftreten.

python
import json
from crawlbase import CrawlingAPI

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})

endpoint = "https://example.com/api/items?page=1&limit=20"

def fetch_json(url):
    response = api.get(url)
    if response["headers"]["cb_status"] == "200":
        return json.loads(response["body"].decode("utf-8"))
    print(f"Request failed: {response['headers']['cb_status']}")
    return None

if __name__ == "__main__":
    data = fetch_json(endpoint)
    print(data if data else "No data returned")

Führen Sie das Skript mit python ajax_scraper.py aus und Sie sollten das rohe JSON sehen, das die Seite im Browser geladen hätte, abgerufen in einem einzigen Aufruf ohne das Rendern von irgendetwas. Das bestätigt, dass der Endpunkt erreichbar ist, bevor Sie eine einzige Zeile Parsing schreiben.

Crawlbase Crawling API

Der oben genannte Abruf erreichte einen AJAX-Endpunkt, ohne dass Sie einen Browser ausführen oder IPs verwalten müssen, was genau das ist, was die Crawling API übernimmt. Geben Sie ihr ein normales Token für einen sauberen JSON-Endpunkt oder das JS-Token plus ajax_wait und page_wait, wenn Sie die vollständige Seite gerendert benötigen. Sie rotiert Residential IPs serverseitig und gibt fertigen Inhalt zurück, sodass Sie keine eigene Headless-Flotte und keinen Proxy-Pool betreiben müssen. Richten Sie sie zuerst auf einer öffentlichen Seite im kostenlosen Kontingent aus.

Schritt 3: Die JSON-Antwort analysieren

Der Endpunkt gibt strukturiertes JSON zurück, also gibt es kein HTML zu parsen. Gehen Sie durch das Objekt zur Liste der Artikel und lesen Sie die gewünschten Felder aus. Die genauen Schlüsselnamen hängen von Ihrem Ziel ab, also untersuchen Sie die Antwort aus Schritt 1 und ordnen Sie sie zu. Beim Platzhalter befinden sich die Artikel unter einem items-Schlüssel, jeder mit name, price, category und url.

python
def parse_items(data):
    records = []
    for item in data.get("items", []):
        records.append({
            "name": item.get("name"),
            "price": item.get("price"),
            "category": item.get("category"),
            "link": item.get("url"),
        })
    return records

Die Verwendung von dict.get anstelle des eckigen Klammer-Zugriffs bedeutet, dass ein fehlender Schlüssel None zurückgibt, anstatt einen KeyError auszulösen, sodass ein fehlerhafter Artikel den Durchlauf nicht beendet. Übergeben Sie das JSON aus Schritt 2 an parse_items und Sie erhalten eine ordentliche Liste von Datensätzen, bereit zum Exportieren.

Schritt 4: Die vollständige Seite rendern, wenn kein sauberer Endpunkt vorhanden ist

Manchmal ist der AJAX-Aufruf signiert, an einen Sitzungs-Cookie gebunden oder auf mehrere Anfragen aufgeteilt, und ihn zu replizieren ist mehr Aufwand als es wert ist. Rendern Sie in diesem Fall die gesamte Seite mit dem JS-Token, lassen Sie Crawlbase auf den asynchronen Inhalt warten und analysieren Sie dann das fertige Markup mit BeautifulSoup wie jede andere statische Seite.

python
from bs4 import BeautifulSoup

RENDER_OPTIONS = {
    "ajax_wait": "true",
    "page_wait": 5000,
}

def fetch_rendered(url):
    response = api.get(url, RENDER_OPTIONS)
    if response["headers"]["cb_status"] == "200":
        return response["body"].decode("utf-8")
    print(f"Request failed: {response['headers']['cb_status']}")
    return None

def parse_cards(html):
    soup = BeautifulSoup(html, "html.parser")
    records = []
    for card in soup.select("div.item-card"):
        link = card.select_one("a.item-link")
        records.append({
            "name": text_of(card, "h2.item-name"),
            "price": text_of(card, "span.item-price"),
            "category": text_of(card, "span.item-category"),
            "link": link["href"] if link else None,
        })
    return records

Die zwei Warteoptionen tragen hier die Last. ajax_wait weist die API an, auf das Ende des asynchronen Inhalts zu warten, und page_wait fügt nach dem Laden eine feste Pause in Millisekunden hinzu, damit spät gerenderte Karten vor der Aufzeichnung erscheinen. Fünf Sekunden sind ein vernünftiger Ausgangspunkt; erhöhen Sie ihn, wenn Artikel dünn zurückkommen. Der parse_cards-Helfer liest dann jede div.item-card und ordnet dieselben vier Felder zu, sodass seine Ausgabe genau mit parse_items übereinstimmt. Der hier verwendete text_of-Helfer ist im vollständigen Skript unten definiert.

Schritt 5: Paginierung verwalten und das Skript zusammensetzen

Eine Seite ist selten das gesamte Dataset. Die meisten AJAX-Angebote paginieren über einen Abfrageparameter (hier page), also iterieren Sie über Seitenzahlen, sammeln Datensätze von jeder und stoppen, wenn eine Seite leer zurückkommt. Verbinden Sie diese Schleife mit den Abruf-, Analyse- und Exportschritten in einem einzigen lauffähigen Skript.

python
import csv
import json
import time
from crawlbase import CrawlingAPI
from bs4 import BeautifulSoup

api = CrawlingAPI({"token": "YOUR_CRAWLBASE_TOKEN"})
BASE = "https://example.com/api/items?limit=20&page="

def fetch_json(url):
    response = api.get(url)
    if response["headers"]["cb_status"] == "200":
        return json.loads(response["body"].decode("utf-8"))
    print(f"Request failed: {response['headers']['cb_status']}")
    return None

def text_of(node, selector):
    el = node.select_one(selector)
    return el.get_text(strip=True) if el else None

def parse_items(data):
    records = []
    for item in data.get("items", []):
        records.append({
            "name": item.get("name"),
            "price": item.get("price"),
            "category": item.get("category"),
            "link": item.get("url"),
        })
    return records

def collect_all(max_pages=5):
    all_records = []
    for page in range(1, max_pages + 1):
        data = fetch_json(f"{BASE}{page}")
        if not data:
            break
        records = parse_items(data)
        if not records:
            break
        all_records.extend(records)
        time.sleep(2)
    return all_records

def save_outputs(records):
    with open("items.json", "w") as f:
        json.dump(records, f, indent=2)
    if not records:
        return
    with open("items.csv", "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=records[0].keys())
        writer.writeheader()
        writer.writerows(records)

def main():
    records = collect_all(max_pages=5)
    save_outputs(records)
    print(f"Saved {len(records)} items")

if __name__ == "__main__":
    main()

Das Skript durchläuft bis zu fünf Seiten des AJAX-Endpunkts, analysiert jede in Datensätze, stoppt, sobald eine Seite nichts zurückgibt, und taktet die Schleife mit einem Zwei-Sekunden-Sleep. save_outputs schreibt sowohl eine JSON-Datei als auch eine CSV, wobei die Schlüssel des ersten Datensatzes als Header verwendet werden. Wenn Ihr Ziel keinen sauberen Endpunkt hat, ersetzen Sie fetch_json plus parse_items durch das Paar fetch_rendered plus parse_cards aus Schritt 4; der Exportschritt ändert sich nicht.

So sieht die Ausgabe aus

Führen Sie das vollständige Skript mit python ajax_scraper.py aus und Sie erhalten einen sauberen strukturierten Datensatz pro Artikel, bereit für die Analyse, eine Datenbank oder eine Tabellenkalkulation.

json
[
  {
    "name": "Wireless Keyboard",
    "price": "49.00",
    "category": "Accessories",
    "link": "https://example.com/items/wireless-keyboard"
  },
  {
    "name": "Standing Desk",
    "price": "299.00",
    "category": "Furniture",
    "link": "https://example.com/items/standing-desk"
  }
]

Die passende CSV enthält dieselben Spalten, eine Zeile pro Artikel, die sich direkt in pandas oder eine Tabellenkalkulation zum Filtern nach Preisbereich oder Kategorie einfügt. Um die Analyse weiterzuführen, setzt pandas zur Datenanalyse verwenden dort an, wo dieser Export aufhört, und JSON vs. CSV erklärt, welches Format für welchen Job geeignet ist.

Bei größeren Crawls entsperrt bleiben

Auch mit gehandhabtem Rendering und einer vertrauenswürdigen IP beobachtet ein AJAX-Ziel Scraper-ähnlichen Traffic. Einige Gewohnheiten halten einen längeren Durchlauf gesund.

  • Anfragen takten. Aufrufe in einer engen Schleife abzufeuern ist der schnellste Weg, gedrosselt zu werden. Der oben genannte Zwei-Sekunden-Sleep ist der Boden, nicht die Decke; erweitern Sie ihn für größere Jobs.
  • Auf Rotation setzen. Ein Pool von Residential IPs verteilt Anfragen auf viele echte Benutzeradressen, sodass keine einzelne ein Rate-Limit auslöst. Die Crawling API erledigt das für Sie.
  • Statuscodes lesen. Ein Durchlauf, der beginnt, Nicht-200-cb_status-Werte zurückzugeben, signalisiert, dass die aktuelle Rate oder IP-Ebene nicht mehr ausreicht. Behandeln Sie das als Signal zum Verlangsamen.

Für größere Crawls stellt der asynchrone Crawler Anfragen in die Warteschlange und liefert Ergebnisse an einen Webhook, was für das Ausführen vieler AJAX-Seiten ohne offene Verbindungen geeignet ist. Das umfassendere Handbuch finden Sie unter So scrapen Sie Websites ohne Blockiert zu werden und JavaScript-Seiten mit Python scrapen.

Verantwortungsvoll scrapen

Beschränken Sie diese Arbeit auf öffentliche Daten und behandeln Sie die Regeln des Ziels als Grenze. Lesen Sie die Nutzungsbedingungen und die robots.txt der Website, bevor Sie einen Scraper darauf richten, und sammeln Sie nur Daten, die jeder Besucher ohne Konto sehen kann. Takten Sie Ihre Anfragen, damit Sie den Server nicht belasten, und berühren Sie niemals etwas hinter einem Login oder versuchen Sie, die Authentifizierung zu umgehen. Wenn die Daten identifizierbare Personen betreffen, gilt das Datenschutzrecht wie DSGVO oder CCPA, also vermeiden Sie persönliche oder Kontaktdaten, es sei denn, Sie haben eine klare rechtliche Grundlage für deren Erhebung. Wenn ein Ziel eine offizielle API für die benötigten Daten anbietet, ist das in der Regel der sauberere und dauerhaftere Weg als das Scrapen der gerenderten Seite.

Zusammenfassung

Wichtigste Erkenntnisse

  • AJAX-Inhalte laden nach der Hülle. Eine einfache Anfrage stoppt beim anfänglichen HTML und führt nie die Skripte aus, die die echten Daten abrufen, sodass der geparste Body größtenteils leer ist.
  • Zwei Wege erreichen dieselben Daten. Den Hintergrund-XHR-Endpunkt direkt replizieren für Geschwindigkeit, oder die vollständige Seite mit dem JS-Token rendern, wenn der Endpunkt signiert oder schwer zu reproduzieren ist.
  • Auf den Inhalt warten. Beim gerenderten Weg halten ajax_wait und page_wait an, bis asynchrone Aufrufe abgeschlossen sind, bevor Crawlbase die Seite aufzeichnet.
  • Normalisieren, dann exportieren. Ordnen Sie beide Wege demselben Datensatzformat zu, paginieren Sie über den Abfrageparameter und schreiben Sie die Ergebnisse aus einer Funktion in JSON und CSV.
  • Verantwortungsvoll scrapen. Respektieren Sie Nutzungsbedingungen und robots.txt, bleiben Sie bei öffentlichen Daten, takten Sie Anfragen und wenden Sie DSGVO- oder CCPA-Regeln an, wenn personenbezogene Daten beteiligt sind.

Häufig gestellte Fragen

Was ist AJAX und warum macht es das Scrapen schwieriger?

AJAX (Asynchronous JavaScript and XML) ist eine Technik, die es einer Seite ermöglicht, Inhalte im Hintergrund abzurufen und einen Teil des DOM zu aktualisieren, ohne neu zu laden. Es macht das Scrapen schwieriger, weil die Daten nicht im anfänglichen HTML enthalten sind; sie kommen erst an, nachdem der Browser das JavaScript der Seite ausgeführt hat und die Hintergrundaufrufe zurückgekehrt sind. Eine einfache HTTP-Anfrage führt dieses JavaScript nie aus, erfasst also eine dünne Hülle, bei der der echte Inhalt fehlt.

Kann ich AJAX-Inhalte scrapen, ohne einen Browser zu rendern?

Oft ja. Filtern Sie den Netzwerk-Tab Ihrer Dev-Tools nach XHR und finden Sie die Anfrage, die die Daten trägt. Wenn dieser Endpunkt erreichbar ist, können Sie ihn direkt anfragen und das zurückgegebene JSON analysieren, was schneller ist als die Seite zu rendern. Wenn der Endpunkt signiert, an eine Sitzung gebunden oder auf mehrere Aufrufe aufgeteilt ist, ist das Rendern mit einem JS-Token der zuverlässigere Weg.

Benötige ich das normale Token oder das JS-Token?

Es hängt vom Weg ab. Für einen sauberen JSON-Endpunkt, den Sie im Netzwerk-Tab gefunden haben, reicht das normale Token aus, da nichts zu rendern ist. Um eine vollständige Seite zu laden, deren Inhalt erst nach der Skriptausführung erscheint, verwenden Sie das JS-Token zusammen mit ajax_wait und page_wait, damit Crawlbase auf den Abschluss der asynchronen Aufrufe wartet, bevor das HTML aufgezeichnet wird.

Was machen ajax_wait und page_wait eigentlich?

ajax_wait weist die API an, zu warten, bis die asynchronen Anfragen der Seite fertig geladen sind, anstatt den Moment aufzuzeichnen, an dem das anfängliche HTML eintrifft. page_wait fügt nach dem Laden eine feste Pause in Millisekunden hinzu, die Inhalte abdeckt, die etwas später rendern. Fünf Sekunden sind ein vernünftiger Ausgangspunkt; erhöhen Sie ihn, wenn Artikel dünn zurückkommen, und senken Sie ihn, sobald Sie bestätigen, dass die Seite schneller settled.

Meine analysierte Liste ist leer. Was ist schiefgelaufen?

Prüfen Sie drei Dinge der Reihe nach. Erstens, bestätigen Sie, dass cb_status 200 zurückgegeben hat; ein Nicht-200-Wert bedeutet, dass die Anfrage fehlgeschlagen ist. Zweitens, beim Endpunkt-Weg, untersuchen Sie die JSON-Schlüssel erneut, da sie sich von den hier verwendeten Platzhalternamen unterscheiden können. Drittens, beim gerenderten Weg, erhöhen Sie page_wait und überprüfen Sie Ihre CSS-Selektoren gegen die Live-Seite, da sich Klassennamen bei generiertem Markup ohne Vorankündigung ändern.

Wie skaliere ich das auf viele Seiten?

Iterieren Sie über den Paginierungsparameter und stoppen Sie, wenn eine Seite keine Artikel zurückgibt, wie es die obige Funktion collect_all tut, und halten Sie dabei einen kurzen Sleep zwischen den Anfragen ein. Für große Jobs wechseln Sie zum asynchronen Crawler, damit Anfragen in die Warteschlange gestellt werden und Ergebnisse an einen Webhook geliefert werden, anstatt offene Verbindungen zu halten, und setzen Sie auf integrierte IP-Rotation, damit keine einzelne Adresse ein Rate-Limit auslöst.

Jetzt loslegen

Crawlen Sie jede Website im großen Maßstab, ohne gegen die Infrastruktur zu kämpfen.

Crawlbase übernimmt Proxys, Fingerprints und CAPTCHAs, damit Ihr Team Datenpipelines ausliefert, statt Crawl-Infrastruktur zu pflegen. 1.000 Anfragen kostenlos, keine Karte erforderlich.

Self-Service · Kein Verkaufsgespräch erforderlich · Enterprise-Crawl-Volumen verfügbar