# Deutsche Bahn / Fahrplan-Datenabruf: Realität & Workarounds (2026-08-08) Nützlich beim Bau von Reise-/Verbindungs-Websites (z.B. Zugverbindungs-Liste urlaub.perfekt-ohne-en.de), die echte Fahrtzeiten abfragen müssen. ## Kernbefund: DB automatisiert NICHT von einem Server aus Beim Versuch, echte deutsche Nahverkehrs-Verbindungen (RE/RB/S-Bahn) automatisiert zu bekommen, schlugen ALLE Server-seitigen Wege fehl: | Weg | Ergebnis | |-----|----------| | `https://www.bahn.de/` im **Browserbase-Browser** | ❌ **Bot-Erkennung**: Fehler 753 „Verhalten Ihres Browsers ähnelt dem eines Bots" — auch im echten Browser (stealth-Warnung: „Running WITHOUT residential proxies"). | | DB-Web-API (`/web/api/reiseloesung`) | ❌ `OPS_BLOCKED` | | VRR EFA (`efa.vrr.de`) | ⚠️ Funktionierte für **Verbund-Haltestellen** (Radevormwald/Remscheid), liefert aber **keine Fernziele** (Frankfurt/Hannover außerhalb des VRR-Gebiets) — nur lokale Verbunde. | | `api.transport.rest` / GTFS | ❌ nicht erreichbar (DNS/Timeout) | | Web-Suche (Firecrawl) | ❌ nicht konfiguriert | **Warum es so ist:** bahn.de + DB-API betreiben aktive Anti-Bot-Abwehr, unabhängig davon ob der Zugriff von einem Python-Skript oder einem headless-Browser kommt. Das ist eine bewusste, dauerhafte Serverseite — kein Setup-Fehler, der sich fixen lässt. ## Funktionierende Workarounds 1. **Regionale EFA (Verbund-API) für lokale Strecken**: `https://efa.vrr.de/std3/` (JSON via `XSLT_STOPFINDER_REQUEST` / `XSLT_TRIP_REQUEST2`) funktioniert für Haltestellen im jeweiligen Verkehrsverbund. Nur für Nahziele im Verbund nutzen, nicht für Fernziele. 2. **Nutzer liefert die Zeiten manuell** (bewährt): Auf bahn.de / DB-Navigator funktioniert die Suche für **menschliche Nutzer** einwandfrei. Bei 1-2 Zielen: Nutzer gibt die schnellste Verbindung durch ☞ Master trägt die echten Zeiten ein. Das ist zuverlässig + kostenlos + umgeht die Bot-Sperre. 3. **Schätz-/Richtwerte mit ehrlichem Hinweis** (Standard-Fall für viele Ziele): Wenn keine echte Verifikation möglich ist, Netzwerk-Takte/Geographie als **Richtwert** aufführen und auf der Seite + im PDF deutlich markieren: „vor der Fahrt gegen bahn.de/DB-Navigator prüfen". NICHT als echte Fahrzeit ausgeben. 4. **DB-Deep-Link pro Verbindung** (Nutzer-Idee): statt die Zeiten automatisiert zu verifizieren, einen Link auf die DB-Suche VORBEFÜLLEN, den der Nutzer in seinem Browser öffnet. Da der Nutzer keine Bot-Sperre hat, sieht er dort die echten aktuellen Zeiten. ⚠️ **WICHTIG — LIVE-VERIFIZIERT am 2026-08-08 (Muss über den früheren Stand): Die Deutsche Bahn bietet 2026 KEIN stabiles URL-Deep-Link-Format mehr an.** Alle vorher als „stabil" geltenden Schemata sind empirisch tot: - ❌ `reiseauskunft.bahn.de/bin/query.exe/dn?...` → **toter DNS** (`DNS_PROBE_FINISHED_NXDOMAIN`), Domain existiert nicht mehr. - ❌ `reiseauskunft.de/bin/query.exe/...` → toter Pfad („Seite existiert nicht mehr"). - ❌ `www.bahn.de/buchung/fahrplan/suche?...` mit Query-Parametern → DB-SPA übernimmt die Eingaben NICHT (zeigt nur leere Suchseite). - ❌ `www.bahn.de/buchung/fahrplan/suche#query=start:…;destination:…` → undokumentierte interne SPA-Fragmentroute, bricht (Einträge werden nicht übernommen, nur Fehlermeldung → leere Suche). ✅ **Funktionierende, ehrliche Lösung 2026-08-08:** Ein einfacher, zuverlässiger Link auf die **noch erreichbare Sucheseite** `https://www.bahn.de/buchung/fahrplan/suche` auf einer Zwischenseite, die Start+Ziel (readonly) anzeigt + einen Button „In DB-Suche öffnen" (neuer Tab) + Fallback-Link. Der Nutzer tippt Start/Ziel dort manuell ein (10 Sekunden) — aber der Link landet garantiert auf einer funktonierenden Seite statt auf einem Deadlink. **Verifiziere jede DB-URL mit `curl -s -o /dev/null -w "%{http_code}"` VOR dem Einsatz** — Sub-Agenten-Reviews können veraltete Formate als „korrekt/getestet" bestätigen, ohne es live geprüft zu haben (passiert hier real). Eine muntere Vorbefüllung ist 2026 nicht mehr zuverlässig möglich; wer sie trotzdem versucht, scheitert an der SPA. ⚠️ Die offizielle DB-API (api.deutschebahn.com) wäre die robusteste Quelle für programmatische Zeiten, braucht aber einen API-Key — und ist weiterhin vorbehaltlich Bot/Key-Zugang zu prüfen. ## Google Maps als ÖPNV-Quelle? NEIN — ICE lässt sich nicht rausfiltern (2026-08-08) Nutzer-Idee beim Bau einer Verbindungs-Site: „Kann man Google Maps-Öffis-Daten nehmen?" — ehrliche Antwort, die in Zukunft nicht neu hergeleitet werden muss: - Google Maps **zeigt** ÖPNV (Bus, S-Bahn, RE, ICE, IC), aber als schnellste Route schlägt es **standardmäßig ICE/IC vor**. - Es gibt **KEINEN sauberen „nur Regionalverkehr / Deutschlandticket"-Filter** in Google Maps (nur ein grober Transit-Filter). `regional-only` von ICE zu trennen ist nicht zuverlässig möglich. - Dieselben GTFS-Fahrpläne wie überall (DB/Verkehrsverbünde) — also nichts Besseres /qchlechteres als reiseauskunft. - Google Maps blockt zudem Bots (wie die DB), sodass saubere Automatisierung nicht klappt. - **Fazit:** Für eine „kein ICE / nur Deutschlandticket“-Anforderung ist unser DB-Deep-Link zur noch erreichbaren Sucheseite (`https://www.bahn.de/buchung/fahrplan/suche`, ohne Vorbefüllung, siehe Abschnitt 4) besser — die Seite hat einen expliziten Deutschlandticket-Filter, den der Nutzer einen Klick entfernt sieht. (Der früher genannte `query.exe/dn`-Deep-Link ist tot — siehe Abschnitt 4.) ## Label nach ECHTER Fahrtzeit, nicht nach Wunsch-Kategorie (2026-08-08) Nutzer: „Nicht EINE Fahrt 5-7h in der Liste, ich sehe nur <5h oder >7h." Ursache war **zweifach**, beides wartbar: 1. **Label-Mismatch-Bug:** 168 Ebene-2-Zeilen waren als `Kernziel (5-7h)` markiert, obwohl ihre Fahrtzeiten real 0,3–4h waren. Beim Seeden wird gern „5-7h" als Wunsch-Category an die Fernziele gehängt. **Fix: Kategorie aus der echten Fahrtzeit ableiten** (Parser `~h` → float, dann `<5 → unter 5h`, `5–7 → Kernziel (5-7h)`, `>7 → über 7h`), nicht als Design-Wunsch beim INSERT setzen. 2. **Geographische Realität:** per reinem Nahverkehr sind echte 5–7h-Ziele selten (viele Großstädte entweder <3h Nahzone oder >8h Fernzone). Das ist kein Bug, sondern Physik — die Seite soll alle Kategorien ehrlich anzeigen und „5-7h" nur dort führen, wo es sie wirklich gibt (ab weiter südlich/östlich gelegenen Städten: Frankfurt→München/Dresden, Hannover→Stuttgart etc.). ## Ehrlichkeits-Regel (Nutzer erwartet dies) - Niemals fabrizierte „fahrhatige" Verbindungen oder konkrete Uhrzeiten als real ausgeben, wenn sie nur geschätzt sind. - Schätzwerte IMMER als Bereich (`~5-5,5 h`) + mit Verweis auf bahn.de labeln. ## RAPTOR statt Dijkstra für echte GTFS-Routing (2026-08-19, Team-Unraid) Für GTFS-Verbindungssuche auf großen Knoten ist **einfacher Dijkstra instabil** — bei benachbarten Zielen kamen absurde Differenzen (Mainz→Berlin 11h vs. Nachbar Wiesbaden 3h). Standard-Lösung ist **RAPTOR** (Round-Based Public Transit Optimized Route): erweitert die Reichweite Runde um Runde über echte Trips und behandelt "Zug nehmen → aussteigen → umsteigen" natürlich, statt auf einem statischen Graphen zu kürzen. - **Stop-Mehrdeutigkeit** (ein Bahnhof, mehrere stop_ids, nur 1 aktiv in `st`) → eindeutig über `parent_station`/Geo-Mapping auflösen. - **Umstiegsbuffer** (4 min) + sauberes Ziel-Stop-Matching verhindern die "benachbarte Ziele, extrem abweichende Zeiten"-Inkonsistenz. - Verifizieren mit Plausibilitäts-Ankern (z.B. Trier→Saarbrücken ~1h, Köln→Aachen ~50min) — nie den Algorithmus blind vertrauen. - Wenn der Nutzer einen Konkreten echten Wert liefert (z.B. „Trier 6h"), ihn bevorzugt übernehmen statt der Schätzung. ## Fakten zur Ziel-Menge (Zugverbindungs-Website, Kontext) - Schwelle kann variieren (250k → 100k Einwohner → weit mehr Ziele, ~33 in dieser Site). - Reichweite per **reinem Nahverkehr** (kein ICE) ab Bergisch-Land/Ruhrgebiet: die meisten Großstädte sind entweder **<3h** (Nahzone: Köln, Düsseldorf, Ruhrgebiet) oder **>8h** (Fernzone: Berlin, München). Echte **5–7h**-Ziele sind selten — das sind genau die Grenzfälle (Frankfurt ~5h, Wiesbaden ~5,5h, Hannover ~5,5–6,5h, Kassel ~6,5–7h). Regional-Agenten (SwitchHermes/LaptopHermes) lieferten dazu gute Geographie-Analysen.