Architektur im Überblick
Quelldateien
Diese Seite wurde aus den folgenden Quelldateien erstellt:
- trendradar/main.py
- trendradar/context.py
- trendradar/core/config.py
- trendradar/core/scheduler.py
- trendradar/storage/base.py
- trendradar/notification/dispatcher.py
- trendradar/utils/time.py
- trendradar/core/analyzer.py
- trendradar/core/init.py
- trendradar/core/loader.py
- mcp_server/server.py
- pyproject.toml
- requirements.txt
- docker/Dockerfile
- docker/docker-compose.yml
- docker/manage.py
- mcp_server/init.py
- trendradar/init.py
- docs/assets/script.js
- trendradar/ai/client.py
TrendRadar ist ein modulares System zur Aggregation, Analyse und Verteilung von Trend-Nachrichten aus verschiedenen Quellen. Die Architektur folgt einer klaren Trennung der Verantwortlichkeiten zwischen Datenerfassung, -verarbeitung, -speicherung und -benachrichtigung.
Modulare Paketstruktur
Das Projekt ist in sechs logische Hauptmodule unterteilt, die jeweils isolierte Verantwortungsbereiche abdecken:
| Modul | Verantwortung | Schlüsselkomponenten |
|---|---|---|
core | Konfiguration, Zeitplanung, Datenanalyse | Config, Scheduler, Analyzer, Loader |
storage | Datenmodelle und Persistenz | NewsItem, RSSItem, RSSData |
notification | Multi-Kanal-Benachrichtigungen | Dispatcher, Channel-Adapter |
utils | Gemeinsame Hilfsfunktionen | Time-Utilities |
ai | KI-gestützte Funktionen | Client, Translator |
mcp_server | MCP-Protokoll-Interface | FastMCP Tools, Resources |
Das core-Modul exportiert zentrale Funktionen für Konfigurationsmanagement, Datenverarbeitung und statistische Analysen (trendradar/core/init.py:1-49). Der MCP-Server implementiert ein separates Tool-Interface mit FastMCP 2.0, das unabhängig vom Hauptsystem operiert (mcp_server/server.py:1-44).
正在加载图表渲染器...
Architektur-Highlights:
- Einstiegspunkte: Zwei unabhängige Entry Points — CLI via
__main__.pyund MCP-Server für KI-Assistenten-Integration - Core-Isolation: Das Core-Modul kapselt alle Geschäftslogik ohne direkte Abhängigkeiten zu externen Diensten
- Storage-Abstraktion: Datenmodelle sind reine Python dataclasses ohne ORM-Abhängigkeiten
- Dispatcher-Pattern: Zentrale Benachrichtigungssteuerung mit Plugin-artigen Kanal-Adaptern
Datenmodelle und Speicherstruktur
Die Speicherschicht definiert drei zentrale Datenmodelle, die alle serialisierbar sind und statistische Metadaten verwalten:
NewsItem — 热榜 Datenmodell
Das NewsItem-Modell repräsentiert einen einzelnen Trend-Eintrag von Aggregator-Plattformen:
python1@dataclass 2class NewsItem: 3 title: str # Nachrichtentitel 4 source_id: str # Quell-Plattform-ID (z.B. toutiao, baidu) 5 source_name: str = "" # Anzeigename (Runtime) 6 rank: int = 0 # Aktuelle Position 7 url: str = "" # Link URL 8 mobile_url: str = "" # Mobile URL 9 crawl_time: str = "" # Erfassungszeit (HH:MM) 10 11 # Statistik-Felder 12 ranks: List[int] = field(default_factory=list) # Historische Rankings 13 first_time: str = "" # Erste Sichtung 14 last_time: str = "" # Letzte Sichtung 15 count: int = 1 # Häufigkeit 16 rank_timeline: List[Dict[str, Any]] = field(default_factory=list)
(trendradar/storage/base.py:14-67)
Das rank_timeline-Feld speichert die vollständige Ranking-Historie im Format [{"time": "09:30", "rank": 1}, {"time": "10:00", "rank": 2}, ...], wobei None für脱榜 (aus der Liste gefallen) steht.
RSSItem und RSSData — Feed-Aggregation
RSSItem modelliert einzelne RSS-Einträge mit Metadaten wie Autor, Veröffentlichungszeit und Zusammenfassung (trendradar/storage/base.py:71-119). RSSData fungiert als Container-Klasse, die Einträge nach feed_id gruppiert:
python1@dataclass 2class RSSData: 3 date: str # Datum (YYYY-MM-DD) 4 crawl_time: str # Erfassungszeit (HH:MM) 5 items: Dict[str, List[RSSItem]] # Nach feed_id gruppiert 6 id_to_name: Dict[str, str] = field(default_factory=dict) 7 failed_ids: List[str] = field(default_factory=list)
(trendradar/storage/base.py:123-173)
Serialisierungsstrategie: Alle Modelle implementieren to_dict() und from_dict()-Klassenmethoden für JSON-basierte Persistenz ohne externe Serialisierungsbibliotheken.
Konfigurations- und Zeitmanagement
Context-Klasse — Zentraler Runtime-Kontext
Die Context-Klasse in trendradar/context.py:82-169 verwaltet zeitzonenbezogene Operationen zentral:
| Methode | Rückgabe | Zweck |
|---|---|---|
timezone | str | Konfigurierte Zeitzone (Default: Asia/Shanghai) |
get_time() | datetime | Aktuelle Zeit in konfigurierter Zeitzone |
format_time() | str | Zeitformatierung für Dateinamen (HH-MM) |
Die Zeit-Utilities in utils/time.py unterstützen verschiedene Zeitzonen und Dateiformate mit Fallback-Logik bei unbekannten Zeitzonen (trendradar/utils/time.py:17-78).
Scheduler — Timeline-basierte Ausführungssteuerung
Der Scheduler unterstützt zwei Konfigurationsmodi:
- Preset-basiert: Vordefinierte Zeitpläne wie
always_on,work_hours - Custom: Benutzerdefinierte Timeline-Konfiguration
python1def _build_timeline( 2 self, 3 schedule_config: Dict[str, Any], 4 timeline_data: Dict[str, Any], 5) -> Dict[str, Any]: 6 preset = schedule_config.get("preset", "always_on") 7 8 if preset == "custom": 9 timeline = copy.deepcopy(timeline_data.get("custom", {})) 10 else: 11 presets = timeline_data.get("presets", {}) 12 if preset not in presets: 13 raise ValueError(f"Unbekanntes Preset: '{preset}'") 14 timeline = copy.deepcopy(presets[preset])
(trendradar/core/scheduler.py:77-100)
Timeline-Validierung
Beim Start führt der Scheduler eine umfassende Validierung durch (trendradar/core/scheduler.py:313-361):
- Pflichtfelder:
default,periods,day_plans,week_map - week_map-Vollständigkeit: Muss Tage 1-7 abdecken
- Referenzintegrität: day_plan → period Verweise
- Zeitformat-Validierung: HH:MM Format für start/end
- Überlappungserkennung: Bei
error_on_overlap-Policy
正在加载图表渲染器...
Validierungsfehler-Beispiele:
timeline fehlt Pflichtfeld: periodsweek_map[3] referenziert unbekannten day_plan: weekendperiod 'morning' start und end dürfen nicht identisch sein: 09:00
Notification Dispatcher Pattern
Architektur des NotificationDispatcher
Der NotificationDispatcher fungiert als zentrale Steuerung für alle Push-Kanäle mit Multi-Account-Unterstützung (trendradar/notification/dispatcher.py:46-75):
python1class NotificationDispatcher: 2 def __init__( 3 self, 4 config: Dict[str, Any], 5 get_time_func: Callable, 6 split_content_func: Callable, 7 translator: Optional["AITranslator"] = None, 8 ): 9 self.config = config 10 self.get_time_func = get_time_func 11 self.split_content_func = split_content_func 12 self.max_accounts = config.get("MAX_ACCOUNTS_PER_CHANNEL", 3) 13 self.translator = translator
dispatch_all — Multi-Kanal-Routing
Die dispatch_all-Methode orchestriert die Verteilung an alle konfigurierten Kanäle (trendradar/notification/dispatcher.py:216-322):
| Kanal | Konfigurationsschlüssel | Besonderheit |
|---|---|---|
| Feishu | FEISHU_WEBHOOK_URL | Multi-Account via ; getrennt |
| DingTalk | DINGTALK_WEBHOOK_URL | Batch-Größenlimit: 20KB |
| WeWork | WEWORK_WEBHOOK_URL | Enterprise WeChat |
| Telegram | TELEGRAM_BOT_TOKEN + TELEGRAM_CHAT_ID | Paar-Validierung erforderlich |
| ntfy | NTFY_SERVER_URL + NTFY_TOPIC | Paar-Validierung erforderlich |
| Bark | BARK_URL | iOS-Push |
| Slack | SLACK_WEBHOOK_URL | Webhook-basiert |
EMAIL_FROM + EMAIL_PASSWORD + EMAIL_TO | SMTP-basiert |
Integrierte AI-Übersetzung
Vor dem Versand kann der Dispatcher Inhalte automatisch übersetzen:
python1def _translate_content(self, report_data: Dict, ...) -> tuple: 2 if not self.translator or not self.translator.enabled: 3 return report_data, rss_items, rss_new_items, standalone_data 4 5 # Sammle alle zu übersetzenden Titel 6 titles_to_translate = [] 7 title_locations = [] # Position für Rückführung 8 9 # 1. 热榜 Titel (wenn scope/region aktiv) 10 if scope.get("HOTLIST", True) and display_regions.get("HOTLIST", True): 11 for stat in report_data.get("stats", []): 12 for title_data in stat.get("titles", []): 13 titles_to_translate.append(title_data.get("title", ""))
Multi-Account-Verwaltung
Die _send_to_multi_accounts-Methode implementiert die Account-Limitierung (trendradar/notification/dispatcher.py:323-356):
python1def _send_to_multi_accounts( 2 self, 3 channel_name: str, 4 config_value: str, 5 send_func: Callable[..., bool], 6 **kwargs, 7) -> bool: 8 accounts = parse_multi_account_config(config_value) 9 accounts = limit_accounts(accounts, self.max_accounts, channel_name) 10 11 results = [] 12 for i, account in enumerate(accounts): 13 account_label = f"账号{i+1}" if len(accounts) > 1 else "" 14 result = send_func(account, account_label=account_label, **kwargs) 15 results.append(result) 16 17 return any(results) # Erfolg wenn mindestens ein Account funktioniert
Account-Limitierung: Standardmäßig max. 3 Accounts pro Kanal, um GitHub Actions Laufzeitbegrenzungen zu respektieren.
Utility-Funktionen und Analyse
Zeit-Utilities
Das utils/time.py-Modul stellt zeitzonenbewusste Hilfsfunktionen bereit (trendradar/utils/time.py:17-78):
| Funktion | Format | Verwendung |
|---|---|---|
get_configured_time() | datetime | Aktuelle Zeit mit Zeitzone |
format_time_filename() | HH-MM | Dateinamen-kompatibel |
get_current_time_display() | HH:MM | Anzeigeformat |
Zeitzonen-Fallback: Bei unbekannten Zeitzonen wird automatisch Asia/Shanghai verwendet mit Warnung.
Analyzer-Funktionen
Der Analyzer stellt Formatierungsfunktionen für Zeitdarstellungen bereit (trendradar/core/analyzer.py:64-88):
python1def format_time_display( 2 first_time: str, 3 last_time: str, 4 convert_time_func: Callable[[str], str], 5) -> str: 6 if not first_time: 7 return "" 8 9 first_display = convert_time_func(first_time) 10 last_display = convert_time_func(last_time) 11 12 if first_display == last_display or not last_display: 13 return first_display 14 else: 15 return f"[{first_display} ~ {last_display}]"
YAML-Konfigurations-Loader
Der loader.py lädt Timeline-Konfigurationen mit Fallback-Logik (trendradar/core/loader.py:136-170):
python1def _load_timeline_data(config_dir: str = "config") -> Dict: 2 timeline_path = Path(config_dir) / "timeline.yaml" 3 4 if not timeline_path.exists(): 5 print(f"[Scheduler] timeline.yaml nicht gefunden, verwende leeres Template") 6 return { 7 "presets": {}, 8 "custom": { 9 "default": {"collect": True, "analyze": False, "push": False, ...}, 10 "periods": {}, 11 "day_plans": {"all_day": {"periods": []}}, 12 "week_map": {i: "all_day" for i in range(1, 8)}, 13 }, 14 }
Systemarchitektur-Gesamtübersicht
正在加载图表渲染器...
Datenfluss-Highlights:
- Erfassung: Crawler sammeln Daten von 热榜-Plattformen und RSS-Feeds in konfigurierten Intervallen
- Speicherung: Daten werden als
NewsItem/RSSItemmit statistischen Metadaten persistiert - Analyse: Häufigkeitsanalyse und Ranking-Gewichtung identifizieren Trends
- Ausgabe: Generierte Reports werden über
NotificationDispatcheran konfigurierte Kanäle verteilt - MCP-Integration: Externe KI-Assistenten können über MCP-Protokoll auf Daten zugreifen
Kern-Design-Entscheidungen
1. Dataclass-basierte Datenmodelle ohne ORM
Entscheidung: Verwendung von Python @dataclass statt SQLAlchemy/Django ORM.
Begründung:
- Geringere Abhängigkeiten und einfachere Serialisierung
- Direkte JSON-Persistenz ohne Datenbank-Overhead
- Bessere Eignung für GitHub Actions-Umgebung ohne persistente Datenbank
Nachteil: Keine komplexen Abfragemöglichkeiten wie SQL-JOINS.
2. Dispatcher-Pattern für Benachrichtigungen
Entscheidung: Zentraler NotificationDispatcher mit Plugin-artigen Kanal-Adaptern.
Begründung:
- Einheitliche Schnittstelle für alle Kanäle
- Einfache Erweiterbarkeit für neue Kanäle
- Zentrale Übersetzungs- und Multi-Account-Logik
Implementierung: Jeder Kanal hat eine _send_<channel>-Methode, die _send_to_multi_accounts aufruft.
3. Timeline-basierte Zeitplanung
Entscheidung: Deklarative Timeline-Konfiguration in YAML statt Cron-Ausdrücke.
Begründung:
- Lesbarere Konfiguration für nicht-technische Nutzer
- Unterstützung für tageszeitbasierte Profile (work_hours, always_on)
- Validierung beim Start statt zur Laufzeit
Nachteil: Weniger flexibel als programmatische Zeitplanung.
4. MCP-Server als separates Interface
Entscheidung: Eigenständiger MCP-Server mit FastMCP 2.0 statt Integration in Hauptanwendung.
Begründung:
- Klare Trennung zwischen Batch-Verarbeitung und interaktiven Abfragen
- Unabhängige Skalierung und Deployment
- Standardisiertes Protokoll für KI-Assistenten-Integration
5. Zeitzonenbewusste Zeitverarbeitung
Entscheidung: Alle Zeitoperationen laufen durch Context-Klasse mit konfigurierbarer Zeitzone.
Begründung:
- Korrekte Darstellung für internationale Nutzer
- Einheitliche Zeitbasis für alle Komponenten
- Fallback-Mechanismus bei Fehlkonfiguration
Technologie-Stack
| Technologie | Verwendungszweck | Auswahlbegründung | Alternativen |
|---|---|---|---|
| Python 3.x | Hauptsprache | Breite Bibliotheksunterstützung, GitHub Actions native | Node.js, Go |
| dataclasses | Datenmodelle | Eingebaute Unterstützung, keine externen Abhängigkeiten | Pydantic, attrs |
| YAML | Konfiguration | Lesbarkeit, Kommentare, hierarchische Struktur | TOML, JSON |
| pytz | Zeitzonen | IANA-Datenbank, bewährte Bibliothek | zoneinfo (Python 3.9+) |
| requests | HTTP-Client | Einfache API, weit verbreitet | httpx, aiohttp |
| FastMCP 2.0 | MCP-Server | Modernes Framework für KI-Tool-Integration | Gradio, FastAPI |
| asyncio | Asynchrone Operationen | Non-blocking I/O für MCP-Server | Threading |
Modulabhängigkeiten
正在加载图表渲染器...
Abhängigkeitsregeln:
- Richtung: Abhängigkeiten zeigen von außen nach innen (Utils ← Core ← App)
- Isolation:
storagehat keine Abhängigkeiten zu anderen Modulen - MCP-Unabhängigkeit:
mcp_serveroperiert isoliert mit eigenem Tool-Set
