Aller au contenu
Retour au blog

Web scraping avec AWS Lambda : guide pour Python et Java 2026

Suciu DanDernière mise à jour le 33 min read
Web scraping avec AWS Lambda : guide pour Python et Java 2026
En bref : le web scraping avec AWS Lambda fonctionne mieux lorsque chaque invocation est brève, bien délimitée et peut faire l’objet d’une nouvelle tentative de manière indépendante. Commencez par utiliser les requêtes HTTP directes, AWS SAM et S3, puis ajoutez SQS, des conteneurs, le rendu dans un navigateur, des proxys ou une couche de récupération gérée uniquement lorsque la charge de travail démontre qu’elle en a besoin.

Le web scraping avec AWS Lambda consiste à exécuter du code de récupération et d’extraction de pages sous forme de fonctions AWS à courte durée de vie, plutôt que de gérer des serveurs de scraping. Lambda lance ce code en réponse à des plannings, des files d’attente, des appels HTTP ou d’autres événements AWS.

Ce modèle opérationnel est séduisant, mais il ne rend pas tous les robots d’indexation compatibles avec l’architecture « serverless ». Lambda convient aux vérifications de produits programmées, aux tâches portant sur une seule URL, à l’extraction pilotée par des webhooks et aux travailleurs de file d’attente. Il est moins adapté aux crawls nécessitant des heures d’exécution ininterrompue, une session de navigateur qui doit rester active ou un fan-out incontrôlé sur une cible sensible.

Ce guide adopte une approche axée sur la prise de décision. Vous allez créer un scraper web Python sur AWS Lambda, découvrir son équivalent Java 21 utilisant HttpClient et Jsoup, comparer les formats ZIP et les conteneurs, stocker les résultats dans S3 et faire évoluer le traitement des URL via SQS. Vous découvrirez également une stratégie d’évolution prudente pour JavaScript et le blocage, ainsi que des formules de calcul des coûts qui distinguent les frais de calcul Lambda des dépenses liées au stockage, à la journalisation, à la mise en réseau, aux images, au proxy et aux API. Les valeurs AWS sensibles au facteur temps sont explicitement signalées afin de permettre une vérification par rapport à la documentation officielle référencée.

Commençons par le choix du déploiement : Lambda est-il le runtime adapté au web scraping avec AWS Lambda ?

La réponse courte est oui, lorsqu’un scraping peut s’achever en une seule invocation délimitée ou être divisé en unités indépendantes telles qu’une URL, une page de liste ou un curseur. Le Web Scraping avec AWS Lambda est particulièrement efficace pour les vérifications planifiées et les tâches en pics de charge, car il n’y a pas de flotte de workers à maintenir en fonctionnement entre les événements.

La question importante n’est pas de savoir si Lambda peut récupérer une page. Il en est capable. La question est de savoir si les échecs, les nouvelles tentatives, l’état et le débit restent gérables lorsque chaque invocation est temporaire.

Caractéristiques de la charge de travail

Lambda est un bon choix

Signes d’alerte

Durée d’exécution

Quelques secondes ou quelques minutes

Un crawl nécessite plusieurs heures

État

Les données d'entrée contiennent tout ce qui est nécessaire

État du navigateur ou de connexion de longue durée

Parallélisme

URL indépendantes avec une limite claire

Un robot d’exploration découvre un volume de travail illimité

Dépendances

Analyseur HTTP ou image contrôlée

Pile de bureau volumineuse et fragile

Résultat

Enregistré de manière durable après chaque tâche

Les résultats n’existent qu’en mémoire

Modèle de défaillance

Une URL peut être réessayée en toute sécurité

La nouvelle tentative entraîne des effets secondaires en double

Une règle de conception utile consiste à rendre la fonction jetable. Si AWS arrête un environnement après une requête, un remplacement doit pouvoir traiter le même événement sans avoir à reconstruire l’état local caché. Cela implique de stocker les points de contrôle, les résultats et les identifiants dans des services dédiés plutôt que /tmp ou dans les variables globales du module.

Adaptez le HTML statique, l’exploration, le rendu par navigateur et les API gérées à la tâche

Choisissez la méthode de récupération la moins complexe qui renvoie les données dont vous avez besoin. Les pages statiques nécessitent généralement un client HTTP et un analyseur syntaxique. L’exploration de sites multipages peut justifier l’utilisation de Scrapy. Les applications avec rendu côté client peuvent nécessiter un navigateur, un point de terminaison JSON sous-jacent que vous êtes autorisé à appeler, ou un service de rendu hébergé.

Exigence

Premier choix

Passez au niveau supérieur lorsque

HTML rendu côté serveur

requests plus BeautifulSoup

Le contenu est absent de la réponse

Exploration par suivi de liens

Scrapy dans un fichier ZIP ou une image

Les dépendances natives dépassent les limites du fichier ZIP

Clics, défilements, formulaires

Playwright dans un conteneur

Les opérations du navigateur dominent le temps d'exécution

Risque élevé de blocage

Contrôle du débit, puis utilisation d’un proxy

La réputation IP ou la localisation géographique sont déterminantes

Rendu et opérations via un proxy

API de scraping gérée

La gestion des navigateurs et de la logique de proxy coûte plus cher que l’externalisation de la récupération

Le rendu, l’état de la session, la durée d’exécution, le volume de requêtes et le risque de blocage doivent guider ce choix. Une architecture de navigateur « headless » offre plus de contrôle, mais consomme également plus de mémoire, augmente la charge de travail au démarrage à froid et génère davantage de modes de défaillance que l’analyse syntaxique du HTML.

Optez pour Fargate, Batch ou EC2 pour les charges de travail que Lambda ne peut pas gérer

Utilisez un service de calcul à durée de vie plus longue lorsque l’unité de travail ne peut pas être délimitée. Fargate constitue une évolution pratique pour les workers conteneurisés qui nécessitent des exécutions plus longues sans gestion des hôtes. Batch est plus adapté lorsque les tâches sont mises en file d’attente, gourmandes en ressources de calcul et s’expriment naturellement sous forme de soumissions par lots. EC2 offre le plus de contrôle pour les navigateurs persistants, les réseaux spécialisés, les caches locaux ou les robots d’indexation fonctionnant en continu, mais il confie également à votre équipe la gestion des correctifs des hôtes et de la capacité.

Le choix est d’abord qualitatif avant d’être financier. Si vous êtes contraint d’effectuer des points de contrôle toutes les quelques minutes uniquement pour éviter la limite de la fonction, ou de télécharger à plusieurs reprises une pile de navigateurs volumineuse, un service de conteneurs peut s’avérer plus simple et moins coûteux sur le plan opérationnel. Lambda doit réduire la charge de travail liée à l’infrastructure, et non la transférer vers un code de récupération complexe.

Utilisez une architecture de production qui sépare le flux de contrôle et le flux de données

Un scraper de production est plus facile à exploiter lorsque ses composantes sont séparées. Considérez le déclencheur comme un flux de contrôle, la fonction Lambda comme un worker remplaçable, S3 ou une base de données comme un flux de données durable, et CloudWatch associé à un magasin de secrets comme le plan opérationnel.

Un parcours typique de web scraping avec AWS Lambda se présente comme suit :

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

Cette séparation évite les problèmes courants de couplage. Une planification ne doit pas contenir de logique d’analyse. Un « worker » ne doit pas conserver la seule copie de ses résultats. Un analyseur ne doit pas savoir comment un secret est chiffré. La surveillance doit recevoir des informations structurées sur chaque exécution plutôt que de les reconstituer à partir de traces de pile non structurées.

Définissez un contrat d’événement pour chaque langage et chaque déclencheur. Un contrat concis suffit :

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

Renvoyer ou stocker une enveloppe de résultats correspondante avec ok, request_id, url, count, items, et s3_uri. Python et Java peuvent alors être comparés sur le plan opérationnel, et les réessais SQS ne dépendent pas de formats d’événements spécifiques à un langage.

Choisissez EventBridge, SQS ou Step Functions comme déclencheur

Utilisez EventBridge pour le web scraping planifié sur AWS lorsque la tâche est de petite envergure et périodique. Une règle peut appeler une fonction toutes les heures ou tous les jours, et la fonction peut écrire l’instantané résultant sur S3. Évitez les chevauchements en limitant la durée d’exécution, en ajoutant des clés de sortie idempotentes et en appliquant une concurrence réservée si des exécutions simultanées risqueraient d’être préjudiciables.

Utilisez SQS lorsqu’un producteur peut énumérer des URL ou des curseurs de page. La file d’attente met en mémoire tampon les pics de trafic, Lambda traite de petits lots, et une URL ayant échoué peut faire l’objet d’une nouvelle tentative sans redémarrer le travail qui a abouti. Il s’agit de l’architecture par défaut pour la collecte multi-URL, car la vitesse du producteur ne dicte plus le débit de requêtes cible.

Utilisez Step Functions lorsque la tâche comporte des étapes distinctes, telles que la récupération de pages de listes, la génération d’URL de détail, l’enrichissement des enregistrements et la publication d’un manifeste. Ce service offre des branchements explicites et des politiques de réessai, mais les transitions d’état entraînent un surcoût et nécessitent la gestion d’un service supplémentaire.

Déclencheur

Idéal pour

Contrôle principal

EventBridge

Instantanés périodiques

Planification et concurrence réservée

SQS

Nombreuses URL indépendantes

Taille des lots, concurrence des sources d’événements, DLQ

Step Functions

Workflows en plusieurs étapes

Nouvelles tentatives et embranchements au niveau de l'état

API Gateway

Demandes à la demande

Limites d’authentification, de charge utile et de délai d’expiration

Événement S3

Traitement des graines téléchargées

Conventions relatives aux clés d’objet et gestion des doublons

Acheminement des résultats, des secrets, des journaux et des métriques vers des services dédiés

S3 constitue un choix par défaut judicieux pour le HTML brut, les enregistrements JSON et les manifestes d’exploration, car chaque invocation peut écrire un objet durable et renvoyer un petit URI. Utilisez des clés déterministes telles que scrapes/site/date/request-id.json; si le même message est réessayé, il doit remplacer le même objet ou détecter que le résultat existe déjà.

Conservez les clés d’API, les mots de passe de proxy et les identifiants de session dans SSM Parameter Store ou Secrets Manager. N’insérez que le nom du paramètre ou l’ARN du secret dans l’environnement Lambda. Accordez au rôle d’exécution l’autorisation de lire cette ressource précise, et non l’ensemble des secrets du compte.

CloudWatch reçoit les journaux, les métriques et les alarmes de la fonction. Enregistrez un identifiant de corrélation, l’hôte normalisé, le code d’état, le temps écoulé, le nombre de tentatives, le nombre d’éléments et la clé de sortie. Évitez les chaînes de requête complètes, les en-têtes d’autorisation, les cookies, le corps des pages et les valeurs de secrets. X-Ray ou un outil de traçage équivalent peut aider à distinguer le temps passé au sein de Lambda de la latence liée au DNS, à la cible, au proxy, à S3 ou au magasin de secrets.

Cette séparation des services offre également des points d’extension naturels. Vous pouvez ajouter des règles de cycle de vie à S3, une tâche de catalogage après des écritures réussies ou un outil de relecture pour une file d’attente de messages perdus sans modifier le code d’extraction.

Nommez les fonctions et les ressources en fonction de leur responsabilité plutôt que du site cible. Séparez la production d’URL de la récupération des pages lorsqu’elles évoluent à des rythmes différents, et gérez les versions du schéma d’événements avant que plusieurs producteurs n’en dépendent. Cela évite que les déploiements de parseurs ne modifient accidentellement la planification ou le comportement des files d’attente.

Concevez votre architecture en tenant compte des quotas Lambda avant de coder

Le web scraping avec AWS Lambda est soumis à des limites strictes de la plateforme, dont plusieurs sont liées au temps. À la date indiquée par les sources fournies, les valeurs couramment citées sont approximativement celles ci-dessous. Vérifiez-les pour votre région et votre compte sur la page officielle des quotas AWS Lambda avant toute publication ou tout déploiement.

Contrainte

Valeur indiquée

Conséquence sur le scraping

Nombre maximal d’invocations

Environ 900 secondes

Diviser la pagination longue en unités mises en file d’attente

Mémoire

Entre environ 128 Mo et 10 240 Mo

Une mémoire plus importante modifie également la puissance de calcul disponible

Mémoire éphémère /tmp

Entre environ 512 Mo et 10 240 Mo

Les fichiers du navigateur et les téléchargements nécessitent une allocation de mémoire explicite

Archive ZIP

Environ 50 Mo compressé, 250 Mo décompressé

Les bibliothèques natives peuvent imposer des calques ou une image

Image de conteneur

Environ 10 Go non compressée

Empaquetage plus simple, mais compilations plus lentes et téléchargements plus volumineux

Charge utile synchrone

Environ 6 Mo dans chaque sens

Stockage des résultats dans S3 et renvoi d’une référence

Concurrence régionale

Souvent citée comme étant de 1 000 par défaut

Ce chiffre peut être inférieur pour les nouveaux comptes ou les comptes soumis à des restrictions

Ne configurez pas le délai d’expiration HTTP à la même valeur que le délai d’expiration de la fonction. Prévoyez suffisamment de temps pour l’analyse, les écritures sur S3, les métriques et une réponse d’erreur contrôlée. Pour une fonction de 60 secondes, par exemple, un délai d’expiration de la requête compris entre 35 et 45 secondes constitue un point de départ plus sûr que 60 secondes, bien que la valeur correcte dépende de la cible.

La concurrence sert également à contrôler le trafic sortant. Définissez une concurrence réservée pour le scraper et, lorsque cela est pris en charge, une concurrence maximale par source d’événements pour SQS. Un quota de service ne vous autorise pas à envoyer autant de requêtes simultanées à un site web.

Configurer le référentiel et l’infrastructure avec AWS SAM

AWS SAM définit les fonctions, les autorisations IAM, les planifications, les files d’attente et les compartiments dans un modèle CloudFormation. Il offre à « Web Scraping With AWS Lambda » un workflow local et de déploiement reproductible sans nécessiter Docker pour une fonction HTML statique légère.

Un référentiel compact peut prendre en charge à la fois les langages et les chemins d’installation :

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/

Installez une version prise en charge de Python, l’AWS CLI, l’AWS SAM CLI et Docker si vous prévoyez d’utiliser sam local ou des images de conteneur. Configurez un profil AWS nommé et vérifiez le compte avant le déploiement :

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

Conservez les événements de test dans le contrôle de version, mais n’incluez jamais de cookies actifs, d’identifiants de proxy ou d’URL signées. Paramétrez le compartiment de sortie et les identifiants secrets par environnement. Un workflow SAM reproductible est validate, build, local invoke, deploy, ainsi qu’une invocation dans le cloud avec inspection des journaux.

Choisissez un packaging ZIP, des couches ou une image de conteneur

Utilisez un paquet ZIP pour requests, BeautifulSoup, Jsoup et d’autres graphes de dépendances de taille similaire. SAM peut installer les dépendances dans l’artefact de build, et les démarrages à froid restent relativement légers.

Les couches sont utiles lorsque plusieurs fonctions partagent un ensemble de dépendances stable, mais elles impliquent une coordination des versions. Elles ne suppriment pas les limites liées aux paquets, et une couche qui change à chaque déploiement n’est généralement qu’un artefact supplémentaire à gérer.

Optez pour une image de conteneur AWS Lambda lorsque vous avez besoin de paquets système natifs, de dépendances Scrapy difficiles à intégrer dans un fichier ZIP ou d’un navigateur sans interface graphique. Une image améliore la cohérence de l’environnement, mais pas l’adéquation à l’exécution.

Empaquetage

Meilleure option

Principal compromis

Fichier ZIP

HTML statique, petits analyseurs syntaxiques

Limites de dépendances très strictes

Couche plus ZIP

Bibliothèques stables partagées

Couplage inter-fonctions des versions

Image de conteneur

Bibliothèques natives, Scrapy, Playwright

Coûts liés à la compilation, à l’analyse, au stockage et au démarrage à froid

Ne privilégiez pas systématiquement Docker sous prétexte qu’il semble plus adapté à la production. Pour du code HTML simple, l’artefact le plus petit est généralement plus facile à corriger, à tester et à observer.

Conservez les différences d’environnement dans les paramètres SAM ou les fichiers de configuration, et non dans des modèles modifiés manuellement. Un déploiement doit être reproductible à partir d’un commit, d’un profil et d’un ensemble de paramètres, les sorties de la pile exposant les noms de fonctions, les noms de buckets, les URL de files d’attente et les alias nécessaires aux tests de fumée.

Créer le scraper Python pour HTML statique

Pour le HTML statique, un scraper web Python sur AWS Lambda n’a besoin que d’un client HTTP, d’un parseur HTML et d’un client SDK AWS pour une sortie durable. BeautifulSoup analyse la réponse qu’il reçoit ; il n’exécute pas de JavaScript et n’attend pas une application côté client.

Le gestionnaire ci-dessous accepte les invocations directes, les corps JSON d’API Gateway ou un objet EventBridge detail . Il valide l’URL, réutilise les clients lors d’appels « warm », définit des délais d’expiration explicites, analyse les sélecteurs CSS remplaçables, écrit le résultat sur S3 et renvoie des erreurs structurées.

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"}}

Remplacez les sélecteurs par ceux couverts par les tests de fixture. Si l’absence d’éléments est inattendue, traitez-la comme un échec d’analyse plutôt que comme un scraping vide réussi. Déterminez également si les redirections sont autorisées et si les URL fournies par l’utilisateur nécessitent une liste blanche afin de réduire le risque de falsification de requêtes côté serveur.

Testez en local, déployez, lancez l’appel et enregistrez le JSON sur S3

Utilisez un petit fichier de dépendances :

requests
beautifulsoup4

Les ressources SAM ci-dessous créent un compartiment de sortie et n’autorisent que les écritures d’objets sous le préfixe du scraper. Fixez les environnements d’exécution et les versions des dépendances pris en charge dans le dépôt réel après validation.

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/*"

Créer events/scrape.json:

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

Exécutez ensuite une séquence reproductible :

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

Validez l’objet dans S3 et inspectez le flux de journaux de la fonction dans CloudWatch. Une réussite en local prouve que l’analyse est correcte, mais ne garantit pas les autorisations cloud, le comportement DNS, l’accès à la cible ou la marge de délai d’expiration. La validation dans le cloud doit confirmer le rôle déployé exact, les variables d’environnement, l’architecture des artefacts, la clé de sortie, le code d’état, la durée et le nombre d’éléments.

Avant la mise en production, ajoutez une politique de taille maximale de réponse et rejetez les types de contenu que vous n’avez pas l’intention d’analyser. Cela protège la mémoire et évite de consacrer l’invocation restante à un téléchargement volumineux. Ne conservez des échantillons HTML bruts que lorsque cela est nécessaire à des fins de diagnostic, en appliquant des contrôles de rétention et d’accès.

Intégrez Scrapy ou Playwright dans un conteneur Lambda

L’utilisation d’un conteneur se justifie lorsque le graphe de dépendances, les bibliothèques natives ou le runtime du navigateur ne tiennent plus facilement dans un fichier ZIP. Il ne s’agit pas d’un moyen de contourner le modèle d’exécution de Lambda. Le processus reste soumis à une invocation limitée, à un stockage local éphémère et à des environnements d’exécution jetables.

Pour un worker Scrapy AWS Lambda, isolez chaque exécution de spider dans un sous-processus. Le réacteur de Twisted n’est pas conçu pour être arrêté et redémarré à plusieurs reprises au sein du même processus Python, ce qui peut surprendre les invocations Lambda « chaudes ». Un sous-processus ajoute une surcharge au démarrage, mais il offre à chaque exploration un réacteur propre et une limite de délai d’expiration claire.

Une image minimale orientée Lambda peut se présenter comme suit :

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"]

Fixez et hachagez les dépendances dans la version déployée. L’image de base et le runtime présentés ici doivent être vérifiés par rapport aux images de base Lambda prises en charge au moment du déploiement.

Un gestionnaire compact peut exécuter un spider existant, collecter des données JSON dans /tmp, puis le télécharger :

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}",
    }

Le spider doit accepter start_url, utiliser des délais d’expiration explicites pour les téléchargements, limiter la pagination et produire des enregistrements conformes au schéma de sortie Python et Java. Scrapy peut également écrire un flux directement sur S3, mais un téléchargement explicite permet de mieux visualiser le contrat de sortie et les limites d’erreur.

Le packaging Playwright pour AWS Lambda suit le même principe d’image mais est plus lourd. Chromium, les polices, les bibliothèques partagées et les caches du navigateur doivent tous correspondre à l’architecture de l’image. Prévoyez davantage de mémoire et de temps de démarrage à froid, n’enregistrez les téléchargements que dans /tmp, fermez les contextes dans finally, et ne partez jamais du principe qu’un profil de navigateur survit à l’invocation. Une configuration Scrapy-Playwright n’est appropriée que lorsque le rendu par navigateur est nécessaire pour les données.

Compilez, transférez vers ECR et déployez l’image avec SAM

Construisez une architecture adaptée à la fonction. L’exemple x86 suivant utilise des balises versionnées ; remplacez-les par une image de base et une région actuellement prises en charge.

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"

Transmettez l’URI de l’image versionnée à SAM plutôt que de déployer une latest :

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

Déployez avec la balise exacte :

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

Pour une meilleure reproductibilité, enregistrez le digest de l’image généré par ECR dans les métadonnées de déploiement. N’activez les contrôles d’analyse et de conservation du référentiel qu’après avoir vérifié les options ECR actuelles et la politique de votre organisation. Nettoyez les anciennes images non référencées, les couches locales, les piles de test et les objets de test S3 afin que les expériences sur les conteneurs ne se transforment pas en coûts permanents.

Testez l’image localement avec le point de terminaison d’exécution de Lambda ou sam local invoke, puis effectuez un test « cloud smoke ». L’architecture Docker locale, l’architecture Lambda, les autorisations du système de fichiers et les bibliothèques système du navigateur sont des sources courantes d’échecs du type « ça marche sur ma machine ».

Rendez les builds de conteneurs déterministes en enregistrant dans le CI le digest de l’image de base, le verrouillage des dépendances, l’architecture cible et le digest de l’image générée. Reconstruisez régulièrement pour les mises à jour de sécurité, mais transférez un digest testé d’un environnement à l’autre plutôt que de reconstruire des images différentes pour le développement et la production.

Construisez le scraper Java 21 avec HttpClient et Jsoup

Le scraper Web Java pour AWS Lambda doit utiliser les mêmes contrats d’événement et de résultat que celui en Python. HttpClient gère les requêtes HTTP limitées, Jsoup analyse le code HTML, Jackson normalise les corps d’événements JSON, et le SDK AWS écrit le résultat durable.

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);
  }
}

Réutilisez les clients statiques lors des invocations « à chaud », mais ne stockez pas l’état de l’exploration, critique pour la justesse, dans des champs statiques. Les mêmes mises en garde s’appliquent qu’en Python : nettoyez les journaux, traitez les analyses inattendues ne contenant aucun élément comme des échecs, et maintenez le délai d’expiration HTTP en dessous du délai d’expiration de la fonction. Une référence sur l’analyse HTML en Java avec Jsoup est utile lorsque les sélecteurs ou le comportement des liens absolus nécessitent un traitement plus approfondi.

Créez un package avec Maven et réduisez le temps de démarrage grâce à SnapStart

Le projet Maven nécessite les interfaces principales de Lambda, Jsoup, Jackson, le SDK S3, ainsi qu’une étape de compilation produisant un fichier JAR de déploiement. Fixez les versions après avoir vérifié les versions actuelles et votre politique de dépendances.

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

Configurez maven-shade-plugin pendant package, puis indiquez à SAM le fichier JAR compilé. La configuration SnapStart attendue utilise une version publiée ou un alias plutôt que $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

Compilez et exécutez avec mvn clean package, sam build, sam local invoke JavaScraper -e events/scrape.json, et sam deploy. Au moment de la rédaction de ce document, vérifiez que Java 21 et SnapStart sont pris en charge dans la région de déploiement et confirmez les éventuelles restrictions concernant l’initialisation, les connexions réseau, l’entropie et les identifiants mis en cache après la restauration d’un instantané.

Gérez JavaScript, les proxys et les défenses anti-bot de manière réfléchie

Le Web scraping avec AWS Lambda ne modifie pas la manière dont une cible affiche son contenu ou évalue son trafic. Commencez par le chemin de requête autorisé le plus simple, observez l’échec et ne passez au niveau supérieur que pour une raison diagnostiquée.

  1. HTTP direct : utilisez-le lorsque la réponse contient les données. Ajoutez un agent utilisateur valide, un nombre limité de tentatives en cas d’échecs passagers, un rythme raisonnable et des tests de parseur.
  2. Requête de données sous-jacentes : si une page charge des données publiques à partir d’un point de terminaison JSON, n’utilisez ce point de terminaison que lorsque ses conditions d’utilisation et son autorisation le permettent. Ne partez pas du principe qu’un point de terminaison non documenté est stable.
  3. Navigateur « headless » : utilisez Playwright lorsque le workflow nécessite réellement l’exécution de JavaScript, des clics, du défilement ou l’envoi de formulaires. Le rendu du navigateur ne garantit pas l’accès.
  4. Proxy explicite : utilisez un proxy lorsque la localisation géographique, une sortie stable ou la rotation d’adresses IP constituent une exigence légitime. La réputation du proxy, l’affinité de session et la politique cible restent des facteurs importants.
  5. Service de scraping géré : externalisez la récupération lorsque la maintenance du navigateur, du proxy, des CAPTCHA et des opérations de réessai coûte plus cher que la logique d’extraction.

Les plages d’adresses des fournisseurs de cloud peuvent être reconnues ou bloquées par certains sites web. Cela dépend du contexte, et le passage à un proxy ou à un navigateur ne garantit pas le succès. Les en-têtes et les plugins furtifs peuvent également créer un faux sentiment de fiabilité si le véritable problème réside dans le taux de requêtes, l’autorisation, l’état du compte ou l’évolution du balisage.

Une politique pratique de scraping anti-bot classe les résultats séparément : échec de transport, délai d’expiration, blocage HTTP, page de vérification, exigence de connexion, rendu vide et incompatibilité d’analyseur. Chaque catégorie nécessite une solution différente. Tenter à nouveau toutes les requêtes de la même manière représente un gaspillage d’argent et peut accroître la pression sur la cible.

Comparez les proxys explicites à une API de scraping gérée

Un proxy explicite conserve la construction des requêtes et l’analyse syntaxique dans votre code. Chargez ses identifiants à partir d’un magasin de secrets, et non à partir de l’événement ou du référentiel :

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()

Une API de scraping gérée peut délocaliser la récupération du code HTML brut, la rotation des proxys, la gestion des CAPTCHA ou le rendu du navigateur hors de Lambda, en fonction des capacités documentées du fournisseur. Conservez une intégration générique jusqu’à ce que vous ayez vérifié l’authentification et les paramètres actuels :

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

Vous contrôlez

Vous gérez

Évolution des coûts

Requêtes directes

HTTP et analyse syntaxique

Nouvelles tentatives, régulation du débit, chemin IP

Lambda et réseau

Proxys explicites

Sélection de proxy et sessions

Rotation, défaillances, identifiants

Trafic proxy et ressources de calcul

Conteneur de navigateur

Interaction complète

Image et stabilité du navigateur

Mémoire et durée accrues

API gérée

Options de requête et analyse

Intégration des fournisseurs et solution de secours

Par requête, crédit ou résultat

Un guide d’utilisation des proxys avec les requêtes Python est un complément indispensable lors de la mise en œuvre de l’authentification, de la rotation et de la gestion des erreurs. Avant d’opter pour une solution gérée, vérifiez la latence, le format de réponse, la taille maximale du corps de la requête, l’unité de facturation, le routage régional, le traitement des données et le comportement en cas d’échec des requêtes.

Faites évoluer en toute sécurité les tâches multi-URL avec SQS

Le scraping basé sur des files d’attente place une unité de travail indépendante dans chaque message SQS, généralement une URL accompagnée d’un identifiant de requête, de la version de l’analyseur et, éventuellement, de métadonnées de crawl. Lambda reçoit un petit lot, traite chaque message et écrit chaque résultat séparément. Cela permet de conserver les URL réussies dans leur intégralité tandis que les échecs font l’objet d’une nouvelle tentative.

Pour le scraping Web avec AWS Lambda, la granularité des messages relève d’un choix de contrôle de débit. Une URL par message offre le comportement de réessai et d’idempotence le plus propre. Un petit groupe d’URL étroitement liées peut réduire la surcharge de la file d’attente, mais une seule URL lente retarde alors l’ensemble du message.

Veillez à ce que les messages restent courts. Stockez les grandes listes de départ ou les artefacts de session dans S3 et ne placez que leur référence d’objet dans SQS. Incluez suffisamment d’informations pour reproduire la requête, mais n’incluez pas de mots de passe de proxy, de cookies ou de jetons d’API.

Harmonisez les délais d’expiration entre les couches :

  • Le délai d’expiration HTTP doit être inférieur au délai d’expiration de la fonction.
  • Le délai d’expiration de la fonction doit laisser suffisamment de temps pour la sortie et les diagnostics.
  • Le délai d’expiration de visibilité SQS doit dépasser la durée pendant laquelle un lot peut rester en cours d’exécution, en incluant une marge de réessai appropriée.
  • La durée de conservation des messages et celle des messages non remis doivent être suffisamment longues pour permettre aux opérateurs d’effectuer des analyses.
  • La concurrence réservée et celle liée à la source d’événements doivent refléter un taux de requêtes adapté à la cible, et non pas simplement la capacité du compte.

Les paramètres AWS et les valeurs minimales imposées peuvent changer ; vérifiez donc le comportement actuel de l’intégration dans la documentation officielle « Lambda avec SQS ».

Mettez en œuvre les tentatives de réessai, les échecs partiels de lots, les files d’attente de messages perdus (DLQ), l’idempotence et les limites de concurrence

Activez les réponses partielles aux lots afin que Lambda ne renvoie que les ID des messages ayant échoué. Sans ce comportement, un seul enregistrement erroné peut entraîner la réapparition de tous les enregistrements du lot.

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}

Dans SAM, déclarez le mode de réponse sur la source d’événement :

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

Rendez scrape_one idempotent. Une clé déterministe telle qu’ scrapes/{job_id}.json, une écriture conditionnelle DynamoDB ou une vérification de l’existence d’un résultat empêche les messages en double de produire des effets en aval redondants.

Associez une file d’attente de messages perdus via la politique de relance SQS et choisissez son seuil de réception en fonction du comportement de relance que vous souhaitez réellement. Envoyez-y les messages épuisés pour inspection et relecture sélective. Limitez la concurrence à la fois au niveau de la fonction et de la source d’événement lorsque cela est possible. Ces contrôles protègent le site web, réservent de la capacité pour d’autres fonctions et empêchent qu’un arriéré ne se transforme en pic de trafic accidentel.

La contre-pression commence au niveau du producteur. Si l’ancienneté de la file d’attente augmente, mettez en pause ou ralentissez la découverte d’URL plutôt que de laisser s’accumuler un arriéré illimité. Suivez chaque cible ou hôte séparément lorsqu’ils nécessitent des politiques différentes en matière de concurrence, de rythme, d’identifiants ou de réessais.

Ajoutez des fonctionnalités d’observabilité et de diagnostic des défaillances

Un scraper AWS Lambda doit générer un résumé structuré par tentative. Utilisez des champs JSON pouvant être interrogés sans analyse syntaxique du texte :

{
  "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"
}

Normalisez l’hôte et omettez les chaînes de requête, sauf si leur sécurité est avérée. N’enregistrez jamais les en-têtes d’autorisation, les cookies, les URL de proxy contenant des identifiants, les corps HTML complets ou les valeurs secrètes.

Suivez au moins les classes de métriques suivantes :

  • Travail : tentatives, succès, éléments extraits, octets stockés.
  • Santé des requêtes : latence, délais d’expiration, erreurs de connexion, familles de statuts HTTP.
  • Signaux de blocage : détections de pages de vérification, refus d’accès, redirections de connexion.
  • Santé de l’analyseur : résultats sans élément, champs obligatoires manquants, échecs de sélection.
  • État de santé AWS : erreurs, limitations de débit, durée, mémoire, ancienneté SQS et profondeur DLQ.

Déclenchez des alarmes sur les taux et les tendances persistantes, et non sur chaque échec individuel. Une alarme liée à l’analyseur doit être distincte d’une alarme liée au blocage, car le guide d’intervention et le responsable peuvent différer. Définissez la durée de conservation des journaux de manière réfléchie, associez des identifiants de corrélation aux messages de file d’attente et aux clés S3, et utilisez le traçage lorsque cela permet de distinguer clairement la latence cible des appels aux services AWS.

CloudWatch reçoit les journaux Lambda standard dans le groupe de journaux de la fonction. X-Ray peut aider à visualiser la latence en aval, mais le traçage de chaque requête à fort volume peut entraîner des coûts supplémentaires et du bruit. Effectuez un échantillonnage de manière intentionnelle et conservez suffisamment d’exemples d’échecs, après suppression des contenus sensibles, pour pouvoir reproduire les incidents.

Concevez des tableaux de bord axés sur les décisions : faut-il ralentir le trafic, revenir à une version antérieure d’un analyseur syntaxique, faire tourner les identifiants, augmenter la mémoire ou rejouer les échecs ? Un graphique qui ne peut pas influencer la prochaine action d’un opérateur est probablement du bruit.

Testez et déployez les modifications via CI/CD

Considérez les analyseurs syntaxiques comme du code déterministe et les réseaux comme des frontières remplaçables. Stockez des exemples HTML représentatifs pour les pages normales, les pages vides, les balises modifiées, les pages de vérification et les réponses mal formées. Les tests unitaires doivent appeler directement les fonctions d’analyse syntaxique et vérifier les champs obligatoires, les URL absolues et le comportement en cas d’échec.

Simulez les réponses HTTP, les écritures S3, les lectures SSM et les délais d’expiration dans les tests des gestionnaires. Ajoutez des tests de contrat qui s’exécutent de la même url, limitet request_id événement sur Python et Java, puis de comparer l’enveloppe de résultats, et non les détails internes spécifiques au langage.

Un pipeline pratique comprend :

  1. formateur, linter, vérifications de type ou de compilation, et tests unitaires.
  2. sam validate --lint auxquels s’ajoutent des vérifications des politiques CloudFormation,
  3. Vérifications des dépendances et des vulnérabilités des conteneurs.
  4. sam build ou une création d’image spécifique à l’architecture.
  5. Exécution locale de tests de fumée avec un serveur de fixtures.
  6. Déploiement vers une pile hors production et un environnement « canary » contrôlé en production.
  7. Promotion progressive vers la production avec une voie de retour en arrière immédiate.

Ne rendez pas l’intégration continue (CI) dépendante d’un site web public non contrôlé à chaque commit. Utilisez un serveur de test local pour garantir la reproductibilité et planifiez des tests d’intégration distincts pour vérifier le comportement attendu. Vérifiez que les rôles de déploiement ne peuvent pas étendre les privilèges du rôle « scraper », que les secrets n’apparaissent jamais dans les journaux de build et que les anciennes images de conteneurs et les buckets de test sont nettoyés.

Calculez le coût total, pas seulement le coût de calcul Lambda

Le coût du scraping avec AWS Lambda dépend des requêtes et de la durée, mais cela ne concerne que la partie calcul. Calculez l’utilisation avant d’appliquer un tarif régional :

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

Une fonction exécutée quotidiennement utilisant 256 Mo pendant 3 secondes consomme environ 22,5 Go-secondes sur 30 jours. Pour un million de pages, un analyseur statique utilisant 256 Mo pendant 2 secondes consomme 500 000 Go-secondes. Un « browser worker » utilisant 2 Go pendant 10 secondes consomme 20 000 000 Go-secondes. Ces chiffres d’utilisation sont plus fiables qu’une estimation en dollars, car les tarifs, les remises liées à l’architecture, l’éligibilité au forfait gratuit et les régions peuvent varier.

La documentation fournie mentionne un quota mensuel gratuit approximatif d’un million de requêtes et de 400 000 Go-secondes, puis estime le coût à environ 1,33 $ pour l’exemple statique et à 261,33 $ pour l’exemple du worker de navigateur, selon ses hypothèses. Considérez ces montants en dollars comme des références de planification non vérifiées, et non comme des devis pour 2026. Recalculez-les à partir de la page officielle des tarifs AWS Lambda pour la région de déploiement.

Ajoutez chaque ligne annexe :

Domaine de coût

Facteur typique

S3

Écritures d’objets, stockage, lectures, cycle de vie

CloudWatch

Ingestion des journaux, conservation, métriques, alarmes

ECR

Stockage d’images, analyse, transfert inter-régions

Réseau

Transfert de données et traitement éventuel via une passerelle NAT

SQS ou Step Functions

Requêtes, transitions, nouvelles tentatives

Exécution dans un navigateur

Plus de mémoire, durée et taille d’image

Proxy ou API gérée

Trafic, requêtes, crédits ou résultats réussis

Le NAT peut devenir un frein pour une charge de travail réduite si Lambda est placé dans un VPC uniquement pour garantir une sortie stable. Il en va de même pour les tentatives de modèle et les réponses bloquées. Elles consomment des ressources de calcul et du trafic tiers même lorsqu’elles ne produisent aucune donnée.

Liste de contrôle pour le lancement en production : sécurité, conformité et exploration respectueuse

Avant de déployer « Web Scraping With AWS Lambda », examinez le système à la fois en tant que charge de travail AWS et en tant que collecteur de données automatisé.

  • IAM : attribuez à chaque fonction uniquement le préfixe S3, la file d’attente, l’espace de noms de métriques et l’ARN secret dont elle a besoin. Séparez les autorisations de déploiement des autorisations d’exécution.
  • Secrets : stockez les identifiants de proxy, d’API et de connexion dans SSM Parameter Store ou Secrets Manager. Renouvelez-les régulièrement et veillez à ce que les valeurs déchiffrées n’apparaissent pas dans les journaux.
  • Chiffrement : utilisez le chiffrement pour S3, les files d’attente, les journaux et les configurations sensibles, conformément à votre classification des données et à la politique de votre organisation.
  • Contrôles d’entrée : validez les schémas et les hôtes. Si les appelants fournissent des URL, envisagez une liste blanche et des protections contre les requêtes visant des métadonnées, des adresses privées ou des adresses « link-local ».
  • Contrôles du trafic : définissez une concurrence réservée, des limites de file d’attente, des délais d’expiration des requêtes, des plafonds de tentatives et un « kill switch » global. Un indicateur de configuration qui bloque les nouvelles requêtes est plus rapide qu’un déploiement de code d’urgence.
  • Minimisation des données : ne collectez que les champs nécessaires à la finalité déclarée, définissez les durées de conservation et limitez l’accès au code HTML brut susceptible de contenir des données personnelles ou sensibles.
  • Vérification des cibles : vérifiez le fichier robots.txt, les conditions d’utilisation, les autorisations, les recommandations de débit, les règles relatives aux comptes, les obligations en matière de confidentialité et la législation applicable avant toute collecte. Ces éléments ont des implications juridiques différentes ; un cadre de conformité juridique dédié au web scraping et l’avis d’un conseiller juridique qualifié sont donc recommandés en cas de risque significatif.
  • Responsabilité opérationnelle : mettez en place des alertes, une procédure de relecture DLQ, un guide d’intervention en cas de modification du parseur et désignez un interlocuteur pour les réclamations des cibles.
  • Sécurité de la mise en production : Commencez par une faible concurrence, observez l’état et les métriques de l’analyseur, puis augmentez le débit de manière progressive.

Il s’agit de recommandations techniques, et non de conseils juridiques. La licéité de la collecte dépend des données, de la juridiction, de la méthode d’accès, de la relation contractuelle et de l’utilisation prévue. Ne confondez pas l’accessibilité technique avec l’autorisation.

Que déployer en premier ?

Commencez par le plus petit système utile : HTTP direct en Python, packagé par SAM, déclenché par un événement de test et écrivant du JSON sur S3. Cette base de référence permet de valider les autorisations, la mise en réseau, l’analyse, le stockage et l’observabilité avec un minimum de composants mobiles.

Ajoutez EventBridge pour une planification à petite échelle. Ajoutez SQS lorsque les URL deviennent des tâches indépendantes. Passez à un conteneur uniquement pour les dépendances qui le justifient, et n’utilisez un navigateur que lorsque les données nécessitent un rendu ou une interaction. Ne passez aux proxys ou à la récupération gérée qu’après avoir évalué les modes de défaillance. Cette séquence permet de garder le « Web Scraping avec AWS Lambda » compréhensible à mesure qu’il évolue.

Points clés à retenir

  • Utilisez Lambda lorsque chaque extraction est courte, délimitée et peut faire l’objet d’une nouvelle tentative de manière indépendante ; optez pour une instance de calcul à durée de vie plus longue pour les sessions persistantes ou les explorations s’étalant sur plusieurs heures.
  • Conservez un contrat d’événement et de résultat unique pour Python, Java, les planifications, les files d’attente et les tests locaux.
  • Stockez les résultats dans S3, les identifiants dans un magasin de secrets géré, et les données opérationnelles dans des journaux et des métriques structurés.
  • Intégrez la gestion des échecs partiels SQS, les DLQ, l’idempotence et les limites de concurrence avant d’augmenter le volume d’URL.
  • Calculez les coûts liés à la puissance de calcul, au stockage, aux journaux, aux images, à la mise en réseau, aux tentatives de reprise, aux proxys et aux services gérés comme des lignes de coût distinctes.

FAQ

Un scraper AWS Lambda doit-il s’exécuter au sein d’un VPC ?

Non. Une fonction Lambda peut accéder à des sites web publics sans être rattachée à votre VPC. N’utilisez un VPC que lorsqu’il doit accéder à des ressources privées, effectuer une inspection réseau contrôlée ou acheminer le trafic via une passerelle NAT dotée d’une adresse IP publique fixe. Le rattachement à un VPC implique une configuration réseau supplémentaire et peut entraîner des coûts liés au NAT ; il doit donc répondre à un besoin spécifique.

Comment Lambda peut-il utiliser une adresse IP sortante stable lorsqu’un proxy ou une cible nécessite une liste blanche ?

Acheminez les fonctions Lambda associées à un VPC via des sous-réseaux privés et une passerelle NAT associée à une adresse IP élastique, ou utilisez un point de terminaison proxy avec une adresse stable. Configurez une redondance réseau si la disponibilité est un critère essentiel. Une passerelle NAT entraîne des frais horaires et liés au traitement des données, tandis qu’un proxy ajoute ses propres contraintes en matière de trafic et d’authentification.

Les cookies et les sessions de connexion peuvent-ils persister d’une invocation Lambda à l’autre ?

Pas de manière fiable en mémoire. Un environnement d’exécution « chaud » peut être réutilisé, mais AWS ne garantit pas que l’invocation suivante atteigne le même environnement. Conservez un « cookie jar » chiffré ou l’état de la session dans un magasin durable approprié, appliquez des contrôles d’expiration et d’accès, et concevez le système pour permettre des mises à jour simultanées. Vérifiez que la connexion automatisée et l’utilisation des identifiants sont autorisées.

Comment stocker les résultats de scraping dont la taille dépasse la limite de charge utile synchrone de Lambda ?

Enregistrez le corps de la requête sur S3 et renvoyez une petite clé d’objet, une URI, une somme de contrôle et une enveloppe de métadonnées. Pour le traitement en aval, publiez cette référence via SQS, EventBridge ou un enregistrement de base de données plutôt que de transmettre le résultat complet. Utilisez la compression et les téléchargements en plusieurs parties lorsque cela est approprié, et limitez toute URL pré-signée en fonction de sa durée de vie et des autorisations.

Comment la pagination doit-elle être répartie lorsqu’un crawl risque de dépasser la durée d’exécution maximale de Lambda ?

Représentez chaque numéro de page, curseur ou jeton de continuation sous la forme d’une tâche distincte mise en file d’attente. Stockez le travail de la page suivante découverte dans SQS et conservez un identifiant d’exploration ainsi qu’un point de contrôle afin que les nouvelles tentatives restent idempotentes. Limitez la découverte des pages, détectez les curseurs répétés et n’utilisez Step Functions que lorsque l’état explicite du workflow présente plus d’intérêt qu’un simple modèle « producteur-file d’attente ».

Conclusion

Un scraper « serverless » en production est avant tout un exercice de gestion des limites. Veillez à ce que chaque invocation soit brève, rendez l’événement autonome, enregistrez durablement les résultats et partez du principe que tout message peut être transmis plusieurs fois. L’HTTP direct associé à un analyseur syntaxique constitue la bonne solution par défaut pour les pages statiques, tandis que SQS offre la voie la plus claire vers une évolutivité contrôlée multi-URL.

Les conteneurs sont utiles pour Scrapy, les paquets natifs et les dépendances de navigateur, mais ils ne suppriment pas les limites d’exécution et d’état de Lambda. Java 21 peut respecter le même contrat que Python, avec HttpClient, Jsoup, S3 et une configuration SnapStart vérifiée. Pour les pages bloquées ou riches en JavaScript, diagnostiquez l’échec réel avant d’ajouter un proxy, un navigateur ou un service géré, et ne considérez jamais ces options comme un accès garanti.

Enfin, calculez le coût total du système et vérifiez toutes les valeurs AWS sensibles au facteur temps dans votre région. Si le blocage des requêtes devient le principal obstacle technique, WebScrapingAPI peut servir de couche gérée de récupération de HTML brut qui gère la rotation des proxys, les blocages et les CAPTCHA, tandis que votre fonction Lambda conserve la responsabilité de l’analyse, de la validation et du stockage. N’utilisez cette solution que si elle réduit la complexité opérationnelle globale.

À propos de l'auteur

Suciu Dan, cofondateur @ WebScrapingAPI

Suciu Dan

cofondateur

Suciu Dan est le cofondateur de WebScrapingAPI et rédige des guides pratiques destinés aux développeurs sur le web scraping avec Python et Ruby, ainsi que sur les infrastructures de proxy.

Comment utiliser GoSpider : exploration, nettoyage des URL et extraction de données
Guides

Comment utiliser GoSpider : exploration, nettoyage des URL et extraction de données

En bref : GoSpider est un robot d'exploration en ligne de commande destiné à découvrir des URL, et non un outil complet de collecte de données structurées. Ce guide d'utilisation de GoSpider présente comment effectuer une exploration limitée, gérer correctement les résultats, transférer les données de Colly vers un fichier CSV, ainsi qu'une procédure de diagnostic pour les réponses 403 ou les pages nécessitant un rendu JavaScript.

Suciu Dan24 min read
Lire l'article
Comment récupérer les données de Redfin : Guide Python des données immobilières
Guides

Comment récupérer les données de Redfin : Guide Python des données immobilières

TL;DR : Redfin expose des points d'extrémité d'API cachés qui renvoient du JSON structuré pour les listes de propriétés, ce qui permet d'ignorer complètement l'analyse HTML fragile. Ce guide vous accompagne dans la construction d'un scraper Python qui extrait les données de location et de vente, effectue des recherches par emplacement, surveille les nouvelles inscriptions via des sitemaps XML et exporte des résultats propres au format CSV ou JSON.

Suciu Dan15 min read
Lire l'article
XPath Web Scraping : Un guide pratique avec des exemples en Python
Guides

XPath Web Scraping : Un guide pratique avec des exemples en Python

TL;DR : XPath est un langage de requête permettant de naviguer dans les arbres HTML/XML par chemin, attribut ou contenu textuel. Ce guide couvre la syntaxe, les axes et les fonctions XPath, puis montre des scrapers Python fonctionnels avec lxml et Selenium. Vous obtiendrez également un aide-mémoire consolidé et une section de dépannage pour les erreurs XPath les plus courantes.

Suciu Dan11 min read
Lire l'article

Commencez à créer

Prêt à faire évoluer votre système de collecte de données ?

Rejoignez plus de 2 000 entreprises qui utilisent WebScrapingAPI pour extraire des données Web à l'échelle de l'entreprise, sans aucun coût d'infrastructure.