Saltar al contenido
Volver al blog

Extracción de datos web con AWS Lambda: guía para Python y Java 2026

Suciu DanÚltima actualización el 33 min read
Extracción de datos web con AWS Lambda: guía para Python y Java 2026
En resumen: el web scraping con AWS Lambda funciona mejor cuando cada invocación es breve, está delimitada y se puede reintentar de forma independiente. Empieza con HTTP directo, AWS SAM y S3, y añade SQS, contenedores, renderización en navegador, proxies o una capa de recuperación gestionada solo cuando la carga de trabajo demuestre que los necesita.

El web scraping con AWS Lambda consiste en ejecutar código de obtención y extracción de páginas como funciones de AWS de corta duración, en lugar de mantener servidores de scraping. Lambda inicia ese código en respuesta a programaciones, colas, llamadas HTTP u otros eventos de AWS.

Ese modelo operativo resulta atractivo, pero no hace que todos los rastreadores sean compatibles con el modelo «serverless». Lambda es adecuado para comprobaciones programadas de productos, tareas de una sola URL, extracción impulsada por webhooks y trabajadores de colas. Resulta menos adecuado para rastreos que requieren horas de tiempo de ejecución ininterrumpido, una sesión de navegador que debe permanecer activa o un fan-out descontrolado contra un objetivo sensible.

Esta guía adopta un enfoque que prioriza la toma de decisiones. Crearás un rastreador web en Python con AWS Lambda, verás su equivalente en Java 21 utilizando HttpClient y Jsoup, compararás el empaquetado en ZIP y en contenedores, almacenarás los resultados en S3 y escalarás el trabajo con URL a través de SQS. También obtendrás una estrategia de escalado conservadora para JavaScript y el bloqueo, además de fórmulas de cálculo de costes que separan los gastos de computación de Lambda de los de almacenamiento, registro, red, imágenes, proxy y API. Los valores de AWS sujetos a cambios se indican explícitamente para que puedas verificarlos en la documentación oficial enlazada.

Empieza por la decisión de implementación: ¿es Lambda el entorno de ejecución adecuado para el web scraping con AWS Lambda?

La respuesta breve es sí, siempre que el scraping pueda completarse en una sola invocación delimitada o dividirse en unidades independientes, como una URL, una página de listados o un cursor. El «Web Scraping con AWS Lambda» resulta especialmente eficaz para comprobaciones programadas y trabajos intermitentes, ya que no hay una flota de trabajadores que deba mantenerse en funcionamiento entre eventos.

La cuestión importante no es si Lambda puede recuperar una página. Puede hacerlo. La cuestión es si los fallos, los reintentos, el estado y el rendimiento siguen siendo manejables cuando cada invocación es temporal.

Características de la carga de trabajo

Adecuado para Lambda

Señal de alerta

Tiempo de ejecución

Segundos o unos pocos minutos

Un rastreo tarda horas

Estado

La entrada contiene todo lo necesario

Estado del navegador o de sesión de larga duración

Paralelismo

URL independientes con un límite claro

Un rastreador descubre una cantidad ilimitada de trabajo

Dependencias

Analizador HTTP o imagen controlada

Pila de escritorio grande y frágil

Salida

Se guarda de forma permanente tras cada tarea

Los resultados solo existen en memoria

Modelo de fallo

Se puede volver a intentar una URL de forma segura

Reintentar provoca efectos secundarios duplicados

Una regla de diseño útil es hacer que la función sea desechable. Si AWS detiene un entorno tras una solicitud, un sustituto debería poder procesar el mismo evento sin tener que reconstruir el estado local oculto. Esto hace que los puntos de control, los resultados y las credenciales se almacenen en servicios dedicados en lugar de /tmp o a variables globales del módulo.

Adapta el HTML estático, el rastreo, la visualización en el navegador y las API gestionadas a la tarea

Elige el método de recuperación menos complejo que devuelva los datos que necesitas. Las páginas estáticas suelen necesitar un cliente HTTP y un analizador sintáctico. El rastreo de varias páginas puede justificar el uso de Scrapy. Las aplicaciones renderizadas por el cliente pueden requerir un navegador, un punto final JSON subyacente al que estés autorizado a acceder o un servicio de renderización alojado.

Requisito

Primera opción

Pásate a la siguiente opción cuando

HTML renderizado por el servidor

requests más BeautifulSoup

El contenido no aparece en la respuesta

Rastreo siguiendo enlaces

Scrapy en ZIP o imagen

Las dependencias nativas superan los límites del ZIP

Clics, desplazamientos y formularios

Playwright en un contenedor

Las operaciones del navegador dominan el tiempo de ejecución

Alto riesgo de bloqueo

Control de velocidad y, a continuación, un proxy

La reputación de la IP o la ubicación geográfica son factores importantes

Renderización más operaciones de proxy

API de scraping gestionada

El mantenimiento de los navegadores y la lógica del proxy cuesta más que externalizar la recuperación

La renderización, el estado de la sesión, la duración de la ejecución, el volumen de solicitudes y el riesgo de bloqueo deben determinar esta elección. Una arquitectura de navegador sin interfaz ofrece más control, pero también consume más memoria, aumenta el trabajo de arranque en frío y genera más modos de fallo que el análisis de HTML.

Elige Fargate, Batch o EC2 para las cargas de trabajo que Lambda no puede gestionar

Utiliza un servicio de computación de mayor duración cuando la unidad de trabajo no pueda delimitarse. Fargate es el siguiente paso más práctico para los trabajadores en contenedores que necesitan ejecuciones más prolongadas sin tener que gestionar hosts. Batch es más adecuado cuando los trabajos se ponen en cola, requieren un gran esfuerzo de computación y se expresan de forma natural como envíos por lotes. EC2 ofrece el mayor control para navegadores persistentes, redes especializadas, cachés locales o rastreadores en ejecución continua, pero también devuelve a tu equipo la responsabilidad de aplicar parches a los hosts y gestionar la capacidad.

La decisión es cualitativa antes que financiera. Si te ves obligado a establecer puntos de control cada pocos minutos únicamente para evitar el límite de funciones, o a descargar repetidamente una gran pila de navegadores, un servicio de contenedores puede resultar más sencillo y económico desde el punto de vista operativo. Lambda debería reducir el trabajo de infraestructura, no trasladarlo a un código de recuperación complejo.

Utiliza una arquitectura de producción que separe el control y el flujo de datos

Un rastreador de producción es más fácil de manejar cuando sus componentes están separados. Considera el disparador como el flujo de control, la función Lambda como un trabajador sustituible, S3 o una base de datos como el flujo de datos duradero, y CloudWatch junto con un almacén de secretos como el plano operativo.

Una ruta típica de web scraping con AWS Lambda tiene este aspecto:

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

Esta separación evita problemas comunes de acoplamiento. Una programación no debe contener lógica de análisis sintáctico. Un «trabajador» no debe conservar la única copia de sus resultados. Un analizador sintáctico no debe saber cómo se cifra un secreto. La monitorización debe recibir datos estructurados sobre cada ejecución, en lugar de reconstruirlos a partir de trazas de pila no estructuradas.

Define un contrato de evento para cada lenguaje y cada disparador. Basta con un contrato conciso:

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

Devolver o almacenar una envoltura de resultados coincidente con ok, request_id, url, count, items, y s3_uri. De este modo, Python y Java pueden compararse a nivel operativo, y los reintentos de SQS no dependen de formas de eventos específicas de cada lenguaje.

Elige EventBridge, SQS o Step Functions como disparador

Utiliza EventBridge para el scraping web programado en AWS cuando el trabajo sea pequeño y periódico. Una regla puede invocar una función cada hora o cada día, y la función puede escribir la instantánea resultante en S3. Evita solapamientos acortando la ejecución, añadiendo claves de salida idempotentes y aplicando concurrencia reservada si las ejecuciones simultáneas pudieran resultar perjudiciales.

Utiliza SQS cuando un productor pueda enumerar URL o cursores de página. La cola almacena en búfer los picos de tráfico, Lambda consume pequeños lotes y se puede volver a intentar una URL fallida sin reiniciar el trabajo que se ha realizado correctamente. Esta es la arquitectura predeterminada para la recopilación de múltiples URL, ya que la velocidad del productor ya no dicta la tasa de solicitudes de destino.

Utiliza Step Functions cuando el trabajo tenga etapas diferenciadas, como la obtención de páginas de listado, la generación de URL de detalle, el enriquecimiento de registros y la publicación de un manifiesto. Proporciona ramificaciones explícitas y políticas de reintento, pero las transiciones de estado añaden costes y otro servicio que gestionar.

Desencadenador

Ideal para

Control principal

EventBridge

Instantáneas periódicas

Programación y concurrencia reservada

SQS

Numerosas URL independientes

Tamaño del lote, concurrencia de fuentes de eventos, DLQ

Step Functions

Flujos de trabajo de varias etapas

Reintentos y ramificaciones a nivel de estado

API Gateway

Solicitudes bajo demanda

Límites de autenticación, carga útil y tiempo de espera

Evento S3

Procesamiento de semillas cargadas

Convenciones de claves de objetos y gestión de duplicados

Envía la salida, los secretos, los registros y las métricas a servicios específicos

S3 es una opción predeterminada sensata para HTML sin formato, registros JSON y manifiestos de rastreo, ya que cada invocación puede escribir un objeto duradero y devolver un URI pequeño. Utiliza claves determinísticas como scrapes/site/date/request-id.json; si se vuelve a intentar el mismo mensaje, debería sustituir el mismo objeto o detectar que el resultado ya existe.

Guarda las claves de API, las contraseñas de proxy y las credenciales de sesión en SSM Parameter Store o Secrets Manager. Introduce solo el nombre del parámetro o el ARN del secreto en el entorno de Lambda. Concede al rol de ejecución permiso para leer ese recurso concreto, no todos los secretos de la cuenta.

CloudWatch recibe los registros, las métricas y las alarmas de la función. Registra un ID de correlación, el host normalizado, el código de estado, el tiempo transcurrido, el recuento de reintentos, el recuento de elementos y la clave de salida. Evita las cadenas de consulta completas, los encabezados de autorización, las cookies, los cuerpos de las páginas y los valores de los secretos. X-Ray o un sistema de rastreo equivalente puede ayudar a distinguir el tiempo empleado dentro de Lambda de la latencia del DNS, el destino, el proxy, S3 o el almacén de secretos.

Esa separación de servicios también deja puntos de extensión naturales. Puedes añadir reglas de ciclo de vida a S3, una tarea de catalogación tras escrituras correctas o una herramienta de reproducción para una cola de mensajes no entregados sin cambiar el código de extracción.

Asigna nombres a las funciones y los recursos en función de su responsabilidad, en lugar de según el sitio de destino. Separa la generación de URL de la recuperación de páginas cuando escalen de forma diferente, y crea versiones del esquema de eventos antes de que varios productores dependan de él. Esto evita que las implementaciones del analizador modifiquen accidentalmente la programación o el comportamiento de las colas.

Diseña teniendo en cuenta las cuotas de Lambda antes de programar

El web scraping con AWS Lambda tiene límites de plataforma estrictos, y varios de ellos dependen del tiempo. En el momento al que se refieren las fuentes proporcionadas, los valores más citados son aproximadamente los que se indican a continuación. Confírmalos para tu región y cuenta en la página oficial de cuotas de AWS Lambda antes de la publicación o la implementación.

Restricción

Valor comunicado

Consecuencias para el scraping

Número máximo de invocaciones

Aproximadamente 900 segundos

Dividir la paginación larga en unidades en cola

Memoria

Entre 128 MB y 10 240 MB aproximadamente

Una mayor cantidad de memoria también modifica la CPU disponible

Efémera /tmp

Aproximadamente entre 512 MB y 10 240 MB

Los archivos del navegador y las descargas requieren un tamaño específico

Paquete ZIP

Unos 50 MB comprimidos, 250 MB descomprimidos

Las bibliotecas nativas pueden obligar a utilizar capas o una imagen

Imagen de contenedor

Unos 10 GB sin comprimir

Empaquetado más sencillo, pero compilaciones más lentas y descargas más pesadas

Carga útil síncrona

Aproximadamente 6 MB en cada sentido

Almacenar los resultados en S3 y devolver una referencia

Concurrencia regional

A menudo se indica como 1.000 por defecto

Las cuentas nuevas o con restricciones pueden tener un valor inferior

No configures el tiempo de espera de HTTP igual al tiempo de espera de la función. Deja tiempo suficiente para el análisis, las escrituras en S3, las métricas y una respuesta de error controlada. Para una función de 60 segundos, por ejemplo, un tiempo de espera de solicitud de entre 35 y 45 segundos es un valor inicial más seguro que 60 segundos, aunque el valor correcto depende del objetivo.

La concurrencia también sirve para controlar el tráfico saliente. Establece la concurrencia reservada para el rastreador y, cuando sea compatible, una concurrencia máxima por fuente de eventos para SQS. Una cuota de servicio no es un permiso para enviar tantas solicitudes simultáneas a un sitio web.

Configurar el repositorio y la infraestructura con AWS SAM

AWS SAM define funciones, permisos de IAM, programaciones, colas y buckets en una plantilla de CloudFormation. Proporciona a «Web Scraping With AWS Lambda» un flujo de trabajo local y de implementación repetible sin necesidad de Docker para una función ligera de HTML estático.

Un repositorio compacto puede admitir ambos lenguajes y rutas de empaquetado:

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/

Instala una versión compatible de Python, la CLI de AWS, la CLI de AWS SAM y Docker si tienes pensado utilizar sam local o imágenes de contenedor. Configura un perfil de AWS con nombre y verifica la cuenta antes de la implementación:

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

Mantén los eventos de prueba en el control de código fuente, pero nunca incluyas cookies activas, credenciales de proxy ni URL firmadas. Parametriza el depósito de salida y los identificadores secretos por entorno. Un flujo de trabajo SAM repetible es validate, build, local invoke, deploy, así como la invocación en la nube con inspección de registros.

Elige empaquetado ZIP, capas o una imagen de contenedor

Utiliza un paquete ZIP para requests, BeautifulSoup, Jsoup y grafos de dependencias igualmente pequeños. SAM puede instalar las dependencias en el artefacto de compilación, y los arranques en frío siguen siendo relativamente ligeros.

Las capas son útiles cuando varias funciones comparten un conjunto estable de dependencias, pero añaden la coordinación de versiones. No eliminan los límites de los paquetes, y una capa que cambia con cada despliegue suele ser simplemente otro artefacto más que gestionar.

Elige una imagen de contenedor de AWS Lambda cuando necesites paquetes nativos del sistema, dependencias de Scrapy que resulten complicadas de compilar en un ZIP o un navegador sin interfaz gráfica. Una imagen mejora la coherencia del entorno, no la idoneidad en tiempo de ejecución.

Empaquetado

La mejor opción

Principal inconveniente

ZIP

HTML estático, analizadores sintácticos pequeños

Límites de dependencia más estrictos

Capa más ZIP

Bibliotecas estables compartidas

Acoplamiento de versiones entre funciones

Imagen de contenedor

Bibliotecas nativas, Scrapy, Playwright

Sobrecarga de compilación, análisis, almacenamiento y arranque en frío

No utilices Docker por defecto solo porque se parezca más a un entorno de producción. Para HTML sencillo, el artefacto más pequeño suele ser más fácil de parchear, probar y supervisar.

Mantén las diferencias de entorno en los parámetros de SAM o en los archivos de configuración, no en plantillas editadas manualmente. Una implementación debe ser reproducible a partir de un commit, un perfil y un conjunto de parámetros, con salidas de la pila que expongan los nombres de las funciones, los nombres de los buckets, las URL de las colas y los alias necesarios para las pruebas de humo.

Crea el rastreador de HTML estático en Python

Para el HTML estático, un rastreador web de Python en AWS Lambda solo necesita un cliente HTTP, un analizador de HTML y un cliente del SDK de AWS para obtener una salida duradera. BeautifulSoup analiza la respuesta que recibe; no ejecuta JavaScript ni espera a una aplicación del lado del cliente.

El controlador que se muestra a continuación acepta invocaciones directas, cuerpos JSON de API Gateway o un objeto de EventBridge detail . Valida la URL, reutiliza los clientes en invocaciones «en caliente», establece tiempos de espera explícitos, analiza selectores CSS sustituibles, escribe el resultado en S3 y devuelve errores estructurados.

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

Sustituye los selectores por otros cubiertos por pruebas de fixture. Si no se espera ningún elemento, trátalo como un fallo del analizador en lugar de como un raspado vacío correcto. Decide también si se permiten las redirecciones y si las URL proporcionadas por el usuario requieren una lista de permitidos para reducir el riesgo de falsificación de solicitudes del lado del servidor.

Prueba localmente, implementa, ejecuta y escribe JSON en S3

Utiliza un pequeño archivo de dependencias:

requests
beautifulsoup4

Los recursos SAM que se indican a continuación crean un bucket de salida y solo permiten la escritura de objetos bajo el prefijo del rastreador. Fija los entornos de ejecución compatibles y las versiones de las dependencias en el repositorio real tras la validación.

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

Crea events/scrape.json:

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

A continuación, ejecuta una secuencia repetible:

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

Valida el objeto en S3 e inspecciona el flujo de registros de la función en CloudWatch. Un éxito a nivel local demuestra que el análisis se ha realizado correctamente, pero no garantiza los permisos en la nube, el comportamiento del DNS, el acceso al destino ni el margen de tiempo de espera. La validación en la nube debe confirmar el rol desplegado exacto, las variables de entorno, la arquitectura de los artefactos, la clave de salida, el código de estado, la duración y el recuento de elementos.

Antes de la puesta en producción, añade una política de tamaño máximo de respuesta y rechaza los tipos de contenido que no pretendas analizar. Esto protege la memoria y evita gastar la invocación restante en una descarga de gran tamaño. Conserva muestras de HTML sin procesar solo cuando sea necesario para el diagnóstico, con controles de retención y acceso.

Empaqueta Scrapy o Playwright en un contenedor de Lambda

El uso de un contenedor está justificado cuando el gráfico de dependencias, las bibliotecas nativas o el entorno de ejecución del navegador ya no caben cómodamente en un archivo ZIP. No se trata de una forma de eludir el modelo de tiempo de ejecución de Lambda. El proceso sigue teniendo una invocación limitada, almacenamiento local efímero y entornos de ejecución desechables.

Para un trabajador de Scrapy en AWS Lambda, aísla cada ejecución de la araña en un subproceso. El reactor de Twisted no está diseñado para detenerse y reiniciarse repetidamente en el mismo proceso de Python, lo que puede afectar a las invocaciones de Lambda en estado «caliente». Un subproceso añade una sobrecarga de arranque, pero proporciona a cada rastreo un reactor limpio y un límite de tiempo de espera claro.

Una imagen mínima orientada a Lambda puede tener este aspecto:

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

Fija y genera un hash de las dependencias en la compilación desplegada. La imagen base y el entorno de ejecución que se muestran aquí deben contrastarse con las imágenes base de Lambda compatibles en el momento del despliegue.

Un controlador compacto puede ejecutar una araña existente, recopilar datos JSON en /tmpy subirlo:

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

El rastreador debe aceptar start_url, utilizar tiempos de espera explícitos para las descargas, limitar la paginación y generar registros que se ajusten al esquema de salida de Python y Java. Scrapy también puede escribir un feed directamente en S3, pero una subida explícita facilita la visualización del contrato de salida y los límites de error.

El empaquetado de Playwright para AWS Lambda sigue el mismo principio de imagen, pero es más pesado. Chromium, las fuentes, las bibliotecas compartidas y las cachés del navegador deben ajustarse a la arquitectura de la imagen. Hay que prever más memoria y tiempo de arranque en frío, y escribir las descargas únicamente en /tmp, cerrar los contextos en finally, y nunca des por sentado que un perfil de navegador sobrevive a la invocación. Una configuración de Scrapy-Playwright solo es adecuada cuando se requiere la representación del navegador para los datos.

Compila, envía a ECR e implementa la imagen con SAM

Compila una arquitectura que se ajuste a la función. El siguiente ejemplo para x86 utiliza etiquetas versionadas; sustituye la imagen base y la región por otras que estén actualmente soportadas.

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"

Pasa la URI de la imagen versionada a SAM en lugar de implementar una 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

Implementa con la etiqueta exacta:

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

Para una mayor reproducibilidad, registra el resumen de la imagen generado por ECR en los metadatos de la implementación. Activa los controles de análisis y retención del repositorio solo después de verificar las opciones actuales de ECR y la política de tu organización. Elimina las imágenes antiguas sin referencias, las capas locales, las pilas de prueba y los objetos de prueba de S3 para que los experimentos con contenedores no se conviertan en un coste permanente.

Prueba la imagen localmente con el punto final de tiempo de ejecución de Lambda o sam local invoke, a continuación, realiza una prueba de humo en la nube. La arquitectura local de Docker, la arquitectura de Lambda, los permisos del sistema de archivos y las bibliotecas del sistema del navegador son fuentes habituales de fallos del tipo «funciona en mi máquina».

Haz que las compilaciones de contenedores sean deterministas registrando en CI el resumen de la imagen base, el bloqueo de dependencias, la arquitectura de destino y el resumen de la imagen generada. Recompila regularmente para aplicar actualizaciones de seguridad, pero transfiere un resumen probado entre entornos en lugar de recompilar bytes diferentes para desarrollo y producción.

Compila el rastreador de Java 21 con HttpClient y Jsoup

El scraper web de Java para AWS Lambda debe utilizar el mismo contrato de eventos y resultados que el de Python. HttpClient «handles» gestiona las solicitudes HTTP acotadas, Jsoup analiza el HTML, Jackson normaliza los cuerpos de los eventos JSON y el SDK de AWS escribe el resultado persistente.

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

Reutiliza los clientes estáticos durante las invocaciones en caliente, pero no almacenes el estado del rastreo, que es crítico para la corrección, en campos estáticos. Se aplican las mismas advertencias que en Python: depura los registros, trata los análisis inesperados de cero elementos como fallos y mantén el tiempo de espera de HTTP por debajo del tiempo de espera de la función. Una referencia sobre el análisis de HTML en Java con Jsoup resulta útil cuando los selectores o el comportamiento de los enlaces absolutos requieren un tratamiento más detallado.

Empaqueta con Maven y reduce el tiempo de inicio con SnapStart

El proyecto Maven necesita las interfaces principales de Lambda, Jsoup, Jackson, el SDK de S3 y un paso de compilación que genere un único JAR de implementación. Fija las versiones tras comprobar las versiones actuales y tu política de dependencias.

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

Configurar maven-shade-plugin durante package, y a continuación indica a SAM el JAR compilado. La configuración esperada de SnapStart utiliza una versión publicada o un alias en lugar de $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

Compila e invoca con mvn clean package, sam build, sam local invoke JavaScraper -e events/scrape.json, y sam deploy. En el momento de redactar este documento, compruebe que Java 21 y SnapStart sean compatibles con la región de implementación y confirme si existen restricciones en cuanto a la inicialización, las conexiones de red, la entropía y las credenciales almacenadas en caché tras la restauración de la instantánea.

Gestiona JavaScript, los proxies y las defensas antibots de forma deliberada

El web scraping con AWS Lambda no altera la forma en que el destino muestra el contenido ni evalúa el tráfico. Empieza con la ruta de solicitud autorizada más sencilla, observa el fallo y escala el problema solo por un motivo diagnosticado.

  1. HTTP directo: utilícelo cuando la respuesta contenga los datos. Añada un agente de usuario veraz, reintentos limitados para fallos transitorios, un ritmo de solicitud razonable y pruebas de análisis sintáctico.
  2. Solicitud de datos subyacentes: si una página carga datos públicos desde un punto final JSON, utiliza ese punto final solo cuando sus condiciones y autorización lo permitan. No des por sentado que un punto final no documentado es estable.
  3. Navegador sin interfaz gráfica: utiliza Playwright cuando el flujo de trabajo requiera realmente la ejecución de JavaScript, clics, desplazamiento o envío de formularios. La representación del navegador no garantiza el acceso.
  4. Proxy explícito: utiliza un proxy cuando la ubicación geográfica, una salida estable o la rotación de IP sean requisitos legítimos. La reputación del proxy, la afinidad de sesión y la política de destino siguen siendo importantes.
  5. Servicio de scraping gestionado: externaliza la recuperación cuando el mantenimiento del navegador, el proxy, el CAPTCHA y las operaciones de reintento supongan un coste mayor que la lógica de extracción.

Es posible que algunos sitios web reconozcan o bloqueen los rangos de direcciones de los proveedores de nube. Esto depende del contexto, y pasar a utilizar un proxy o un navegador no garantiza el éxito. Los encabezados y los complementos de ocultación también pueden crear una falsa sensación de fiabilidad si el verdadero problema es la frecuencia de las solicitudes, la autorización, el estado de la cuenta o los cambios en el marcado.

Una política práctica de scraping web antibots clasifica los resultados por separado: fallo de transporte, tiempo de espera agotado, bloqueo HTTP, página de desafío, requisito de inicio de sesión, estado de renderizado vacío y discrepancia del analizador sintáctico. Cada categoría tiene una solución diferente. Reintentar todas ellas de la misma forma supone un derroche de dinero y puede aumentar la presión sobre el objetivo.

Compara los proxies explícitos con una API de scraping gestionada

Un proxy explícito mantiene la construcción de la solicitud y el análisis en tu código. Carga sus credenciales desde un almacén de secretos, no desde el evento o el repositorio:

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

Una API de scraping gestionada puede trasladar la recuperación de HTML sin procesar, la rotación de proxies, la gestión de CAPTCHA o la representación del navegador fuera de Lambda, dependiendo de las capacidades documentadas del proveedor. Mantén la integración genérica hasta que verifiques la autenticación y los parámetros actuales:

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

Opción

Tú controlas

Tú te encargas de

Estructura de costes

Solicitudes directas

HTTP y análisis sintáctico

Reintentos, control de ritmo, ruta IP

Lambda más redes

Proxies explícitos

Selección de proxy y sesiones

Rotación, fallos, credenciales

Tráfico de proxy más recursos de computación

Contenedor de navegador

Interacción completa

Imagen y estabilidad del navegador

Mayor memoria y duración

API gestionada

Opciones de solicitud y análisis

Integración con proveedores y soluciones alternativas

Por solicitud, crédito o resultado

Una guía sobre el uso de proxies con Python Requests es un complemento imprescindible a la hora de implementar la autenticación, la rotación y la gestión de errores. Antes de optar por una vía gestionada, comprueba la latencia, el formato de respuesta, el tamaño máximo del cuerpo, la unidad de facturación, el enrutamiento regional, el tratamiento de datos y el comportamiento ante solicitudes fallidas.

Escala trabajos con múltiples URL de forma segura con SQS

El scraping basado en colas coloca una unidad de trabajo independiente en cada mensaje de SQS, normalmente una URL más un ID de solicitud, la versión del analizador y metadatos de rastreo opcionales. Lambda recibe un pequeño lote, procesa cada mensaje y escribe cada resultado por separado. Esto permite que las URL que tienen éxito permanezcan completas mientras se reintentan los fallos.

Para el rastreo web con AWS Lambda, la granularidad de los mensajes es una decisión relacionada con el control de la tasa. Una URL por mensaje ofrece el comportamiento más limpio en cuanto a reintentos e idempotencia. Un pequeño grupo de URL estrechamente relacionadas puede reducir la sobrecarga de la cola, pero una sola URL lenta retrasa entonces todo el mensaje.

Mantén los mensajes pequeños. Almacena las listas de semillas grandes o los artefactos de sesión en S3 y coloca solo su referencia de objeto en SQS. Incluye información suficiente para reproducir la solicitud, pero no incluyas contraseñas de proxy, cookies ni tokens de API.

Alinee los tiempos de espera entre las distintas capas:

  • El tiempo de espera de HTTP debe ser inferior al de la función.
  • El tiempo de espera de la función debe dejar tiempo suficiente para la salida y el diagnóstico.
  • El tiempo de espera de visibilidad de SQS debe superar el tiempo que un lote puede permanecer en curso, incluyendo un margen adecuado para reintentos.
  • La retención de mensajes y la retención de mensajes no entregados deben ser lo suficientemente largas como para que los operadores puedan investigarlas.
  • La concurrencia reservada y la de origen de eventos deben reflejar una tasa de solicitudes adecuada para el destino, no solo la capacidad de la cuenta.

La configuración de AWS y los mínimos obligatorios pueden cambiar, por lo que debes validar el comportamiento actual de la integración en la documentación oficial de Lambda con SQS.

Implementa reintentos, fallos parciales de lotes, colas de mensajes no entregados (DLQ), idempotencia y límites de concurrencia

Habilite las respuestas parciales de lotes para que Lambda devuelva únicamente los ID de los mensajes fallidos. Sin este comportamiento, un solo registro erróneo puede provocar que todos los registros del lote vuelvan a aparecer.

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}

En SAM, declara el modo de respuesta en la fuente de eventos:

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

Haz que scrape_one idempotente. Una clave determinista, como scrapes/{job_id}.json, una escritura condicional de DynamoDB o una comprobación de resultado existente, evita que los mensajes duplicados produzcan efectos duplicados en las etapas posteriores.

Conecta una cola de mensajes perdidos mediante la política de reenvío de SQS y elige su umbral de recepción en función del comportamiento de reintento que realmente desees. Envía allí los mensajes agotados para su inspección y reproducción selectiva. Limita la concurrencia tanto en la función como en la fuente de eventos, siempre que sea posible. Esos controles protegen el sitio web, reservan capacidad para otras funciones y evitan que una acumulación de mensajes pendientes se convierta en un pico de tráfico accidental.

La contrapresión comienza en el productor. Si la antigüedad de la cola aumenta, pausa o ralentiza el descubrimiento de URL en lugar de permitir una acumulación ilimitada. Realiza un seguimiento por separado de cada destino u host cuando requieran diferentes políticas de concurrencia, ritmo, credenciales o reintentos.

Añade observabilidad y diagnóstico de fallos

Un rastreador de AWS Lambda debe emitir un resumen estructurado por cada intento. Utiliza campos JSON que puedan consultarse sin necesidad de analizar el texto:

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

Normaliza el host y omite las cadenas de consulta, a menos que se sepa que son seguras. Nunca registres en el registro encabezados de autorización, cookies, URL de proxy con credenciales, cuerpos HTML completos ni valores secretos.

Realiza un seguimiento al menos de estas clases de métricas:

  • Trabajo: intentos, éxitos, elementos extraídos, bytes almacenados.
  • Estado de las solicitudes: latencia, tiempos de espera, errores de conexión, familias de códigos de estado HTTP.
  • Señales de bloqueo: detecciones de páginas de desafío, denegaciones de acceso, redireccionamientos de inicio de sesión.
  • Estado del analizador: resultados con cero elementos, campos obligatorios que faltan, fallos en los selectores.
  • Estado de AWS: errores, limitaciones de ancho de banda, duración, memoria, antigüedad de SQS y profundidad de la cola DLQ.

Activa alarmas en función de las tasas y las tendencias sostenidas, no ante cada fallo individual. Una alarma del analizador debe diferenciarse de una alarma de bloqueo, ya que el manual de procedimientos y el responsable pueden ser distintos. Configura la retención de registros de forma deliberada, adjunta identificadores de correlación a los mensajes de cola y a las claves de S3, y utiliza el rastreo cuando este separe de forma significativa la latencia objetivo de las llamadas a los servicios de AWS.

CloudWatch recibe los registros estándar de Lambda en el grupo de registros de la función. X-Ray puede ayudar a visualizar la latencia en las etapas posteriores, pero el rastreo de cada solicitud de gran volumen puede suponer un coste adicional y generar ruido. Realiza muestreos de forma intencionada y conserva suficientes ejemplos fallidos, tras eliminar el contenido sensible, para poder reproducir los incidentes.

Crea paneles de control en torno a las decisiones: si ralentizar el tráfico, revertir un analizador, rotar credenciales, aumentar la memoria o reproducir fallos. Un gráfico que no pueda influir en la siguiente acción de un operador probablemente sea ruido.

Prueba e implementa los cambios mediante CI/CD

Trata los analizadores como código determinista y las redes como límites sustituibles. Almacena ejemplos HTML representativos de páginas normales, páginas vacías, marcado modificado, páginas de desafío y respuestas mal formadas. Las pruebas unitarias deben llamar directamente a las funciones de análisis y verificar los campos obligatorios, las URL absolutas y el comportamiento ante fallos.

Simula respuestas HTTP, escrituras en S3, lecturas en SSM y tiempos de espera en las pruebas de los controladores. Añade pruebas de contrato que se ejecuten de la misma url, limity request_id evento tanto en Python como en Java y comparar el conjunto de resultados, no los detalles internos específicos del lenguaje.

Un proceso práctico incluye:

  1. formateador, linter, comprobaciones de tipos o compilación y pruebas unitarias.
  2. sam validate --lint además de comprobaciones de políticas de CloudFormation.
  3. Comprobaciones de dependencias y vulnerabilidades de los contenedores.
  4. sam build O la creación de una imagen específica para la arquitectura.
  5. Ejecución de pruebas de humo locales con un servidor de fixtures.
  6. Despliegue en una pila que no sea de producción y en un entorno «canario» controlado.
  7. Promoción por etapas a producción con una ruta de reversión inmediata.

No hagas que la CI dependa de un sitio web público no controlado para cada commit. Utiliza un servidor de pruebas local para garantizar la repetibilidad y programa pruebas de integración independientes para comprobar el comportamiento esperado. Verifica que los roles de despliegue no puedan ampliar el rol del scraper, que los secretos nunca aparezcan en los registros de compilación y que se eliminen las imágenes de contenedores antiguas y los buckets de pruebas.

Calcula el coste total, no solo el de computación de Lambda

El coste del scraping con AWS Lambda depende de las solicitudes y la duración, pero eso es solo la parte de computación. Calcula el uso antes de aplicar el precio regional:

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

Una función diaria que utiliza 256 MB durante 3 segundos consume unos 22,5 GB-segundos en 30 días. Con un millón de páginas, un analizador estático que utiliza 256 MB durante 2 segundos consume 500 000 GB-segundos. Un «browser worker» que utiliza 2 GB durante 10 segundos consume 20 000 000 GB-segundos. Estas cifras de uso son más comparables que una estimación en dólares, ya que las tarifas, los descuentos por arquitectura, la elegibilidad para el nivel gratuito y las regiones pueden variar.

El material de referencia proporcionado cita una asignación gratuita mensual aproximada de un millón de solicitudes y 400 000 GB-segundos, y luego estima aproximadamente 1,33 dólares para el ejemplo estático y 261,33 dólares para el ejemplo del navegador, según sus supuestos. Considera esas cifras en dólares como referencias de planificación sin verificar, no como presupuestos para 2026. Vuelva a calcularlas a partir de la página oficial de precios de AWS Lambda para la región de implementación.

Añada todas las partidas adicionales:

Área de coste

Factor determinante típico

S3

Escrituras de objetos, almacenamiento, lecturas, ciclo de vida

CloudWatch

Ingesta de registros, retención, métricas, alarmas

ECR

Almacenamiento de imágenes, escaneo, transferencia entre regiones

Redes

Transferencia de datos y posible procesamiento a través de una puerta de enlace NAT

SQS o Step Functions

Solicitudes, transiciones, reintentos

Ejecución en el navegador

Más memoria, duración y tamaño de imagen

Proxy o API gestionada

Tráfico, solicitudes, créditos o resultados satisfactorios

El NAT puede saturar una carga de trabajo pequeña si Lambda se coloca en una VPC solo para garantizar una salida estable. Ten en cuenta también los reintentos del modelo y las respuestas bloqueadas. Estos consumen recursos de computación y tráfico de terceros incluso cuando no generan datos.

Lista de comprobación para el lanzamiento en producción: seguridad, cumplimiento normativo y rastreo respetuoso

Antes de lanzar «Web Scraping con AWS Lambda», revisa el sistema tanto como carga de trabajo de AWS como recopilador de datos automatizado.

  • IAM: Asigna a cada función únicamente el prefijo de S3, la cola, el espacio de nombres de métricas y el ARN secreto que necesite. Separa los permisos de implementación de los permisos de ejecución.
  • Secretos: almacena las credenciales de proxy, API e inicio de sesión en SSM Parameter Store o Secrets Manager. Rótalas y evita que los valores descifrados aparezcan en los registros.
  • Cifrado: Utilice el cifrado para S3, las colas, los registros y la configuración confidencial de acuerdo con su clasificación de datos y la política de su organización.
  • Controles de entrada: Valida los esquemas y los hosts. Si los solicitantes proporcionan URL, considera la posibilidad de utilizar una lista de permitidos y protecciones contra solicitudes dirigidas a metadatos, direcciones privadas o direcciones de enlace local.
  • Controles de tráfico: Establece la concurrencia reservada, los límites de las colas, los tiempos de espera de las solicitudes, los límites máximos de reintentos y un «kill switch» global. Un indicador de configuración que detenga las nuevas solicitudes es más rápido que una implementación de código de emergencia.
  • Minimización de datos: Recopila únicamente los campos necesarios para la finalidad indicada, define el periodo de conservación y restringe el acceso al código HTML sin procesar que pueda contener datos personales o sensibles.
  • Revisión de los objetivos: comprueba el archivo robots.txt, los términos y condiciones, la autorización, las directrices de frecuencia, las normas de la cuenta, las obligaciones de privacidad y la legislación aplicable antes de la recopilación. Estas señales tienen diferentes implicaciones legales, por lo que, ante un riesgo significativo, es recomendable contar con un marco de cumplimiento legal para el web scraping y con asesoramiento jurídico cualificado.
  • Responsabilidad operativa: Asigna alertas, un procedimiento de repetición de DLQ, un manual de procedimientos para cambios en el analizador sintáctico y una persona de contacto para las reclamaciones de los sitios objetivo.
  • Seguridad de la puesta en marcha: Comienza con una baja concurrencia, observa el estado y las métricas del analizador sintáctico y, a continuación, aumenta el rendimiento de forma deliberada.

Se trata de una guía de ingeniería, no de asesoramiento jurídico. La licitud de la recopilación depende de los datos, la jurisdicción, el método de acceso, la relación contractual y el uso previsto. No se debe considerar la accesibilidad técnica como una autorización.

Qué implementar primero

Empieza con el sistema útil más pequeño: HTTP directo en Python, empaquetado por SAM, activado por un evento de prueba y que escriba JSON en S3. Esa línea de base comprueba los permisos, la red, el análisis, el almacenamiento y la observabilidad con un mínimo de componentes móviles.

Añade EventBridge para una pequeña programación. Añade SQS cuando las URL se conviertan en elementos de trabajo independientes. Pasa a un contenedor solo para las dependencias que lo justifiquen, y utiliza un navegador únicamente cuando los datos requieran visualización o interacción. Pasa a proxies o a la recuperación gestionada solo después de evaluar el modo de fallo. Esta secuencia hace que el «Web Scraping con AWS Lambda» siga siendo comprensible a medida que crece.

Puntos clave

  • Utiliza Lambda cuando cada rastreo sea breve, delimitado y se pueda reintentar de forma independiente; opta por recursos de computación de mayor duración para sesiones persistentes o rastreos de varias horas.
  • Mantén un único contrato de eventos y resultados en Python, Java, programaciones, colas y pruebas locales.
  • Almacena los resultados en S3, las credenciales en un almacén de secretos gestionado y los datos operativos en registros y métricas estructurados.
  • Incorpora fallos parciales de SQS, DLQ, idempotencia y límites de concurrencia antes de aumentar el volumen de URL.
  • Calcula los costes de computación, almacenamiento, registros, imágenes, red, reintentos, proxies y servicios gestionados como partidas de coste independientes.

Preguntas frecuentes

¿Es necesario que un scraper de AWS Lambda se ejecute dentro de una VPC?

No. Una función de Lambda puede acceder a sitios web públicos sin estar conectada a tu VPC. Utiliza una VPC solo cuando sea necesario acceder a recursos privados, aplicar una inspección de red controlada o enviar tráfico a través de una puerta de enlace NAT con una IP pública fija. La conexión a una VPC añade configuración de red y puede suponer un coste adicional por NAT, por lo que debe responder a un requisito específico.

¿Cómo puede Lambda utilizar una IP de salida estable cuando un proxy o un destino requiere una lista de permitidos?

Enruta las funciones Lambda conectadas a una VPC a través de subredes privadas y una puerta de enlace NAT asociada a una IP elástica, o utiliza un punto final de proxy con una dirección estable. Configura una red redundante si la disponibilidad es importante. Una puerta de enlace NAT añade cargos por hora y por procesamiento de datos, mientras que un proxy añade sus propios problemas de tráfico y autenticación.

¿Pueden las cookies y las sesiones de inicio de sesión persistir entre invocaciones de Lambda?

No de forma fiable en memoria. Es posible reutilizar un entorno de ejecución «caliente», pero AWS no garantiza que la siguiente invocación llegue al mismo entorno. Mantén un almacén de cookies cifrado o el estado de la sesión en un almacén duradero adecuado, aplica controles de caducidad y acceso, y diseña el sistema para permitir actualizaciones simultáneas. Confirma que el inicio de sesión automático y el uso de credenciales estén autorizados.

¿Cómo deben almacenarse los resultados de rastreo que superen el límite de carga útil sincrónica de Lambda?

Escribe el cuerpo en S3 y devuelve una clave de objeto pequeña, un URI, una suma de comprobación y un sobre de metadatos. Para el procesamiento posterior, publica esa referencia a través de SQS, EventBridge o un registro de base de datos, en lugar de pasar el resultado completo. Utiliza compresión y cargas multiparte cuando sea apropiado, y restringe cualquier URL prefirmada por tiempo de vida y permisos.

¿Cómo se debe dividir la paginación cuando un rastreo puede superar el tiempo de ejecución máximo de Lambda?

Representa cada número de página, cursor o token de continuación como una tarea en cola independiente. Almacena el trabajo de la página siguiente detectada en SQS y conserva un ID de rastreo más un punto de control para que los reintentos sigan siendo idempotentes. Limita la detección de páginas, detecta cursores repetidos y utiliza Step Functions solo cuando el estado explícito del flujo de trabajo sea más valioso que un simple patrón de productor y cola.

Conclusión

Un rastreador sin servidor en producción es, sobre todo, un ejercicio de límites. Mantén cada invocación breve, haz que el evento sea autónomo, escribe la salida de forma duradera y asume que cualquier mensaje puede entregarse más de una vez. El HTTP directo más un analizador sintáctico es la opción predeterminada adecuada para páginas estáticas, mientras que SQS ofrece la vía más limpia para una escalabilidad controlada de múltiples URL.

Los contenedores son útiles para Scrapy, los paquetes nativos y las dependencias del navegador, pero no eliminan las limitaciones de tiempo de ejecución y de estado de Lambda. Java 21 puede seguir el mismo protocolo que Python, con HttpClient, Jsoup, S3 y una configuración SnapStart verificada. En el caso de páginas bloqueadas o con gran cantidad de JavaScript, diagnostica el fallo real antes de añadir un proxy, un navegador o un servicio gestionado, y nunca consideres esas opciones como un acceso garantizado.

Por último, calcula el coste total del sistema y verifica todos los valores de AWS sensibles al tiempo en tu región. Si el bloqueo de solicitudes se convierte en la principal carga de ingeniería, WebScrapingAPI puede servir como una capa gestionada de obtención de HTML sin procesar que se encarga de la rotación de proxies, los bloqueos y los CAPTCHAs, mientras que tu función Lambda sigue siendo responsable del análisis, la validación y el almacenamiento. Recurre a esa escalación solo cuando reduzca la complejidad operativa total.

Acerca del autor

Suciu Dan, Cofundador @ WebScrapingAPI

Suciu Dan

Cofundador

Suciu Dan es cofundador de WebScrapingAPI y escribe guías prácticas dirigidas a desarrolladores sobre el scraping web con Python, el scraping web con Ruby y las infraestructuras de proxy.

Cómo utilizar GoSpider: rastrear, depurar URL y extraer datos
Guías

Cómo utilizar GoSpider: rastrear, depurar URL y extraer datos

En resumen: GoSpider es un rastreador de línea de comandos diseñado para descubrir URL, no un extractor completo de datos estructurados. Esta guía sobre cómo utilizar GoSpider muestra cómo realizar un rastreo limitado, gestionar correctamente los resultados, pasar los datos de Colly a CSV y seguir una ruta de diagnóstico para respuestas 403 o páginas que requieren renderización con JavaScript.

Suciu Dan24 min read
Leer artículo
Cómo raspar Redfin: Guía Python de Datos Inmobiliarios
Guías

Cómo raspar Redfin: Guía Python de Datos Inmobiliarios

TL;DR: Redfin expone puntos finales de API ocultos que devuelven JSON estructurado para los listados de propiedades, lo que permite omitir por completo el frágil análisis HTML. Esta guía te guía a través de la construcción de un raspador de Python que extrae datos de alquiler y venta, busca por ubicación, supervisa los nuevos listados a través de mapas de sitio XML y exporta resultados limpios a CSV o JSON.

Suciu Dan14 min read
Leer artículo
XPath Web Scraping: Guía práctica con ejemplos en Python
Guías

XPath Web Scraping: Guía práctica con ejemplos en Python

TL;DR: XPath es un lenguaje de consulta para navegar árboles HTML/XML por ruta, atributo o contenido de texto. Esta guía cubre la sintaxis XPath, ejes y funciones, a continuación, muestra raspadores Python de trabajo con lxml y Selenium. También obtendrá una hoja de trucos consolidada y una sección de solución de problemas para los errores más comunes de XPath.

Suciu Dan11 min read
Leer artículo

Empieza a crear

¿Estás listo para ampliar tu recopilación de datos?

Únete a más de 2000 empresas que utilizan WebScrapingAPI para extraer datos de la web a escala empresarial sin ningún gasto de infraestructura.