Architektur für Web Scraping im großen Maßstab: Ein Praxisleitfaden
Ein paar hundert Seiten sind ein Skript. Millionen sind ein verteiltes System. Hier ist die Architektur, die Sie dorthin bringt — Warteschlangen, asynchrone Worker, Proxy-Tiering, Wiederholungsversuche, Deduplizierung und Datenqualitätsprüfungen, mit funktionierendem Code.
Das Scraping von ein paar hundert Seiten ist ein Skript. Das Scraping von Millionen ist ein verteiltes System. Sobald Ihre Zielanzahl von "läuft über Nacht auf meinem Laptop" zu "muss diese Woche fertig sein, ohne zu überhitzen" wechselt, hört der schwierige Teil auf, das Parsen zu sein, und wird alles darum herum — wie Sie Arbeit in die Warteschlange stellen, sie verteilen, Blockaden vermeiden, Fehler wiederholen und das Ergebnis speichern und überprüfen. Dies ist Web Scraping im großen Maßstab als Architektur, kein Schnipsel, mit funktionierendem Code in jeder Phase.
Skalierung ist Parallelität, nicht eine größere Schleife
Eine Zahl macht es konkret. Nehmen Sie eine Kategorie mit 20.000 Listenseiten, jeweils 20 Elemente — 400.000 Seiten zum Abrufen. Bei realistischen 2,5 Sekunden pro Seite dauert ein strikt sequentieller Durchlauf etwa 1.000.000 Sekunden, ungefähr 11,5 Tage Wartezeit auf Seitenladezeiten, bevor Sie ein einziges Feld parsen. Treiben Sie 200 Seiten parallel an und diese 11,5 Tage schrumpfen auf eine Stunde Echtzeit. Zeit ist die Einschränkung im großen Maßstab, und Parallelität ist, wie Sie sie zurückgewinnen. Alles andere in der Architektur existiert, um diese Parallelität überlebensfähig zu machen.

Die Architektur auf einen Blick
Ein Scraper, der Millionen von Seiten übersteht, ist ein kleines verteiltes System mit einer Handvoll benannter Teile, die jeweils ein Problem lösen, das nur bei großem Volumen auftritt:
- Eine Warteschlange hält die URLs, die noch abgerufen werden müssen, und entkoppelt die Entdeckung von der Arbeit.
- Asynchrone oder verteilte Worker ziehen aus der Warteschlange und rufen gleichzeitig ab — hier liegen die Einsparungen in Echtzeit.
- Eine Proxy- und Anti-Bot-Schicht rotiert IPs und präsentiert echten Browser-Verkehr, sodass keine einzelne Adresse ein Ratenlimit auslöst.
- Rendering, nur wenn nötig, da ein Headless-Browser das teuerste Element in der Pipeline ist.
- Wiederholungsversuche mit Backoff, Deduplizierung, Speicherung und Überwachung plus Datenqualitätsprüfungen runden es ab.
Zuerst die Warteschlange: Entkoppeln Sie die Entdeckung vom Abrufen
Die wichtigste strukturelle Entscheidung ist, eine Warteschlange zwischen "was zu scrapen ist" und "das Scrapen durchführen" zu setzen. Ein Producer zählt URLs auf; ein Pool von Workern leert sie. Keine Seite weiß, wie schnell die andere läuft, und Sie können Worker hinzufügen, ohne den Producer zu berühren. In Python ist dies Celery oder RQ über Redis; in Node, BullMQ; im größeren Maßstab, RabbitMQ oder Kafka. Das Muster in einer Datei:
import asyncio, aiohttp
CONCURRENCY = 50
queue = asyncio.Queue()
async def worker(session):
while True:
url = await queue.get()
try:
async with session.get(url, timeout=20) as resp:
await handle(url, await resp.text(), resp.status)
except Exception as err:
await on_failure(url, err)
finally:
queue.task_done()
async def run(urls):
for u in urls:
queue.put_nowait(u)
async with aiohttp.ClientSession() as session:
tasks = [asyncio.create_task(worker(session)) for _ in range(CONCURRENCY)]
await queue.join()
for t in tasks:
t.cancel()
Der Knopf, der zählt, ist CONCURRENCY. Zu niedrig verschwendet die Parallelität, die den Maßstab ermöglicht; zu hoch überfordert sowohl das Ziel als auch Ihren eigenen Ausgang. Sie finden den richtigen Wert, indem Sie die Fehlerraten beobachten — genau deshalb ist Überwachung ein erstklassiger Teil des Systems, kein nachträglicher Gedanke.

Die Proxy-Schicht bricht zuerst
Bei geringem Volumen bemerken Sie kaum Anti-Bot-Abwehrmaßnahmen; im großen Maßstab brechen sie den Lauf zuerst. Senden Sie ein paar hunderttausend Anfragen von einer IP und Sie werden rate-limitiert, dann herausgefordert, dann blockiert. Die Lösung ist die Rotation über viele Adressen. Stufen Sie es: günstige Rechenzentrums-IPs für nachsichtige Ziele und APIs, rotierende Wohn-IPs für schwierige kommerzielle Ziele, die echten Nutzerverkehr erwarten. Aber Rotation allein reicht nicht aus — moderne Abwehrmaßnahmen lesen auch TLS-Fingerabdrücke und Header-Reihenfolge, sodass der Verkehr wie ein Browser aussehen muss, nicht nur von einer frischen IP kommen.
Wiederholungsversuche: Scheitern ist der Normalzustand
Bei einer Million Anfragen ist eine 1% vorübergehende Fehlerquote 10.000 fehlgeschlagene Seiten. Fehler sind bei diesem Volumen kein Randfall — sie sind Routine, und die Pipeline muss einen fehlgeschlagenen Abruf als normal behandeln, anstatt als fatal. Wiederholen Sie mit exponentiellem Backoff und einem Limit, dann verschieben Sie die URL in eine Dead-Letter-Warteschlange, anstatt den Lauf zu blockieren. Lesen Sie warum es fehlgeschlagen ist: ein Timeout oder 503 ist es wert, erneut versucht zu werden, ein harter 404 nicht.
import asyncio, random
async def fetch_with_retry(session, url, tries=4):
for attempt in range(tries):
try:
async with session.get(url, timeout=20) as r:
if r.status == 404:
return None # don't retry a hard 404
if r.status < 400:
return await r.text()
except Exception:
pass
await asyncio.sleep(2 ** attempt + random.random()) # backoff + jitter
await dead_letter(url) # give up after the cap
return None
Deduplizierung: nicht dieselbe Seite zweimal crawlen
Entdeckung im großen Maßstab produziert ständig Duplikate — dasselbe Produkt erreichbar über drei Pfade, Tracking-Parameter, die eine Seite wie zehn aussehen lassen. Normalisieren Sie URLs, bevor sie in die Warteschlange gelangen, und halten Sie dann eine Gesehen-Menge (ein Redis-Set oder ein Bloom-Filter, sobald das Set Hunderte von Millionen erreicht):
from urllib.parse import urlsplit, urlunsplit, parse_qsl, urlencode
seen = set()
def normalize(url):
s = urlsplit(url.lower())
q = [(k, v) for k, v in parse_qsl(s.query) if not k.startswith("utm_")]
return urlunsplit((s.scheme, s.netloc, s.path.rstrip("/"), urlencode(sorted(q)), ""))
def enqueue(url):
key = normalize(url)
if key not in seen:
seen.add(key)
queue.put_nowait(key)
Speicherung und Datenqualität
Zwei maßstabspezifische Gewohnheiten: in Chargen schreiben, damit die Speicherung nicht Ihr Engpass ist, und Rohdaten von geparsten Daten trennen, damit Sie ohne erneutes Crawlen neu parsen können, wenn sich Selektoren ändern. Fügen Sie dann die Hälfte der Überwachung hinzu, die die meisten Teams überspringen — Datenqualitätsprüfungen. Ein Lauf kann 100% HTTP-Erfolg melden und trotzdem Müll produzieren, wenn sich das Layout verschoben hat und Ihre Selektoren jetzt nichts mehr treffen. Bestätigen Sie, dass erforderliche Felder nicht leer sind und Werte sinnvoll sind:
def validate(row):
assert row.get("title"), "empty title — selector may have drifted"
price = row.get("price")
assert isinstance(price, (int, float)) and 0 < price < 1_000_000, "bad price"
return row
# fail loud on page 5,000, not silently after 5,000,000 empty rows
Proxies, Anti-Bot und Rendering an eine API auslagern
Rendern Sie nur, wenn Sie müssen
Ein Headless-Browser ist die teuerste Operation in der Pipeline — CPU, Speicher und Sekunden pro Seite, die alles bei einer Million Seiten dominieren. Viele Seiten liefern Daten immer noch im initialen HTML oder einem JSON-Endpunkt; ein einfacher Abruf plus ein Parser ist um Größenordnungen billiger. Versuchen Sie zuerst den günstigen Weg, bestätigen Sie, dass die Felder vorhanden sind, und eskalieren Sie das Rendering nur für die Seiten, die es benötigen. Unsere Aufschlüsselung der Renderkosten versus HTTP liefert reale Zahlen zu dieser Lücke.
Bauen vs. kaufen der schwierigen Schichten
Alles oben ist baubar, also ist die ehrliche Frage, welche Teile Ihre Ingenieurszeit verdienen. Das Datenmodell, die Parsing-Logik, die Qualitätsprüfungen und das Speicherschema sind spezifisch für Ihr Projekt — nur Sie können sie gut bauen. Der Proxy-Pool, die Anti-Bot-Behandlung, die Headless-Render-Flotte und die Wiederholungs- und Lieferwarteschlange sind generische Infrastruktur, die teuer zu bauen ist und mühsam gesund zu halten ist, wenn sich Ziele entwickeln. Das ist die Grenze, auf der eine verwaltete Scraper API sitzt: Mieten Sie die Teile, die für alle gleich sind. Unsere Bauen-gegen-Kaufen-Aufschlüsselung arbeitet die Wartungssteuer im Detail heraus, und Scraper als Produktionssoftware ausführen behandelt die Beobachtbarkeit des Ganzen.
Häufig gestellte Fragen
Wie scrapen Sie Millionen von Seiten?
Mit Parallelität, nicht mit einer größeren Schleife. Setzen Sie eine Warteschlange zwischen URL-Entdeckung und Abrufen, leeren Sie sie mit einem Pool von asynchronen oder verteilten Workern, rotieren Sie IPs, um Blockaden zu vermeiden, wiederholen Sie vorübergehende Fehler mit Backoff, deduplizieren Sie URLs und schreiben Sie Chargen in die Speicherung. Ein sequentieller Millionenseitenlauf dauert Tage; derselbe Job über einen parallelen Worker-Pool ist in Stunden fertig.
Was ist die beste Architektur für Web Scraping im großen Maßstab?
Eine Warteschlangen- und Worker-Pipeline: ein Producer zählt URLs in eine Warteschlange (Redis, RabbitMQ oder Kafka), Worker rufen gleichzeitig über eine rotierende Proxy-Schicht ab, rendern nur die Seiten, die JavaScript benötigen, wiederholen Fehler in eine Dead-Letter-Warteschlange, deduplizieren mit einer Gesehen-Menge und speichern Roh- und geparste Daten getrennt. Verpacken Sie es in Überwachung mit Datenqualitätsprüfungen, damit Abweichungen frühzeitig sichtbar werden.
Wie viele Anfragen können Sie parallel ausführen?
Es hängt vom Ziel und Ihrem Ausgang ab, nicht von einer festen Zahl. Scraping ist IO-gebunden, sodass eine bescheidene Box viele Hunderte von laufenden Anfragen halten kann. Beginnen Sie mit einer Parallelität von etwa 50, beobachten Sie die Fehlerrate und erhöhen Sie sie, bis die Fehler steigen — das ist Ihre Obergrenze. Über die Grenzen einer Maschine hinaus fügen Sie verteilte Worker hinzu, anstatt einen einzelnen Knoten stärker zu belasten.
Wie gehen Sie mit Fehlern im großen Maßstab um?
Gehen Sie von Fehlern aus — bei einer Million Anfragen ist selbst eine 1% Fehlerquote 10.000 tote Seiten. Wiederholen Sie vorübergehende Fehler (Timeouts, 503s) mit exponentiellem Backoff und Jitter, begrenzen Sie die Versuche und verschieben Sie dauerhafte Fehler in eine Dead-Letter-Warteschlange, anstatt den Lauf zu blockieren. Wiederholen Sie keine harten 404s. Die zufällige Sammlungsauswahl verteilt auch Fehler, sodass Sie nicht bei jedem Lauf auf denselben Seiten scheitern.
Skalierung besteht größtenteils aus den Teilen, die nicht Spaß machen zu bauen: rotierende IPs, Anti-Bot, Headless-Rendering, Warteschlangen und Wiederholungen. Besitzen Sie die Teile, die spezifisch für Ihre Daten sind — das Modell, die Parser, die Qualitätsprüfungen — und mieten Sie die generische Infrastruktur, die für alle der gleiche Aufwand ist. Richten Sie zuerst die Warteschlange und die Parallelität richtig ein; alles andere dient dazu, diese Parallelität beim Kontakt mit einer Million echter Seiten zu überstehen.