Das Ticket-Preis-System der Deutschen Bahn - oder: wie findest du den optimalen Fahrschein-Preis?

Keywords: #analyse #deutsche-bahn #fahrscheine #mobilitaet #statistik #tickets

Update 3.10.2020 - Flexpreis Plus, siehe Teil 1

Ich reise gerne. Nicht viel, aber gerne. Am liebsten jage ich mit einem Porsche Cayenne quer durch die Republik. Auf der linken Spur der Autobahn. Mit 250 Sachen. Der linke Blinker im Dauerbetrieb. Den Finger am Hebel für die Lichthupe, wie an einem Abzug.

Die Sache hat natürlich einen kleinen Haken: Ich habe weder einen Porsche noch eine Tankkarte mit Flatrate. Ich hab nicht mal ein Auto, das 250 km/h fährt.

Ich fahre aber auch sehr gerne mit der Bahn. Am liebsten in der 1. Klasse. Bequeme Sitze, ein Schokoherzchen zur Begrüßung, Sitzplatzreservierung inklusive, breite Ledersitze sowieso. Und dann sitze ich da und schaue verträumt nach links aus dem Fenster, zur Autobahn, wo ein paar Unverbesserliche im Stau stehen.

1. Klasse? Moment mal. Ist das nicht versnobt? Unbezahlbar? Nein. Ist es nicht. Man kann relativ günstig mit der 1. Klasse fahren, wenn man sich durch das Preis-System der Deutschen Bahn gequält hat und etwas flexibel ist. Und da hätten wir auch schon unser Problem und damit auch die erste und auch oberste Prämisse für diesen Essay: Die Suche nach dem optimalen Preis macht nur dann Sinn, wenn Geld mindestens eine Rolle spielt und wenn man flexibel in der Wahl der Abfahrtszeit ist, vielleicht sogar was den Reisetag angeht. Und wenn man trotzdem damit leben kann, an eine Verbindung gebunden zu sein, sobald das Ticket gebucht ist.

Nun ist das Preis-System bzw. dessen Darstellung aber ein wenig unübersichtlich, so dass die Suche nach dem besagten optimalen Preis zu einem Geduldsspiel wird, weshalb man sich am Ende wohl schlicht mit dem begnügt, was am günstigsten erscheint: 2. Klasse, Super-Sparpreis. Dem muss aber nicht so sein…

Überblick

Was dich hier erwartet: Im 1. Teil erkläre ich, wie das Preis-System der Deutschen Bahn aufgebaut ist. Im 2. Teil zeige ich, warum die aktuelle Oberfläche kaum dabei hilft, das komplexe Preissystem zu begreifen. Im 3. Teil erkläre ich dir, wenn dich der technische Hintergrund interessiert, wie du an die Preise für die Verbindungen kommst. Im 4. Teil versuche ich herauszufinden, ob sich die Preisbildung nachvollziehen und vielleicht sogar eine Faust-Formel erkennen lässt und im 5. Teil schließe stelle ich eine alternative Oberfläche vor. Viel Spaß.

Wenn du dich nur für die alternative Oberfläche interessiert und dich der analytische und technische Teil nicht interessiert, dann schau einfach direkt auf nickets.de vorbei bzw. überspringe Teil 1 bis 4,

Zur Verteidigung der Bahn möchte ich vorab noch festhalten, dass der Vorgang der Bepreisung von Fahrkarten sicher nicht banal ist. Eine Verbindung von Berlin nach Stuttgart ist nicht nur einfach das. Sie kann über mehrere Zwischenbahnhöfe gehen und es gibt zig Faktoren, die die Preisfindung beeinflussen können.

Teil 1: Die Preis-Kategorien der Deutschen Bahn

Das Preis-System der Deutschen Bahn ist komplex. Die Deutsche Bahn bietet zwei Klassen mit jeweils drei Kategorien an. Es gibt in der 1. und 2. Klasse jeweils den Flex-Preis, den Spar-Preis und den Super-Spar-Preis:

Abbildung 1: Preis-Kategorien für Tickets der Deutschen Bahn

Die insgesamt sechs Katgegorien habe ihre Vor- und Nachteile und sind offensichtliche Faktoren, die den Ticketpreis beeinflussen. Ich will die Eigenschaften der Kategorien nur ganz grob beschreiben, um die Komplexität im Ansatz begreifbar zu machen. Daneben gibt es übrigens noch den Flexpreis Business für Geschäftskunden. Diese Tickets sind einen Tag vor und sieben Tage nach gebuchtem Reisetag gültig.

Update 3.10.2020 - Flexpreis Plus

Offenbar hat die Bahn eine neue Preis-Kategorie eingeführt. Mit dem Flexpreis Plus ist das Ticket nun einen Tag vor und zwei Tage nach gewähltem Reisetag gültig. Die Stornierung ist solange möglich, wie das Ticket gültig ist. Diese Kategorie gibt es offenbar nur in der 1. Klasse und erst für Fahrten ab dem 1. November. Auf der Seite der Bahn habe ich dazu noch keine Informationen gefunden. Der Übersicht ist das natürlich nicht gerade zuträglich.

Der Flexpreis Plus, ab dem 1. November 2020?

Freie Zugwahl

Fangen wir mit dem Flex-Preis an, der den wirklich angenehmen Vorteil der freien Zugwahl am Reisetag ermöglicht. Man kann die Fahrt sogar unterbrechen und erst am nächsten Tag fortsetzen. Der Flexpreis soll grundsätzlich gleich hoch sein, unterscheidet sich aber in der Realität, um die “Nachfragelenkung zu optimieren”. Beim Flex-Preis gibt es allerdings eine zusätzliche Ebene, die Einfluss auf den Preis hat: Die Zug-Art wird, wie z.B. bei der Auto-Vermietung, ebenfalls unterschiedlich bepreist. Ein ICE-Ticket gehört zu einer höheren Kategorie als ein IC-Ticket. Buchst du ein Ticket für den IC, kannst du damit am gleichen Tag alle IC-Verbindungen nutzen, nicht jedoch den ICE (dafür wäre ein Aufschlag nötig, Stichwort Produktübergang).

Sitzplatzreservierung

Außerdem gibt es ein wichtiges Detail zur Sitzplatzreservierung. Diese ist bei der 1. Klasse inklusive. In der 2. Klasse kostet sie 4 Euro. Wer sich nicht auf ein Abenteuer (Reise nach Jerusalem) einlassen will, sollte diesen Aufschlag immer einkalkulieren. Gerade bei populären Verbindungen ist die Auslastung der Züge relativ hoch.

Der Super-Spar-Preis der 2. Klasse ist (siehe Abbildung 1) dann “nur” noch knapp 25 Euro günstiger als in der 1. Klasse. Der einzige Vorteil des Spar-Preises gegenüber dem Super-Spar-Preis ist übrigens die Stornomöglichkeit und das Cityticket. Der Storno ist bis vor den Reisetag gegen eine Gebühr von 10 Euro möglich. Irritierend daran: Die Gebühr ist immer gleich, egal ob das Ticket 17,90 Euro oder über 200 Euro kostet. Am Reisetag ist die Stornierung des Flex-Tickets übrigens auch kostenpflichtig, hier sogar 19,90 Euro.

Das City-Ticket

Den Vorteil des City-Ticket habe ich in meinen Betrachtungen aus mehreren Gründen als vernachlässigbar eingestuft. Zunächst verbaut man sich damit am Start- und Zielort die Möglichkeit, das optimale Reisemittel selber zu wählen. Es gibt ja durchaus günstige Alternativen. Außerdem ist das City-Ticket nur bei Verbindungen über 100 km und in “nur” 130 Städten kostenlos dabei. Hinzu kommen lokalspezifische Einschränkungen: So gilt das City-Ticket z.B. in Berlin nur in den Tarifbereichen A und B, nicht jedoch C. Aus diesen Gründen hab ich das City-Ticket und damit die Spar-Preise in den folgenden Betrachtungen komplett ignoriert (ein weiterer Grund ist technischer Natur, dazu später mehr).

Zum Problem der Komplexität trägt der Spar-Preis übrigens auch noch bei da der Aufpreis nicht fix ist, sondern zwischen (laut meinen Stichproben) 3,60 und 10 Euro schwankt. Auch das ist absurd, da das City-Ticket im Preis relativ beständig sein sollte und die Storno-Option, nunja, nur ein “Service” ist.

Andere Einflüsse auf den Preis

Soviel zu den offensichtlichen Faktoren. Hinzu kommen noch weniger offensichtliche Faktoren, wie z.B. die Reisezeit. Wie sehr der Spar-Preis abhängig von der Tageszeit schwanken kann, siehst du in der folgenden Abbildung. Es handelt sich um den geringsten Super-Spar-Preis für die Verbindung Berlin-Stuttgart über den Tag verteilt.

Abbildung 2: Kleinster Super-Spar-Preis für die Verbindung Berlin - Stuttgart in Abhängigkeit von der Tageszeit (Abreise), ohne Umsteigen

Die günstigste Zeit zum Reisen ist für diese Verbindung also der Vormittag. In der ersten Klasse macht es sogar Sinn, erst abends zu reisen. Gegen 21 Uhr ist das Super-Spar-Preis-Ticket sogar günstiger als zu anderen Zeiten in der 2. Klasse. Achtung: Es handelt sich um die aggregierten Werte über die Tageszeit. Das besagte günstige 1. Klasse-Ticket bezieht sich auf einen anderen Tag. Es ist also nicht nur die Uhrzeit ausschlaggebend, sondern auch der Wochentag. Die Preise über den Wochentag aggregiert zeigt folgende Abbildung: Dienstag ist also 1.-Klasse-Spar-und-Fahrtag:

Abbildung 3: Kleinster Spar-Preis für die Verbindung Berlin - Stuttgart in Abhängigkeit von der Tageszeit (Abreise), ohne Umsteigen

Der Preis einer Verbindung ist also von vielen Faktoren abhängig. Da wäre es doch schön, wenn man die Preise in einer schönen Übersicht vergleichen könnte. (Übrigens, just als ich diesen Artikel geschrieben habe, hat die Deutsche Bahn einen Sonderpreis für “junge Leute bis 26” angekündigt. Es handelt sich dabei um ein auf 1 Mio. Tickets limitiertes Sonderangebot für den Super-Spar-Preis der 2. Klasse.) Das Problem: Derzeit gibt es im Buchungssystem der DB keinen Hinweis darauf.

Wollen wir doch mal schauen, wie einfach es ist, den optimalen Preis für ein Ticket der Deutschen Bahn zu finden.

Teil 2: Das Buchungssystem der Deutschen Bahn

Um ein Ticket bei der Bahn zu buchen sind mir bisher zwei (bzw. drei) Wege bekannt: Das Portal für Tickets und Angebote (auch Reiseauskunft) und der Sparpreis-Finder. Wir möchten diese Portale Oberfläche 1 und 2 nennen. Hinzu kommt Oberfläche 3, das ebenfalls zum Sparpreis-Finder gehört. Oberfläche 1 und 2 erreichst du recht simpel, wenn du bahn.de direkt aufrufst, über das Menü Tickets & Angebote. Oberfläche 2 führt dich am Ende zu Oberfläche 3. Oberfläche 3 ist entweder über die Google-Suche oder wenn du dich durch das Hover-Menü klickst. Es ist liegt außerdem auf einer anderen Sub-Domain, auf der das Hover-Menü “Tickets & Angebote” nicht mehr existiert. Du kommst also nur auf Umwegen wieder zurück_._

Hinweis: Es gibt noch eine zusätzliche Oberfläche, die mobile Anwendung. Hier ist es aber aufgrund technischer Maßnahmen nicht ohne weiteres möglich, an die HTTP-Abfragen zu kommen. Diese sind per TLS geschützt, lassen sich also nicht ohne weiteres über z.B. einen Proxy abfangen. SSL-Pinning sorgt außerdem dafür, dass man mit einfache Boardmitteln kein eigenes SSL-Zertifikat unterschummeln kann. Der Aufwand hat den Nutzen überstiegen.

Abbildung 4: Oberfläche 1 - Die Reiseauskunft

Abbildung 5: Oberfläche 2 - Der Sparpreis-Finder

Abbildung 6: Oberfläche 3 - Der “eigentliche” Sparpreis-Finder

Warum gibt es nun drei bzw. zwei Oberflächen? Was ist der Unterscheid? Schauen wir mal.

Die Ergebnisseiten

Die Verbindung Berlin - Stuttgart gehört zu meinen “Lieblingsverbindungen”, da ich hier relativ oft unterwegs bin bzw. war. Als Reisedatum nehme ich den 17.09.2020, einen wunderschönen Donnerstag. Da wir auf der Suche nach Schnäppchen sind, wähle ich die 2. Klasse und außerdem die einfache Fahrt. Ohne Rückreise! Warum? Wenn du beide Fahrten gleichzeitig buchst, kannst du diese nicht getrennt voneinander stornieren. Stornierst du die Hinfahrt, ist auch die Rückfahrt weg. Macht keinen Sinn, ist aber leider so. Deswegen solltest du die Rückreise immer getrennt buchen. Keine Sorge, du verschenkst dadurch keinen Preisnachlass, wie z.B. bei Flugreisen.

Oberfläche 2 und 3 führen beide zur gleichen Ergebnissseite: Eine ziemlich lange Liste mit Spar-Preisen (sic!), den Abfahrtszeiten, Dauer und Umsteigezeiten Das ganze für den kompletten Tag (bzw. den gewählten Zeitraum). Außerdem wird angeblich auch der Flex-Preis angezeigt - laut Checkbox. Findest du ihn?

Abbildung 7: Die Tagesübersicht aller Super-Spar-Preis

Der Flex-Preis wird erstens nicht immer ausgewiesen und zweitens nicht explizit als solcher markiert. Er wird erst angezeigt, wenn du mit der Maus darüber hoverst, die Details anklickst oder zur Buchung gehst.

Abbildung 8: Der versteckte Flex-Preis

Verwirrend außerdem. Der Flex-Preis ist an dieser Stelle sogar um einiges günstiger, als der Spar-Preis. Nur blöd, dass man den Flexpreis, mit all seinen Vorteilen, nicht sofort erkennt.

Geht es weiter zur Buchung, erfährst du außerdem, dass die Liste die tatsächlich nur die Super-Spar-Preise enthält, nicht die Spar-Preise. Preislich und leistungstechnisch macht das, siehe oben, durchaus einen Unterschied. Diese Information erhält man auch, wenn man auf das kleine i-Symbol der jeweiligen Verbindung klickt.

Diese Übersicht ist trotz allem, wie wir gleich sehen werden, hilfreicher als bei Oberfläche 1, weil man für eine Klasse die Preise eines ganzen Tages sieht.

Kommen wir zu Oberfläche 1, die auf den ersten Blick einen viel kompakteren Eindruck macht. Hier wird der Flex-Preis zwar besser ausgezeichnet und die Informationen sind optisch etwas schöner aufbereitet, es gibt sogar eine Auslastungsinformation, allerdings werden in der Regel nur drei Verbindungen angezeigt.

Abbildung 9: Die Preise der Obefläche 1

Mit Klick auf “früher” oder “später” kannst du den Zeitraum zwar erweitern, wirst aber nie den kompletten Tag als Übersicht erhalten. Schade.

Soviel zu der Möglichkeit, die für sich optimalste Preiskategorie bzw. Verbindung herauszusuchen.

Zwischenfazit: Das Problem der Vergleichbarkeit

Das größte Problem, an dem das Buchungssystem der Deutschen Bahn krankt, ist die Vergleichbarkeit. Der Aufschlag für den Spar-Preis schwankt, die Preise schwanken nach der Tageszeit, sie sind abhängig davon, wie knapp man ein Ticket bucht usw. usf. Es ist nachvollziehbar, dass Preise geändert werden, um die Nachfrage zu steuern. Umso hilfreicher wäre es aber, die Preise vergleichbarer darzustellen.

Eine Oberfläche zeigt zwar die Preise im Tagesverlauf an, dafür aber nur die Super-Spar-Preise. Eine andere Oberfläche zeigt die Preise für einen weitaus kleinen Zeitraum an, dafür aber auch den Flex-Preis. Keine Oberfläche zeigt einen direkten Vergleich der Klassen an, außer wenn die 2. Klasse ausgebucht ist:

Abbildung 10: 1. Klasse-Preis bei der 2. Klasse-Übersicht

Um das beste Preis-Leistungs-Verhältnis zu finden, fehlt es an einer einheitlichen Darstellung. Man kann mit der Komlexität leben, wenn die Übersicht nur etwas sinnvoller gestaltet wäre. Bevor wir uns diesem Problem widmen, will ich trotzdem mal versuchen, eine Regelmäßigkeit in der Bepreisung zu entdecken.

Teil 3: Wie funktioniert die Ticket-API der Deutschen Bahn?

Bevor ich die Daten auswerte, erkläre ich kurz, für alle technisch interessierten, wie man an die Preise kommt. Auf der Suche nach einer Beschreibung der Preis-Api bin ich auf folgendes Dokument gestoßen: Externe Aufrufparameter und Rückgabeparameter an externe System (pdf). Diese Information konnte ich als Grundlage nutzen, um die Anfragen an den DB-Server zu formulieren (die Dokumentation ist allerdings unvollständig und weicht in einigen Punkten ab, ist also vermutlich veraltet).

Die Identifikation der Bahnhöfe

Die beiden wichtigsten Parameter zur Ticket-Suche sind, wie sollte es anders sein, der Start- und Zielbahnhof. Hier gibt es mehrere Möglichkeiten, diese zu bestimmen.

Die erste Methode ist der offizielle Weg über die (nach Registrierung) frei zugängliche Stations-Daten-API an. Die Liste enthält zu jedem Bahnhof den Namen im Klartext, die Geo-Lokation und eine Kategorie (Hauptbahnhof, Wald-und-Wiesen-Bahnhof, …) Die dort aufgeführten Bahnhöfe werden in der Regel ohne Probleme auch von der Preis-Auskunft erkannt.

Die zweite Methode führt über einen GET-Request, der dir drei verwertbare Informationen liefert. Der öffentliche Endpunkt dazu lautet:

https://reiseauskunft.bahn.de/bin/ajax-getstop.exe/dn

Die wichtigsten Parameter lauten hier:

  • S - Der Suchbegriff, entweder als
    • expliziter String (z.B. Berlin)
    • String mit Platzhalter (z.B. Berlin*, alles was mit Berlin beginnt)
    • Fuzzy-Search-String (z.B. Kaulsdorf?, liefert als Ergebnis auch Zaulsdorf)
  • REQ0JourneyStopsB - die Anzahl der Ergebnisse, optional

Das Ergebnis ist ein JSON-Objekt mit sogenannten Vorschlägen (“suggestions”):

  {
            "value": "BERLIN",
            "id": "A=1@O=BERLIN@X=13386988@Y=52520501@U=80@L=008096003@B=1@p=1598555590@",
            "extId": "008096003",
            "type": "1",
            "typeStr": "[Bhf/Hst]",
            "xcoord": "13386988",
            "ycoord": "52520501",
            "state": "id",
            "prodClass": "319",
            "weight": "30849"
        }

Die Daten erklären sich fast von selber, für uns relevant sind “value”, “id” sowie “extId”. Alle drei Werten können für die Preissuche verwendet werden.

Oberfläche 1 - Die Reiseauskunft

Wir bleiben bei der Verbindung Berlin-Stuttgart am 17.09.2020, einem wunderschönen Donnerstag. 2. Klasse und der URL

https://www.bahn.de/p/view/angebot/index.shtml?dbkanal_007=L01_S01_D001_KIN0014_top-navi-tickets-angebote_LZ01 bzw. https://www.bahn.de/p/view/angebot/index.shtml 

Ein Klick auf Suchen feuert einen GET-Request an diese URL ab:

https://reiseauskunft.bahn.de/bin/query.exe/dn

Und natürlich gibt es einen ganzen Stapel Parameter. Den kompletten Aufruf mit allen Parametern kannst du dir unten als Postman-Collection herunterladen. Im folgenden werde ich nur die wichtigsten davon besprechen, also die, die mindestens erforderlich sind, um eine Preis-Information zu erhalten:

  • start=1, liefere Ergebnisse zurück (starte die Suche), ansonsten wird die erweiterte Suchmaske angezeigt
  • S=BERLIN - der Name des Startbahnhofs, hier funktioniert in einigen Fällen der String (siehe oben)
  • REQ0JourneyStopsSID=A=1@O=BERLIN@X=13386988@Y=52520501@U=80@L=008096003@B=1@p=1598383868@ - grundsätzlich macht es aber Sinn, hier die explizite Id des Bahnhofs zu verwenden
  • Z=Stuttgart Hbf - der Zielbahnhof
  • date=Do, 17.09.20 - Tag der Reise
  • time=08:00 - Uhrzeit der Reise
  • timesel=depart - Bestimmen Tag und Uhrzeit Abfahrt oder Ankunft (arrival)?
  • tariffTravellerType.1=E - E oder F für Erwachsener oder Kind
  • tariffClass=2 - 1. oder 2. Klasse
  • tariffTravellerReductionClass.1=… - hiermit lässt sich mit Ziffern von 0 bis 17 festlegen, ob Vergünstigungen einberechnet werden sollen, wie z.B. eine BahnCard. Das kann sich sicherlich auf den Preis der Tickets auswirken, hat aber keinen Einfluss auf die Vergleichbarkeit der Preise bzw. Analyse der Preisstruktur. Ich ignoriere diesen Parameter also.

Eine Handvoll anderer Parameter ist wie gesagt in der Postman-Datei genauer beschrieben. Diese Anfrage liefert uns nun die bekannte Übersicht mit in der Regel drei Verbindungen zurück:

Abbildung 11: Preise der 2. Klasse für Berlin -> Stuttgart am 17.09.2020

Außerdem wird ein Cookie gesetzt, der drei sehr wichtige Daten enthält:

  • ld - ein Identifier, der vorerst unverändert bleibt, z.B: 37226
  • seqnr - in Integer, der initial auf 1 gesetzt wird und dann hochzählt
  • ident - ein weiterer Identifier, der sich mit jedem Request ändert

Diese drei Informationen werden offenbar auch serverseitig gespeichert, um den Request zuordnen bzw. mehr Details liefern zu können. Wenn man mit Klick auf “Später” weitere Preise anfordert, wird ein GET-Request abgesetzt, der anhand dieser drei Parameter die Preise für drei weitere Abfahrten abfragt. Hinzu kommt noch der Parameter REQ0HafasScrollDir=1 bzw. REQ0HafasScrollDir=2 für spätere oder frühere Abfahrten.

Seqnr muss außerdem mit jedem Request iteriert werden, um zusätzliche Slots abzufragen. So lassen sich zumindest programmatisch beliebig viele darauffolgende Abfahrtszeiten abfragen. In einem Test konnte ich seqnr bis 20 erhöhen und so auch den kompletten Folgetag abzufragen. Das klappt allerdings nur über Postman, im Browser ist nach seqnr=3 Schluss:

Abbildung 12: Beschränkte Zeitraum-Abfrage im Browser

Zum Vergleich die Abfrage des darauffolgenden Tages mit Postman - perfekte gute Voraussetzungen, um die Preise automatisiert abzufragen:

Abbildung 13: Abfrage eines großen Zeitraums mit Postman

Der Nachteil ist allerdings, dass die Antwort vom Server reines HTML ist. Man muss die Preise also mit einem DOM-Parser aufwendig manuell extrahieren.

Oberfläche 2 - Der Sparpreis-Finder

Diese Oberfläche nutzt einen anderen Endpunkt, der etwas bequemer zu bedienen ist. Hier kommt eine Schnittstelle zum Einsatz, die etwas bequemer mit einem JSON-Object gefüttert wird und sogar ein JSON-Object zurückliefert. Aber der Reihe nach.

Die erste Abfrage, aus der Maske heraus fragt diesen Endpunkt ab:

https://ps.bahn.de/preissuche/preissuche/psc_start.post

Hier werden eine Menge Verbindungs-Parameter in dem GET-Request übermittelt (die wie gehabt in der Postman-Collection genauer beschrieben werden). Dieser Request liefert allerdings nur einen Schlüssel aus Timestamp und offenbar zufälligem Hex-Wert zurück: Dieser Schlüssel wird in einer weiteren Abfrage benötigt, um die tatsächliche Preisauskunft zu erhalten. Der Schlüssel nennt sich pscexpires und hat z.B. folgenden Inhalt:

0209201557-3f1a35cab18604c875235a194738d672

Der Request wurde um 15:37 Uhr abgesetzt, offenbar ist der Schlüssel also 20 Minuten gültig. Um einen aktuellen Schlüssel zu erhalten, reicht ein minimaler Request, der z.B. nur den Start-Bahnhof enthält:

https://ps.bahn.de/preissuche/preissuche/psc_start.post?S=BERLIN

Damit ausgestatttet kann jetzt ein weiterer Request abgesetzt werden, um die tatsächlichen Preise zu erhalten. Der Endpunkt dafür lautet:

https://ps.bahn.de/preissuche/preissuche/psc_service.go

Anmerkung: Ist der eben erwähnte Schlüssel pscexpires abgelaufen, erhälst du folgende Fehlermeldung:

"PSC-Captcha-Session ist abgelaufen!"

Hier gibt es zwei relevante Parameter:

  • service=pscangebotsuche sowie
  • data={} ein JSON-Objekt mit den Reise-Parametern

Nun können also die Reise-Parameter für die Preissuche übermittelt werden. Das JSON-Objekt hat folgenden Aufbau (an dieser Stelle im YAML-Format, zur besseren Dokumentation, hiermit kannst du daraus ein gültiges JSON-Objekt erstellen):

Anmerkung: Im Gegensatz zur ersten Schnittstelle, kann hier mit der Id der Bahnhöfe gearbeitet werden, die weitaus weniger sperrig ist.

---
# Id des Start-Bahnhofs
s: 008096003
# Id des Ziel-Bahnhofs
d: 008000096
# Datum
dt: 13.09.20
# Uhrzeit
t: '0:00'
# Zeitraum für die Abfrage in Minuten, mindestens 1, maximal 1440 / 1 Tag
dur: 1440
# der eindeutige Schlüssel (siehe oben)
pscexpires: "{{pscexpires}}"
# ?
dir: 1
# Nur schnellste Verbindungen
sv: false
# Nur Verbindungen ohne ICE
ohneICE: false
# Fahrrad-Mitnahme
bic: true
# ?
tct: '0'
# Klasse (1/2)
c: '2'
# Reisende
travellers:
# E - Erwachsener, F/K - Kind 6 bis 14 Jahre, B - Kind bis 5 Jahre
- typ: E
# Ermäßigungs-Klasse
  bc: '0'
# Alter?
  alter: ''

Eine derartige Abfrage liefert ein ziemlich umfangreiches JSON-Objekt zurück, dass ich hier nur grob beschreibe. Unter “angebote” werden zunächst alle Preise aufgeführt. Die Preiskategorie (Flexpreis, Super Spar-Preis) ist mutmaßlich anhand des Feldes “tt” erkennbar. SP steht für Super Spar-Preis, NP für Flexpreis. Außerdem gibt es das Feld “pky”, was wohl für Preiskategorie steht:

  • 71830 - Super Sparpreis (Achtung, in der Oberfläche wird oft nur von Spar-Preis gesprochen)
  • 7001 - Flexpreis

Unter Verbindungen werden nun alle Fahrten aufgeführt, inklusive der Umstiege und Zeiten und sogar der Zug-Nummern, z.B. ICE 695.

Die Schnittstelle liefert also ein maschinell sehr einfach zu verarbeitendes Ergebnis zurück. Es gibt aber ein Problem: Es ist nicht klar, anhand welcher Kriterien entschieden wird, auch den Flexpreis mitzuteilen. Dieser ist nämlich leider nicht immer Teil der Antwort. Bisher konnte ich auch noch nicht herausfinden, ob ich das mit einem Parameter in der Anfage steuern kann.

Zwischenfazit

Wir haben also ein ziemlich komplexes Preissystem und zwei verschiedene Schnittstellen. Wollte man eine alternative Preisübersicht für einen größeren Zeitraum aufbauen, kann man nun

  • die 1. Schnittstelle nutzen, die zwar Flex-Preis und Spar-Preis auszeichnet, aber immer nur für ein sehr kleines Zeitfenster,
  • oder auf die 2. Schnittstelle zurückgreifen, die zwar einen großen Zeitraum mit allen Preisen liefert, aber den Flex-Preis nicht zuverlässig zurück gibt.

Bei beiden Schnittstellen fehlt dennoch die Vergleichbarkeit mit der 1. Klasse sowie die Aufzählung des (Super-)Spar-Preises, der ja, wie wir wissen, ebenfalls einer Schwankung unterliegt.

Nun, da ich weiß, wie ich programmatisch an die Preise der Deutschen Bahn komme, habe ich ein Script geschrieben, das die Preise für beliebige Verbindungen regelmäßig abrufen kann. Dazu habe ich in mehreren Durchläufen Verbindungen und Preise bis zu 90 Tage im Voraus abgerufen (90 Tage ist das Limit für das Buchen im Voraus). Die ersten Durchläufe (Ende 2019 und Mitte 2020) waren mehr ein Testen und Herantasten, im finalen Durchlauf (die “relevante Stichprobe”) Anfang September 2020 habe ich die folgenden Verbindungen abgefragt:

Erfasst werden die Abfahrtszeit, die Dauer der Reise, die Anzahl der Umstiege, die erwartete Auslastung, die beteiligten Zugarten und natürlich der Flex-Preis sowie der Super Spar-Preis. Zur Qualitätssicherung habe ich außerdem die URL zur Preisübersicht erfasst.

Mein Ziel ist es zu erkennen, inwiefern Preise von der Tageszeit oder dem Wochentag abhängen. Wie stark ist der Einfluss der Auslastung? Wann muss man ein Ticket spätestens buchen, um noch einen guten Preis zu bekommen? Im nächsten Teil schaue ich mir an, welche Zusammenhänge sich erkennen lassen:

Teil 4 - Analyse der Ticket-Preise der Deutschen Bahn

Als Einstieg werfen wir mal einen Blick auf die Verteilung der Preis. Die folgende Übersicht zeigt die Preise für alle Verbindungen, Klassen und Preis-Kategorien an:

Abbildung 14: Preis-Überblick

Anmerkung: “Durchschn. Preis” meint immer arithmetisches Mittel.

Die 1. Klasse bekommt man bei der Deutschen Bahn schon für 12,30 Euro, das obere Limit liegt bei stabilen 259,70 Euro. Die günstigste Reise tritt man für 5,60 Euro an. In Testläufen von 2019 kostete das teuerste Ticket übrigens 294,00 Euro (Berlin -> Stuttgart, 02.10.2019). Offensichtlich wurden die Preise in 2020 also angepasst.

Anmerkung: Die folgende Auswertung zeigt, dass die Verbindung Offenburg -> Greifswald nicht mehr als Direktverbindung zu existieren scheint und somit auch nicht, wie angepriesen, die längste zusammenhängende Strecke ist. Offenbar fährt der ICE 2216 nur noch von Stuttgart nach Kiel.

Abbildung 15: Gegenprobe: Keine Direktverbindungen für Offenburg -> Greifswald?

Die nächste Abbildung zeigt die Dauer, Streckenlänge und mittlere erwartete Auslastung aller abgefragten Verbindungen. Um die Entfernung zu ermittelten, nutze ich dieses Tool.

Abbildung 16: Entferung, Dauer und Auslastung aller Verbindungen

Insgesamt gibt es fünf Stufen der Auslastung, die bei der Ticket-Auswahl angezeigt werden:

  • Außergewöhnliche hohe Auslastung (Stufe 5)
  • Sehr hohe Auslastung
  • Hohe Auslastung
  • Besetzung von mehr als der hälfte der Sitzplätze
  • Geringe bis mittlere Auslastung (Stufe 1)

Im finalen Durchlauf wurde bei alle Verbindungen nur eine Auslastungen von 1 und 2 erwartet. In früheren Durchläufen gab es auch Auslastungsstufen bis 4 und sogar 5 (nur in 2019). Nichtsdestotrotz: Die Strecke Berlin -> Hannover war (siehe oben) zwar eine Verbindung mit den meisten Verspätungen, obgleich sie bei der Auslastung eher im Mittelfeld liegt. Eine Erklärung könnte sein, dass die Deutsche Bahn in 2020 den Fahrplan angepasst hat, um dem entgegen zu wirken. Eine andere Erklärung: Corona und damit ein geringeres Reise-Aufkommen.

Der Buchungs-Zeitpunkt

Schauen wir uns mal die Flex-Preis-Entwicklung (Median über den Tagespreis) aller Direktverbindungen (inkl. Offenburg -> Greifswald mit einem Umstieg) an (2. Klasse). Zur Erinnerung, die Preise wurden am 06. Sept. 2020 abgerufen:

Abbildung 17: Median Flex-Preis 2. Klasse, alle Direktverbindungen (inkl. Offenburg -> Greifswald, 1 Umstieg)

Was fällt auf? Je höher der Preis, desto größer die Schwankungen. Der Trend über die 90 Tage ist allerdings relativ stabil. In der 1. Klasse sieht es ähnlich aus. Da wir aber auf der Suche nach Schnäppchen sind, werfen wir doch mal ein Blick auf den günstigsten Preis in dieser Kategorie:

Abbildung 18: günstigster Flex-Preis, 2. Klasse, alle Direktverbindungen (inkl. Offenburg -> Greifswald)

Bei den teuren Strecken (über 100 Euro) und der kurzen Gotha -> Eisenach Verbindung ändert sich nichts. Dazwischen sinken die Preise aber auffällig.

Schauen wir uns mal die Super-Spar-Preise an, auch hier wieder die 2. Klasse (die 1. Klasse verhält sich ähnlich).

Abbildung 19: 2. Klasse, Super-Spar-Preis, Direktverbindungen (inkl. Offenburg -> Greifswald)

Der Preis sinkt in den hohen Preis-Regionen stärker, je länger das Abfahrtsdatum in der Zukunft liegt. Die teuren Tickets schwanken außerdem auffällig, während es im Niedrig-Preis-Segment kaum noch Preisschwankungen gibt.

Schauen wir uns das ganze noch mal für alle Preis-Kategorien einer Verbindung an, hier Köln -> Dresden:

Abbildung 20: Preise für die Verbindung Köln -> Dresden (nur Direkt)

Wenig überraschend kann man feststellen: Die Kurven der 1. und 2.. Klasse verlaufen ziemlich synchron. Sinkt der Preis in der 1. Klasse, macht es die 2. Klasse ihm gleich. Außerdem erscheinen die Schwankungen zyklisch. Zum Vergleich die Preise für die berüchtigte Verbindung Berlin -> Hannover:

Abbildung 21: Preise für die Verbindung Berlin -> Hannover (nur Direkt)

Ich will die Preise noch etwas genauer untersuchen. Die folgende Abbildung zeigt die Preise der 1. Klasse für die Verbindung Berlin -> Stuttgart. Da jeder Punkt für eine Verbindung bzw. einen Preis steht, lässt sich nun ganz deutlich ein Zusammenhang erkennen. Bzw. nicht.

Abbildung 22: 1. Klasse Preise für die Verbindung Berlin -> Stuttgart (nur Direkt)

Der Flex-Preis folgt einem Schema. Der Grundpreis liegt bei 227,80 Euro. Am Freitag steigt dieser stark an, am Samstag sinkt er sehr stark und am Sonntag ist er wieder etwas höher als der Grundpreis, aber nicht so hoch wie am Freitag.

Und der Super-Spar-Preis? Tja nun. Hier scheint es kein Konzept zu geben. Es scheint, als würde der Preis zufällig bestimmt werden. Auf einer anderen Strecke sieht es bei weitem nicht ganz so chaotisch aus, aber trotzdem noch nicht nachvollziehbar:

Abbildung 23: 1. Klasse Preise für die Verbindung Ingolstadt -> Nürnberg (nur Direkt)

Beim Flex-Preis ist fast es egal, wie früh du buchst, es gibt aber durchaus Unterschiede, nämlich am Wochenende. Ansonsten ist das Spar-Potential bei teureren Tickets tendentiell höher. Beim Super-Spar-Preis ist es eigentlich auch nicht wichtig, möglichst früh zu buchen. Auch hier kann man kurz vor Reiseantritt noch günstige Preise finden. Es gibt mitunter sehr starke Preisschwankungen, die in höheren Preisregionen größer sind.

Gibt es eine Nachfrage-Steuerung?

Das wirft die Frage auf, inwieweit versucht wird, die Nachfrage zu steuern. Klar ist nun, dass der Flex-Preis an Wochenenden stark schwankt. Untertags macht eine Schwankung auch gar keinen Sinn, da es keine Zugbindung gibt. Hier ist der Super-Spar-Preis besser geeignet. Und tatsächlich schwankt dieser auch, bedingt durch die Tagszeit (dazu unten mehr).

Allerdings steigt die erwartete Auslastung der Züge auch, je näher das Reisedatum liegt. Dazu ein Überblick für die erwartete Auslastung aller Verbindungen (Abbildung 35a berücksichtigt auch Daten aus den vergangenen Abfragen, x-Achse auf 90 Tage gestreckt, um den Vergleich mit Abbildung 22 und 23 zu erleichtern).

Abbildung 35a: Erwartete Auslastung für alle Verbindungen, 1. und 2. Klasse (alle Stichproben, 2019 und 2020)

Bis zu einem Monat vor Fahrtbeginn wird vom System in der Regel keine Auslastungs-Information gemeldet. Die Auslastung in der 1. Klasse ist geringfügig kleiner. In der 2. Klasse scheint es noch viele Buchungen einen Tag vor Fahrtantritt zu geben - am Reisetag selber ist die erwartete Auslastung überraschenderweise geringer. Klar wird aber, dass die Auslastung steigt, je weniger das Reisedatum in der Zukunft liegt.

Abbildung 35b: Erwartete Auslastung Berlin -> Stuttgart, 1. und 2. Klasse

Bei der Betrachtung der einzelnen Verbindung Berlin -> Stuttgart ist dieser Trend vielleicht nicht ganz so deutlich, aber er existiert, vor allem in der 2. Klasse. Ein Zusammenhang zur Abbildung 22 lässt sich aber kaum erkennen. Zugegeben: Der Flex-Preis ist in der fernen Zukunft an zwei Punkten günstiger. Nur beim Super-Spar-Preis ist keine klare Reaktion auf die erwartete steigende Nachfrage erkennbar.

Um hier eine wirklich zuverlässige Aussage treffen zu können, müsste man eine konkrete Verbindungen im zeitlichen Verlauf betrachten. So lässt sich nur die Mutmaßung anstellen, dass die Nachfragesteuerung kaum dynamisch auf die erwartete Auslastung reagiert, sondern vielmehr die typischen Reisezeiten berücksichtigt (Freitag / Sonntag). Aber gut, das Ziel war nicht, die Berechnung des Ticket-Preises zu analysieren, sondern seine Komplexität darzustellen.

Der Preis des Umsteigens

Umsteigen ist unbequem und man will es gerne vermeiden. Der Wunsch, sprich die Nachfrage, nach einer Verbindung mit mehreren Umstiegen dürfte also recht gering sein. Im Preis schlägt sich das allerdings kaum wieder. Es gibt durchaus Verbindungen mit mehreren Umstiegen, die teurer sind als die Direktverbindung.

Hier als Beispiel die jeweils günstigsten Flex-Preise der 1. Klasse:

Abbildung 24: 1. Klasse, günstigster Flex-Preis

Die Strecken Berlin -> Stuttgart und München -> Berlin haben also Schnäppchenpotential im Flex-Preis-Segment (wenn die unterschiedlichen Preise an einem Tag verfügbar sind). Nun ist es aber so, dass man mit dem Flexpreis nicht in der Zug-Kategorie aufsteigen darf (siehe oben). Wer also ein Ticket im IC bucht, darf nicht ICE fahren. Schauen wir uns doch mal die Verbindung Berlin -> Stuttgart genauer an:

Abbildung 25: 1. Klasse, Flex-Preis, Mindest-Flex-Preis x durchschnittliche Dauer und Umstiege, Berlin - Stuttgart

Du siehst, das untere Limit bei der ICE-Fahrt liegt bei etwas über 200 Euro. Wenn du ein paar Euro auf der Verbindung sparen willst, musst du fast 3 Stunden mehr einplanen und z.B. IC fahren. Aber was mit der Verbindung mit vier Umstiegen (blauer Kreis)? Das Ticket kostet 188 Euro und beinhaltet eine ICE-Strecke. Handelt es sich damit um ein vollwertiges ICE-Ticket und kann ich damit auch die direkte aber teurere Verbindung am gleichen Tag nutzen? Der Twitter-Support hat das mehr oder weniger bestätigt:

Abbildung 26: Quelle: Twitter

Anmerkung: Die gestellte Frage lautete: “Bei einem Flex-Preis-Ticket mit einem Umstieg (ICE zu IC) habe ich ja ein ICE-Ticket - für den ersten Teil der Strecke. Das Ticket ist dann nur für den 1. Teil gültig?” Mein Verdacht ist allerdings, dass der Support nicht ganz verstanden hat, worauf ich hinaus wollte. Nachfragen lohnt sich also.

Wenn dem so ist, ergibt sich daraus ein wichtiges Kriterium bei der Preissuche: Wenn du ein günstiges Flex-Preis-Ticket zu einer “verrückten” Tageszeit findest, z.B. 0:12 Uhr in der Nacht, das nur eine ICE-Verbindung enthält, kannst du damit den ganzen Tag auf dieser Strecke reisen. Idealerweise auch auf einer Direktverbindung, nur mit dem ICE. In dem Fall ist erst Recht wichtig, Preise immer in einem großzügigen Zeitfenster zu vergleichen.

Noch mal zurück zur Abbildung 25. Die folgende Abbildung 27 zeigt die Flex-Preise für die 2. Klasse:

Abbildung 27: 1. Klasse, Flex-Preis, Mindest-Flex-Preis x durchschnittliche Dauer und Umstiege, Berlin - Stuttgart

Das Preisgefüge ändert sich nicht. Man kann also zumindest festhalten, dass die Dauer und die Anzahl der Umstiege zu den festen Faktoren der Preisbildung gehören.

Beim Super-Spar-Preis gibt es die feste Zugbindung. Hier macht es keinen Sinn, ein ggf. günstigeres Ticket mit vielen Umstiegen zu suchen und dann einfach die Direkt-Verbindung zu nutzen. Das dürfte sich auch bei der Preis-Gestaltung zeigen: Sind weniger bequeme Verbindungen mit mehreren Umstiegen teurer?

Abbildung 28: 1. Klasse, kleinster Super-Spar-Preis

Ja! Der günstigste Preis wird immer für die Direktverbindung aufgerufen. Kann man die Verbindungen mit mehreren Umstiegen also komplett ignorieren? Schwer zu sagen, denn hierbei handelt es sich um die aggregierte Darstellung. Wie sieht es in der Realität aus? Dazu gleich mehr. Zunächst noch mal der Vergleich mit der 2. Klasse:

Abbildung 29: 2. Klasse, kleinster Super-Spar-Preis

Was fällt auf? Die Verbindung Ingolstadt -> Nürnberg mit einem Umstieg kostet 23,00 Euro, inklusive Sitzplatz also 27,00 Euro. Und ist etwas teurer als die Direktverbindung in der 1. Klasse für 26,40 Euro.

Eine weitere wichtige Erkenntnis lautet demnach: Beim Super-Spar-Preis lohnt sich der Vergleich mit der 1. Klasse.

Gibt es Regelmäßigkeiten bei der Preisbildung?

Noch mal zurück zum Median Flex-Preis für alle Verbindungen. Als Grundlage für die Berechnung eines Preises je Reise-Minute möchte ich einmal die angebene und damit ideale Reisedauer der ICE-Verbindungen ohne Umstiege verwenden, da diese im Datensatz direkt vorliegt. Außerdem kann vielleicht der Preis je Kilometer Aufschluss geben. Die folgende Abbildung zeigt die Verhältnisse der 2. zur 1. Klasse für die Flex-Preise aller Direkt-Verbindungen die mit dem ICE bedient werden:

Abbildung 30: Flex-Preis für alle DirektVerbindungen, nur ICE

Der Faktor vom günstigsten zum teuersten Ticket ist in etwa gleich, nämlich knapp 10. Wenn man aber die Dauer als Indiz für die Entfernung nimmt, steigt der Preis nicht so stark an wie die Entfernung. Das zeigt sich in der Veränderung des Preis / Minute. Bei kurzen Strecken ist dieser höher als bei langen Strecken. Das bestätigt auch die Gegenüberstellung in einem Chart:

Abbildung 31: Flex-Preis / Minute und Dauer aller Direkt-Verbindungen, nur ICE

Steigt der Preis also nicht proportional zur Länge der Verbindung, sprich Dauer? Die Werte für Preis / km können das leider nicht eindeutig bestätigen. Hier benötigen wir also mehr Daten.

Aber dafür lässt sich eine andere Feststellung relativ sicher belegen:

Abbildung 32: Differenz 1. und 2. Klasse, Median Flex-Preis

Offensichtlich beträgt der Preis-Aufschlag für die 1. Klasse knapp 168% (bzw. der Nachlass zur 2. Klasse etwa 40%). Das ist jetzt keine bahnbrechende Erkenntnis, obgleich wir ja wissen, dass man die Preise der 1. und 2. Klasse nicht ohne weiteres vergleichen kann. Beim Super-Spar-Preis ist das übrigens nicht ganz so offensichtlich. Dieser schwankt aber auch zu stark, um hier eine zuverlässige Abschätzung zu geben.

Die optimale Reisezeit

Abschließend will ich die Granularität noch etwas erhöhen und mir die Preise über den Tag verteilt anschauen. Die nächste Abbildung zeigt den geringsten Super-Spar-Preise für beide Klassen je Verbindung, über den Tag verteilt:

Abbildung 33: Median-Preis für Direktverbindungen (inkl. Offenburg -> Greifswal) über den Tag verteilt

Die Super-Spar-Preise scheinen relativ stabil zu sein, der Flex-Preis schwankt etwas stärker. Zur Abendzeit werden die Preise aller Kategorien günstiger. Sicherlich ist es hilfreich, hier die Verbindungen im Detail zu untersuchen. Die folgende Abbildung zeigt die Preise der Verbindung München -> Berlin:

Abbildung 34: Median-Preise über die Tageszeit für die Verbindung München -> Berlin

Ein Rückschluss auf die Nachfrage-Steuerung, die ich oben schon betrachtet habe, ist hier übrigens nur schwer möglich. Es handelt sich um die aggregierten Preise und Reisezeiten. Nur soviel: Zur Nachmittagszeit, also vermutlich der Haupt-Reisezeit, gibt es Preis-Spitzen beim Super-Spar-Preis. Der Flex-Preis ist relativ stabil.

Teil 5 - Eine alternative Oberfläche

Nun, da wir die spannenden Zahlenspiele und bunten Grafiken abgearbeitet haben, wollen wir uns eine mögliche Alternative anschauen. Ich habe mich oben schon recht intensiv mit den “Preis-APIs” der Deutschen Bahn beschäftigt. Diese habe ich nicht nur zur stumpfen Abfrage unzähliger Preise verwendet, sondern auch um eine alternative Ticket-Preis-Suche zu bauen: nickets.de

Abbildung 36: nickets - Visuelle Ticket-Preissuche

Die Bedienung sollte eigentlich relativ intutiv sein, das war ja das Ziel der ganzen Übung. Du gibst Start- und Zielbahnhof an, legst den Abfahrts-Zeitpunkt fest und lässt dir die entsprechenden Preise in einer Grafik anzeigen. Dort siehst du zu jeder Verbindung den Super-Spar-Preis sowie den Flex-Preis und die Anzahl der Umstiege. Fährst du mit dem Maus-Cursor über die Kreise, erhälst du zusätzliche Informationen zu dem jeweiligen Ticket. Mit Klick auf die kleinen Kreise kommst du zur Buchung auf der Seite der Deutschen Bahn.

Die Oberfläche befindet sich noch im Beta-Stadium, es kann also zu ungewohnten Verhalten kommen. Im Grunde sollten die Preise aber korrekt angezeigt werden und vor allem aktuell sein. Feedback und Wünsche sind natürlich sehr gerne gesehen.

Download der Postman-Collection

Deutsche-Bahn-Tickets.postman_collection.json_Herunterladen