¿Por qué Rust es el nuevo rey del parseo?
\n\nSi alguna vez has escrito un parser en Python, conoces ese momento: el script funciona, los datos se descargan, pero de repente — error de memoria, o el recolector de basura decide «descansar» en el momento más inoportuno. ¿Te suena? Ahora imagina que tu parser vuela a la velocidad de C, no consume memoria extra y además es seguro. Bienvenido al mundo del parseo en Rust.
\n\nRust no es solo un lenguaje de moda. Para tareas de scraping de datos ofrece lo que Python nunca podrá: control total sobre la memoria, cero sobrecarga del recolector de basura y una velocidad increíble. Además, el ecosistema para trabajar con HTTP y HTML ya es bastante maduro. En este artículo te mostraré cómo construir un parser real en Rust que podrás usar para monitoreo de precios, recolección de contenido o análisis SEO.
\n\nLo que necesitaremos: herramientas de Rust para parseo
\n\nPara web scraping en Rust hay tres pilares:
\n\n- \n
- reqwest — cliente HTTP. Puede navegar por sitios web, soporta asincronía y proxies. \n
- scraper (o select.rs) — biblioteca para parsear HTML. Permite usar selectores CSS. \n
- tokio — runtime asíncrono. Sin él, tu parser se ralentizará en cada solicitud. \n
Vamos a crear un nuevo proyecto e instalar las dependencias:
\n\ncargo new rust_parser\ncd rust_parser\ncargo add reqwest --features json\ncargo add scraper tokio\n\nListo. Ahora tenemos todo para escribir un parser en Rust en 10 minutos.
\n\nPrimer parser: obtenemos títulos de Habr
\n\nEmpecemos con algo simple: parsearemos los títulos de artículos de Habr. Es un clásico del género, como el «Hello, World!» del scraping en Rust.
\n\nuse scraper::{Html, Selector};\nuse reqwest;\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n // 1. Enviamos una solicitud GET\n let resp = reqwest::get("https://habr.com/ru/articles/")\n .await?\n .text()\n .await?;\n\n // 2. Parseamos el HTML\n let document = Html::parse_document(&resp);\n\n // 3. Creamos un selector para los títulos\n let selector = Selector::parse("h2.article-title a").unwrap();\n\n // 4. Iteramos y mostramos\n for element in document.select(&selector) {\n let title = element.text().collect::<Vec<_>>().join("");\n println!("{}", title.trim());\n }\n\n Ok(())\n}\n\n¿Qué sucede aquí? Usamos reqwest para la solicitud asíncrona, scraper para analizar el HTML y el selector CSS h2.article-title a para encontrar todos los enlaces en los títulos. Ejecutamos cargo run — y vemos la lista de artículos. Rápido, ¿verdad?
Parseo asíncrono: descargamos 100 páginas en un segundo
\n\nLa principal ventaja del parseo en Rust es la asincronía. En Python usarías asyncio y aiohttp, pero Rust con tokio ofrece aún más control. Vamos a parsear varias páginas en paralelo.
use scraper::{Html, Selector};\nuse reqwest;\nuse tokio::task;\n\nasync fn fetch_page(url: &str) -> Result<String, reqwest::Error> {\n let resp = reqwest::get(url).await?.text().await?;\n Ok(resp)\n}\n\nfn parse_titles(html: &str) -> Vec<String> {\n let document = Html::parse_document(html);\n let selector = Selector::parse("h2.article-title a").unwrap();\n document\n .select(&selector)\n .map(|el| el.text().collect::<Vec<_>>().join("").trim().to_string())\n .collect()\n}\n\n#[tokio::main]\nasync fn main() -> Result<(), Box<dyn std::error::Error>> {\n let urls: Vec<String> = (1..=5)\n .map(|i| format!("https://habr.com/ru/articles/page{}/", i))\n .coll