← Zurueck zum Blog
PlaywrightX.comAutomatisierungNode.jsPM2

Playwright statt API — Browser-Automatisierung für X.com-Posts

Marco Carstensen·8. März 2026·5 Min. Lesezeit

Playwright statt API — Browser-Automatisierung fuer X.com-Posts

Nach dem Einbau der KI-Teaser-Generierung (OpenAI GPT-4o-mini erzeugt aus jedem Blogbeitrag einen Twitter-Thread) stellte sich die Frage: Wie kommen die Teaser auf X.com? Die offizielle API verlangt inzwischen 5 Cent pro Write-Aktion — bei regelmaessigem Posten summiert sich das schnell. Also haben wir einen anderen Weg gewaehlt: Browser-Automatisierung mit Playwright.

Das Problem

Die X.com API (v2) hat ein Preismodell, das fuer kleine Projekte unattraktiv ist. Ein einzelner Tweet kostet 5 Cent, ein Thread (Haupt-Tweet + Reply) also 10 Cent. Klingt wenig, aber bei Tests, Dry Runs und regelmaessigem Posten laeutert sich das schnell. Ausserdem aendert X regelmaessig die API-Bedingungen.

Die KI-Teaser-Generierung via OpenAI war bereits implementiert und speichert die generierten Texte in der Datenbank (x_main_tweet und x_reply_tweet). Bisher musste man die Teaser manuell kopieren und auf X.com einfuegen. Das wollten wir automatisieren — ohne API-Kosten.

Die Architektur

Das System hat drei unabhaengige Ausfuehrungspfade, die einen gemeinsamen Browser-Core teilen:

  1. Teaser generieren — Die bestehende Route /api/blog/[id]/share-x nutzt OpenAI, um aus Titel, Excerpt und Content einen Twitter-Thread zu generieren. Die Texte werden in der Datenbank gespeichert.

  2. Jetzt posten / In Warteschlange — Die neue Route /api/blog/[id]/publish-x-browser startet Playwright, oeffnet X.com mit einer gespeicherten Session, tippt den Text mit menschenaehnlichen Delays ein und klickt auf "Posten".

  3. Dry Run — Gleiche Route mit ?dry=true: Der Composer wird gefuellt und ein Screenshot gemacht, aber nicht abgeschickt. Perfekt zum Testen.

Anti-Bot-Strategie

X.com erkennt Bots zuverlaessig. Deshalb setzt die Integration auf mehrere Massnahmen, um wie ein normaler User auszusehen:

  • Persistente Session — Login-Cookies werden gespeichert und bei jedem Start wiederverwendet. Kein "neues Geraet" bei jedem Aufruf.
  • Menschenaehnliches Timing — Variable Tipp-Geschwindigkeiten (statt konstanter Delays), natuerliche Pausen zwischen Aktionen und laengere Wartezeiten vor dem Absenden.
  • Browser-Fingerprint — Verschiedene Einstellungen sorgen dafuer, dass der Browser nicht als automatisiert erkannt wird.
  • Rate-Limiting — Maximal 3 Posts pro Tag, mindestens 30 Minuten Abstand.
  • Locale und Timezonede-DE und Europe/Berlin, passend zum Datacenter-Standort.

Das Bootstrap-Script

Bevor automatisch gepostet werden kann, muss einmalig eine Session eingerichtet werden:

npm run x:bootstrap

Das Script oeffnet ein sichtbares Chrome-Fenster auf x.com/login. Man loggt sich manuell ein, drueckt ENTER im Terminal, und die Session-Cookies werden gespeichert. Das Script validiert, ob die relevanten Session-Cookies vorhanden sind.

Das muss nur einmal gemacht werden — die gespeicherte Session wird bei jedem Aufruf geladen und am Ende aktualisiert (falls Cookies refresht wurden).

Der PM2-Worker

Fuer automatisches Posten laeuft ein separater Node.js-Prozess via PM2:

// ecosystem.config.cjs
{
  name: "x-publisher",
  script: "node_modules/.bin/tsx",
  args: "scripts/x/publish-worker.ts",
  restart_delay: 60000,
  max_restarts: 3,
}

Der Worker pollt alle 5 Minuten die Datenbank nach Posts mit x_publish_status = "queued" und verarbeitet sie der Reihe nach. Bei Challenge-Erkennung (CAPTCHA, Login-Redirect) beendet sich der Worker — PM2 startet ihn nach 60 Sekunden neu, maximal 3 Mal.

Challenge-Detection

Nach jeder Navigation und nach jedem Senden prueft das System auf:

  • Arkose CAPTCHA — iframe mit src*="arkose"
  • Login-Redirect — URL enthaelt /login oder /i/flow/login
  • Identitaetspruefung — Text "Verify your identity"
  • Rate-Limit — Text "Rate limit exceeded"

Bei Erkennung wird ein Screenshot gespeichert und der Status auf challenge_detected oder review_required gesetzt. Im Dashboard sieht man sofort, was passiert ist.

Dashboard-Integration

Der Blog-Editor zeigt jetzt einen neuen Abschnitt unter der Teaser-Vorschau:

X-Logs Dashboard — Aktivitaeten des Publish-Workers und der API-Route

  • Status-Badge — Farbcodiert: Blau fuer "In Warteschlange", Teal fuer "Gepostet", Lila fuer "Dry Run OK", Rot fuer Fehler.
  • Drei Buttons — "Dry Run" (testen ohne Posten), "Jetzt posten" (sofort ausfuehren), "In Warteschlange" (Worker uebernimmt).
  • Fehler-Anzeige — Bei Problemen erscheint die Fehlermeldung in einer roten Box.
  • Tweet-Link — Nach erfolgreichem Posten erscheint ein klickbarer Link zum Tweet.

Datenbank-Erweiterung

Sechs neue Spalten auf blog_posts tracken den Publishing-Status:

  • x_publish_status — Der aktuelle Status (queued, in_progress, published, failed, etc.)
  • x_publish_error — Die letzte Fehlermeldung
  • x_attempt_count — Anzahl der Versuche (max. 3)
  • x_last_attempt_at — Zeitstempel des letzten Versuchs
  • x_reply_post_id — Die Post-ID des Reply-Tweets
  • x_post_url — Die vollstaendige URL zum geposteten Tweet

Ein partieller Index auf x_publish_status beschleunigt das Worker-Polling.

Proxmox LXC: Headed Chrome ohne Display

Der Produktionsserver ist ein Proxmox LXC-Container — kein Desktop, kein Display. Playwright mit headless: false braucht aber einen Bildschirm. Die Loesung: Xvfb (X Virtual Framebuffer):

apt-get install -y xvfb
Xvfb :99 -screen 0 1280x800x24 -nolisten tcp &
export DISPLAY=:99

Xvfb stellt einen virtuellen Bildschirm bereit, auf dem Chrome normal rendert. Der Overhead ist minimal — ein paar MB RAM.

Rollout in drei Stufen

  1. Dry Run (1-2 Wochen) — Nur testen: Session laden, Composer fuellen, Screenshot machen. Kein Tweet wird gepostet.
  2. Test-Account (1 Woche) — Mit einem Zweit-Account real posten. Einen Post pro Tag, engmaschig monitoren.
  3. Produktion — Mit dem echten Account. Worker starten, Dashboard nutzen.

Der manuelle Fallback bleibt: Die generierten Teaser koennen jederzeit per Copy-Paste manuell auf X.com gepostet werden.

Fazit

Playwright-basierte Browser-Automatisierung ist kein Ersatz fuer eine offizielle API — aber bei den aktuellen Preisen eine pragmatische Alternative. Mit persistenten Sessions, menschenaehnlichem Timing und einem robusten Error-Handling-System laesst sich das Risiko minimieren. Und wenn X.com die Erkennung verschaerft? Dann bleibt immer noch das manuelle Copy-Paste der KI-generierten Teaser.