jordan gonzález
publicado

Web Scraping con AWS Lambda en Rust

Recientemente, he estado trabajando en la entrega de productos de observabilidad de primer nivel para AWS Lambda, y resulta que están desarrollados en Rust. Sin embargo, aún no había tenido la oportunidad de construir una función utilizando este lenguaje.

Para un primer proyecto, decidí programar un web scraper que analizara la documentación de AWS y me alertara cuando se haya cambios en los lenguajes disponibles y obsoletos en AWS Lambda.


Puede parecer intimidante trabajar en un entorno que no se proporciona directamente (a diferencia de NodeJS o Python), pero la comunidad de Rust está creciendo rápidamente, e incluso AWS mantiene otros paquetes además de sus SDKs.

Una ventaja impresionante de usar Rust para microservicios es que el tiempo de inicialización será extremadamente reducido en comparación con otros lenguajes populares. Según los últimos benchmarks de Maxime David, en su sitio lambda-perf, el tiempo promedio de inicialización para un "Hello World" es de aproximadamente ~14ms.

Hello World

Primero, te enseñaré cómo configurar fácilmente un AWS Lambda en Rust utilizando la herramienta recomendada Cargo Lambda.

Después de installar la herramienta en tu entorno, simplemente ejecuta el siguiente comando y estarás listo para comenzar.

cargo lambda new hello-world

Ahora deberías tener un nuevo directorio, que se parece mucho a un proyecto de Cargo, y tu archivo principal (main.rs) debería verse así.

// main.rs
use lambda_runtime::{service_fn, Error, LambdaEvent};
use serde_json::{json, Value};

#[tokio::main]
async fn main() -> Result<(), Error> {
    let func = service_fn(func);
    lambda_runtime::run(func).await?;
    Ok(())
}

async fn func(event: LambdaEvent<Value>) -> Result<Value, Error> {
    let (_event, _context) = event.into_parts();

    Ok(json!({ "message": "Hello Ferris!" }))
}

Prueba localmente ejecutando el emulador con el comando watch.

cargo lambda watch

Invoca la función desde otra terminal con invoke.

cargo lambda invoke --data-ascii "{ \"hello\": \"world\" }"
# {"message":"Hello Ferris!"}

Simple enough, right? We will dive into deploying to AWS later.

Web Scraping

Existen múltiples formas de hacer web scraping con Rust. En mi caso, elegí utilizar scraper debido a la simplicidad de mi caso de uso.

Solo necesito consultar una página, extraer algunas tablas y verificar si ha habido cambios desde mi última captura de datos.


AWS Lambda con adaptador HTTP

Voy a modificar el ejemplo anterior porque quiero poder activar esta función a través de una URL de Lambda.

cargo add lambda_http

Separaré el método principal del código base para inyectar nuestro adaptador HTTP.

// main.rs
use lambda_http::{run, service_fn, tracing, Error};
mod http_handler;
use http_handler::handler;

#[tokio::main]
async fn main() -> Result<(), Error> {
    tracing::init_default_subscriber();

    run(service_fn(handler)).await
}

Ahora, nuestro método se asemeja más a una API HTTP en Rust.

// http_handler.rs
use lambda_http::{Body, Error, Request, Response};

pub(crate) async fn handler(_event: Request) -> Result<Response<Body>, Error> {
    let resp = Response::builder()
        .status(200)
        .header("content-type", "text/html")
        .body(“hello world”.into())
        .map_err(Box::new)?;
    Ok(resp)
}

Usando scraper

Además, necesitamos instalar un cliente HTTP y nuestro scraper.

cargo add reqwest scraper

Para comenzar a hacer scraping, simplemente tenemos que hacer una solicitud al sitio del que queremos extraer datos, convertir la respuesta en texto y luego navegar por el documento.

// http_handler.rs
use lambda_http::{Body, Error, Request, Response};
use reqwest::{header::HeaderMap, Client};
use scraper::{Html, Selector};
// … rest of code

async fn scrape(client: Client) {
    let response = client.get(AWS_LAMBDA_RUNTIMES_URL).send().await?;

    // Get the page as text
    let body = response.text().await?;

    // Parse the document
    let document = Html::parse_document(&body);
}

En mi caso, los elementos que me interesan en la página de documentación de AWS Lambda son las tablas HTML, por lo que necesito crear un selector para ellas y luego encontrar las referencias dentro del documento.

HTML table to scrape Así es como se ve una fila en la tabla.

// scrape method

// Select the table element
let table_selector = Selector::parse(“table”).unwrap();

// Get the reference to the first table element in the document
let tables_ref = document.select(&table_selector).next().unwrap();

Repetiré el mismo proceso de creación de un selector y luego lo usaré para obtener cada fila de la tabla.

// scrape method

// Get the table body
let tbody_selector = Selector::parse("tbody").unwrap();
let tbody_ref = table_ref.select(&tbody_selector).next().unwrap();

// Get the table rows from the table body
let tr_selector = Selector::parse("tr").unwrap();
let tr_elements = tbody_ref.select(&tr_selector);

// On every row, filter out empty spaces and breaklines
let rows = tr_elements.for_each(|e| {
   e.text()
    .map(|t| t.trim())
    .filter(|t| !t.is_empty())
    .collect::<Vec<_>>();
});

Esto me permitirá obtener un array de filas, Vec<Vec<String>>, que se verá así.

[ 
   [
      “Node.js 22”,”nodejs22.x”,”Amazon Linux 2023”,”Not scheduled”,”Not scheduled”,”Not scheduled”
   ],
]

Scraped data printed in the terminal

Desplegando con AWS CDK

Siempre es una buena práctica mantener nuestros recursos en la nube bajo control a través de Infraestructura como Código (IaC). AWS proporciona el Cloud Development Kit (CDK) para que los desarrolladores puedan gestionar sus recursos de manera más eficiente.

El mismo equipo de desarrollo de cargo-lambda proporciona un constructo de CDK (una abstracción que representa recursos de AWS CloudFormation y su configuración) para una AWS Lambda desarrollada en Rust.

En tu proyecto de CDK, simplemente instala el CDK construct cargo-lambda-cdk, disponible en múltiples lenguajes, aunque aquí usaremos NPM para instalarlo.

npm install cargo-lambda-cdk

Luego, usalo en tu stack como lo harías con cualquier otra AWS Lambda.

import { RustFunction } from 'cargo-lambda-cdk';

new RustFunction(stack, 'Rust function', {
  manifestPath: 'path/to/package/directory/with/Cargo.toml',
});

Eso es todo. Mi tiempo de inicialización promedio, incluyendo dependencias, terminó siendo de aproximadamente ~40ms, mientras que una invocación para procesamiento tomó en promedio ~300ms. Son números bastante buenos, aunque no invertí mucho esfuerzo en optimización de rendimiento.

El código fuente de mi proyecto es Open Source, para que pueda servirte de guía según tus necesidades. Allí incluyo más procesamiento para comparar capturas de datos extraídos. ¡Échale un vistazo!

duncanista/aws-lambda-examples#4

Resumen

Configurar Rust en AWS Lambda es mucho más simple de lo que parece. Combinado con sus impresionantes tiempos de inicio, diría que es un competidor poderoso para microservicios. Sin embargo, esto no significa que debas reescribir todo en Rust.

El web scraping puede ser mucho más complejo. En esta guía, solo explico un caso de uso sencillo para extraer un elemento dentro de una página. Siempre asegúrate de investigar más por tu cuenta, pero espero que esto te anime a explorar el scraping en Rust.


Referencias

[1] David, M. (2025). Lambda Cold Start benchmarks. Lambda-perf.

[2] Cargo Lambda. (2025). Rust functions on AWS Lambda made simple. Cargo Lambda.

[3] Rust Scraper. (2024) HTML parsing and querying with CSS selectors. scraper.

[4] Cargo Lambda. (2024). About CDK Construct to build Rust functions with Cargo Lambda. Cargo Lambda CDK construct.