Tanker-Tracker: Wie ich mir eine eigene Kraftstoffpreis-App gebaut habe – und was ich dabei gelernt habe
Wer regelmäßig tankt, kennt das Gefühl: Man fährt an einer Tankstelle vorbei, sieht den Preis – und drei Straßen weiter wäre es zwei Cent günstiger gewesen. Das hat mich irgendwann genug geärgert, dass ich beschlossen habe, der Sache auf den Grund zu gehen. Nicht nur einmalig, sondern systematisch. Das Ergebnis ist der Tanker-Tracker – eine selbst gehostete Web-App, die Kraftstoffpreise im Raum Dessau-Roßlau kontinuierlich erfasst, speichert und statistisch auswertet.
In diesem Artikel erzähle ich, warum ich das Projekt gestartet habe, wie es technisch aufgebaut ist, welche Erkenntnisse die Daten liefern – und was mich bei der Entwicklung am meisten überrascht hat.
Die Ausgangslage: Zu viel für Sprit bezahlen
Ich wohne in Dessau-Roßlau, einer mittelgroßen Stadt in Sachsen-Anhalt. In meinem Umkreis gibt es rund ein Dutzend Tankstellen – von ESSO über ARAL bis zur freien Tankstelle um die Ecke. Die Preise schwanken täglich, manchmal sogar stündlich. Wer weiß, wann und wo er am günstigsten tankt, kann über das Jahr gesehen eine beachtliche Summe sparen.
Natürlich gibt es dafür bereits Apps. Aber ich wollte mehr: Ich wollte meine Daten, langfristig gespeichert, frei analysierbar – und ohne Abhängigkeit von einem Drittanbieter. Also habe ich angefangen zu bauen.
Die Datenbasis: Die Tankerkönig-API
Ermöglicht wird das Ganze durch die kostenlose Tankerkönig-API der Markttransparenzstelle für Kraftstoffe (MTS-K). Sie stellt aktuelle Kraftstoffpreise aller deutschen Tankstellen unter der Creative-Commons-Lizenz CC BY 4.0 zur freien Nutzung bereit – eine Attribution ist Pflicht, was ich auf jeder Seite der App eingeblendet habe.
Die API bietet zwei zentrale Endpunkte:
- list.php – liefert alle Tankstellen in einem definierten Radius inklusive aktueller Preise in einem einzigen Request.
- prices.php – aktualisiert die Preise für bis zu 10 Stationen pro Anfrage (das API-Limit).
- detail.php – liefert Detailinfos zu einer einzelnen Station: Öffnungszeiten, Sonderregelungen, 24h-Status.
Mein Cronjob ruft die Preise alle 15 Minuten ab – bewusst versetzt auf die Minuten 5, 20, 35 und 50 statt auf die runden Zeiten, wie es die Terms ausdrücklich empfehlen, um die API-Server zu entlasten.
Der Tech-Stack: Kein Framework, kein Build-Step
Ich habe mich bewusst für einen schlanken Stack entschieden. Keine Dependency-Hölle, kein Webpack, kein React – stattdessen:
- PHP 8.3 als Server-Sprache. Nativ, ohne Framework. Für ein Projekt dieser Größe ist das vollkommen ausreichend und macht Deployment und Wartung trivial.
- SQLite als Standard-Datenbank, optional MySQL 8.0+ für größere Installationen. Das Schema wird beim ersten Start automatisch angelegt.
- Bootstrap 5.3 für das Layout – mobil-optimiert, responsive, ohne eigenes CSS-Framework.
- Chart.js 4.4 mit dem date-fns-Adapter für alle Diagramme.
- Nginx + PHP-FPM im Docker-Container, deployed via
docker compose.
Alles läuft über CDN, ohne Build-Step. Wer den Container einmal gestartet hat, kann sofort loslegen – kein npm install, kein Transpiling.
Was die App kann: Ein Überblick
Dashboard
Die Startseite zeigt auf einen Blick die günstigsten aktuellen Preise für E5, E10 und Diesel – mit Tankstellenname und Entfernung. Darunter eine sortierbare Tabelle aller Stationen im Radius. Jede Zeile ist anklickbar: Ein Popup zeigt die vollständigen Stationsdaten inklusive Öffnungszeiten und Links zu OpenStreetMap und Google Maps.
Das Highlight ist die Heatmap: Sie visualisiert, an welchem Wochentag zu welcher Uhrzeit der Durchschnittspreis historisch am niedrigsten war. So sieht man auf einen Blick, dass montags früh morgens oft günstiger getankt werden kann als freitags am Nachmittag.
Preisentwicklung
Ein Zeitreihen-Diagramm zeigt den Preisverlauf aller Stationen – von der letzten Stunde bis zum letzten Jahr. Wählt man eine einzelne Station aus, sieht man deren Preishistorie allein. Zeitachse und Granularität passen sich automatisch an den gewählten Zeitraum an.
Statistische Auswertungen
Hier wird es analytisch interessant. Die App bietet mehrere Tabs:
- Beste Zeit – durchschnittlicher Preis nach Uhrzeit und Wochentag, für alle drei Kraftstoffsorten.
- Feiertage – wie stark weichen die Preise an gesetzlichen Feiertagen vom Wochentags-Durchschnitt ab?
- Wochentage – welcher Wochentag ist teuer, welcher günstig?
- Schulferien – ändert sich das Preisniveau in den Ferien?
- Betreiber – Vergleich der Marken: Welche Kette ist systematisch teurer? Welche hebt Preise in großen Sprüngen an, welche senkt sie schnell wieder?
Automatische Anomalie-Erkennung
Das für mich spannendste Feature: Der Anomalien-Tab analysiert die letzten 14 Tage (oder weniger, wenn nicht genug Daten vorhanden sind) automatisch und meldet textlich, was ungewöhnlich ist. Beispiele:
- Eine Station liegt mehr als 1,5 Standardabweichungen über dem Schnitt.
- An einem bestimmten Tag haben über 40 % aller Stationen gleichzeitig ihre Preise erhöht – ein Hinweis auf koordinierte Preisanpassungen.
- Der Preis ist nach einer Erhöhung nicht wieder auf das Ausgangsniveau zurückgekehrt – ein sogenannter „klebender Preis“.
- Eine Station ist im Zeitraum die volatilste – sie verändert ihre Preise besonders häufig und stark.
Für die Spike-Erkennung nutze ich SQL-Window-Funktionen (LAG()), die mir zeigen, an welchem Zeitpunkt welche Station ihren Preis in einem einzigen Schritt um wie viel erhöht hat – mit Datum und Uhrzeit.
Was mich überrascht hat: Die Tücken der Zeitzone
Was auf den ersten Blick trivial klingt, hat mich ein halbes Wochenende gekostet: Zeitzonen in verteilten Systemen.
PHP-FPM löscht standardmäßig seine Umgebungsvariablen (clear_env = yes). Das bedeutet: Obwohl der Docker-Container mit TZ=Europe/Berlin läuft, kommt diese Information nicht zuverlässig beim PHP-Prozess an. SQLites eingebauter 'localtime'-Modifier zur UTC-Konvertierung schlug damit fehl – alle Zeitangaben in den Statistiken lagen um ein bis zwei Stunden daneben.
Die Lösung: Ich berechne den aktuellen UTC-Offset jetzt in PHP direkt (new DateTime()->getOffset()) und injiziere ihn als expliziten Modifier-String in die SQLite-Abfragen ('+2 hours' im Sommer, '+1 hours' im Winter). Das funktioniert zuverlässig, ist DST-korrekt und unabhängig davon, was PHP-FPM an Umgebungsvariablen weitergibt.
Für MySQL war die Situation eine andere: Dort habe ich auf DATETIME-Spalten statt TIMESTAMP umgestellt und speichere direkt Lokalzeit – so entfällt jede Timezone-Konvertierung auf Datenbankebene komplett.
Terms-Compliance: Was die API erlaubt
Die Tankerkönig-API ist kostenlos – aber nicht ohne Bedingungen. Ich habe die Terms systematisch gegen meinen Code geprüft. Das Ergebnis:
- Der maximale Suchradius von 25 km ist im Formular erzwungen.
- Abrufzeiten sind auf versetzten Minuten (5/20/35/50) statt runden Viertelstunden konfiguriert.
- Die vorgeschriebene Namensnennung (CC BY 4.0) erscheint als Footer auf jeder Seite.
- Der HTTP-Endpunkt zum manuellen Abruf ist mit einem eigenen API-Key geschützt, sodass nicht jeder Besucher beliebig viele Anfragen auslösen kann.
Was ich gelernt habe – und was als Nächstes kommt
Das Projekt hat mir gezeigt, wie viel man aus einfachen Zeitreihendaten herausholen kann, wenn man sie konsequent sammelt und auswertet. Ein paar Dinge haben mich besonders überrascht:
- Preiserhöhungen passieren oft koordiniert – mehrere Stationen gleichzeitig, am selben Tag, in ähnlicher Höhe.
- Manche Marken erhöhen Preise in großen Sprüngen und senken sie dann langsam in vielen kleinen Schritten. Andere machen es umgekehrt.
- Der günstigste Tankezeitpunkt ist statistisch messbar – und er ist nicht zufällig.
Hier geht’s zur fertigen App:
Daten: Tankerkönig · Lizenz: CC BY 4.0
