>_ DevTrendspt

Idioma

Início

Linguagens

Seções

Frontend Backend Mobile DevOps AI / ML GameDev Blockchain Embarcados Segurança
Elixir

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.

Crawly Management UI

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:

  1. Monitoramento de preços em lojas online. Quando você precisa rastrear regularmente milhares de cards de produtos, verificar mudanças de estoque e descontos.
  2. Coleta de datasets de treinamento para ML. Parsing de artigos, reviews e fóruns com salvamento em formatos jsonl.
  3. Agregadores de classificados. Coletando ofertas de imóveis ou automóveis de dezenas de boards regionais.
  4. 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