Preise

Architektur im Überblick

Quelldateien

Diese Seite wurde aus den folgenden Quelldateien erstellt:

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:

ModulVerantwortungSchlüsselkomponenten
coreKonfiguration, Zeitplanung, DatenanalyseConfig, Scheduler, Analyzer, Loader
storageDatenmodelle und PersistenzNewsItem, RSSItem, RSSData
notificationMulti-Kanal-BenachrichtigungenDispatcher, Channel-Adapter
utilsGemeinsame HilfsfunktionenTime-Utilities
aiKI-gestützte FunktionenClient, Translator
mcp_serverMCP-Protokoll-InterfaceFastMCP 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__.py und 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:

python
1@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:

python
1@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:

MethodeRückgabeZweck
timezonestrKonfigurierte Zeitzone (Default: Asia/Shanghai)
get_time()datetimeAktuelle Zeit in konfigurierter Zeitzone
format_time()strZeitformatierung 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:

  1. Preset-basiert: Vordefinierte Zeitpläne wie always_on, work_hours
  2. Custom: Benutzerdefinierte Timeline-Konfiguration
python
1def _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: periods
  • week_map[3] referenziert unbekannten day_plan: weekend
  • period '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):

python
1class 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):

KanalKonfigurationsschlüsselBesonderheit
FeishuFEISHU_WEBHOOK_URLMulti-Account via ; getrennt
DingTalkDINGTALK_WEBHOOK_URLBatch-Größenlimit: 20KB
WeWorkWEWORK_WEBHOOK_URLEnterprise WeChat
TelegramTELEGRAM_BOT_TOKEN + TELEGRAM_CHAT_IDPaar-Validierung erforderlich
ntfyNTFY_SERVER_URL + NTFY_TOPICPaar-Validierung erforderlich
BarkBARK_URLiOS-Push
SlackSLACK_WEBHOOK_URLWebhook-basiert
EmailEMAIL_FROM + EMAIL_PASSWORD + EMAIL_TOSMTP-basiert

Integrierte AI-Übersetzung

Vor dem Versand kann der Dispatcher Inhalte automatisch übersetzen:

python
1def _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):

python
1def _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):

FunktionFormatVerwendung
get_configured_time()datetimeAktuelle Zeit mit Zeitzone
format_time_filename()HH-MMDateinamen-kompatibel
get_current_time_display()HH:MMAnzeigeformat

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):

python
1def 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):

python
1def _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:

  1. Erfassung: Crawler sammeln Daten von 热榜-Plattformen und RSS-Feeds in konfigurierten Intervallen
  2. Speicherung: Daten werden als NewsItem/RSSItem mit statistischen Metadaten persistiert
  3. Analyse: Häufigkeitsanalyse und Ranking-Gewichtung identifizieren Trends
  4. Ausgabe: Generierte Reports werden über NotificationDispatcher an konfigurierte Kanäle verteilt
  5. 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

TechnologieVerwendungszweckAuswahlbegründungAlternativen
Python 3.xHauptspracheBreite Bibliotheksunterstützung, GitHub Actions nativeNode.js, Go
dataclassesDatenmodelleEingebaute Unterstützung, keine externen AbhängigkeitenPydantic, attrs
YAMLKonfigurationLesbarkeit, Kommentare, hierarchische StrukturTOML, JSON
pytzZeitzonenIANA-Datenbank, bewährte Bibliothekzoneinfo (Python 3.9+)
requestsHTTP-ClientEinfache API, weit verbreitethttpx, aiohttp
FastMCP 2.0MCP-ServerModernes Framework für KI-Tool-IntegrationGradio, FastAPI
asyncioAsynchrone OperationenNon-blocking I/O für MCP-ServerThreading

Modulabhängigkeiten

正在加载图表渲染器...

Abhängigkeitsregeln:

  • Richtung: Abhängigkeiten zeigen von außen nach innen (Utils ← Core ← App)
  • Isolation: storage hat keine Abhängigkeiten zu anderen Modulen
  • MCP-Unabhängigkeit: mcp_server operiert isoliert mit eigenem Tool-Set