Quando o Scrapy Fica Apertado e o Python É Lento: Coletando Dados com Elixir Usando Crawly
Quando se trata de parsing de sites e coleta de dados em escala industrial, quase todo desenvolvedor pensa imediatamente em Python e Scrapy. É um padrão da indústria, comprovado ao longo dos anos. Mas Python tem uma peculiaridade: quando você precisa baixar centenas de páginas em paralelo, manter milhares de conexões abertas e limpar dados em tempo real, você acaba esbarrando em limitações de threads, async e GIL.
No mundo da programação funcional, a máquina virtual BEAM é ideal para essas tarefas. Processos em Erlang e Elixir são isolados, pesam apenas alguns kilobytes e podem ser gerados em milhões. O framework Crawly pega os melhores conceitos arquiteturais do Scrapy (spiders, middlewares, pipelines de processamento) e os traz para o Elixir. O resultado é uma ferramenta pronta para lidar com cargas massivas de requisições de rede sem necessidade de ajuste complexo de runtime async.
O que tem por dentro e como funciona
Se você já escreveu um parser no Scrapy, a arquitetura do Crawly vai parecer nativa. Todo o trabalho é dividido em três partes claras: Spiders, Middlewares e Pipelines.
Um spider descreve os pontos de entrada e a lógica para extrair dados das páginas.
Aqui está um exemplo típico de um spider que navega por um catálogo de livros, coleta títulos com preços e segue as páginas de paginação:
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
O código é limpo e declarativo. O parsing de HTML é feito através da biblioteca Floki com seletores CSS familiares. Da função parse_item, retornamos uma estrutura contendo dados prontos para uso e um lote de novas requisições para o scheduler.
Configurando pipelines e proteção contra bloqueios
Um parser raramente consiste apenas de um cliente HTTP e um parser HTML. Você precisa rastrear uniqueness de URLs, limitar o número de requisições concorrentes para um domínio, modificar headers, filtrar duplicatas e validar o schema antes de salvar.
No Crawly, tudo isso é movido para a configuração:
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"}
]
O que está acontecendo aqui? As requisições primeiro passam por uma cadeia de middlewares: um filtro de domínio externo não deixará o spider vagar acidentalmente para escanear toda a internet, e UniqueRequest cortará visitas repetidas às mesmas páginas. Quando o spider extraiu os dados, ele entra no pipeline. Lá, campos obrigatórios são verificados, duplicatas são filtradas por título e resultados válidos são empacotados em JSON Lines e escritos no disco.
Geradores economizam tempo na inicialização: o comando mix crawly.gen.spider criará um template de spider com todos os callbacks necessários, e mix crawly.gen.config preparará uma config padrão.
Painel de gerenciamento integrado
Um detalhe interessante: a partir da versão 0.15.0, o projeto adicionou uma interface web de gerenciamento pronta para uso. Ela está disponível em localhost:4001.
Através desse painel administrativo, você pode:
- iniciar e parar spiders manualmente,
- visualizar a fila de requisições agendadas,
- baixar itens coletados e visualizar logs de execução,
- acompanhar o status do crawler em tempo real.

Se você está integrando o Crawly em uma aplicação web Phoenix ou Plug existente, não precisa manter o painel administrativo em uma porta separada. Você pode simplesmente roteá-lo através do roteador comum via forward "/admin", Crawly.API.Router.
Renderizando páginas dinâmicas e rodando sem Elixir
A web moderna está sobrecarregada de JavaScript. Se o conteúdo carrega assincronamente via AJAX ou a aplicação é construída com React, um GET HTTP normal retornará um esqueleto vazio da página. O Crawly pode trabalhar com renderizadores externos como Chrome ou Splash. Você configura um navegador headless, e o framework busca o DOM já renderizado com todos os scripts executados.
Outro recurso não óbvio é o modo standalone. Se ninguém na sua equipe escreve em Elixir, você não precisa subir uma base de código inteira só para um crawler. O Crawly pode rodar em um container Docker minimalista onde as regras de scraping de páginas são descritas em arquivos YAML simples. Este é um exemplo raro de uma ferramenta Elixir aberta para desenvolvedores de outras stacks.
Cenários práticos
Onde o Crawly mais se destaca:
- Monitoramento de preços em lojas online. Quando você precisa rastrear regularmente milhares de cards de produtos, verificar mudanças de estoque e descontos.
- Coleta de datasets de treinamento para ML. Parsing de artigos, reviews e fóruns com salvamento em formatos jsonl.
- Agregadores de classificados. Coletando ofertas de imóveis ou automóveis de dezenas de boards regionais.
- Arquivamento de conteúdo histórico. Download rápido de blogs e documentos com filtragem de links quebrados.
Quem se beneficiaria deste projeto
Se sua linguagem principal é Elixir ou Erlang, o Crawly é inquestionavelmente a melhor escolha para web scraping. Você não terá que construir workarounds desajeitados em Python ao lado do seu serviço principal e configurar comunicação entre serviços através de filas.
Se você escreve em Python e está cansado de lutar contra a performance do Scrapy em grandes volumes de dados, o Crawly definitivamente vale uma olhada. A barreira de entrada é baixa: os conceitos correspondem quase um para um, e a sintaxe de Elixir lê muito facilmente depois de Python. Você pode começar com a documentação oficial no HexDocs e um tutorial rápido no site de testes books.toscrape.com.
Projetos relacionados