Asynchroniczny parser mangi w Pythonie: od architektury do mikrousług
Tworzenie skalowalnego parsera mangi to zadanie łączące wyzwania programowania asynchronicznego, projektowania kontraktów i zarządzania stanem. Manga-Day to praktyczny przykład przejścia od monolitu do architektury mikrousługowej z wykorzystaniem asyncio, Pydantic i SQLAlchemy. Projekt początkowo był pomyślany jako narzędzie do automatycznego zbierania danych z dziesiątek stron internetowych, ale stał się podręcznikiem do projektowania rozszerzalnych systemów.
Podstawy architektury: kontrakty i asynchroniczność
Jądro systemu jest zbudowane wokół abstrakcyjnej klasy BaseSpider. Każdy konkretny parser (np. dla multi-manga) implementuje jednolity interfejs, co pozwala na dynamiczne podłączanie nowych źródeł bez zmiany rdzenia. Kontrakt obejmuje trzy kluczowe metody:
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]: ...
Taki podejście zapewnia:
- Jednolitość obsługi błędów i timeoutów
- Możliwość testowania każdego pająka niezależnie
- Wsparcie dla poziomego skalowania poprzez asynchroniczne generatory
Asynchroniczność jest zaimplementowana na poziomie asyncio, co pozwoliło efektywnie obsługiwać setki jednoczesnych żądań bez blokowania I/O. Szczególnie ważne jest to przy pracy z wolnymi lub niestabilnymi źródłami.
Walidacja danych i modele ORM
Do ścisłej typizacji i walidacji używa się Pydantic. System jest zbudowany na trzech poziomach modeli:
- BaseManga — minimalny zestaw pól: title, poster, url, sku (hash SHA256)
- MangaWithGallery — dodaje listę URL obrazów
- MangaSchema — pełny model z gatunkami, autorem i językiem (opcjonalnie)
W bazie danych zarządzanej przez SQLAlchemy zaimplementowano następujące encje:
- Manga, Author, Genre, Language — główne tabele
- GenreManga — relacja wiele-do-wielu dla gatunków
- Gallery — oddzielna tabela dla obrazów (aby nie obciążać głównego zapytania)
- GeneratedPdf — przestarzała tabela, planowana do usunięcia
Podział galerii na oddzielną encję pozwolił uniknąć problemów N+1 przy ładowaniu list mangi — teraz obrazy są ładowane tylko na wyraźne żądanie.
Menedżery: logika ponad danymi
Warstwą logiki biznesowej są menedżery — klasy odpowiedzialne za konkretne operacje:
- MangaManager: operacje CRUD po SKU, URL, ID
- RequestManager: żądania HTTP z logiką retry i timeoutami
- AlertManager: uniwersalny system powiadomień (Telegram, Discord itp.)
- SpiderManager: centralny orkiestrator wszystkich pająków
SpiderManager jest szczególnie interesujący swoją modularnością. Jest podzielony na cztery pliki:
_load.py— dynamiczne ładowanie pająków przez__all___status.py— modele statusów i wyliczenia_starter.py— uruchamianie i zarządzanie cyklem życia_spider.py— łączenie komponentów w jednolity interfejs
Taka dekompozycja uprościła testowanie i pozwoliła izolować zmiany — na przykład, dodanie nowego pająka wymaga tylko jego rejestracji w __init__.py, bez zmian w rdzeniu.
Przejście na mikrousługi: punkty bólu i rozwiązania
Początkowo projekt był monolityczny, ale dwa serwisy stały się katalizatorami zmian:
- PDFService — zużywał zbyt dużo RAM, został wydzielony do oddzielnego kontenera
- Bot — wymagał WebSocket i uwierzytelniania JWT, również zmigrowany do izolowanego serwisu
Migracja przeszła przez kilka etapów:
- Frontend odłączony od rdzenia — teraz działa wyłącznie przez REST API, z minimalnym Jinja tylko dla konfiguracji
- Bot przepisany z użyciem aiohttp dla WebSocket i wdrożeniem modelu ról przez JWT
- Generowanie PDF teraz uruchamiane jako zadanie tła z ograniczonym zużyciem pamięci
Główne trudności:
- Serializacja złożonych obiektów między serwisami
- Spójność danych przy częściowych awariach
- Konfiguracja health-checków i graceful shutdown
Co ważne
- Kontrakty ważniejsze od implementacji — abstrakcyjne klasy pozwoliły skalować system bez refaktoryzacji rdzenia
- Asynchroniczność ≠ automatyczna wydajność — trzeba kontrolować liczbę konkurencyjnych zadań i używać semaforów
- ORM to nie panaceum — ręczna optymalizacja zapytań i denormalizacja (np. wyodrębnienie galerii) są kluczowe dla wydajności
- Mikrousługi komplikują, ale dają elastyczność — wyodrębnienie PDFService rozwiązało problem z pamięcią, ale dodało złożoność wdrożenia
- Testowalność to rezultat architektury — modularność SpiderManager pozwoliła pisać testy jednostkowe bez mocków zewnętrznych zależności
Projekt stał się punktem wyjścia do zrozumienia, jak budować systemy, które można rozwijać latami. Baza kodu urosła do dziesiątek tysięcy linii, ale dzięki jasnemu podziałowi odpowiedzialności pozostaje łatwa w utrzymaniu. Kolejne kroki — wdrożenie message broker dla kolejek zadań i pełne CI/CD z autotesty na każdy pull request.
— Editorial Team
Brak komentarzy.