Gdy Scrapy Staje się Ciasny, a Python Wolny: Zbieranie Danych w Elixir za Pomocą Crawly
Gdy mowa o parsowaniu stron internetowych i zbieraniu danych na skalę przemysłową, niemal każdy programista od razu myśli o Pythonie i Scrapy. To standard branżowy, sprawdzony przez lata. Jednak Python ma swoją specyfikę: gdy potrzebujesz pobrać setki stron równolegle, utrzymywać tysiące otwartych połączeń i czyścić dane w locie, zaczynasz natykać się na ograniczenia wątków, async i GIL.
W świecie programowania funkcyjnego maszyna wirtualna BEAM idealnie sprawdza się w takich zadaniach. Procesy w Erlangu i Elixirze są izolowane, ważą zaledwie kilka kilobajtów i można je spawnować w milionach. Framework Crawly przenosi najlepsze koncepcje architektoniczne ze Scrapy (spidery, middleware'y, potoki przetwarzania) do Elixira. Efektem jest narzędzie gotowe od razu do obsługi masowych obciążeń sieciowych bez skomplikowanego dostrajania środowiska async.
Co w środku i jak to działa
Jeśli kiedykolwiek pisałeś parser w Scrapy, architektura Crawly będzie ci native. Cała praca podzielona jest na trzy wyraźne części: Spidery, Middleware'y i Potoki.
Spider opisuje punkty wejścia i logikę wyodrębniania danych ze stron.
Oto typowy przykład spidera, który przegląda katalog książek, zbiera tytuły z cenami i podąża za stronami paginacji:
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
Kod jest czysty i deklaratywny. Parsowanie HTML odbywa się za pomocą biblioteki Floki z znajomymi selektorami CSS. Z funkcji parse_item zwracamy strukturę zawierającą gotowe do użycia dane i partię nowych żądań dla schedulera.
Konfiguracja potoków i ochrona przed blokowaniem
Parser rzadko składa się tylko z klienta HTTP i parsera HTML. Musisz śledzić unikalność URL, ograniczać liczbę współbieżnych żądań do domeny, modyfikować nagłówki, filtrować duplikaty i walidować schemat przed zapisem.
W Crawly wszystko to przenosi się do konfiguracji:
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"}
]
Co tu się dzieje? Żądania najpierw przechodzą przez łańcuch middleware'ów: filtr obcych domen nie pozwoli spiderowi przypadkowo zboczyć na skanowanie całego internetu, a UniqueRequest odetnie powtarzające się wizyty na tych samych stronach. Gdy spider wyodrębnił dane, trafiają do pipeline. Tam sprawdzane są wymagane pola, duplikaty filtrowane są po tytule, a poprawne wyniki pakowane są do JSON Lines i zapisywane na dysku.
Generatory oszczędzają czas przy starcie: polecenie mix crawly.gen.spider stworzy szablon spidera ze wszystkimi niezbędnymi callbackami, a mix crawly.gen.config przygotuje domyślną konfigurację.
Wbudowany panel zarządzania
Interesujący szczegół: począwszy od wersji 0.15.0 projekt dodał webowy interfejs zarządzania od razu out of the box. Dostępny jest pod adresem localhost:4001.
Przez ten panel administracyjny możesz:
- ręcznie uruchamiać i zatrzymywać spidery,
- przeglądać kolejkę zaplanowanych żądań,
- pobierać zebrane elementy i przeglądać logi wykonania,
- śledzić status crawlera w czasie rzeczywistym.

Jeśli osadzasz Crawly w istniejącej aplikacji webowej Phoenix lub Plug, nie musisz trzymać panelu administracyjnego na osobnym porcie. Możesz go po prostu przekierować przez wspólny router za pomocą forward "/admin", Crawly.API.Router.
Renderowanie dynamicznych stron i uruchamianie bez Elixira
Współczesny internet jest przeciążony JavaScriptem. Jeśli treść ładuje się asynchronicznie przez AJAX lub aplikacja jest zbudowana z React, zwykły HTTP GET zwróci pusty szkielet strony. Crawly może współpracować z zewnętrznymi rendererami jak Chrome czy Splash. Konfigurujesz przeglądarkę headless, a framework pobiera już wyrenderowany DOM ze wszystkimi wykonanymi skryptami.
Inną nieoczywistą funkcją jest tryb standalone. Jeśli nikt w twoim zespole nie pisze w Elixirze, nie musisz odpalać pełnego codebase'u tylko dla crawlera. Crawly może działać w minimalistycznym kontenerze Docker, gdzie reguły scrapowania stron opisane są w prostych plikach YAML. To rzadki przykład narzędzia Elixir otwartego dla programistów z innych stacków.
Praktyczne scenariusze
Gdzie Crawly sprawdza się najlepiej:
- Monitorowanie cen w sklepach internetowych. Gdy potrzebujesz regularnie crawlować tysiące kart produktów, sprawdzać zmiany stanów magazynowych i promocji.
- Zbieranie zbiorów treningowych dla ML. Parsowanie artykułów, recenzji i forów z zapisem do formatów jsonl.
- Agregatory ofert. Zbieranie ofert nieruchomości lub motoryzacji z dziesiątek regionalnych tablic.
- Archiwizacja treści historycznych. Szybkie pobieranie blogów i dokumentów z filtrowaniem uszkodzonych linków.
Kto skorzysta na tym projekcie
Jeśli twoim głównym językiem jest Elixir lub Erlang, Crawly jest jednoznacznie najlepszym wyborem do web scrapingu. Nie będziesz musiał budować niezdarnych obejść w Pythonie obok głównej usługi i konfigurować komunikacji międzyusługowej przez kolejki.
Jeśli piszesz w Pythonie i masz dość walki z wydajnością Scrapy przy dużych wolumenach danych, Crawly zdecydowanie warto sprawdzić. Próg wejścia jest niski: koncepcje pasują niemal jeden do jednego, a składnia Elixira czyta się bardzo łatwo po Pythonie. Możesz zacząć od oficjalnej dokumentacji na HexDocs i szybkiego tutoriala na stronie testowej books.toscrape.com.
Powiązane projekty