Wie funktioniert das Nitter-Polling? ← Dashboard

Technische Dokumentation · Stand Februar 2026

1 · Architektur

Ein eigener Nitter-Docker läuft auf heissa.de:9495 und spiegelt Twitter/X-Inhalte als RSS-Feeds. Ein Python-Skript (nitter_poll.py) ruft diese Feeds regelmäßig per Cron ab und speichert neue Beiträge in einer MariaDB-Tabelle (nitter_posts). PHP-Seiten visualisieren die gesammelten Posts.

X.com / Twitter
      │  (Twitter-API intern)
      ▼
Nitter-Docker  heissa.de:9495
      │  RSS-Feed  /<account>/rss
      ▼
nitter_poll.py  (Cron, stündlich)
      │  INSERT IGNORE
      ▼
MariaDB · wagodb · nitter_posts
      │
      ▼
SZwanglos.php / Impf_Info.php / StHomburg.php

2 · Wie lange dauert ein vollständiger Durchlauf?

Damit Nitter und Twitter nicht überlastet werden, werden pro Cron-Lauf nur 20 Accounts gepollt. Zwischen jedem Account-Request wartet das Skript 1,5 – 4,5 Sekunden zufällig (natürliches Timing). Die drei Feeds laufen versetzt, um sich nicht zu überschneiden:

Feed Following-Accounts Batch / Stunde Läufe bis komplett Gesamtdauer
@SZwanglos 947 20 · Cron :00 48 Läufe ≈ 48 Stunden (2 Tage)
@Impf_Info 173 20 · Cron :15 9 Läufe ≈ 9 Stunden
@StHomburg 3 20 · Cron :30 1 Lauf ≈ 1 Stunde

Ein einzelner Batch (20 Accounts) dauert 30 – 90 Sekunden netto. Danach ist der Cron-Job abgeschlossen; beim nächsten Stunden-Tick werden die nächsten 20 Accounts nahtlos weitergepollt (rotierende Batch-Position, gespeichert in ~/.nitter_batch_SZwanglos).

Wichtig: „Vollständiger Durchlauf" bedeutet, dass jeder Account mindestens einmal auf neue Posts geprüft wurde. In der Praxis sind frische Posts von aktiven Accounts bereits nach dem ersten Durchlauf verfügbar – je nachdem, wann der Account in der Rotationsreihenfolge dran ist.

3 · Methode: nur neue Posts holen (Incremental Polling)

Das Skript holt nicht jedes Mal alle Posts eines Accounts, sondern merkt sich die ID des zuletzt gesehenen Beitrags. Beim nächsten Aufruf stoppt es die Verarbeitung sobald diese ID auftaucht – es werden also nur die neueren Einträge verarbeitet.

Ablauf im Detail

1
RSS-Feed abrufen
GET http://heissa.de:9495/<account>/rss
Nitter liefert die letzten ~20 Tweets als Atom/RSS-Feed zurück.
2
Letzte bekannte ID laden
Aus der Zustandsdatei ~/.nitter_seen.json wird der Schlüssel following:SZwanglos:<account> gelesen. Beim ersten Aufruf ist dieser leer → alle Einträge im Feed werden übernommen.
3
Nur neue Einträge verarbeiten
Die Feed-Einträge werden von oben (neueste) nach unten durchlaufen. Sobald ein Eintrag dieselbe ID wie der gespeicherte Wert hat, bricht die Schleife ab – alles darunter ist bereits bekannt.
4
In Datenbank schreiben
Neue Posts werden per INSERT IGNORE in nitter_posts gespeichert. Das IGNORE verhindert Duplikate falls doch einmal dieselbe Post-ID zweimal auftaucht (z. B. bei Batch-Überlapp).
5
Neue letzte ID speichern
Die ID des neuesten Posts wird als neuer Checkpoint in ~/.nitter_seen.json geschrieben und beim nächsten Cron-Lauf als Startpunkt verwendet.
Ergebnis: Nach dem ersten vollständigen Durchlauf (~2 Tage für SZwanglos) werden bei jedem stündlichen Lauf nur noch die tatsächlich neuen Posts seit dem letzten Check geholt – typischerweise 0–5 Posts pro Account. Der Traffic gegen Nitter und Twitter bleibt damit minimal.

Fallback bei Rate-Limiting (HTTP 429)

Wenn Nitter einen 429 Too Many Requests zurückgibt, schaltet das Skript automatisch auf Playwright um: Ein headless Chromium-Browser öffnet die entsprechende x.com-Seite mit einer gespeicherten Login-Session (~/.auth_state.json) und extrahiert die Posts direkt aus dem gerenderten HTML – ohne RSS-Feed.

4 · Datenbankstruktur

nitter_posts
├── id            BIGINT  AUTO_INCREMENT
├── post_id       VARCHAR(200)  UNIQUE   ← Twitter Snowflake-ID oder URL
├── for_user      VARCHAR(100)           ← SZwanglos / Impf_Info / StHomburg
├── account       VARCHAR(100)           ← gepollter Account
├── author        VARCHAR(100)           ← Anzeigename
├── title         TEXT                   ← Tweet-Text (120 Zeichen)
├── link          VARCHAR(500)           ← Nitter-URL zum Tweet
├── post_time     VARCHAR(20)            ← "DD.MM HH:MM" (aus RSS)
├── published_at  DATETIME               ← rekonstruiert aus Snowflake-ID
├── content       TEXT                   ← Tweet-Text (200 Zeichen)
└── created_at    TIMESTAMP              ← Import-Zeitpunkt

Der published_at-Timestamp wird aus der Twitter Snowflake-ID berechnet:
published_at = (snowflake_id >> 22) + 1288834974657 ms seit Epoch
Dies ermöglicht korrekte chronologische Sortierung unabhängig vom Import-Zeitpunkt.

5 · Übersicht der Dateien

~/python/nitter_poll.py          Polling-Skript (RSS + Playwright-Fallback)
~/.nitter_seen.json              Checkpoint: letzte bekannte Post-ID pro Account
~/.nitter_batch_SZwanglos        Batch-Position: wo die Rotation weitermacht
~/.nitter_following/SZwanglos.json  Cache der Following-Liste (24h TTL)
~/python/nitter_notify.py        Bot: @mentions-Tweets über neue Feed-Seite

Zurück zum Dashboard  ·  🇬🇧 English version