>_ DevTrendsde

Sprache

Start

Sprachen

Bereiche

Frontend Backend Mobile DevOps AI / ML GameDev Blockchain Embedded Sicherheit
Elixir

Wenn Scrapy beengt wirkt und Python lahmt: Datensammlung mit Elixir und Crawly

Wenn es um Website-Parsing und Datensammlung im industriellen Maßstab geht, denken fast alle Entwickler sofort an Python und Scrapy. Das ist ein Industriestandard, der sich über Jahre bewährt hat. Aber Python hat eine Eigenheit: Wenn Sie Hunderte von Seiten parallel herunterladen, Tausende offene Verbindungen aufrechterhalten und Daten gleichzeitig bereinigen müssen, stoßen Sie an die Grenzen von Threads, Async und GIL.

In der Welt der funktionalen Programmierung ist die BEAM-Virtual-Machine für solche Aufgaben ideal. Prozesse in Erlang und Elixir sind isoliert, wiegen nur wenige Kilobytes und können in Millionen gestartet werden. Das Crawly-Framework nimmt die besten architektonischen Konzepte von Scrapy (Spiders, Middlewares, Verarbeitungs-Pipelines) und bringt sie nach Elixir. Das Ergebnis ist ein Tool, das out-of-the-box bereit ist, massive Netzwerkanfragen zu bewältigen, ohne komplexes Async-Runtime-Tuning.

Was steckt drin und wie es funktioniert

Wenn Sie schon einmal einen Parser in Scrapy geschrieben haben, wird Ihnen Crawlys Architektur vertraut vorkommen. Alle Arbeit ist in drei klare Teile gegliedert: Spiders, Middlewares und Pipelines.

Eine Spider beschreibt die Einstiegspunkte und die Logik zur Datenextraktion aus Seiten.

Hier ein typisches Beispiel einer Spiders, die einen Buecherkatalog durchsucht, Titel mit Preisen sammelt und den Pagination-Seiten folgt:

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

Der Code ist sauber und deklarativ. Das HTML-Parsing erfolgt ueber die Floki-Bibliothek mit vertrauten CSS-Selektoren. Aus der parse_item-Funktion geben wir eine Struktur zurueck, die verarbeitungsfertige Daten und einen Batch neuer Anfragen fuer den Scheduler enthaelt.

Konfiguration von Pipelines und Schutz vor Blockierungen

Ein Parser besteht selten nur aus einem HTTP-Client und einem HTML-Parser. Sie muessen die URL-Eindeutigkeit verfolgen, die Anzahl gleichzeitiger Anfragen an eine Domain begrenzen, Header modifizieren, Duplikate filtern und das Schema vor dem Speichern validieren.

In Crawly wird all das in die Konfiguration ausgelagert:

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"}
  ]

Was passiert hier? Anfragen durchlaufen zunaechst eine Kette von Middlewares: Ein Foreign-Domain-Filter laesst die Spider nicht versehentlich auf die gesamte Internet-Suche abdriften, und UniqueRequest schneidet wiederholte Besuche derselben Seiten ab. Wenn die Spider die Daten extrahiert hat, gehen diese in die Pipeline. Dort werden Pflichtfelder geprueft, Duplikate nach Titel herausgefiltert, und gueltige Ergebnisse werden im JSON-Lines-Format auf die Festplatte geschrieben.

Generatoren sparen Zeit beim Start: Der Befehl mix crawly.gen.spider erstellt eine Spider-Vorlage mit allen notwendigen Callbacks, und mix crawly.gen.config bereitet eine Standardkonfiguration vor.

Integriertes Verwaltungspanel

Ein interessantes Detail: Ab Version 0.15.0 wurde dem Projekt eine Web-Verwaltungsoberflaeche direkt mitgeliefert. Sie ist unter localhost:4001 verfuegbar.

Ueber dieses Admin-Panel koennen Sie:

  • Spiders manuell starten und stoppen,
  • die Warteschlange der geplanten Anfragen anzeigen,
  • gesammelte Items herunterladen und Ausfuehrungsprotokolle anzeigen,
  • den Crawler-Status in Echtzeit verfolgen.

Crawly Management UI

Wenn Sie Crawly in eine bestehende Phoenix- oder Plug-Webanwendung einbetten, muessen Sie das Admin-Panel nicht auf einem separaten Port halten. Sie koennen es einfach ueber den gemeinsamen Router mit forward "/admin", Crawly.API.Router weiterleiten.

Rendering dynamischer Seiten und Ausfuehrung ohne Elixir

Das moderne Web ist ueberladen mit JavaScript. Wenn Inhalte asynchron ueber AJAX laden oder die Anwendung mit React gebaut ist, gibt ein normaler HTTP-GET eine leere Seitenstruktur zurueck. Crawly kann mit externen Renderern wie Chrome oder Splash zusammenarbeiten. Sie konfigurieren einen Headless-Browser, und das Framework holt das bereits gerenderte DOM mit allen ausgefuehrten Skripten.

Ein weiteres nicht offensichtliches Feature ist der Standalone-Modus. Wenn niemand in Ihrem Team in Elixir schreibt, muessen Sie keine vollstaendige Codebasis nur fuer einen Crawler aufsetzen. Crawly kann in einem minimalistischen Docker-Container laufen, wobei Seiten-Scraping-Regeln in einfachen YAML-Dateien beschrieben werden. Das ist ein seltenes Beispiel dafuer, dass ein Elixir-Tool fuer Entwickler aus anderen Stacks geoeffnet ist.

Praktische Szenarien

Wo Crawly am besten brilliert:

  1. Preismonitoring in Online-Shops. Wenn Sie regelmaessig Tausende von Produktkarten durchsuchen, Bestandsaenderungen und Rabatte pruefen muessen.
  2. Sammeln von Trainingsdatensaetzen fuer ML. Parsen von Artikeln, Rezensionen und Foren mit Speicherung im jsonl-Format.
  3. Listing-Aggregatoren. Sammeln von Immobilien- oder Autoangeboten von Dutzenden regionaler Börsen.
  4. Archivierung historischer Inhalte. Schnelles Herunterladen von Blogs und Dokumenten mit Filterung defekter Links.

Wem das Projekt nutzen wird

Wenn Ihre Hauptsprache Elixir oder Erlang ist, ist Crawly eindeutig die beste Wahl fuer Web-Scraping. Sie muessen keine umstaendlichen Workarounds in Python neben Ihrem Hauptdienst aufbauen und inter-service Kommunikation ueber Queues einrichten.

Wenn Sie in Python schreiben und es leid sind, gegen Scrapys Performance bei grossen Datenmengen anzukaempfen, ist Crawly definitiv einen Blick wert. Die Einstiegshuerde ist niedrig: Die Konzepte entsprechen fast eins-zu-eins, und Elixir-Syntax liest sich nach Python sehr leicht. Sie koennen mit der offiziellen Dokumentation auf HexDocs und einem Schnellstart-Tutorial auf der Testseite books.toscrape.com beginnen.

Ähnliche Projekte