Zum Inhalt springen
Zurück zum Blog

Web-Scraping mit AWS Lambda: Leitfaden für Python und Java 2026

Suciu DanZuletzt aktualisiert am 26 min read
Web-Scraping mit AWS Lambda: Leitfaden für Python und Java 2026
TL;DR: Web-Scraping mit AWS Lambda funktioniert am besten, wenn jeder Aufruf kurz, begrenzt und unabhängig wiederholbar ist. Beginnen Sie mit direktem HTTP, AWS SAM und S3 und fügen Sie erst dann SQS, Container, Browser-Rendering, Proxys oder eine verwaltete Abrufschicht hinzu, wenn sich im Arbeitsaufkommen zeigt, dass diese benötigt werden.

Web-Scraping mit AWS Lambda bezeichnet die Praxis, Code zum Abrufen und Extrahieren von Seiten als kurzlebige AWS-Funktionen auszuführen, anstatt eigene Scraper-Server zu betreiben. Lambda startet diesen Code als Reaktion auf Zeitpläne, Warteschlangen, HTTP-Aufrufe oder andere AWS-Ereignisse.

Dieses Betriebsmodell ist attraktiv, macht jedoch nicht jeden Crawler für den serverlosen Betrieb geeignet. Lambda eignet sich für geplante Produktprüfungen, Aufgaben mit einer einzigen URL, Webhook-gesteuerte Extraktion und Queue-Worker. Weniger gut geeignet ist es für Crawls, die stundenlange ununterbrochene Laufzeit erfordern, eine Browser-Sitzung, die aktiv bleiben muss, oder unkontrolliertes Fan-Out gegenüber einem sensiblen Ziel.

Dieser Leitfaden verfolgt einen „Decision-First“-Ansatz. Sie werden einen Python-Web-Scraper für AWS Lambda erstellen, ein Java-21-Äquivalent mit HttpClient und Jsoup kennenlernen, ZIP- und Container-Paketierungen vergleichen, Ergebnisse in S3 speichern und URL-Verarbeitungen über SQS skalieren. Außerdem erhalten Sie einen konservativen Eskalationspfad für JavaScript und Blocking sowie Kostenformeln, die die Lambda-Rechenkosten von den Ausgaben für Speicher, Protokollierung, Netzwerk, Images, Proxy und APIs trennen. Zeitkritische AWS-Werte sind ausdrücklich gekennzeichnet, damit Sie sie anhand der verlinkten offiziellen Dokumentation überprüfen können.

Beginnen Sie mit der Entscheidung zur Bereitstellung: Ist Lambda die richtige Laufzeitumgebung für Web-Scraping mit AWS Lambda?

Die kurze Antwort lautet „Ja“, wenn ein Scrape innerhalb eines begrenzten Aufrufs abgeschlossen werden kann oder in unabhängige Einheiten wie eine URL, eine Listenseite oder einen Cursor unterteilt werden kann. Web-Scraping mit AWS Lambda ist besonders effektiv für geplante Überprüfungen und spitzenbelastete Jobs, da keine Worker-Flotte zwischen den Ereignissen am Laufen gehalten werden muss.

Die wichtige Frage ist nicht, ob Lambda eine Seite abrufen kann. Das kann es. Die Frage ist, ob Fehler, Wiederholungsversuche, Status und Durchsatz überschaubar bleiben, wenn jeder Aufruf nur vorübergehend ist.

Merkmale der Arbeitslast

Gute Eignung für Lambda

Warnzeichen

Laufzeit

Sekunden oder wenige Minuten

Ein Crawl dauert Stunden

Status

Die Eingabe enthält alles Notwendige

Langlebiger Browser- oder Anmeldestatus

Parallelität

Unabhängige URLs mit einer klaren Obergrenze

Ein Crawler entdeckt unbegrenzt viele Aufgaben

Abhängigkeiten

HTTP-Parser oder kontrolliertes Bild

Großer, anfälliger Desktop-Stack

Ausgabe

Wird nach jeder Aufgabe dauerhaft gespeichert

Ergebnisse existieren nur im Arbeitsspeicher

Fehlermodell

Eine URL kann sicher erneut aufgerufen werden

Ein erneuter Versuch dupliziert Nebenwirkungen

Eine nützliche Designregel ist es, die Funktion wegwerfbar zu gestalten. Wenn AWS eine Umgebung nach einer Anfrage beendet, sollte ein Ersatz in der Lage sein, dasselbe Ereignis zu verarbeiten, ohne den verborgenen lokalen Zustand rekonstruieren zu müssen. Dadurch werden Checkpoints, Ergebnisse und Anmeldedaten in dedizierte Dienste statt /tmp in globale Variablen des Moduls.

Passen Sie statisches HTML, Crawling, Browser-Rendering und verwaltete APIs an die jeweilige Aufgabe an

Wählen Sie die am wenigsten komplexe Abrufmethode, die die benötigten Daten liefert. Statische Seiten erfordern in der Regel einen HTTP-Client und einen Parser. Beim Crawlen mehrseitiger Inhalte kann der Einsatz von Scrapy sinnvoll sein. Für clientseitig gerenderte Anwendungen sind möglicherweise ein Browser, ein zugrunde liegender JSON-Endpunkt, für dessen Aufruf Sie autorisiert sind, oder ein gehosteter Rendering-Dienst erforderlich.

Anforderung

Erste Wahl

Wechseln Sie zu einer höheren Stufe, wenn

Server-gerendertes HTML

requests plus BeautifulSoup

In der Antwort fehlt der Inhalt

Crawling mit Link-Verfolgung

Scrapy in ZIP-Datei oder Bild

Native Abhängigkeiten überschreiten die ZIP-Größenbeschränkungen

Klicks, Scrollen, Formulare

Playwright in einem Container

Browser-Operationen dominieren die Laufzeit

Hohes Blockierungsrisiko

Ratenbegrenzung, dann ein Proxy

IP-Reputation oder geografische Lage sind entscheidend

Rendering plus Proxy-Vorgänge

Verwaltete Scraping-API

Die Wartung von Browsern und Proxy-Logik kostet mehr als das Outsourcing der Datenabfrage

Rendering, Sitzungsstatus, Ausführungsdauer, Anfragemenge und Blockierungsrisiko sollten diese Entscheidung bestimmen. Eine Headless-Browser-Architektur bietet mehr Kontrolle, verbraucht jedoch auch mehr Speicher, erhöht den Aufwand beim Kaltstart und verursacht mehr Fehlerquellen als das Parsen von HTML.

Wählen Sie Fargate, Batch oder EC2 für Workloads, die Lambda nicht bewältigen kann

Verwenden Sie einen Compute-Dienst mit längerer Laufzeit, wenn die Arbeitseinheit nicht begrenzt werden kann. Fargate ist ein praktischer nächster Schritt für containerisierte Worker, die längere Laufzeiten benötigen, ohne Hosts verwalten zu müssen. Batch eignet sich besser, wenn Jobs in einer Warteschlange stehen, rechenintensiv sind und sich naturgemäß als Batch-Aufträge darstellen lassen. EC2 bietet die größte Kontrolle für persistente Browser, spezialisierte Netzwerkkonfigurationen, lokale Caches oder kontinuierlich laufende Crawler, überträgt jedoch auch die Verantwortung für Host-Patches und das Kapazitätsmanagement wieder an Ihr Team.

Die Entscheidung ist in erster Linie qualitativer Natur und erst in zweiter Linie finanzieller. Wenn Sie alle paar Minuten Checkpoints erzwingen, nur um das Funktionslimit zu umgehen, oder wiederholt einen großen Browser-Stack herunterladen, ist ein Containerdienst möglicherweise einfacher und kostengünstiger im Betrieb. Lambda sollte den Infrastrukturaufwand reduzieren und ihn nicht in aufwendigen Wiederherstellungscode verlagern.

Verwenden Sie eine Produktionsarchitektur, die Kontroll- und Datenfluss trennt

Ein Produktions-Scraper lässt sich leichter betreiben, wenn seine Komponenten voneinander getrennt sind. Behandeln Sie den Trigger als Steuerungsfluss, die Lambda-Funktion als austauschbaren Worker, S3 oder eine Datenbank als dauerhaften Datenfluss und CloudWatch sowie einen Secret Store als operative Ebene.

Ein typischer AWS-Lambda-Web-Scraping-Ablauf sieht wie folgt aus:

EventBridge schedule or URL producer
                 |
           SQS or Step Functions
                 |
        Lambda scraper workers
          |       |        |
         S3   CloudWatch   SSM/Secrets Manager

Diese Trennung verhindert häufige Kopplungsprobleme. Ein Zeitplan sollte keine Parser-Logik enthalten. Ein Worker sollte nicht die einzige Kopie seiner Ergebnisse aufbewahren. Ein Parser sollte nicht wissen, wie ein Geheimnis verschlüsselt ist. Die Überwachung sollte strukturierte Fakten zu jedem Durchlauf erhalten, anstatt diese aus unstrukturierten Stack-Traces zu rekonstruieren.

Definieren Sie für jede Sprache und jeden Trigger einen Ereignisvertrag. Ein kompakter Vertrag reicht aus:

{
  "url": "https://example.com/catalog",
  "limit": 20,
  "request_id": "job-2026-03-001"
}

Geben Sie eine passende Ergebnishülle zurück oder speichern Sie diese mit ok, request_id, url, count, items, und s3_uri. Python und Java lassen sich dann operativ vergleichen, und SQS-Wiederholungsversuche hängen nicht von sprachspezifischen Ereignisformaten ab.

Wählen Sie EventBridge, SQS oder Step Functions als Trigger

Verwenden Sie EventBridge für geplantes Web-Scraping auf AWS, wenn der Auftrag klein und periodisch ist. Eine Regel kann stündlich oder täglich eine Funktion aufrufen, und die Funktion kann den resultierenden Snapshot in S3 schreiben. Verhindern Sie Überschneidungen, indem Sie die Laufzeit kurz halten, idempotente Ausgabeschlüssel hinzufügen und reservierte Parallelität anwenden, falls gleichzeitige Ausführungen schädlich wären.

Verwenden Sie SQS, wenn ein Produzent URLs oder Seitencursor auflisten kann. Die Warteschlange puffert Spitzenlasten ab, Lambda verarbeitet kleine Stapel, und eine fehlgeschlagene URL kann erneut versucht werden, ohne erfolgreiche Vorgänge neu zu starten. Dies ist die Standardarchitektur für die Erfassung mehrerer URLs, da die Geschwindigkeit des Produzenten nicht mehr die Zielanforderungsrate bestimmt.

Verwenden Sie Step Functions, wenn der Job verschiedene Phasen umfasst, wie z. B. das Abrufen von Listenseiten, das Erstellen von Detail-URLs, das Anreichern von Datensätzen und das Veröffentlichen eines Manifests. Es bietet explizite Verzweigungen und Wiederholungsrichtlinien, jedoch verursachen Zustandsübergänge zusätzliche Kosten und erfordern den Betrieb eines weiteren Dienstes.

Auslöser

Am besten geeignet für

Primäre Steuerung

EventBridge

Regelmäßige Snapshots

Zeitplan und reservierte Parallelität

SQS

Viele unabhängige URLs

Stapelgröße, Parallelität der Ereignisquellen, DLQ

Step Functions

Mehrstufige Workflows

Wiederholungsversuche und Verzweigungen auf Statusebene

API-Gateway

On-Demand-Anfragen

Grenzen für Authentifizierung, Nutzdaten und Zeitüberschreitungen

S3-Ereignis

Verarbeitung hochgeladener Seeds

Konventionen für Objektschlüssel und Umgang mit Duplikaten

Weiterleitung von Ausgabe, Geheimnissen, Protokollen und Metriken an dedizierte Dienste

S3 ist eine sinnvolle Standardwahl für rohes HTML, JSON-Datensätze und Crawl-Manifeste, da bei jedem Aufruf ein dauerhaftes Objekt geschrieben und eine kurze URI zurückgegeben werden kann. Verwenden Sie deterministische Schlüssel wie scrapes/site/date/request-id.json; wenn dieselbe Nachricht erneut aufgerufen wird, sollte sie dasselbe Objekt überschreiben oder erkennen, dass das Ergebnis bereits vorhanden ist.

Speichern Sie API-Schlüssel, Proxy-Passwörter und Sitzungsanmeldedaten im SSM Parameter Store oder im Secrets Manager. Geben Sie in der Lambda-Umgebung nur den Parameternamen oder die ARN des Geheimnisses an. Erteilen Sie der Ausführungsrolle die Berechtigung, genau diese Ressource zu lesen – nicht jedes Geheimnis im Konto.

CloudWatch empfängt Funktionsprotokolle, Metriken und Alarme. Protokollieren Sie eine Korrelations-ID, den normalisierten Host, den Statuscode, die verstrichene Zeit, die Anzahl der Wiederholungsversuche, die Anzahl der Elemente und den Ausgabeschlüssel. Vermeiden Sie vollständige Abfragezeichenfolgen, Autorisierungs-Header, Cookies, Seiteninhalte und geheime Werte. X-Ray oder eine gleichwertige Ablaufverfolgung kann dabei helfen, die innerhalb von Lambda verbrachte Zeit von der Latenz bei DNS, Ziel, Proxy, S3 oder im Geheimnisspeicher zu unterscheiden.

Diese Trennung der Dienste bietet zudem natürliche Erweiterungspunkte. Sie können S3 Lebenszyklusregeln hinzufügen, einen Katalogisierungsauftrag nach erfolgreichen Schreibvorgängen ausführen oder ein Wiedergabetool für eine Dead-Letter-Warteschlange einsetzen, ohne den Extraktionscode zu ändern.

Benennen Sie Funktionen und Ressourcen nach ihrer Aufgabe und nicht nach der Zielseite. Trennen Sie die URL-Erstellung vom Seitenabruf, wenn diese unterschiedlich skalieren, und versionieren Sie das Ereignisschema, bevor mehrere Produzenten davon abhängig werden. So wird verhindert, dass Parser-Bereitstellungen versehentlich das Scheduling- oder Warteschlangenverhalten verändern.

Berücksichtigen Sie die Lambda-Kontingente bereits vor der Programmierung

Web-Scraping mit AWS Lambda unterliegt strengen Plattformbeschränkungen, von denen einige zeitabhängig sind. Zum Zeitpunkt der Erstellung der angegebenen Quellen entsprechen die häufig genannten Werte in etwa den unten aufgeführten. Überprüfen Sie diese vor der Veröffentlichung oder Bereitstellung für Ihre Region und Ihr Konto auf der offiziellen Seite zu den AWS-Lambda-Quoten.

Beschränkung

Gemeldeter Wert

Auswirkungen auf das Scraping

Maximale Aufrufe

Etwa 900 Sekunden

Lange Paginierung in Warteschlangeneinheiten aufteilen

Speicher

Etwa 128 MB bis 10.240 MB

Mehr Arbeitsspeicher verändert auch die verfügbare CPU-Leistung

Temporär /tmp

Etwa 512 MB bis 10.240 MB

Für Browser-Dateien und Downloads muss die Größe explizit festgelegt werden

ZIP-Paket

Etwa 50 MB komprimiert, 250 MB entpackt

Native Bibliotheken können Layer oder ein Bild erzwingen

Container-Image

Etwa 10 GB unkomprimiert

Einfachere Paketierung, aber langsamere Builds und größere Pulls

Synchrone Nutzdaten

Etwa 6 MB pro Richtung

Ergebnisse in S3 speichern und eine Referenz zurückgeben

Regionale Parallelität

Wird standardmäßig oft mit 1.000 angegeben

Bei neuen oder eingeschränkten Konten kann dieser Wert niedriger sein

Konfigurieren Sie das HTTP-Timeout nicht gleich dem Funktions-Timeout. Lassen Sie genügend Zeit für das Parsen, S3-Schreibvorgänge, Metriken und eine kontrollierte Fehlerantwort. Bei einer 60-Sekunden-Funktion ist beispielsweise ein Request-Timeout von 35 bis 45 Sekunden ein sicherer Ausgangswert als 60 Sekunden, wobei der korrekte Wert vom Ziel abhängt.

Die Parallelität dient auch der Steuerung des ausgehenden Datenverkehrs. Legen Sie eine reservierte Parallelität für den Scraper fest und, sofern unterstützt, eine maximale Parallelität für die Ereignisquelle bei SQS. Eine Service-Quote ist keine Berechtigung, so viele gleichzeitige Anfragen an eine Website zu senden.

Richten Sie das Repository und die Infrastruktur mit AWS SAM ein

AWS SAM definiert Funktionen, IAM-Berechtigungen, Zeitpläne, Warteschlangen und Buckets in einer CloudFormation-Vorlage. Es bietet „Web Scraping mit AWS Lambda“ einen wiederholbaren lokalen und Bereitstellungs-Workflow, ohne dass Docker für eine leichtgewichtige statische-HTML-Funktion erforderlich ist.

Ein kompaktes Repository kann beide Sprachen und Paketierungswege unterstützen:

lambda-scraper/
├── template.yaml
├── events/
│   ├── scrape.json
│   └── sqs.json
├── python/
│   ├── app.py
│   └── requirements.txt
├── java/
│   └── pom.xml
├── container/
│   ├── Dockerfile
│   ├── lambda_function.py
│   └── spider.py
└── tests/
    └── fixtures/

Installieren Sie eine unterstützte Python-Version, die AWS CLI, die AWS SAM CLI und Docker, falls Sie sam local oder Container-Images nutzen möchten. Konfigurieren Sie ein benanntes AWS-Profil und verifizieren Sie das Konto vor der Bereitstellung:

aws configure --profile scraper-dev
aws sts get-caller-identity --profile scraper-dev
sam validate --lint

Behalten Sie Testereignisse in der Quellcodeverwaltung bei, nehmen Sie jedoch niemals Live-Cookies, Proxy-Anmeldedaten oder signierte URLs auf. Parametrisieren Sie den Ausgabe-Bucket und die geheimen Kennungen je nach Umgebung. Ein wiederholbarer SAM-Workflow ist validate, build, local invoke, deploysowie der Cloud-Aufruf mit Protokollprüfung.

Wählen Sie ZIP-Pakete, Layers oder ein Container-Image

Verwenden Sie ein ZIP-Paket für requests, BeautifulSoup, Jsoup und ähnlich kleine Abhängigkeitsgraphen. SAM kann Abhängigkeiten in das Build-Artefakt installieren, und Kaltstarts bleiben relativ leicht.

Layer sind nützlich, wenn mehrere Funktionen einen stabilen Abhängigkeitssatz gemeinsam nutzen, erfordern jedoch eine Versionskoordination. Sie heben Paketbeschränkungen nicht auf, und ein Layer, der sich bei jeder Bereitstellung ändert, ist in der Regel nur ein weiteres zu verwaltendes Artefakt.

Wählen Sie ein AWS-Lambda-Container-Image, wenn Sie native Systempakete, Scrapy-Abhängigkeiten, die sich nur schwer in ein ZIP-Paket integrieren lassen, oder einen Headless-Browser benötigen. Ein Image verbessert die Konsistenz der Umgebung, nicht die Eignung zur Laufzeit.

Paketierung

Am besten geeignet

Wichtigster Kompromiss

ZIP

Statisches HTML, kleine Parser

Strengste Abhängigkeitsgrenzen

Layer plus ZIP

Gemeinsam genutzte stabile Bibliotheken

Funktionsübergreifende Versionskopplung

Container-Image

Native Bibliotheken, Scrapy, Playwright

Overhead bei Build, Scan, Speicherung und Kaltstart

Verwenden Sie Docker nicht automatisch, nur weil es sich produktionsähnlicher anfühlt. Bei einfachem HTML lässt sich das kleinste Artefakt in der Regel leichter patchen, testen und überwachen.

Halten Sie Umgebungsunterschiede in SAM-Parametern oder Konfigurationsdateien fest, nicht in manuell bearbeiteten Vorlagen. Eine Bereitstellung sollte anhand eines Commits, eines Profils und eines Parametersatzes reproduzierbar sein, wobei die Stack-Ausgaben Funktionsnamen, Bucket-Namen, Warteschlangen-URLs und Aliase offenlegen, die für Smoke-Tests benötigt werden.

Erstellen Sie den Python-Scraper für statisches HTML

Für statisches HTML benötigt ein Python-Web-Scraper auf AWS Lambda lediglich einen HTTP-Client, einen HTML-Parser und einen AWS-SDK-Client für dauerhafte Ausgabe. BeautifulSoup analysiert die empfangene Antwort; es führt kein JavaScript aus und wartet nicht auf eine clientseitige Anwendung.

Der unten stehende Handler akzeptiert direkte Aufrufe, JSON-Payloads von API Gateway oder ein EventBridge- detail Objekt. Er validiert die URL, verwendet Clients bei Warm-Aufrufen wieder, legt explizite Timeouts fest, analysiert ersetzbare CSS-Selektoren, schreibt das Ergebnis in S3 und gibt strukturierte Fehler zurück.

import json
import os
from datetime import datetime, timezone
from urllib.parse import urljoin, urlparse

import boto3
import requests
from bs4 import BeautifulSoup

SESSION = requests.Session()
SESSION.headers.update({"User-Agent": "catalog-monitor/1.0 (+ops@example.com)"})
S3 = boto3.client("s3")
BUCKET = os.environ["OUTPUT_BUCKET"]
REQUEST_TIMEOUT = (5, 35)

def normalize_event(event):
    payload = event or {}
    if isinstance(payload.get("body"), str):
        payload = json.loads(payload["body"] or "{}")
    elif isinstance(payload.get("body"), dict):
        payload = payload["body"]
    elif isinstance(payload.get("detail"), dict):
        payload = payload["detail"]

    url = str(payload.get("url", "")).strip()
    parsed = urlparse(url)
    if parsed.scheme not in {"http", "https"} or not parsed.netloc:
        raise ValueError("url must be an absolute HTTP or HTTPS URL")

    try:
        limit = max(1, min(int(payload.get("limit", 20)), 100))
    except (TypeError, ValueError):
        raise ValueError("limit must be an integer")

    return {
        "url": url,
        "limit": limit,
        "request_id": str(payload.get("request_id", "")).strip(),
    }

def parse_items(html, base_url, limit):
    soup = BeautifulSoup(html, "html.parser")
    items = []
    for card in soup.select(".product")[:limit]:
        link = card.select_one("a")
        items.append({
            "title": card.select_one(".title").get_text(" ", strip=True)
                     if card.select_one(".title") else None,
            "price": card.select_one(".price").get_text(" ", strip=True)
                     if card.select_one(".price") else None,
            "availability": card.select_one(".availability").get_text(" ", strip=True)
                     if card.select_one(".availability") else None,
            "url": urljoin(base_url, link.get("href")) if link else None,
        })
    return items

def handler(event, context):
    aws_id = getattr(context, "aws_request_id", "local")
    try:
        job = normalize_event(event)
        request_id = job["request_id"] or aws_id

        response = SESSION.get(job["url"], timeout=REQUEST_TIMEOUT)
        response.raise_for_status()
        if not response.encoding or response.encoding.lower() == "iso-8859-1":
            response.encoding = response.apparent_encoding

        items = parse_items(response.text, job["url"], job["limit"])
        result = {
            "ok": True,
            "request_id": request_id,
            "url": job["url"],
            "count": len(items),
            "items": items,
            "fetched_at": datetime.now(timezone.utc).isoformat(),
        }

        key = f"scrapes/{request_id}.json"
        S3.put_object(
            Bucket=BUCKET,
            Key=key,
            Body=json.dumps(result).encode("utf-8"),
            ContentType="application/json",
        )
        result["s3_uri"] = f"s3://{BUCKET}/{key}"
        return result

    except ValueError as exc:
        return {"ok": False, "error": {"type": "validation", "message": str(exc)}}
    except requests.RequestException as exc:
        status = getattr(exc.response, "status_code", None)
        return {"ok": False, "error": {"type": "request", "status": status}}
    except Exception:
        return {"ok": False, "error": {"type": "internal"}}

Ersetzen Sie die Selektoren durch solche, die durch Fixture-Tests abgedeckt sind. Wenn null Elemente unerwartet sind, behandeln Sie dies als Parser-Fehler und nicht als erfolgreichen leeren Scrape. Legen Sie außerdem fest, ob Weiterleitungen zulässig sind und ob benutzerdefinierte URLs eine Whitelist erfordern, um das Risiko von Server-Side Request Forgery zu verringern.

Lokal testen, bereitstellen, aufrufen und JSON in S3 schreiben

Verwenden Sie eine kleine Abhängigkeitsdatei:

requests
beautifulsoup4

Die folgenden SAM-Ressourcen erstellen einen Ausgabebucket und gewähren ausschließlich das Schreiben von Objekten unter dem Scraper-Präfix. Fixieren Sie unterstützte Laufzeiten und Abhängigkeitsversionen nach der Validierung im eigentlichen Repository.

Resources:
  OutputBucket:
    Type: AWS::S3::Bucket

  PythonScraper:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: python/
      Handler: app.handler
      Runtime: python3.12
      Architectures: [x86_64]
      MemorySize: 512
      Timeout: 60
      Environment:
        Variables:
          OUTPUT_BUCKET: !Ref OutputBucket
      Policies:
        - Statement:
            - Effect: Allow
              Action: s3:PutObject
              Resource: !Sub "${OutputBucket.Arn}/scrapes/*"

Erstellen events/scrape.json:

{
  "url": "https://example.com/catalog",
  "limit": 10,
  "request_id": "local-001"
}

Führen Sie anschließend eine wiederholbare Abfolge aus:

sam validate --lint
sam build
sam local invoke PythonScraper -e events/scrape.json
sam deploy --guided
aws lambda invoke \
  --function-name YOUR_STACK_FUNCTION \
  --payload fileb://events/scrape.json response.json

Validieren Sie das Objekt in S3 und überprüfen Sie den Funktionsprotokollstrom in CloudWatch. Ein lokaler Erfolg belegt die korrekte Analyse, nicht jedoch Cloud-Berechtigungen, DNS-Verhalten, Zielzugriff oder Zeitüberschreitungsspielraum. Die Cloud-Validierung sollte die genau bereitgestellte Rolle, Umgebungsvariablen, die Artefaktarchitektur, den Ausgabeschlüssel, den Statuscode, die Dauer und die Elementanzahl bestätigen.

Fügen Sie vor der Produktionsbereitstellung eine Richtlinie für die maximale Antwortgröße hinzu und lehnen Sie Inhaltstypen ab, die Sie nicht parsen möchten. Dies schont den Arbeitsspeicher und verhindert, dass der verbleibende Aufruf für einen umfangreichen Download aufgewendet wird. Bewahren Sie rohe HTML-Beispiele nur dann auf, wenn sie für die Diagnose benötigt werden, und wenden Sie dabei Aufbewahrungs- und Zugriffskontrollen an.

Scrapy oder Playwright in einen Lambda-Container verpacken

Ein Container ist dann sinnvoll, wenn der Abhängigkeitsgraph, native Bibliotheken oder die Browser-Laufzeitumgebung nicht mehr problemlos in eine ZIP-Datei passen. Dies ist kein Weg, das Laufzeitmodell von Lambda zu umgehen. Der Prozess verfügt weiterhin über einen begrenzten Aufruf, temporären lokalen Speicher und wegwerfbare Ausführungsumgebungen.

Bei einem Scrapy-AWS-Lambda-Worker sollten Sie jeden Spider-Lauf in einem Unterprozess isolieren. Der Reaktor von Twisted ist nicht dafür ausgelegt, im selben Python-Prozess wiederholt angehalten und neu gestartet zu werden, was bei „warmen“ Lambda-Aufrufen zu unerwarteten Verhaltensweisen führen kann. Ein Unterprozess verursacht zwar einen Start-Overhead, bietet aber jedem Crawl einen sauberen Reaktor und eine klare Timeout-Grenze.

Ein minimales Lambda-orientiertes Image könnte wie folgt aussehen:

FROM public.ecr.aws/lambda/python:3.12

COPY requirements.txt ${LAMBDA_TASK_ROOT}/
RUN pip install --no-cache-dir -r requirements.txt

COPY lambda_function.py spider.py ${LAMBDA_TASK_ROOT}/
CMD ["lambda_function.handler"]

Fixieren und hashen Sie Abhängigkeiten im bereitgestellten Build. Das hier gezeigte Basis-Image und die Laufzeitumgebung müssen zum Zeitpunkt der Bereitstellung mit den unterstützten Lambda-Basis-Images abgeglichen werden.

Ein kompakter Handler kann einen vorhandenen Spider ausführen, JSON in /tmpund diese hochladen:

import json
import os
import subprocess
import uuid

import boto3

S3 = boto3.client("s3")
BUCKET = os.environ["OUTPUT_BUCKET"]

def handler(event, context):
    url = event["url"]
    request_id = event.get("request_id") or str(uuid.uuid4())
    output = f"/tmp/{request_id}.json"

    subprocess.run(
        [
            "scrapy", "runspider", "spider.py",
            "-a", f"start_url={url}",
            "-O", output,
        ],
        check=True,
        timeout=720,
    )

    key = f"scrapes/{request_id}.json"
    S3.upload_file(output, BUCKET, key)
    with open(output, encoding="utf-8") as handle:
        count = len(json.load(handle))

    return {
        "ok": True,
        "request_id": request_id,
        "url": url,
        "count": count,
        "s3_uri": f"s3://{BUCKET}/{key}",
    }

Der Spider sollte start_url, explizite Download-Timeouts verwenden, die Paginierung begrenzen und Datensätze erzeugen, die dem Python- und Java-Ausgabeschema entsprechen. Scrapy kann einen Feed auch direkt in S3 schreiben, aber ein expliziter Upload macht den Ausgabevertrag und die Fehlergrenzen leichter erkennbar.

Die Playwright-AWS-Lambda-Paketierung folgt demselben Image-Prinzip, ist jedoch umfangreicher. Chromium, Schriftarten, gemeinsam genutzte Bibliotheken und Browser-Caches müssen alle mit der Image-Architektur übereinstimmen. Planen Sie mehr Speicher und Kaltstartzeit ein, schreiben Sie Downloads nur in /tmp, schließen Sie Kontexte in finally, und gehen Sie niemals davon aus, dass ein Browserprofil den Aufruf überdauert. Eine Scrapy-Playwright-Konfiguration ist nur dann sinnvoll, wenn für die Daten eine Browser-Rendering-Verarbeitung erforderlich ist.

Erstellen Sie das Image, übertragen Sie es in ECR und stellen Sie es mit SAM bereit

Erstellen Sie eine Architektur, die zur Funktion passt. Das folgende x86-Beispiel verwendet versionierte Tags; ersetzen Sie diese durch ein aktuell unterstütztes Basis-Image und eine Region.

ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGION=us-east-1
REPO=lambda-scraper
TAG=2026-03-01

aws ecr create-repository --repository-name "$REPO" 2>/dev/null || true
aws ecr get-login-password --region "$REGION" |
  docker login --username AWS --password-stdin \
  "$ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com"

docker buildx build \
  --platform linux/amd64 \
  -t "$REPO:$TAG" \
  --load container/

docker tag "$REPO:$TAG" \
  "$ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$REPO:$TAG"
docker push "$ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$REPO:$TAG"

Übergeben Sie die URI des versionierten Images an SAM, anstatt eine mehrdeutige latest Referenz:

Parameters:
  ScraperImageUri:
    Type: String

Resources:
  ContainerScraper:
    Type: AWS::Serverless::Function
    Properties:
      PackageType: Image
      ImageUri: !Ref ScraperImageUri
      Architectures: [x86_64]
      MemorySize: 2048
      EphemeralStorage:
        Size: 2048
      Timeout: 840

Stellen Sie das Image mit dem exakten Tag bereit:

sam deploy \
  --parameter-overrides \
  ScraperImageUri="$ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$REPO:$TAG"

Um eine bessere Reproduzierbarkeit zu gewährleisten, speichern Sie den von ECR erzeugten Image-Digest in den Bereitstellungsmetadaten. Aktivieren Sie Repository-Scans und Aufbewahrungskontrollen erst, nachdem Sie die aktuellen ECR-Optionen und die Richtlinien Ihrer Organisation überprüft haben. Bereinigen Sie alte, nicht referenzierte Images, lokale Layer, Test-Stacks und S3-Testobjekte, damit Container-Experimente nicht zu dauerhaften Kosten führen.

Testen Sie das Image lokal mit dem Laufzeit-Endpunkt von Lambda oder sam local invokeund führen Sie anschließend einen Cloud-Smoke-Test durch. Die lokale Docker-Architektur, die Lambda-Architektur, Dateisystemberechtigungen und Browser-Systembibliotheken sind häufige Ursachen für Fehler nach dem Motto „Auf meinem Rechner funktioniert es“.

Sorgen Sie für deterministische Container-Builds, indem Sie den Digest des Basis-Images, die Abhängigkeitssperre, die Zielarchitektur und den Digest des generierten Images in CI protokollieren. Führen Sie regelmäßige Neu-Builds für Sicherheitsupdates durch, übertragen Sie jedoch einen getesteten Digest zwischen den Umgebungen, anstatt für Entwicklung und Produktion jeweils unterschiedliche Bytes neu zu erstellen.

Erstellen Sie den Java-21-Scraper mit HttpClient und Jsoup

Der Java-AWS-Lambda-Web-Scraper sollte denselben Ereignis- und Ergebnisvertrag wie Python verwenden. HttpClient „handles“ verarbeitet begrenzte HTTP-Anfragen, Jsoup analysiert HTML, Jackson normalisiert JSON-Ereignis-Inhalte und das AWS SDK schreibt das dauerhafte Ergebnis.

package example;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.time.Instant;
import java.util.*;

public final class Handler
    implements RequestHandler<Map<String, Object>, Map<String, Object>> {

  private static final ObjectMapper JSON = new ObjectMapper();
  private static final HttpClient HTTP = HttpClient.newBuilder()
      .connectTimeout(Duration.ofSeconds(5))
      .followRedirects(HttpClient.Redirect.NORMAL)
      .build();
  private static final S3Client S3 = S3Client.create();
  private static final String BUCKET = System.getenv("OUTPUT_BUCKET");

  @Override
  public Map<String, Object> handleRequest(
      Map<String, Object> event, Context context) {
    try {
      Map<String, Object> input = normalize(event);
      String url = Objects.toString(input.get("url"), "").trim();
      URI uri = URI.create(url);
      if (!Set.of("http", "https").contains(uri.getScheme())) {
        throw new IllegalArgumentException("url must use HTTP or HTTPS");
      }

      int limit = Math.max(1, Math.min(
          Integer.parseInt(Objects.toString(input.getOrDefault("limit", 20))), 100));
      String requestId = Objects.toString(
          input.getOrDefault("request_id", context.getAwsRequestId()));

      HttpRequest request = HttpRequest.newBuilder(uri)
          .timeout(Duration.ofSeconds(35))
          .header("User-Agent", "catalog-monitor/1.0 (+ops@example.com)")
          .GET()
          .build();

      HttpResponse<String> response = HTTP.send(
          request, HttpResponse.BodyHandlers.ofString());
      if (response.statusCode() < 200 || response.statusCode() >= 300) {
        return error("request", requestId, response.statusCode());
      }

      Document doc = Jsoup.parse(response.body(), url);
      List<Map<String, String>> items = new ArrayList<>();
      for (Element card : doc.select(".product")) {
        if (items.size() >= limit) break;
        Element link = card.selectFirst("a");
        Map<String, String> item = new LinkedHashMap<>();
        item.put("title", text(card, ".title"));
        item.put("price", text(card, ".price"));
        item.put("availability", text(card, ".availability"));
        item.put("url", link == null ? null : link.absUrl("href"));
        items.add(item);
      }

      Map<String, Object> result = new LinkedHashMap<>();
      result.put("ok", true);
      result.put("request_id", requestId);
      result.put("url", url);
      result.put("count", items.size());
      result.put("items", items);
      result.put("fetched_at", Instant.now().toString());

      String key = "scrapes/" + requestId + ".json";
      S3.putObject(
          PutObjectRequest.builder().bucket(BUCKET).key(key)
              .contentType("application/json").build(),
          RequestBody.fromString(JSON.writeValueAsString(result)));
      result.put("s3_uri", "s3://" + BUCKET + "/" + key);
      return result;

    } catch (InterruptedException ex) {
      Thread.currentThread().interrupt();
      return error("interrupted", context.getAwsRequestId(), null);
    } catch (Exception ex) {
      return error("internal", context.getAwsRequestId(), null);
    }
  }

  private static String text(Element root, String selector) {
    Element node = root.selectFirst(selector);
    return node == null ? null : node.text();
  }

  private static Map<String, Object> normalize(Map<String, Object> event)
      throws Exception {
    Object body = event.get("body");
    if (body instanceof String text && !text.isBlank()) {
      return JSON.readValue(text, new TypeReference<>() {});
    }
    if (body instanceof Map<?, ?> map) return (Map<String, Object>) map;
    Object detail = event.get("detail");
    if (detail instanceof Map<?, ?> map) return (Map<String, Object>) map;
    return event;
  }

  private static Map<String, Object> error(
      String type, String requestId, Integer status) {
    Map<String, Object> details = new LinkedHashMap<>();
    details.put("type", type);
    if (status != null) details.put("status", status);
    return Map.of("ok", false, "request_id", requestId, "error", details);
  }
}

Verwenden Sie die statischen Clients bei „Warm“-Aufrufen wieder, speichern Sie jedoch keine für die Korrektheit kritischen Crawling-Zustände in statischen Feldern. Es gelten dieselben Vorbehalte wie bei Python: Bereinigen Sie Protokolle, behandeln Sie unerwartete Parsing-Ergebnisse mit null Elementen als Fehler und halten Sie das HTTP-Timeout unterhalb des Funktions-Timeouts. Eine Referenz zum HTML-Parsing in Java mit Jsoup ist nützlich, wenn Selektoren oder das Verhalten absoluter Links einer eingehenderen Behandlung bedürfen.

Mit Maven bündeln und die Startzeit mit SnapStart verkürzen

Das Maven-Projekt benötigt die Lambda-Kernschnittstellen, Jsoup, Jackson, das S3-SDK sowie einen Shading-Schritt, der eine einzige Deployment-JAR-Datei erzeugt. Legen Sie die Versionen fest, nachdem Sie die aktuellen Releases und Ihre Abhängigkeitsrichtlinie überprüft haben.

<dependencies>
  <dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-lambda-java-core</artifactId>
    <version>${lambda.core.version}</version>
  </dependency>
  <dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>${jsoup.version}</version>
  </dependency>
  <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>${jackson.version}</version>
  </dependency>
  <dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>s3</artifactId>
    <version>${aws.sdk.version}</version>
  </dependency>
</dependencies>

Konfigurieren maven-shade-plugin während package, und verweisen Sie SAM anschließend auf die gebündelte JAR-Datei. Die erwartete SnapStart-Konfiguration verwendet eine veröffentlichte Version oder einen Alias anstelle von $LATEST:

JavaScraper:
  Type: AWS::Serverless::Function
  Properties:
    CodeUri: java/target/scraper.jar
    Handler: example.Handler::handleRequest
    Runtime: java21
    MemorySize: 1024
    Timeout: 60
    AutoPublishAlias: live
    SnapStart:
      ApplyOn: PublishedVersions

Erstellen und mit mvn clean package, sam build, sam local invoke JavaScraper -e events/scrape.json, und sam deploy. Vergewissern Sie sich zum Zeitpunkt der Erstellung dieses Artikels, dass Java 21 und SnapStart in der Bereitstellungsregion unterstützt werden, und prüfen Sie etwaige Einschränkungen hinsichtlich Initialisierung, Netzwerkverbindungen, Entropie und zwischengespeicherter Anmeldedaten nach der Wiederherstellung eines Snapshots.

Gehen Sie bewusst mit JavaScript, Proxys und Anti-Bot-Maßnahmen um

Web-Scraping mit AWS Lambda ändert nichts daran, wie ein Ziel Inhalte rendert oder den Datenverkehr auswertet. Beginnen Sie mit dem einfachsten autorisierten Anforderungspfad, beobachten Sie den Fehler und eskalieren Sie nur bei einem diagnostizierten Grund.

  1. Direktes HTTP: Verwenden Sie diese Methode, wenn die Antwort die Daten enthält. Fügen Sie einen authentischen User-Agent hinzu, begrenzte Wiederholungsversuche bei vorübergehenden Fehlern, eine angemessene Abfragerate und Parser-Tests.
  2. Datenzugriff auf Basis öffentlicher Daten: Wenn eine Seite öffentliche Daten von einem JSON-Endpunkt lädt, nutzen Sie diesen Endpunkt nur, wenn dessen Nutzungsbedingungen und Autorisierungsvorschriften dies zulassen. Gehen Sie nicht davon aus, dass ein undokumentierter Endpunkt stabil ist.
  3. Headless-Browser: Verwenden Sie Playwright, wenn der Workflow tatsächlich die Ausführung von JavaScript, Klicks, Scrollen oder das Absenden von Formularen erfordert. Die Darstellung im Browser garantiert keinen Zugriff.
  4. Expliziter Proxy: Verwenden Sie einen Proxy, wenn geografische Anforderungen, eine stabile ausgehende Verbindung oder IP-Rotation legitime Anforderungen sind. Die Reputation des Proxys, die Sitzungsaffinität und die Zielrichtlinien spielen weiterhin eine Rolle.
  5. Verwalteter Scraping-Dienst: Lagern Sie die Datengewinnung aus, wenn die Wartung von Browser, Proxy, CAPTCHA und Wiederholungsversuchen höhere Kosten verursacht als die Extraktionslogik selbst.

Adressbereiche von Cloud-Anbietern können von einigen Websites erkannt oder blockiert werden. Dies ist kontextabhängig, und die Umstellung auf einen Proxy oder Browser ist keine Erfolgsgarantie. Header und Stealth-Plugins können zudem ein falsches Gefühl der Zuverlässigkeit vermitteln, wenn das eigentliche Problem in der Anfragerate, der Autorisierung, dem Kontostatus oder sich änderndem Markup liegt.

Eine praktische Anti-Bot-Richtlinie für das Web-Scraping klassifiziert die Ergebnisse separat: Transportfehler, Timeout, HTTP-Blockierung, Challenge-Seite, Anmeldepflicht, leerer Rendering-Zustand und Parser-Inkompatibilität. Für jede Kategorie gibt es eine andere Lösung. Ein identischer Wiederholungsversuch für alle Fälle verschwendet Geld und kann den Druck auf das Ziel erhöhen.

Vergleichen Sie explizite Proxys mit einer verwalteten Scraping-API

Bei einem expliziten Proxy erfolgen die Erstellung der Anfrage und das Parsen in Ihrem Code. Laden Sie dessen Anmeldedaten aus einem geheimen Speicher, nicht aus dem Event oder Repository:

import boto3
import os
import requests

ssm = boto3.client("ssm")
proxy_url = ssm.get_parameter(
    Name=os.environ["PROXY_PARAMETER"],
    WithDecryption=True,
)["Parameter"]["Value"]

response = requests.get(
    event["url"],
    proxies={"http": proxy_url, "https": proxy_url},
    timeout=(5, 35),
)
response.raise_for_status()

Eine verwaltete Scraping-API kann das Abrufen von Roh-HTML, die Proxy-Rotation, die CAPTCHA-Verarbeitung oder das Browser-Rendering je nach den dokumentierten Fähigkeiten des Anbieters außerhalb von Lambda auslagern. Halten Sie die Integration generisch, bis Sie die aktuelle Authentifizierung und die Parameter überprüft haben:

token = ssm.get_parameter(
    Name=os.environ["API_TOKEN_PARAMETER"],
    WithDecryption=True,
)["Parameter"]["Value"]

response = requests.get(
    os.environ["SCRAPING_API_URL"],
    params={"url": event["url"]},
    headers={"Authorization": f"Bearer {token}"},
    timeout=(5, 50),
)
response.raise_for_status()

Option

Sie haben die Kontrolle

Sie verwalten

Kostenentwicklung

Direkte Anfragen

HTTP und Parsing

Wiederholversuche, Ratenbegrenzung, IP-Pfad

Lambda plus Netzwerk

Explizite Proxys

Proxy-Auswahl und Sitzungen

Rotation, Ausfälle, Anmeldedaten

Proxy-Datenverkehr plus Rechenleistung

Browser-Container

Vollständige Interaktion

Browser-Image und Stabilität

Mehr Arbeitsspeicher und längere Laufzeit

Verwaltete API

Anfrageoptionen und Parsing

Anbieterintegration und Fallback

Pro Anfrage, Guthaben oder Ergebnis

Ein Leitfaden zur Verwendung von Proxys mit Python-Requests ist eine naheliegende Ergänzung bei der Implementierung von Authentifizierung, Rotation und Fehlerbehandlung. Bevor Sie sich für einen verwalteten Ansatz entscheiden, sollten Sie Latenz, Antwortformat, maximale Body-Größe, Abrechnungseinheit, regionales Routing, Datenverarbeitung und das Verhalten bei fehlgeschlagenen Anfragen überprüfen.

Sichere Skalierung von Jobs mit mehreren URLs mit SQS

Beim queuenbasierten Scraping wird in jede SQS-Nachricht eine eigenständige Arbeitseinheit gepackt – in der Regel eine URL sowie eine Anfrage-ID, die Parser-Version und optionale Crawling-Metadaten. Lambda empfängt einen kleinen Stapel, verarbeitet jede Nachricht und schreibt jedes Ergebnis separat. So bleiben erfolgreiche URLs vollständig erhalten, während fehlgeschlagene Anfragen erneut versucht werden.

Beim Web-Scraping mit AWS Lambda ist die Granularität der Nachrichten eine Entscheidung zur Ratenkontrolle. Eine URL pro Nachricht sorgt für das sauberste Verhalten bei Wiederholungsversuchen und Idempotenz. Eine kleine Gruppe eng verwandter URLs kann den Warteschlangenaufwand reduzieren, aber eine langsame URL verzögert dann die gesamte Nachricht.

Halten Sie die Nachrichten klein. Speichern Sie große Startlisten oder Sitzungsartefakte in S3 und legen Sie nur deren Objektreferenz in SQS ab. Fügen Sie genügend Informationen hinzu, um die Anfrage zu reproduzieren, aber geben Sie keine Proxy-Passwörter, Cookies oder API-Token an.

Passen Sie die Timeouts über alle Schichten hinweg an:

  • Das HTTP-Timeout muss kürzer sein als das Funktions-Timeout.
  • Das Timeout der Funktion muss genügend Zeit für die Ausgabe und Diagnose lassen.
  • Das SQS-Sichtbarkeits-Timeout muss die Zeit überschreiten, die ein Batch möglicherweise in Bearbeitung bleibt, einschließlich einer angemessenen Sicherheitsmarge für Wiederholungsversuche.
  • Die Nachrichtenaufbewahrungsdauer und die Aufbewahrungsdauer für Dead-Letter-Nachrichten müssen lang genug sein, damit die Betreiber die Probleme untersuchen können.
  • Die Parallelität bei reservierten und ereignisgesteuerten Aufrufen muss eine zielgerichtete Anforderungsrate widerspiegeln und nicht lediglich die Kapazität des Kontos.

AWS-Einstellungen und vorgeschriebene Mindestwerte können sich ändern; überprüfen Sie daher das aktuelle Integrationsverhalten in der offiziellen Dokumentation zu „Lambda mit SQS“.

Implementieren Sie Wiederholungsversuche, teilweise fehlgeschlagene Batches, DLQs, Idempotenz und Parallelitätsgrenzen

Aktivieren Sie teilweise Batch-Antworten, damit Lambda nur die IDs fehlgeschlagener Nachrichten zurückgibt. Ohne dieses Verhalten kann ein fehlerhafter Datensatz dazu führen, dass jeder Datensatz im Batch erneut erscheint.

import json
import logging

log = logging.getLogger()
log.setLevel("INFO")

def sqs_handler(event, context):
    failures = []

    for record in event.get("Records", []):
        message_id = record["messageId"]
        try:
            job = json.loads(record["body"])
            scrape_one(job)  # Writes deterministic S3 key
        except Exception as exc:
            log.exception("scrape_failed", extra={"message_id": message_id})
            failures.append({"itemIdentifier": message_id})

    return {"batchItemFailures": failures}

Deklarieren Sie in SAM den Antwortmodus für die Ereignisquelle:

Events:
  UrlQueue:
    Type: SQS
    Properties:
      Queue: !GetAtt ScrapeQueue.Arn
      BatchSize: 5
      FunctionResponseTypes:
        - ReportBatchItemFailures

Stellen Sie sicher, dass scrape_one idempotent. Ein deterministischer Schlüssel wie scrapes/{job_id}.json, ein bedingter DynamoDB-Schreibvorgang oder eine Prüfung auf bereits vorhandenes Ergebnis verhindert, dass doppelte Nachrichten doppelte Auswirkungen in nachgelagerten Prozessen hervorrufen.

Fügen Sie über die SQS-Redrive-Richtlinie eine Dead-Letter-Warteschlange hinzu und wählen Sie deren Empfangsschwelle entsprechend dem tatsächlich gewünschten Wiederholungsverhalten aus. Leiten Sie erschöpfte Nachrichten zur Überprüfung und selektiven Wiederholung dorthin weiter. Begrenzen Sie die Parallelität sowohl auf der Funktionsebene als auch auf der Ebene der Ereignisquelle, sofern möglich. Diese Kontrollen schützen die Website, reservieren Kapazität für andere Funktionen und verhindern, dass sich ein Rückstau zu einem unbeabsichtigten Traffic-Anstieg entwickelt.

Der Gegendruck beginnt beim Produzenten. Wenn das Alter der Warteschlange zunimmt, pausieren oder verlangsamen Sie die URL-Ermittlung, anstatt einen unbegrenzten Rückstau zuzulassen. Verfolgen Sie jedes Ziel oder jeden Host separat, wenn diese unterschiedliche Parallelität, Geschwindigkeitsbegrenzung, Anmeldedaten oder Wiederholungsrichtlinien erfordern.

Fügen Sie Observabilität und Fehlerdiagnose hinzu

Ein AWS-Lambda-Scraper sollte pro Versuch eine strukturierte Zusammenfassung ausgeben. Verwenden Sie JSON-Felder, die ohne Parsen von Text abgefragt werden können:

{
  "event": "scrape_complete",
  "request_id": "job-2026-03-001",
  "host": "example.com",
  "status": 200,
  "duration_ms": 842,
  "retry_count": 0,
  "item_count": 20,
  "output_key": "scrapes/job-2026-03-001.json"
}

Normalisieren Sie den Host und lassen Sie Abfragezeichenfolgen weg, sofern diese nicht als sicher gelten. Protokollieren Sie niemals Autorisierungs-Header, Cookies, Proxy-URLs mit Anmeldedaten, vollständige HTML-Inhalte oder geheime Werte.

Verfolgen Sie mindestens diese Metrikklassen:

  • Arbeit: Versuche, Erfolge, extrahierte Elemente, gespeicherte Bytes.
  • Anfragezustand: Latenz, Timeouts, Verbindungsfehler, HTTP-Statusfamilien.
  • Blockierungssignale: Erkennung von Challenge-Seiten, Zugriffsverweigerungen, Anmeldeumleitungen.
  • Parser-Zustand: Ergebnisse ohne Elemente, fehlende Pflichtfelder, Selektorfehler.
  • AWS-Zustand: Fehler, Drosselungen, Dauer, Speicher, SQS-Alter und DLQ-Tiefe.

Alarme sollten bei Raten und anhaltenden Trends ausgelöst werden, nicht bei jedem einzelnen Fehler. Ein Parser-Alarm sollte sich von einem Block-Alarm unterscheiden, da das Runbook und der Verantwortliche unterschiedlich sein können. Legen Sie die Protokollaufbewahrungsdauer bewusst fest, fügen Sie Korrelations-IDs zu Warteschlangen-Nachrichten und S3-Schlüsseln hinzu und nutzen Sie Tracing, wenn dadurch die Ziellatenz deutlich von den AWS-Dienstaufrufen getrennt wird.

CloudWatch empfängt Standard-Lambda-Protokolle unter der Protokollgruppe der Funktion. X-Ray kann dabei helfen, die nachgelagerte Latenz zu visualisieren, doch das Tracing jeder Anfrage mit hohem Datenaufkommen kann zusätzliche Kosten und Störsignale verursachen. Führen Sie gezielte Stichproben durch und bewahren Sie genügend Beispiele für Fehlschläge auf – wobei sensible Inhalte entfernt werden –, um Vorfälle reproduzieren zu können.

Erstellen Sie Dashboards rund um Entscheidungen: ob der Datenverkehr gedrosselt, ein Parser zurückgesetzt, Anmeldedaten rotiert, der Arbeitsspeicher erhöht oder Fehler nachgestellt werden soll. Ein Diagramm, das die nächste Aktion eines Operators nicht beeinflussen kann, ist wahrscheinlich nur Störsignal.

Änderungen über CI/CD testen und bereitstellen

Behandeln Sie Parser als deterministischen Code und Netzwerke als austauschbare Grenzen. Speichern Sie repräsentative HTML-Fixtures für normale Seiten, leere Seiten, geändertes Markup, Challenge-Seiten und fehlerhafte Antworten. Unit-Tests sollten Parsing-Funktionen direkt aufrufen und erforderliche Felder, absolute URLs sowie das Fehlerverhalten überprüfen.

Simulieren Sie HTTP-Antworten, S3-Schreibvorgänge, SSM-Lesevorgänge und Timeouts in Handler-Tests. Fügen Sie Vertragstests hinzu, die genauso ablaufen url, limitund request_id Ereignis sowohl für Python als auch für Java ausführen und die Ergebnishülle vergleichen, nicht die sprachspezifischen Interna.

Eine praktische Pipeline läuft wie folgt ab:

  1. Formatierer, Linter, Typ- oder Kompilierungsprüfungen sowie Unit-Tests.
  2. sam validate --lint sowie CloudFormation-Richtlinienprüfungen.
  3. Prüfungen auf Abhängigkeiten und Container-Sicherheitslücken.
  4. sam build oder eine architektur-spezifische Image-Erstellung.
  5. Lokaler Smoke-Test mit einem Fixture-Server.
  6. Bereitstellung in einem Nicht-Produktions-Stack und einem kontrollierten Live-Canary.
  7. Schrittweise Produktionsbereitstellung mit einem sofortigen Rollback-Pfad.

Machen Sie CI nicht bei jedem Commit von einer unkontrollierten öffentlichen Website abhängig. Verwenden Sie einen lokalen Testserver für die Wiederholbarkeit und planen Sie separate Integrationsprüfungen für das Zielverhalten ein. Stellen Sie sicher, dass Deployment-Rollen die Scraper-Rolle nicht erweitern können, dass Geheimnisse niemals in Build-Protokollen erscheinen und dass alte Container-Images und Test-Buckets bereinigt werden.

Berechnen Sie die Gesamtkosten, nicht nur die Lambda-Rechenkosten

Die Kosten für das Scraping mit AWS Lambda richten sich zunächst nach der Anzahl der Anfragen und der Dauer, doch dies ist nur die Rechenkosten-Zeile. Berechnen Sie die Nutzung, bevor Sie einen regionalen Preis anwenden:

GB-seconds = invocations × average duration in seconds × configured memory in GB
request units = total invocations, including retries

Eine täglich ausgeführte Funktion, die 3 Sekunden lang 256 MB nutzt, verbraucht über 30 Tage etwa 22,5 GB-Sekunden. Bei einer Million Seiten verbraucht ein statischer Parser, der 2 Sekunden lang 256 MB nutzt, 500.000 GB-Sekunden. Ein Browser-Worker, der 10 Sekunden lang 2 GB belegt, verbraucht 20.000.000 GB-Sekunden. Diese Nutzungszahlen sind aussagekräftiger als eine Angabe in Dollar, da Tarife, Architektur-Rabatte, die Berechtigung für die Free Tier und die Regionen variieren können.

Das bereitgestellte Quellenmaterial nennt ein ungefähres monatliches Freikontingent von einer Million Anfragen und 400.000 GB-Sekunden und schätzt dann auf der Grundlage seiner Annahmen die Kosten für das statische Beispiel auf etwa 1,33 US-Dollar und für das Browser-Beispiel auf 261,33 US-Dollar. Betrachten Sie diese Dollarbeträge als unbestätigte Planungswerte und nicht als verbindliche Angebote für das Jahr 2026. Berechnen Sie sie anhand der offiziellen AWS Lambda-Preisseite für die Bereitstellungsregion neu.

Fügen Sie alle Nebenkosten hinzu:

Kostenbereich

Typischer Kostenfaktor

S3

Objektschreibvorgänge, Speicherung, Lesevorgänge, Lebenszyklus

CloudWatch

Protokollaufnahme, Aufbewahrung, Metriken, Alarme

ECR

Image-Speicherung, Scannen, regionenübergreifende Übertragung

Netzwerk

Datenübertragung und mögliche NAT-Gateway-Verarbeitung

SQS oder Step Functions

Anfragen, Übergänge, Wiederholungsversuche

Ausführung im Browser

Mehr Speicher, Laufzeit und Bildgröße

Proxy oder verwaltete API

Datenverkehr, Anfragen, Credits oder erfolgreiche Ergebnisse

NAT kann eine kleine Arbeitslast dominieren, wenn Lambda nur für einen stabilen ausgehenden Datenverkehr in einer VPC platziert wird. Berücksichtigen Sie auch Wiederholungsversuche und blockierte Antworten. Diese verbrauchen Rechenleistung und Datenverkehr von Drittanbietern, selbst wenn sie keine Daten erzeugen.

Checkliste für den Produktionsstart: Sicherheit, Compliance und respektvolles Crawling

Bevor Sie „Web Scraping mit AWS Lambda“ in Betrieb nehmen, überprüfen Sie das System sowohl als AWS-Workload als auch als automatisierten Datensammler.

  • IAM: Weisen Sie jeder Funktion nur das S3-Präfix, die Warteschlange, den Metrik-Namespace und die geheime ARN zu, die sie benötigt. Trennen Sie Deployment-Berechtigungen von Laufzeitberechtigungen.
  • Geheimnisse: Speichern Sie Proxy-, API- und Anmeldedaten im SSM Parameter Store oder im Secrets Manager. Rotieren Sie diese regelmäßig und verhindern Sie, dass entschlüsselte Werte in Protokolle gelangen.
  • Verschlüsselung: Verwenden Sie Verschlüsselung für S3, Warteschlangen, Protokolle und sensible Konfigurationen entsprechend Ihrer Datenklassifizierung und den Richtlinien Ihres Unternehmens.
  • Eingabekontrollen: Überprüfen Sie Schemata und Hosts. Wenn Aufrufer URLs bereitstellen, ziehen Sie eine Zulassungsliste sowie Schutzmaßnahmen gegen Anfragen an Metadaten, private oder link-lokale Adressen in Betracht.
  • Verkehrskontrollen: Legen Sie reservierte Parallelität, Warteschlangenobergrenzen, Zeitüberschreitungen für Anfragen, Obergrenzen für Wiederholungsversuche und einen globalen Kill-Switch fest. Ein Konfigurationsflag, das neue Anfragen stoppt, ist schneller als die Bereitstellung von Notfallcode.
  • Datenminimierung: Erfassen Sie nur die für den angegebenen Zweck erforderlichen Felder, legen Sie die Aufbewahrungsfristen fest und beschränken Sie den Zugriff auf Roh-HTML, das personenbezogene oder sensible Daten enthalten könnte.
  • Überprüfung der Ziele: Überprüfen Sie vor der Datenerhebung die robots.txt-Datei, die Nutzungsbedingungen, die Autorisierung, die Richtlinien zur Zugriffsrate, die Kontoregeln, die Datenschutzverpflichtungen und das geltende Recht. Diese Signale haben unterschiedliche rechtliche Bedeutungen, daher sind ein Rahmenwerk zur Einhaltung der Rechtsvorschriften beim Web-Scraping sowie qualifizierte Rechtsberatung bei wesentlichen Risiken angebracht.
  • Operative Zuständigkeit: Legen Sie Warnmeldungen, ein DLQ-Wiederholungsverfahren, ein Runbook für Parser-Änderungen und einen Ansprechpartner für Beschwerden von Zielseiten fest.
  • Sichere Inbetriebnahme: Beginnen Sie mit geringer Parallelität, beobachten Sie den Status und die Parser-Metriken und erhöhen Sie den Durchsatz dann schrittweise.

Dies ist eine technische Anleitung, keine Rechtsberatung. Ob die Datenerhebung zulässig ist, hängt von den Daten, der Rechtsordnung, der Zugriffsmethode, dem Vertragsverhältnis und dem Verwendungszweck ab. Betrachten Sie technische Zugänglichkeit nicht als Berechtigung.

Was zuerst bereitgestellt werden sollte

Beginnen Sie mit dem kleinsten funktionsfähigen System: direktes HTTP in Python, von SAM gepackt, ausgelöst durch ein Testereignis und mit JSON-Schreiben in S3. Diese Basisversion überprüft Berechtigungen, Netzwerk, Parsing, Speicherung und Überwachbarkeit mit einem Minimum an beweglichen Teilen.

Fügen Sie EventBridge für einen kleinen Zeitplan hinzu. Fügen Sie SQS hinzu, wenn URLs zu eigenständigen Arbeitselementen werden. Wechseln Sie nur dann zu einem Container, wenn die Abhängigkeiten dies rechtfertigen, und verwenden Sie einen Browser nur, wenn die Daten eine Darstellung oder Interaktion erfordern. Steigern Sie erst dann auf Proxys oder verwaltetes Abrufen, nachdem Sie den Ausfallmodus gemessen haben. Diese Reihenfolge sorgt dafür, dass „Web Scraping mit AWS Lambda“ auch bei zunehmender Komplexität verständlich bleibt.

Wichtige Erkenntnisse

  • Verwenden Sie Lambda, wenn jeder Scrape-Vorgang kurz, begrenzt und unabhängig wiederholbar ist; wählen Sie länger laufende Recheninstanzen für persistente Sitzungen oder mehrstündige Crawls.
  • Halten Sie einen einheitlichen Ereignis- und Ergebniskontrakt über Python, Java, Zeitpläne, Warteschlangen und lokale Tests hinweg ein.
  • Speichern Sie Ergebnisse in S3, Anmeldedaten in einem verwalteten Geheimnisspeicher und Betriebsdaten in strukturierten Protokollen und Metriken.
  • Fügen Sie SQS-Teilausfälle, DLQs, Idempotenz und Parallelitätsbeschränkungen hinzu, bevor Sie das URL-Volumen erhöhen.
  • Berechnen Sie Rechenleistung, Speicher, Protokolle, Images, Netzwerk, Wiederholungsversuche, Proxys und verwaltete Dienste als separate Kostenpositionen.

FAQ

Muss ein AWS-Lambda-Scraper innerhalb einer VPC ausgeführt werden?

Nein. Eine Lambda-Funktion kann auf öffentliche Websites zugreifen, ohne an Ihre VPC angebunden zu sein. Verwenden Sie eine VPC nur, wenn sie auf private Ressourcen zugreifen, eine kontrollierte Netzwerküberprüfung durchführen oder Datenverkehr über ein NAT-Gateway mit einer festen öffentlichen IP-Adresse senden muss. Die Anbindung an eine VPC erfordert zusätzliche Netzwerkkonfiguration und kann zusätzliche NAT-Kosten verursachen; sie sollte daher nur zur Erfüllung einer spezifischen Anforderung eingesetzt werden.

Wie kann Lambda eine stabile ausgehende IP-Adresse verwenden, wenn ein Proxy oder ein Ziel in die Zulassungsliste aufgenommen werden muss?

Leiten Sie an eine VPC angebundene Lambda-Funktionen über private Subnetze und ein NAT-Gateway weiter, das mit einer Elastic IP verknüpft ist, oder verwenden Sie einen Proxy-Endpunkt mit einer stabilen Adresse. Konfigurieren Sie redundante Netzwerkverbindungen, wenn die Verfügbarkeit wichtig ist. Ein NAT-Gateway verursacht stündliche Gebühren und Kosten für die Datenverarbeitung, während ein Proxy eigenen Datenverkehr und zusätzliche Authentifizierungsprobleme mit sich bringt.

Können Cookies und Anmeldesitzungen über Lambda-Aufrufe hinweg bestehen bleiben?

Nicht zuverlässig im Arbeitsspeicher. Eine „warme“ Ausführungsumgebung kann zwar wiederverwendet werden, AWS garantiert jedoch nicht, dass der nächste Aufruf dieselbe Umgebung erreicht. Speichern Sie ein verschlüsseltes Cookie-Jar oder den Sitzungsstatus in einem geeigneten dauerhaften Speicher, wenden Sie Ablauf- und Zugriffskontrollen an und planen Sie für gleichzeitige Aktualisierungen. Vergewissern Sie sich, dass automatisierte Anmeldungen und die Verwendung von Anmeldedaten autorisiert sind.

Wie sollten Scraping-Ergebnisse gespeichert werden, die größer sind als das synchrone Payload-Limit von Lambda?

Schreiben Sie den Hauptteil in S3 und geben Sie einen kleinen Objektschlüssel, eine URI, eine Prüfsumme und einen Metadaten-Umschlag zurück. Veröffentlichen Sie diese Referenz für die nachgelagerte Verarbeitung über SQS, EventBridge oder einen Datenbankdatensatz, anstatt das vollständige Ergebnis zu übergeben. Verwenden Sie gegebenenfalls Komprimierung und Multipart-Uploads und beschränken Sie vorab signierte URLs hinsichtlich ihrer Lebensdauer und Berechtigungen.

Wie sollte die Paginierung aufgeteilt werden, wenn ein Crawl die maximale Laufzeit von Lambda überschreiten könnte?

Stellen Sie jede Seitenzahl, jeden Cursor oder jedes Fortsetzungstoken als separate Aufgabe in der Warteschlange dar. Speichern Sie die ermittelten Aufgaben für die nächste Seite in SQS und bewahren Sie eine Crawl-ID sowie einen Checkpoint auf, damit Wiederholungsversuche idempotent bleiben. Begrenzen Sie die Seitenermittlung, erkennen Sie wiederholte Cursor und verwenden Sie Step Functions nur dann, wenn ein expliziter Workflow-Zustand wertvoller ist als ein einfaches Produzenten-und-Warteschlangen-Muster.

Fazit

Ein serverloser Scraper in der Produktion ist vor allem eine Übung im Umgang mit Grenzen. Halten Sie jeden Aufruf kurz, gestalten Sie das Ereignis in sich geschlossen, speichern Sie die Ausgabe dauerhaft und gehen Sie davon aus, dass jede Nachricht möglicherweise mehr als einmal zugestellt wird. Direkter HTTP-Aufruf plus Parser ist die richtige Standardlösung für statische Seiten, während SQS den saubersten Weg zu kontrollierter Skalierung bei mehreren URLs bietet.

Container sind nützlich für Scrapy, native Pakete und Browser-Abhängigkeiten, heben jedoch die Laufzeit- und Zustandsbeschränkungen von Lambda nicht auf. Java 21 kann denselben Konventionen wie Python folgen, mit HttpClient, Jsoup, S3 und einer verifizierten SnapStart-Konfiguration. Bei blockierten oder JavaScript-lastigen Seiten sollten Sie den tatsächlichen Fehler diagnostizieren, bevor Sie einen Proxy, einen Browser oder einen Managed Service hinzufügen, und diese Optionen niemals als garantierten Zugriff betrachten.

Berechnen Sie schließlich die Gesamtsystemkosten und überprüfen Sie alle zeitkritischen AWS-Werte in Ihrer Region. Wenn die Blockierung von Anfragen zur größten technischen Herausforderung wird, kann die WebScrapingAPI als verwaltete Ebene zum Abrufen von Roh-HTML dienen, die Proxy-Rotation, Blockierungen und CAPTCHAs handhabt, während Ihre Lambda-Funktion weiterhin für das Parsen, die Validierung und die Speicherung zuständig ist. Nutzen Sie diese Eskalation nur dann, wenn sie die gesamte betriebliche Komplexität verringert.

Über den Autor

Suciu Dan, Mitbegründer @ WebScrapingAPI

Suciu Dan

Mitbegründer

Suciu Dan ist Mitbegründer von WebScrapingAPI und verfasst praxisorientierte, auf Entwickler zugeschnittene Anleitungen zu den Themen Web-Scraping mit Python, Web-Scraping mit Ruby und Proxy-Infrastruktur.

So verwenden Sie GoSpider: Crawlen, URLs bereinigen und Daten extrahieren
Anleitungen

So verwenden Sie GoSpider: Crawlen, URLs bereinigen und Daten extrahieren

TL;DR: GoSpider ist ein Befehlszeilen-Crawler zum Auffinden von URLs, kein vollständiger Scraper für strukturierte Daten. Diese Anleitung zur Verwendung von GoSpider zeigt einen begrenzten Crawl, die saubere Verarbeitung der Ausgabe, die Übergabe von Colly an CSV sowie einen Diagnosepfad für 403-Antworten oder Seiten, die eine JavaScript-Darstellung erfordern.

Suciu Dan20 min read
Artikel lesen
Wie man Redfin scrappt: Python-Leitfaden für Immobiliendaten
Anleitungen

Wie man Redfin scrappt: Python-Leitfaden für Immobiliendaten

TL;DR: Redfin stellt versteckte API-Endpunkte zur Verfügung, die strukturiertes JSON für Immobilienangebote zurückgeben, wodurch das fragile HTML-Parsing vollständig übersprungen werden kann. Diese Anleitung führt Sie durch den Aufbau eines Python-Scrapers, der Miet- und Verkaufsdaten extrahiert, nach Standort sucht, neue Angebote über XML-Sitemaps überwacht und saubere Ergebnisse in CSV oder JSON exportiert.

Suciu Dan11 min read
Artikel lesen
XPath Web Scraping: Ein praktischer Leitfaden mit Python-Beispielen
Anleitungen

XPath Web Scraping: Ein praktischer Leitfaden mit Python-Beispielen

TL;DR: XPath ist eine Abfragesprache zum Navigieren in HTML/XML-Bäumen nach Pfad, Attribut oder Textinhalt. Dieser Leitfaden behandelt XPath-Syntax, Achsen und Funktionen und zeigt dann funktionierende Python-Scraper mit lxml und Selenium. Sie erhalten auch einen konsolidierten Spickzettel und einen Abschnitt zur Fehlerbehebung für die häufigsten XPath-Fehler.

Suciu Dan9 min read
Artikel lesen

Los geht’s

Sind Sie bereit, Ihre Datenerfassung zu erweitern?

Schließen Sie sich den über 2.000 Unternehmen an, die WebScrapingAPI nutzen, um Webdaten im Unternehmensmaßstab ohne zusätzlichen Infrastrukturaufwand zu extrahieren.