Technische Dokumentation · Stand Februar 2026
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
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).
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.
GET http://heissa.de:9495/<account>/rss~/.nitter_seen.json wird der Schlüssel
following:SZwanglos:<account> gelesen. Beim ersten Aufruf
ist dieser leer → alle Einträge im Feed werden übernommen.
INSERT IGNORE in nitter_posts
gespeichert. Das IGNORE verhindert Duplikate falls doch einmal
dieselbe Post-ID zweimal auftaucht (z. B. bei Batch-Überlapp).
~/.nitter_seen.json geschrieben und beim nächsten
Cron-Lauf als Startpunkt verwendet.
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.
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.
~/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