>_ DevTrendsnl

Taal

Home

Talen

Secties

Frontend Backend Mobiel DevOps AI / ML GameDev Blockchain Embedded Beveiliging
Elixir

Wanneer Scrapy Krap aanvoelt en Python Traag is: Gegevens Verzamelen met Elixir met Behulp van Crawly

Als het gaat om website-parsing en gegevensverzameling op industriële schaal, denkt vrijwel elke ontwikkelaar meteen aan Python en Scrapy. Dit is een industriestandaard, bewezen over jaren. Maar Python heeft een eigenaardigheid: wanneer je honderden pagina's parallel moet downloaden, duizenden open verbindingen moet onderhouden en gegevens onderweg moet opschonen, loop je tegen threads, async en GIL-beperkingen aan.

In de wereld van functioneel programmeren is de BEAM virtual machine ideaal voor dergelijke taken. Processen in Erlang en Elixir zijn geïsoleerd, wegen slechts een paar kilobytes en kunnen in de miljoenen worden gestart. Het Crawly framework neemt de beste architecturale concepten van Scrapy (spiders, middlewares, verwerkingspipelines) en brengt ze naar Elixir. Het resultaat is een tool die out-of-the-box klaar is om enorme netwerkbelastingen te verwerken zonder complexe async runtime-afstemming.

Wat zit erin en hoe het werkt

Als je ooit een parser in Scrapy hebt geschreven, zal Crawly's architectuur vertrouwd aanvoelen. Alle werkzaamheden zijn verdeeld in drie duidelijke onderdelen: Spiders, Middlewares en Pipelines.

Een spider beschrijft de entry points en de logica voor het extraheren van gegevens van pagina's.

Hier is een typisch voorbeeld van een spider die door een boekencatalogus bladert, titels met prijzen verzamelt en paginering volgt:

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

De code is schoon en declaratief. HTML-parsing wordt gedaan via de Floki-bibliotheek met vertrouwde CSS-selectors. Vanuit de parse_item functie retourneren we een structuur met kant-en-klare gegevens en een batch nieuwe verzoeken voor de scheduler.

Pipelines configureren en bescherming tegen blokkades

Een parser bestaat zelden alleen uit een HTTP-client en HTML-parser. Je moet URL-uniekheid bijhouden, het aantal gelijktijdige verzoeken aan een domein beperken, headers wijzigen, duplicaten filteren en het schema valideren voordat je opslaat.

In Crawly is dit alles verplaatst naar configuratie:

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

Wat gebeurt hier? Verzoeken passeren eerst een keten van middlewares: een filter voor externe domeinen voorkomt dat de spider per ongeluk ronddwaalt om het hele internet te scannen, en UniqueRequest snijdt herhaalde bezoeken aan dezelfde pagina's af. Wanneer de spider de gegevens heeft geëxtraheerd, gaat het naar de pipeline. Daar worden verplichte velden gecontroleerd, worden duplicaten gefilterd op titel en worden geldige resultaten verpakt in JSON Lines en naar schijf geschreven.

Generators besparen tijd bij het opstarten: het mix crawly.gen.spider commando maakt een spider-sjabloon met alle noodzakelijke callbacks, en mix crawly.gen.config bereidt een standaardconfiguratie voor.

Ingebouwd beheerpaneel

Een interessant detail: vanaf versie 0.15.0 heeft het project een webbeheerinterface toegevoegd, direct out-of-the-box. Deze is beschikbaar op localhost:4001.

Via dit admin panel kun je:

  • spiders handmatig starten en stoppen,
  • de wachtrij van geplande verzoeken bekijken,
  • verzamelde items downloaden en uitvoeringslogboeken bekijken,
  • de crawlstatus in realtime volgen.

Crawly Management UI

Als je Crawly insluit in een bestaande Phoenix of Plug webapplicatie, hoef je het admin panel niet op een aparte poort te houden. Je kunt het eenvoudig routeren via de gemeenschappelijke router met forward "/admin", Crawly.API.Router.

Dynamische pagina's renderen en draaien zonder Elixir

Het moderne web is overladen met JavaScript. Als inhoud asynchroon wordt geladen via AJAX of de applicatie is gebouwd met React, retourneert een reguliere HTTP GET een lege pagina-skeleton. Crawly kan werken met externe renderers zoals Chrome of Splash. Je configureert een headless browser en het framework haalt de al gerenderde DOM op met alle uitgevoerde scripts.

Een ander niet voor de hand liggend kenmerk is de standalone modus. Als niemand in je team in Elixir schrijft, hoef je geen volledige codebase op te zetten alleen voor een crawler. Crawly kan draaien in een minimalistische Docker-container waar paginaschraapregels worden beschreven in eenvoudige YAML-bestanden. Dit is een zeldzaam voorbeeld van een Elixir-tool die openstaat voor ontwikkelaars van andere stacks.

Praktische scenario's

Waar Crawly het beste tot zijn recht komt:

  1. Prijsbewaking in online winkels. Wanneer je regelmatig duizenden productkaarten moet scrapen, voorraadwijzigingen en kortingen moet controleren.
  2. Trainingsdatasets verzamelen voor ML. Artikelen, reviews en forums parsen met opslag in jsonl-formaten.
  3. Listing-aggregatoren. Vastgoed- of autoaanbiedingen verzamelen van tientallen regionale borden.
  4. Historische content archivering. Snel downloaden van blogs en documenten met filteren van gebroken links.

Wie heeft baat bij dit project

Als je primaire taal Elixir of Erlang is, is Crawly onbetwistbaar de beste keuze voor web scraping. Je hoeft geen onhandige workarounds in Python te bouwen naast je hoofdservice en inter-service communicatie via queues in te stellen.

Als je in Python schrijft en het beu bent om tegen Scrapy's prestaties te vechten bij grote datavolumes, is Crawly zeker het overwegen waard. De instapdrempel is laag: concepten komen vrijwel één-op-één overeen en Elixir-syntax leest zeer gemakkelijk na Python. Je kunt beginnen met de officiële documentatie op HexDocs en een snelle tutorial op de testsite books.toscrape.com.

Gerelateerde projecten