Eine einzelne Seite zu scrapen ist einfach. Tausende Seiten über viele verschiedene Sites in einem Durchlauf zu scrapen, ist der Punkt, an dem die meisten Setups zusammenbrechen: Eine einfache Schleife blockiert bei jeder Anfrage, IPs werden nach wenigen hundert Aufrufen gesperrt, doppelte URLs verschwenden Credits, und eine einzelne langsame Seite bremst den gesamten Job aus. Das Problem liegt nicht beim Parsen. Es liegt beim Durchsatz, beim Umgehen von Blocks und beim sorgfältigen Bookkeeping in großem Maßstab.
Diese Anleitung zeigt Ihnen, wie Sie mehrere Websites gleichzeitig scrapen in Python, so wie ein Produktionsjob tatsächlich läuft. Sie verwenden die Crawling API, um jede Seite hinter einem echten Browser und einer vertrauenswürdigen IP abzurufen und zu rendern, und den asynchronen Crawler, um tausende URLs in eine verwaltete Queue zu schieben, die sie gleichzeitig crawlt und die Ergebnisse an einen Webhook liefert. Wir behandeln das Einreihen vieler URLs, das Steuern der Parallelität, das Deduplizieren von Arbeit und das Zusammenführen der Ergebnisse an einem Ort.
Warum eine einzelne Schleife nicht skaliert
Ein erster Scraper ist fast immer eine einzige Schleife: URL lesen, abrufen, parsen, speichern, wiederholen. Das funktioniert bei zehn Seiten und bricht bei zehntausend zusammen. Jeder Abruf blockiert den nächsten, sodass die Gesamtzeit die Summe aller Anfragen ist. Diese Anfragen von einer einzigen IP zu senden, führt dazu, dass eine Site innerhalb weniger hundert Aufrufe 403-Fehler und CAPTCHAs zurückgibt. Und nichts in der Schleife bemerkt, dass Sie gestern bereits die Hälfte der Liste gecrawlt haben, sodass Sie für das erneute Abrufen von Seiten bezahlen, die Sie bereits haben.
Gleichzeitig über viele Sites zu skalieren bedeutet, drei separate Probleme zu lösen. Sie benötigen Parallelität, damit langsame Seiten schnelle nicht blockieren. Sie benötigen Unblocking, damit rotierende IPs und echtes Browser-Rendering Sie von der Sperrliste fernhalten. Und Sie benötigen Bookkeeping, damit doppelte URLs übersprungen werden und fertige Ergebnisse in einem Store landen. Der Rest dieser Anleitung ordnet jedes Problem einem Tool zu und zeigt den Code.
Halten Sie die Grenze klar. Die Crawling API ruft eine Seite pro Aufruf ab und rendert sie: Sie führt JavaScript aus, rotiert die IP und liefert fertiges HTML zurück. Der asynchrone Crawler ist die Queue darüber: Sie schieben viele URLs hinein, er crawlt sie gleichzeitig, wiederholt fehlgeschlagene Versuche und übergibt jedes Ergebnis per POST an einen Webhook, den Sie hosten. Verwenden Sie die API für einen begrenzten Batch, auf den Sie warten; den Crawler für einen großen Fire-and-Collect-Job.
Was Sie bauen werden
Zwei ausführbare Muster über eine Liste von URLs, die verschiedene Sites umfassen. Erstens ein gleichzeitiger Batch, der einen deduplizierten URL-Satz durch die Crawling API leitet und jedes Ergebnis in eine JSON-Datei schreibt, was die richtige Form für Hunderte bis einige Tausend Seiten ist, die Sie bei Abschluss des Durchlaufs in der Hand haben wollen. Zweitens ein asynchrones Pushing an den Crawler für Jobs, die in die Zehntausende gehen, wo das Blockieren bei jedem Abruf keine Option mehr ist. Beide verwenden den offiziellen crawlbase-Python-Client.
Die Umgebung einrichten
Sie benötigen Python 3.8 oder höher. Bestätigen Sie Ihre Version, erstellen Sie eine virtuelle Umgebung, damit Abhängigkeiten isoliert bleiben, und installieren Sie dann den Client.
python --version python -m venv scrape_env source scrape_env/bin/activate pip install crawlbase
Unter Windows aktivieren Sie die Umgebung mit scrape_env\Scripts\activate statt der source-Zeile. Das crawlbase-Paket ist der offizielle Client und kapselt sowohl die Crawling API als auch den asynchronen Crawler, sodass Sie HTTP-Aufrufe nicht von Hand zusammensetzen müssen. Holen Sie sich nach der Anmeldung zwei Tokens aus Ihrem Crawlbase-Dashboard: einen normalen Token für statische Seiten und einen JavaScript-Token (JS-Token) für clientseitig gerenderte. Lesen Sie sie aus Umgebungsvariablen, anstatt sie im Code fest einzutragen.
export CRAWLBASE_TOKEN=your_normal_token_here export CRAWLBASE_JS_TOKEN=your_js_token_here
Der normale Token ruft statisches HTML ab und ist günstiger und schneller. Der JS-Token rendert die Seite zuerst in einem echten Browser, was Sie für jede Site benötigen, die Inhalte clientseitig lädt. Wenn Sie viele verschiedene Sites gleichzeitig scrapen, werden Sie auf beide Typen stoßen, daher ist ein gängiges Muster, standardmäßig den JS-Token zu verwenden und für bekannte statische Ziele auf den normalen zu wechseln.
Einen deduplizierten URL-Satz aufbauen
Bereinigen Sie die Eingabe, bevor Sie Anfragen abschicken. Eine reale Zielliste, zusammengestellt aus Sitemaps, Kategorieseiten und früheren Durchläufen, ist voller Duplikate und veralteter Einträge. Das Deduplizieren im Voraus ist die einzeln günstigste Optimierung, die Sie vornehmen können, denn eine Anfrage, die Sie nie senden, kostet nichts. Normalisieren Sie jede URL und führen Sie einen Satz dessen, was Sie bereits gecrawlt haben.
import json import os from urllib.parse import urlparse, urlunparse def normalize(url): parts = urlparse(url.strip()) # Drop fragments and trailing slashes so near-duplicates collapse. path = parts.path.rstrip("/") or "/" return urlunparse((parts.scheme, parts.netloc, path, "", parts.query, "")) raw_urls = [ "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html", "https://books.toscrape.com/catalogue/tipping-the-velvet_999/index.html", "https://quotes.toscrape.com/page/1/", "https://quotes.toscrape.com/page/1/#top", # duplicate after normalizing ] targets = sorted({normalize(u) for u in raw_urls}) print(f"{len(targets)} unique URLs to crawl")
Das Set-Comprehension kollabiert exakte und nahezu doppelte URLs in einer Zeile. Für einen Durchlauf, der sich über Tage erstreckt, persistieren Sie den gecrawlten Satz auf der Festplatte und subtrahieren ihn beim Start jedes Durchlaufs von targets, damit Sie eine Seite, die Sie bereits haben, nie erneut abrufen. Das ist die Bookkeeping-Schicht, die ihre Arbeit tut, bevor Sie einen einzigen Credit ausgeben.
Den Batch gleichzeitig mit der Crawling API scrapen
Jetzt rufen Sie den Satz ab. Die naive Version ist eine serielle Schleife, aber seriell ist genau das, was nicht skaliert, also führen Sie die Anfragen durch einen Thread-Pool. Jeder Aufruf an die Crawling API ist I/O-gebunden und wartet auf das Netzwerk, was genau der Fall ist, den ein Thread-Pool gut handhabt. Eine bescheidene Anzahl von Workern hält viele Anfragen gleichzeitig in der Luft, ohne eine einzelne Site zu sehr zu belasten.
from concurrent.futures import ThreadPoolExecutor, as_completed from crawlbase import CrawlingAPI api = CrawlingAPI({"token": os.environ["CRAWLBASE_JS_TOKEN"]}) def fetch(url): options = {"ajax_wait": "true", "page_wait": 2000} response = api.get(url, options) status = response["headers"].get("cb_status") return { "url": url, "status": status, "html": response["body"].decode("utf-8", "ignore"), } results = [] with ThreadPoolExecutor(max_workers=10) as pool: futures = {pool.submit(fetch, u): u for u in targets} for future in as_completed(futures): url = futures[future] try: results.append(future.result()) except Exception as err: print(f"Failed {url}: {err}") print(f"Collected {len(results)} pages")
Zwei Details machen dies robust. Der cb_status (legacy pc_status)-Header trägt den originalen Status, den das Ziel zurückgegeben hat, sodass Sie einen echten 200er von einem Soft-Failure unterscheiden und entscheiden können, ob Sie die Zeile behalten. Und das Einwickeln von future.result() in ein Try/Except bedeutet, dass eine fehlerhafte URL protokolliert wird und weiterläuft, anstatt den gesamten Batch zu beenden. Die Crawling API übernimmt das Rendern und die IP-Rotation pro Aufruf, sodass Ihr Code nur die Parallelität verwaltet.
Ein Aufruf ruft eine Seite hinter einem echten Browser und einer rotierenden Residential-IP ab und rendert sie, sodass ein Batch über viele verschiedene Sites unblockiert bleibt, ohne dass Sie eine Headless-Fleet oder einen Proxy-Pool betreiben müssen. Starten Sie mit einer öffentlichen Seite im Free-Tier und skalieren Sie dann dieselbe Schleife auf tausende URLs.
Die gesammelten Ergebnisse parsen und speichern
Sie haben jetzt rohes HTML für jede Seite in results. Das Parsen variiert je nach Site, aber der Sammelschritt ist derselbe: Ziehen Sie die gewünschten Felder heraus und schreiben Sie einen strukturierten Datensatz pro Seite. Halten Sie die URL und einen Erfassungszeitstempel in jeder Zeile, damit die Ausgabe gleichzeitig als Prüfprotokoll darüber dient, was wann gelaufen ist.
from datetime import datetime, timezone from bs4 import BeautifulSoup def parse_title(html): soup = BeautifulSoup(html, "html.parser") title = soup.find("title") return title.text.strip() if title else None rows = [] for item in results: if item["status"] != "200": continue rows.append({ "url": item["url"], "title": parse_title(item["html"]), "captured_at": datetime.now(timezone.utc).isoformat(), }) with open("scraped.json", "w") as f: json.dump(rows, f, indent=2) print(f"Wrote {len(rows)} rows to scraped.json")
Hierfür wird beautifulsoup4 neben dem Client installiert (pip install beautifulsoup4). Das Überspringen von Zeilen, die keinen sauberen 200er zurückgegeben haben, hält Soft-Failures, also leere Bodies oder Challenge-Seiten, aus Ihrem Datensatz heraus, was die Art von stiller Korruption ist, die einen großen Crawl unbemerkt vergiftet. Für bekannte Ziele wie große Einzelhändler oder Marktplätze können Sie handgeschriebenes Parsen vollständig überspringen und stattdessen die Crawling API vorverarbeitetes JSON zurückliefern lassen.
Mit dem asynchronen Crawler über den Batch hinaus skalieren
Der Thread-Pool-Batch ist das richtige Tool für bis zu einige Tausend URLs, auf die Sie warten möchten. Darüber hinaus ist es nicht mehr praktikabel, Ihren Prozess zu blockieren, während Zehntausende von Seiten gecrawlt werden, und genau dort übernimmt der asynchrone Crawler. Er ist eine push-basierte verwaltete Queue: Sie senden URLs über denselben Client, jede erhält eine Request-ID, das System crawlt sie gleichzeitig und wiederholt Fehler für Sie, dann übergibt es jede fertige Seite per POST an einen Webhook auf Ihrem Server.
from crawlbase import CrawlingAPI crawler = CrawlingAPI({"token": os.environ["CRAWLBASE_JS_TOKEN"]}) # Push each URL to the async Crawler; results arrive at your webhook. for url in targets: response = crawler.post(url, { "callback": "https://your-app.example.com/webhook", "callback_headers": "X-Job-Id:bulk-run-01", }) body = json.loads(response["body"]) print(f"Queued {url} as request {body['rid']}")
Jeder post-Aufruf gibt eine Request-ID (rid) zurück, die Sie protokollieren können, um den Job zu verfolgen. Der Crawler crawlt die Queue im Hintergrund mit eigener Parallelität und Retry-Logik, sodass Ihr Skript fertig ist, sobald jede URL eingereicht wurde, anstatt auf den Crawl zu warten. Wenn eine Seite fertig ist, übergibt das System das Ergebnis per POST an Ihre Callback-URL, und das Feld callback_headers ermöglicht es Ihnen, einen Durchlauf zu kennzeichnen, damit der empfangende Handler weiß, zu welchem Job eine Lieferung gehört.
Die Lieferungen sammeln
Das asynchrone Modell kehrt die Sammlung um: Anstatt Seiten zu ziehen, empfangen Sie sie. Ihr Webhook führt dieselbe Parse-and-Save-Logik aus der Batch-Version aus, nur der Auslöser ändert sich. Ein minimaler Handler in Flask sieht so aus.
from flask import Flask, request app = Flask(__name__) @app.route("/webhook", methods=["POST"]) def webhook(): rid = request.headers.get("rid") original_url = request.headers.get("original_url") html = request.get_data(as_text=True) row = { "url": original_url, "title": parse_title(html), "captured_at": datetime.now(timezone.utc).isoformat(), } with open("bulk.jsonl", "a") as f: f.write(json.dumps(row) + "\n") return "", 200
Das Anhängen an eine JSON-Lines-Datei bedeutet, dass jede Lieferung ein eigenständiger Schreibvorgang ist, sodass gleichzeitige Callbacks sich nicht gegenseitig überschreiben, wie es ein einzelnes neu serialisiertes JSON-Array täte. Der Crawler liefert die ursprüngliche URL und die Request-ID in den Response-Headern, sodass dieselbe parse_title-Funktion und Zeilenform aus der Batch-Version direkt übernommen werden kann. Genau das ermöglicht es, die Pipeline von einigen Tausend Seiten auf Hunderttausende zu skalieren, ohne dass Ihr Prozess jemals warten muss.
Bei großem Volumen können Sie einen Crawl nicht manuell überwachen, also verlassen Sie sich auf das integrierte Monitoring. Das Crawlbase-Dashboard verfolgt Anfragevolumen, Erfolgs- und Fehlerquoten sowie verbrauchte Credits, und der Live-Monitor zeigt die Queue-Tiefe in Echtzeit. Ein schleichender Anstieg der Fehler bedeutet meist, dass ein Ziel begonnen hat, Traffic herauszufordern, und das möchten Sie innerhalb von Minuten bemerken, nicht nachdem ein Durchlauf mit der Hälfte der fehlenden Zeilen beendet ist.
Parallelität, Rate-Limits und unblockiert bleiben
Mehr Worker bedeuten nicht immer mehr Geschwindigkeit. Zu hohe Parallelität erschöpft entweder die Anfragerate Ihres Plans oder belastet eine einzelne Domain so stark, dass ihre Schutzmechanismen ausgelöst werden, was den Durchlauf durch Wiederholungsversuche verlangsamt. Die Lösung besteht darin, die Parallelität pro Domain statt global zu kontrollieren: Zehn gleichzeitige Anfragen, die auf zehn Sites verteilt sind, sind sanft, während zehn gegen eine Site aggressiv sind. Gruppieren Sie Ihren URL-Satz nach Host und begrenzen Sie, wie viele Sie gegen eine davon gleichzeitig in der Luft halten.
Da die Crawling API und der Crawler beide Residential-IPs rotieren und serverseitig hinter einem echten Browser rendern, wird der schwerste Teil des Unblocked-Bleibens für Sie übernommen. Wenn Sie lieber Ihren eigenen Client über einen rotierenden Pool leiten möchten, gibt Ihnen der Smart AI Proxy dieselbe Residential-IP-Rotation als Drop-in-Proxy-Endpunkt. In beiden Fällen: Anfragen dosieren, Ziele variieren und die Statuscodes im Auge behalten, damit Sie sich zurückziehen können, sobald eine Site Gegendruck aufbaut. Das vollständige Playbook findet sich in Websites scrapen ohne blockiert zu werden.
Verantwortungsvoll scrapen
Scraping in großem Maßstab ist eine Verantwortung, nicht nur eine Fähigkeit. Bleiben Sie bei öffentlich verfügbaren Daten; scrapen Sie keine Inhalte hinter einem Login, bezahlpflichtiges Material oder irgendetwas Persönliches oder Urheberrechtlich Geschütztes ohne eindeutiges Recht dazu. Lesen Sie die robots.txt und die Nutzungsbedingungen jeder Site und halten Sie die darin festgelegten Zugriffsregeln ein. Und begrenzen Sie sich selbst: Anfragen zu staffeln und die Parallelität pro Domain zu begrenzen hält Sie von Sperrlisten fern und schont die Server einer Site. Zurückhaltung ist nicht nur die ethische Wahl, sie ist auch die operative, denn ein Job, der Grenzen respektiert, bleibt weit länger am Laufen als einer, der es nicht tut.
Wichtigste Erkenntnisse
- Das Problem aufteilen. Viele Sites gleichzeitig zu scrapen sind drei Probleme, nicht eines: Parallelität, Unblocking und Bookkeeping. Ordnen Sie jedes einem Tool zu, anstatt sie alle in eine Schleife zu pressen.
- Vor dem Abrufen deduplizieren. URLs normalisieren und bereits gecrawlte überspringen, denn die günstigste Anfrage ist die, die Sie nie senden.
- Einen Thread-Pool für begrenzte Batches verwenden. Der Crawling-API-Aufruf ist I/O-gebunden, sodass ein bescheidener Pool von Workern Hunderte bis einige Tausend Seiten weit schneller sammelt als eine serielle Schleife.
- Bei großem Volumen an den asynchronen Crawler pushen. Für Zehntausende von URLs diese in die Queue einschieben und Ergebnisse an einem Webhook empfangen, sodass Parallelität, Wiederholungsversuche und Monitoring kostenlos dazukommen.
- Parallelität pro Domain kontrollieren. Last über Hosts verteilen und gleichzeitige Anfragen pro Site begrenzen, um unblockiert zu bleiben, anstatt Schutzmechanismen auszulösen.
- Verantwortungsvoll scrapen. Nur öffentliche Daten, robots.txt und Nutzungsbedingungen respektieren und sich selbst rate-limiten, damit der Job weiterläuft.
Häufig gestellte Fragen
Wie scrapt man mehrere Websites gleichzeitig in Python?
Einen deduplizierten Satz von URLs aufbauen und diese dann gleichzeitig statt in einer seriellen Schleife abrufen. Für einen begrenzten Batch die Crawling API durch einen ThreadPoolExecutor laufen lassen, damit langsame Seiten schnelle nicht blockieren, und jedes Ergebnis in eine Liste sammeln, die auf Disk geschrieben wird. Für sehr große Jobs stattdessen die URLs an den asynchronen Crawler pushen, der sie im Hintergrund einreiht und crawlt und jede fertige Seite an einen selbst gehosteten Webhook liefert.
Was ist der Unterschied zwischen der Crawling API und dem asynchronen Crawler?
Die Crawling API ist synchron: Sie senden eine URL und warten auf die gerenderte Seite in der Antwort, was ideal für einen einzelnen Scrape oder einen kleinen gleichzeitigen Batch ist. Der asynchrone Crawler ist für Skalierung gebaut: Sie pushen viele URLs, er crawlt sie im Hintergrund mit eigener Parallelität und Wiederholungsversuchen und übergibt jedes Ergebnis per POST an Ihren Webhook. Beide teilen dasselbe Rendering- und Anti-Block-Backbone, sodass Sie das auswählen, das zu Ihrem Durchsatz passt.
Wie vermeidet man Blockierungen beim Scrapen vieler Sites?
IPs rotieren und Seiten hinter einem echten Browser rendern, und Anfragen dosieren, damit keine einzelne Domain überlastet wird. Die Crawling API und der Crawler übernehmen IP-Rotation und Rendering serverseitig, sodass die meisten Blocks abgedeckt sind. Wenn Sie Ihren eigenen Client routen, einen rotierenden Endpunkt wie den Smart AI Proxy verwenden, Parallelität pro Domain kontrollieren und Statuscodes beobachten, damit Sie sich zurückziehen können, wenn eine Site beginnt, Traffic herauszufordern.
Wie geht man mit doppelten URLs über Tausende von Seiten hinweg um?
Jede URL normalisieren, indem Fragmente und abschließende Schrägstriche entfernt werden, und sie dann in einem Set speichern, damit exakte und nahezu doppelte automatisch kollabieren. Für Durchläufe, die sich über die Zeit erstrecken, den Satz bereits gecrawlter URLs auf Disk persistieren und ihn beim Start jedes Durchlaufs von der Zielliste subtrahieren. Dieses Bookkeeping verhindert, dass Sie für das erneute Abrufen von Seiten bezahlen, die Sie bereits haben.
Wie viele gleichzeitige Anfragen sollte man verwenden?
Bescheiden beginnen, etwa zehn Worker, und dann basierend auf der Anfragerate Ihres Plans und der Reaktion der Ziele anpassen. Die Zahl, die wichtig ist, ist die Parallelität pro Domain, nicht die globale Gesamtzahl: Zehn Anfragen auf zehn Sites verteilt ist sanft, während zehn gegen eine Site aggressiv ist. URLs nach Host gruppieren und begrenzen, wie viele gleichzeitig gegen eine einzelne Site in der Luft sind, um unblockiert zu bleiben.
Ist es legal, Tausende von Websites zu scrapen?
Das Scrapen öffentlich verfügbarer Daten ist allgemein akzeptiert, aber die Rechtmäßigkeit hängt von den Nutzungsbedingungen jeder Site, dem Urheberrecht und Datenschutzgesetzen wie DSGVO und CCPA ab. Bei öffentlichen Daten bleiben, Inhalte hinter Logins oder Paywalls sowie alles Persönliche oder Urheberrechtlich Geschützte vermeiden, robots.txt einhalten und sich selbst rate-limiten. Im Zweifel über ein bestimmtes Ziel die Bedingungen prüfen und vor einem großen Durchlauf rechtlichen Rat einholen.
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.
