Asynchroner Manga-Parser in Python: Von der Architektur zu Microservices
Der Aufbau eines skalierbaren Manga-Parsers ist eine Aufgabe, die die Herausforderungen der asynchronen Programmierung, des Schnittstellendesigns und des Zustandsmanagements vereint. Manga-Day ist ein praxisnahes Beispiel für den Übergang von einem Monolithen zu einer Microservices-Architektur mit asyncio, Pydantic und SQLAlchemy. Das Projekt entstand ursprünglich als Tool zur automatischen Datensammlung von Dutzenden von Websites, hat sich aber zu einem Tutorial für das Design erweiterbarer Systeme entwickelt.
Architekturgrundlagen: Schnittstellen und Asynchronität
Der Kern des Systems basiert auf der abstrakten Klasse BaseSpider. Jeder spezifische Parser (z. B. für multi-manga) implementiert eine einzige Schnittstelle, sodass neue Quellen dynamisch hinzugefügt werden können, ohne den Kern zu verändern. Die Schnittstelle umfasst drei zentrale Methoden:
from abc import ABC, abstractmethod
class BaseSpider(ABC):
BASE_URL = "https://example-manga.com"
@abstractmethod
async def get(self, url: str, **kwargs) -> Optional[MangaSchema]: ...
@abstractmethod
async def pages(
self, start_page: int | None = None
) -> AsyncGenerator[list[BaseManga], Any]: ...
Dieser Ansatz gewährleistet:
- Einheitliche Behandlung von Fehlern und Timeouts
- Die Möglichkeit, jeden Spider unabhängig zu testen
- Unterstützung für horizontale Skalierung durch asynchrone Generatoren
Die Asynchronität wird auf asyncio-Ebene umgesetzt und ermöglicht die effiziente Bearbeitung von Hunderten paralleler Anfragen ohne I/O-Blockaden. Das ist besonders wichtig bei langsamen oder instabilen Quellen.
Datenvalidierung und ORM-Modelle
Pydantic sorgt für striktes Typing und Validierung. Das System basiert auf drei Modellstufen:
- BaseManga — minimale Feldmenge: title, poster, url, sku (SHA256-Hash)
- MangaWithGallery — ergänzt Liste von Bild-URLs
- MangaSchema — vollständiges Modell mit genres, author und language (optional)
In der von SQLAlchemy verwalteten Datenbank sind folgende Entitäten implementiert:
- Manga, Author, Genre, Language — Kerntabellen
- GenreManga — Many-to-Many-Beziehung für Genres
- Gallery — separate Tabelle für Bilder (um sie von der Hauptabfrage zu trennen)
- GeneratedPdf — veraltete Tabelle, die zur Entfernung vorgesehen ist
Die Auslagerung der Gallery in eine eigene Entität vermeidet N+1-Probleme beim Laden von Manga-Listen – Bilder werden nun nur bei expliziter Anfrage geladen.
Manager: Geschäftslogik über den Daten
Manager bilden die Geschäftslogikschicht – Klassen für spezifische Operationen:
- MangaManager: CRUD-Operationen nach SKU, URL, ID
- RequestManager: HTTP-Anfragen mit Retry-Logik und Timeouts
- AlertManager: Universelles Benachrichtigungssystem (Telegram, Discord usw.)
- SpiderManager: Zentraler Orchestrator für alle Spider
Besonders interessant ist der SpiderManager wegen seiner Modularität. Er ist in vier Dateien aufgeteilt:
_load.py— dynamisches Laden der Spider über__all___status.py— Statusmodelle und Enums_starter.py— Start und Lebenszyklusverwaltung_spider.py— Zusammenführung der Komponenten zu einer einheitlichen Schnittstelle
Diese Zerlegung vereinfacht Tests und isoliert Änderungen – das Hinzufügen eines neuen Spiders erfordert nur eine Registrierung in __init__.py, ohne Kernanpassungen.
Übergang zu Microservices: Schmerzpunkte und Lösungen
Das Projekt startete als Monolith, doch zwei Dienste wurden zu Auslösern für den Wandel:
- PDFService — verbrauchte zu viel RAM, in einen separaten Container ausgelagert
- Bot — benötigte WebSocket und JWT-Authentifizierung, ebenfalls in einen isolierten Dienst migriert
Die Migration verlief in mehreren Phasen:
- Frontend vom Kern entkoppelt – arbeitet nun ausschließlich über REST API, mit minimalem Jinja nur für Konfigurationen
- Bot mit aiohttp für WebSocket umgeschrieben und rollenbasierter Zugriff via JWT implementiert
- PDF-Generierung läuft nun als Hintergrundtask mit begrenzter Speichernutzung
Hauptprobleme:
- Serialisierung komplexer Objekte zwischen Diensten
- Datenkonsistenz bei partiellen Ausfällen
- Einrichtung von Health Checks und graceful Shutdown
Wichtige Erkenntnisse
- Schnittstellen sind wichtiger als Implementierungen — abstrakte Klassen ermöglichten Skalierung ohne Kern-Refactoring
- Asynchronität ≠ automatische Performance — Anzahl paralleler Tasks kontrollieren und Semaphore nutzen
- ORM ist kein Allheilmittel — manuelle Query-Optimierung und Denormalisierung (z. B. Auslagerung der Gallery) sind für Performance entscheidend
- Microservices erhöhen Komplexität, bieten aber Flexibilität — Auslagerung des PDFService löste das Speicherproblem, steigerte aber den Deploymentsaufwand
- Testbarkeit ist architektonisches Ergebnis — Modularität des SpiderManagers erlaubte Unit-Tests ohne Mocking externer Abhängigkeiten
Das Projekt dient nun als Einstieg, um zu verstehen, wie Systeme aufgebaut werden, die sich über Jahre weiterentwickeln lassen. Der Codebase umfasst Zehntausende Zeilen, doch klare Trennung der Verantwortlichkeiten hält es wartbar. Nächste Schritte: Implementierung eines Message Brokers für Task-Queues und vollständiges CI/CD mit Autotests bei jedem Pull Request.
— Editorial Team
Noch keine Kommentare.