Algroveon-Parser – RSS/Atom-Parser in Python selbst gebaut

Warum ich einen eigenen RSS/Atom-Parser geschrieben habe, was ein robuster Feed-Parser leisten muss und welche Design-Entscheidungen dabei eine Rolle gespielt haben.

Zusammenfassung

Die Entwicklung des Algroveon-Parsers zielt darauf ab, die Abhängigkeit von externen Bibliotheken durch eine maßgeschneiderte Python-Lösung für RSS- und Atom-Feeds zu ersetzen. Dabei werden die komplexen Herausforderungen durch unterschiedliche XML-Standards, Namespace-Variationen und Encoding-Probleme direkt adressiert.


Diese Zusammenfassung wurde mit KI-Unterstützung erstellt.

Der Ausgangspunkt

Wer RSS-Feeds in Python parsen will, greift normalerweise zu einer etablierten Library. Also zu fertigem Code, den man ins eigene Projekt einbindet, statt diese Funktion selbst zu entwickeln. Gemeint ist mit „parsen“ in diesem Fall schlicht: den XML-Inhalt eines Feeds einlesen, seine Struktur erkennen und Felder wie Titel, Link, Datum oder Beschreibung so herauslösen, dass der eigene Code damit weiterarbeiten kann. Das ist pragmatisch. Eine Zeit lang funktioniert das auch gut. Aber irgendwann kommt der Punkt, an dem es sich nicht mehr richtig anfühlt: Die Abhängigkeit sitzt tief im Kern des Projekts, die Library übernimmt Dinge automatisch, die nicht vollständig transparent sind, und jede neue Anforderung führt erst einmal in fremde Dokumentation statt direkt an den eigenen Code.

Das war der Ausgangspunkt für Algroveon-Parser. Nicht weil externe Lösungen das Problem wären – im Gegenteil, viele davon sind über Jahre gereift und für sehr viele Einsatzfälle sinnvoll.


Was ein Feed-Parser eigentlich tun muss

RSS und Atom sind XML-Formate. Auf dem Papier klingt das erst einmal einfach: Die Datei wird eingelesen, die einzelnen Meldungen werden erkannt und dann die wichtigsten Informationen wie Titel, Link, Datum oder Beschreibung herausgeholt. In der Praxis ist das aber deutlich unordentlicher.

Format-Wildwuchs: RSS 2.0 ist heute die häufigste RSS-Variante, aber RSS 0.91 ist draußen weiterhin anzutreffen. Atom 1.0 taucht bei neueren Quellen auf, etwa bei The Verge. RDF/RSS 1.0 ist selten, sollte aber zumindest sauber erkannt oder abgefangen werden. Ein Parser, der nur RSS 2.0 sauber beherrscht, stößt bei realen Quellen schnell an Grenzen.

Namespace-Chaos: Fast kein Feed beschränkt sich auf das Basis-XML. content:encoded für den vollständigen Artikeltext, dc:creator für den Autorennamen, media:thumbnail für Bilder, media:content als Alternative – jeder Feed kombiniert das etwas anders.

Encoding-Lügen: Ein konkreter Feed (motorsport_magazin) deklariert in seiner XML-Prolog-Zeile encoding="ISO-8859-1", liefert aber tatsächlich UTF-8. Python's xml.etree.ElementTree vertraut dieser Deklaration, was in solchen Fällen zu Parse-Problemen führen kann. Der pragmatische Fallback: die Deklaration ignorieren, den Inhalt testweise als UTF-8 verarbeiten und es erneut versuchen.

Datum-Vielfalt: RSS verwendet typischerweise RFC-2822-nahe Datumsangaben (Sat, 21 Mar 2026 20:03:31 +0100), Atom in der Regel ISO 8601 (2026-03-21T13:00:00-04:00). Beides kann mit Timezone-Offsets oder GMT beziehungsweise -0000 auftauchen. email.utils.parsedate_to_datetime hilft bei RFC 2822, löst -0000 aber zu einem naiven datetime auf – und genau das muss dann sauber korrigiert werden.

Bilder, die nirgendwo sauber stehen: Manche Feeds liefern Bilder über media:thumbnail, andere über media:content, wieder andere verstecken das Bild als erstes <img> im HTML-Body von content:encoded oder description. Ohne explizite Bildextraktion bleibt das Bild bei vielen Feeds schlicht unentdeckt.


Design-Entscheidungen

Null externe Abhängigkeiten

Das war die wichtigste und früheste Entscheidung. pyproject.toml hat dependencies = []. Das klingt radikal, ist es im Kern aber nicht: Python's Standardbibliothek bringt alles mit, was für diesen Parser nötig ist. xml.etree.ElementTree für das XML-Parsing, html.parser für den HTML-Sanitizer, email.utils für RFC-2822-Datumsparsing, re für ISO-8601 und Bildextraktion.

Der Vorteil ist sehr konkret: keine zusätzlichen pip-Abhängigkeiten, die sich mit anderen Projekten beißen können, und keine externen Updates, die das Verhalten unbemerkt verändern. Der Parser läuft überall dort, wo Python 3.12 läuft – ohne weitere Vorbereitung. Das macht ihn nicht grundsätzlich besser als etablierte Libraries, aber für diesen eng definierten Einsatzzweck bewusst überschaubar und gut kontrollierbar.

Strikte Eingabe-Schnittstelle: raw bytes

Die öffentliche API nimmt rohe Bytes entgegen – keine URL, kein HTTP-Client, kein automatisches Nachladen. Das ist eine bewusste Einschränkung. Sie sorgt dafür, dass der Transportweg – also HTTP, Datei oder Test-Fixture – vollständig außerhalb des Parsers liegt und dort separat testbar bleibt. Für Tests heißt das: Fixture-Dateien einlesen, direkt parsen, kein Netz nötig.

raw = urllib.request.urlopen(url).read()
feed = parse(raw, source_url=url)

Typisierte Ausgabe

Das Ergebnis ist immer ein Feed-Objekt mit typisierter Entry-Liste – keine Dicts, keine optionalen Keys, die man überall erst defensiv prüfen muss. Entry hat feste Felder: title, url, published (timezone-aware datetime oder None), summary, summary_text, content, author, guid, image_url.

Die zwei Summary-Varianten sind bewusst getrennt: summary als bereinigtes HTML für die Darstellung im Browser, summary_text als Plain Text für die Weitergabe an ein LLM.

HTML-Sanitizer: Allowlist statt Blocklist

Der Sanitizer in sanitize.py arbeitet mit einer Allowlist erlaubter Tags – alles andere wird still entfernt, der Textinhalt bleibt erhalten. script, style, iframe und form werden komplett mitsamt Inhalt gelöscht. Links werden auf http- und https-URLs geprüft, javascript: wird verworfen. Bilder behalten nur ein src mit sicherem Schema.

Das ist kein Extra, sondern Pflicht. Feed-Inhalte kommen aus beliebigen Quellen und werden im Browser gerendert. Ohne Sanitizer ist XSS keine theoretische Möglichkeit, sondern ein sehr naheliegendes Problem.


Die Herausforderungen im Detail

ISO-8601 selbst parsen

email.utils kann RFC 2822, Python's datetime.fromisoformat unterstützt seit 3.11 viel von ISO 8601. In der Praxis gibt es aber trotzdem genug Varianten, bei denen ich mich nicht blind darauf verlassen wollte – gerade dann, wenn Millisekunden, Z oder verschiedene Offset-Schreibweisen zusammenkommen. Die Entscheidung war deshalb: ein handgeschriebenes Regex, das genau die im Projekt relevanten Muster abdeckt und daraus ein timezone-aware datetime konstruiert. Überschaubar, kontrollierbar und für den konkreten Einsatzzweck ausreichend – ohne den Anspruch, damit eine allgemeine Referenzimplementierung für alle ISO-8601-Varianten zu liefern.

media:thumbnail mit korrekten Namespace-URIs

Feeds deklarieren den media:-Namespace als http://search.yahoo.com/mrss/. ElementTree löst das korrekt auf, aber nur dann, wenn man die vollständige Clark-Notation verwendet: {http://search.yahoo.com/mrss/}thumbnail. Das ist nicht besonders intuitiv und war eine der Stellen, an denen die ersten Testläufe still falsche Ergebnisse lieferten – die Thumbnail-Extraktion gab None zurück, obwohl im Feed Bilder vorhanden waren.

Im Atom-Standard hat <link> ein rel-Attribut. rel="alternate" meint den Artikel-Link. Viele Feeds lassen dieses Attribut aber weg – laut Spezifikation ist alternate der Default. Eine XPath-Suche wie [@rel='alternate'] findet dann nichts. Der Fallback ist entsprechend simpel: Wenn kein Link mit rel="alternate" gefunden wird, wird der erste <link>-Tag mit href-Attribut genommen.


Wo Algroveon-Parser verwendet wird

Der Parser ist per API in Algroveon-Agent eingebunden. Dort liest er die konfigurierten News-Feeds, extrahiert Artikel und bereitet sie für die LLM-Zusammenfassung vor – summary_text geht an Ollama, summary und image_url in die Darstellung.

Die Entkopplung war hier die richtige Entscheidung: Algroveon-Agent braucht keine XML-Kenntnisse, Algroveon-Parser keine Ahnung von Ollama. Beide Seiten bleiben dadurch einfacher, klarer und besser testbar, als wenn alles in einem gemeinsamen Block stecken würde.


Sebastian Software Engineer &amp; Wildlife Photographer
← ← Zurück zum Blog

Joni Fussballmanager

Wie und warum ich meinen eigenen Fußballmanager in Python gebaut habe – mit PyQt6, SQLite, einer eigenen Match-Engine und einem lokalen LLM als Pressedienst.