Zum Inhalt springen
Abdullah Al Rafi

Lazy Termin: Terminbenachrichtigungen, die nie doppelt kommen

Ein Selenium-Checker, der eine deutsche Terminbuchungsseite überwacht und verifizierte Abonnenten über eine PostgreSQL-Outbox per Telegram benachrichtigt.

Überblick

Rolle
Alleinentwickler
Zeitraum
Aug. 2026 – heute
Technologien
Python · Selenium · PostgreSQL · python-telegram-bot · Amazon SES · Docker Compose · GitHub Actions

41

Unit-Tests gegen eine echte PostgreSQL

≤1

Meldung pro Abonnent und Sperrfrist

Standard-Sperrfrist: 60 Minuten

3

Zustellversuche, bevor der Admin informiert wird

Auf dieser Seite
  1. Überblick
  2. Problem
  3. Rahmenbedingungen
  4. Vorgehen und Architektur
  5. Ergebnisse
  6. Was ich heute anders machen würde

Problem

In Deutschland vergeben viele Behörden Termine über Online-Buchungsseiten. Freie Termine tauchen unvorhersehbar auf und sind schnell weg – wer einen will, muss denselben mehrseitigen Buchungsablauf immer wieder durchklicken. Lazy Termin übernimmt das Prüfen und meldet sich, sobald ein Termin frei wird. Gebucht wird dann selbst auf der offiziellen Seite.

Rahmenbedingungen

  • Rücksichtsvoll bleiben. Der Checker nutzt den normalen Buchungsablauf, wartet zwischen den Schritten zufällig ein bis drei Sekunden und umgeht nie CAPTCHAs, Rate Limits oder Logins. Er bucht nie selbst. Geplante Läufe finden alle 15 Minuten zu Bürozeiten an Werktagen statt (Europe/Berlin).
  • Keine Meldung verlieren, keine doppelt senden. Checker und Telegram-Bot sind getrennte Prozesse, und beide können jederzeit neu starten.
  • Kein Spam. Jeder Abonnent erhält höchstens eine Meldung pro Sperrfrist, standardmäßig 60 Minuten.
  • Kleiner, echter Nutzerkreis. Für ein Abo braucht es eine E-Mail-Adresse auf einer erlaubten Domain, bestätigt per Einmalcode über Amazon SES. Abos sind begrenzt und laufen nach fünf Tagen ab.
  • So wenig Daten wie möglich. Die Nachrichtentelemetrie speichert Zeitstempel, Telegram-Nutzer-ID und Nachrichtentyp – nie die Nachricht selbst.

Vorgehen und Architektur

    • cron in Docker Compose

      Zeitplan

      Alle 15 Minuten, werktags 07:00–16:45, Europe/Berlin

    • GitHub Actions

      Zeitplan

      Werktägliche Zeitfenster oder manuell gestartet

  1. Selenium-Buchungsablauf

    Checker

    Headless Chrome · zufällig 1–3 s Pause zwischen den Schritten

  2. Termin gefunden → INSERT + NOTIFY

    notification_outbox

    PostgreSQL

    Höchstens eine wartende APPOINTMENT_FOUND-Zeile

  3. LISTEN oder Abfrage alle 20 s

    Mit SKIP LOCKED beanspruchen

    Bot

    Abonnenten außerhalb ihrer Sperrfrist auswählen

  4. Jeden berechtigten Abonnenten benachrichtigen

    Telegram

    Jede Zustellung in alert_delivery protokolliert · 3 Fehlversuche → Admin

Abb. 1 · Benachrichtigungspfad. Checker und Bot begegnen sich nur in PostgreSQL.
Beschreibung des Diagramms

Ein Scheduler startet den Selenium-Checker, entweder per cron in Docker Compose oder über GitHub Actions. Findet der Checker einen freien Termin, fügt er eine APPOINTMENT_FOUND-Zeile in die Benachrichtigungs-Outbox ein, sofern noch keine wartet, und sendet NOTIFY. Der Telegram-Bot wacht durch die Benachrichtigung auf oder fragt alle 20 Sekunden ab, beansprucht wartende Zeilen mit SKIP LOCKED und benachrichtigt jeden Abonnenten außerhalb seiner Sperrfrist, wobei jede Zustellung protokolliert wird. Nach drei Fehlversuchen wird eine Zeile als fehlgeschlagen markiert und der Admin informiert.

  1. /subscribe

    Telegram

    Name, dann eine E-Mail-Adresse

  2. Domain- und Kapazitätsprüfung

    Bot

    Nur erlaubte Domains, begrenzte Zahl aktiver Abos

  3. Einmalcode per E-Mail

    Amazon SES

  4. /confirm

    Telegram

    Fünf Tage abonniert, danach automatisch abgelaufen

Abb. 2 · Abo-Ablauf. Nur bestätigte Adressen auf erlaubten Domains können abonnieren.
Beschreibung des Diagramms

Ein Nutzer sendet /subscribe an den Telegram-Bot und gibt einen Namen und eine E-Mail-Adresse ein. Der Bot prüft, ob die Domain erlaubt und das Abo-Limit nicht erreicht ist, und sendet dann einen Einmalcode über Amazon SES. Nach Bestätigung des Codes ist das Abo fünf Tage lang aktiv.

Entscheidung

Eine transaktionale Outbox in PostgreSQL

Der Checker fügt nur eine APPOINTMENT_FOUND-Zeile ein (sofern noch keine wartet), und die Zustellung liegt beim Bot. Jeder Prozess kann ausfallen, ohne dass der andere es merkt.

Ebenfalls erwogen

  • Die Telegram-API direkt aus dem Checker aufrufenEine Meldung während eines Bot-Ausfalls oder eines Absturzes mitten in der Schleife geht verloren oder kommt doppelt.

Entscheidung

Den Bot per LISTEN/NOTIFY wecken, mit Abfrage alle 20 Sekunden als Rückfallebene

NOTIFY macht die Zustellung nahezu sofortig, und die Abfrageschleife fängt alles auf, was eine abgebrochene Verbindung verpasst.

Ebenfalls erwogen

  • Ein Message Broker wie Redis oder RabbitMQEin weiterer Dienst für eine einzige Warteschlange, die PostgreSQL bereits bietet.

Entscheidung

Zeilen mit SKIP LOCKED beanspruchen und jede Zustellung protokollieren

Jede Zustellung wird in die Tabelle alert_delivery geschrieben, sodass eine wiederholte Zeile bereits benachrichtigte Abonnenten überspringt. Nach drei Fehlversuchen wird die Zeile als failed markiert und der Admin informiert.

Ebenfalls erwogen

  • Bei Fehlern den gesamten Versand wiederholenAlle, die die Meldung schon erhalten haben, bekämen sie erneut.

Entscheidung

Einen echten Browser mit Selenium steuern

Headless Chrome ohne Bilder hält den Speicherbedarf niedrig. Wird ein Klick abgefangen, hält ein JavaScript-Klick als Rückfallebene den Ablauf in Gang.

Ebenfalls erwogen

  • Den Buchungsablauf mit einfachen HTTP-Anfragen nachspielenDer Ablauf erstreckt sich über mehrere Seiten, und ihn nachzuspielen hieße, seinen Zustand von Hand nachzubauen.

Ergebnisse

41

Unit-Tests gegen eine echte PostgreSQL-Datenbank

Outbox-Claims, Wiederholungen, Wiederherstellung hängender Zeilen, Sperrfristen, Abo-Ablauf und Selenium-Rückfallebenen

≤1

Meldung pro Abonnent und Sperrfrist

Durchgesetzt über die Tabelle alert_delivery

30 Tage

bis Outbox- und Telemetriezeilen gelöscht werden

Nächtlich vom Bot bereinigt

Version 1.0.0 ist auf GitHub veröffentlicht. Sie läuft unter Docker Compose mit Heartbeat-Healthcheck oder zeitgesteuert in GitHub Actions.

Was ich heute anders machen würde

  • Einen kaputten Buchungsablauf als eigene Meldung behandeln. Die Selektoren hängen von der Zielseite ab, und eine stille Layoutänderung sollte nie wie „heute keine Termine“ aussehen.
  • Die Zeit bis zur Meldung messen, vom Erscheinen des Termins bis zum Eintreffen der Nachricht. Das ist die eine Zahl, die Abonnenten interessiert.