Cuando Scrapy se queda pequeño y Python es lento: Recolección de datos con Elixir usando Crawly
Cuando se trata de análisis de sitios web y recolección de datos a escala industrial, casi todos los desarrolladores piensan inmediatamente en Python y Scrapy. Es un estándar de la industria, probado durante años. Pero Python tiene una peculiaridad: cuando necesitas descargar cientos de páginas en paralelo, mantener miles de conexiones abiertas y limpiar datos sobre la marcha, terminas chocando con las limitaciones de hilos, async y GIL.
En el mundo de la programación funcional, la máquina virtual BEAM es ideal para estas tareas. Los procesos en Erlang y Elixir están aislados, pesan apenas unos pocos kilobytes y pueden generarse en millones. El framework Crawly toma los mejores conceptos arquitectónicos de Scrapy (spiders, middlewares, pipelines de procesamiento) y los trae a Elixir. El resultado es una herramienta lista desde el primer momento para manejar cargas masivas de solicitudes de red sin necesidad de ajustar un runtime async complejo.
Qué hay dentro y cómo funciona
Si alguna vez has escrito un parser en Scrapy, la arquitectura de Crawly te resultará nativa. Todo el trabajo se divide en tres partes claras: Spiders, Middlewares y Pipelines.
Un spider describe los puntos de entrada y la lógica para extraer datos de las páginas.
Aquí tienes un ejemplo típico de un spider que navega por un catálogo de libros, recopila títulos con precios y sigue las páginas de paginación:
defmodule BooksToScrape do
use Crawly.Spider
@impl Crawly.Spider
def base_url(), do: "https://books.toscrape.com/"
@impl Crawly.Spider
def init() do
[start_urls: ["https://books.toscrape.com/"]]
end
@impl Crawly.Spider
def parse_item(response) do
{:ok, document} = Floki.parse_document(response.body)
items =
document
|> Floki.find(".product_pod")
|> Enum.map(fn x ->
%{
title: Floki.find(x, "h3 a") |> Floki.attribute("title") |> Floki.text(),
price: Floki.find(x, ".product_price .price_color") |> Floki.text(),
url: response.request_url
}
end)
next_requests =
document
|> Floki.find(".next a")
|> Floki.attribute("href")
|> Enum.map(fn url ->
Crawly.Utils.build_absolute_url(url, response.request.url)
|> Crawly.Utils.request_from_url()
end)
%Crawly.ParsedItem{items: items, requests: next_requests}
end
end
El código es limpio y declarativo. El análisis HTML se realiza a través de la librería Floki con selectores CSS familiares. Desde la función parse_item, devolvemos una estructura que contiene datos listos para usar y un lote de nuevas solicitudes para el programador.
Configuración de pipelines y protección contra bloqueos
Un parser rara vez consiste solo en un cliente HTTP y un analizador HTML. Necesitas rastrear la unicidad de las URLs, limitar el número de solicitudes concurrentes a un dominio, modificar headers, filtrar duplicados y validar el esquema antes de guardar.
En Crawly, todo esto se mueve a la configuración:
import Config
config :crawly,
closespider_timeout: 10,
concurrent_requests_per_domain: 8,
closespider_itemcount: 100,
middlewares: [
Crawly.Middlewares.DomainFilter,
Crawly.Middlewares.UniqueRequest,
{Crawly.Middlewares.UserAgent, user_agents: ["Crawly Bot"]}
],
pipelines: [
{Crawly.Pipelines.Validate, fields: [:url, :title, :price]},
{Crawly.Pipelines.DuplicatesFilter, item_id: :title},
Crawly.Pipelines.JSONEncoder,
{Crawly.Pipelines.WriteToFile, extension: "jl", folder: "/tmp"}
]
¿Qué está pasando aquí? Las solicitudes primero pasan a través de una cadena de middlewares: un filtro de dominio externo no dejará que la araña se desvíe accidentalmente a escanear toda internet, y UniqueRequest cortará las visitas repetidas a las mismas páginas. Cuando la araña ha extraído los datos, estos pasan al pipeline. Allí se verifican los campos requeridos, se filtran los duplicados por título y los resultados válidos se empaquetan en JSON Lines y se escriben en disco.
Los generadores ahorran tiempo en el inicio: el comando mix crawly.gen.spider creará una plantilla de spider con todos los callbacks necesarios, y mix crawly.gen.config preparará una configuración por defecto.
Panel de gestión integrado
Un detalle interesante: a partir de la versión 0.15.0, el proyecto añadió una interfaz web de gestión desde el primer momento. Está disponible en localhost:4001.
A través de este panel de administración puedes:
- iniciar y detener spiders manualmente,
- ver la cola de solicitudes programadas,
- descargar los elementos recopilados y ver los logs de ejecución,
- seguir el estado del crawler en tiempo real.

Si estás integrando Crawly en una aplicación web Phoenix o Plug existente, no tienes que mantener el panel de administración en un puerto separado. Simplemente puedes enrutarlo a través del router común mediante forward "/admin", Crawly.API.Router.
Renderizado de páginas dinámicas y ejecución sin Elixir
La web moderna está sobrecargada de JavaScript. Si el contenido se carga de forma asíncrona a través de AJAX o la aplicación está construida con React, un GET HTTP normal devolverá un esqueleto de página vacío. Crawly puede trabajar con renderizadores externos como Chrome o Splash. Configuras un navegador headless y el framework obtiene el DOM ya renderizado con todos los scripts ejecutados.
Otra característica no tan obvia es el modo standalone. Si nadie en tu equipo escribe en Elixir, no necesitas levantar una base de código completa solo para un crawler. Crawly puede ejecutarse en un contenedor Docker minimalista donde las reglas de scraping de páginas se describen en simples archivos YAML. Este es un ejemplo raro de una herramienta de Elixir abierta a desarrolladores de otros stacks.
Escenarios prácticos
Dónde destaca mejor Crawly:
- Monitoreo de precios en tiendas online. Cuando necesitas rastrear regularmente miles de fichas de productos, verificar cambios de inventario y descuentos.
- Recopilación de conjuntos de datos de entrenamiento para ML. Análisis de artículos, reseñas y foros con guardado en formatos jsonl.
- Agregadores de listados. Recopilación de ofertas inmobiliarias o automotrices de docenas de foros regionales.
- Archivado de contenido histórico. Descarga rápida de blogs y documentos con filtrado de enlaces rotos.
A quién le será útil este proyecto
Si tu lenguaje principal es Elixir o Erlang, Crawly es inequívocamente la mejor opción para web scraping. No tendrás que construir soluciones improvisadas incómodas en Python junto a tu servicio principal y configurar la comunicación entre servicios a través de colas.
Si escribes en Python y estás cansado de luchar con el rendimiento de Scrapy en grandes volúmenes de datos, Crawly definitivamente vale la pena echarle un vistazo. La barrera de entrada es baja: los conceptos coinciden casi uno a uno, y la sintaxis de Elixir se lee muy fácilmente después de Python. Puedes comenzar con la documentación oficial en HexDocs y un tutorial rápido en el sitio de prueba books.toscrape.com.
Proyectos relacionados