Google hat seine Flights API schon 2018 dichtgemacht, doch die Flugpreise tanzen munter weiter — auf einer einzigen Inlandsroute mitunter bis zu 17 Mal in 48 Stunden. Wer programmgesteuert an diese Daten will, kommt am Scrapen kaum vorbei.
Ich habe über längere Zeit diverse Wege ausprobiert, Flugdaten aus Google herauszuholen, und das Spielfeld hat sich gehörig verschoben — vor allem, seit Google im Januar 2025 SearchGuard scharf geschaltet hat. In diesem Leitfaden zeige ich dir, wie du mit Playwright einen funktionierenden Python-Scraper für Google Flights aufsetzt, wie du die Anti-Bot-Mechanismen aushebelst, an denen die meisten hängen bleiben, und wie du das Ganze zu einem automatisierten Preis-Tracker mit Warnmeldungen ausbaust. Magst du es ganz ohne Code, gibt es zum Schluss noch einen No-Code-Shortcut mit Thunderbit, der dich in etwa zwei Minuten ans gleiche Ziel bringt.
Warum Google Flights mit Python scrapen?
Google Flights gibt bei der Flugsuche den Ton an. Die Sichtbarkeit in den US-Mobil-Suchergebnissen kletterte auf 47,6 % und ließ damit alle großen OTAs hinter sich. Der dahinterstehende Metasuchmarkt im Reisebereich wird 2024 auf 8,33 Milliarden $ taxiert und legt mit 30,2 % CAGR zu. Seit dem endgültigen Aus der QPX-Express-API am 10. April 2018 führt allerdings kein offizieller Weg mehr zu diesen Daten.
Parallel schwanken die Preise für ein und dieselbe Verbindung um bis zu 50 % — die Spanne zwischen günstigstem und teuerstem Tarif beträgt im Schnitt rund 20 US-Dollar. Airlines wie Delta jonglieren mit 77 Tarifstufen für ihre dynamische Preisgestaltung. Der durchschnittliche US-Returnflug Anfang 2026 schlägt mit 408 US-Dollar zu Buche, und die Flugpreise liegen 14,9 % über dem Vorjahreswert.
Marktbeherrschende Plattform, keine API, Preise im Dauerflimmern — kein Wunder, dass das Scrapen von Google Flights mit Python zu den beliebtesten Projekten auf GitHub und in Reise-Foren zählt.
Davon profitieren vor allem diese Gruppen:
| Nutzertyp | Anwendungsfall | Hauptvorteil |
|---|---|---|
| Privatreisende | Preise für bestimmte Strecken über längere Zeit beobachten | Im Schnitt $50 pro Flug sparen |
| Reisebüros | Wettbewerbsfähige Preisanalysen | Preisparität in Echtzeit überwachen |
| Corporate-Travel-Teams | Kostenoptimierung über verschiedene Routen hinweg | 10–30 % Ersparnis bei Geschäftsreisen |
| Entwickler | Flugpreis-Vergleichs-Apps bauen | Programmgesteuerter Zugriff auf Preisdaten |
| Forschende | Analyse von Preisschwankungen bei Airlines | Für akademische und Marktstudien |
In Foren machen Nutzer kein Geheimnis daraus, warum sie zum Scraping gewechselt sind: „Die Google Flights API wurde eingestellt, also sollte ich stattdessen Web Scraping verwenden“ liest man immer wieder. Und der Aufwand zahlt sich aus: Hopper beziffert seine Prognosegenauigkeit auf 95 % und wertet täglich über 5 Milliarden Preisabfragen aus, während die Expedia-Daten von 2026 zeigen, dass eine Buchung 8–15 Tage im Voraus bei Inlandsflügen rund $25 spart.
Welche Daten kannst du aus Google Flights scrapen?
Eine Ergebnisseite von Google Flights steckt voller Datenfelder. Typischerweise verfügbar sind:
- Name der Airline (inklusive Logo)
- Abflugzeit und Flughafencode
- Ankunftszeit und Flughafencode
- Gesamtdauer des Fluges
- Anzahl der Stopps und Umstiegsdetails (Flughafen, Dauer, Übernachtung)
- Ticketpreis (je nach Währung)
- CO2-Emissionen (kg CO2e, mit prozentualem Vergleich zu typischen Flügen)
- Reiseklasse, Flugnummer, Flugzeugmodell
- Beinfreiheit
- Ausstattung (WLAN, Steckdosen, Media-Streaming)
- Preisniveau (niedrig/typisch/hoch)
- Hinweise zu Verspätungen („Oft mehr als 30 Min. verspätet“)
Was am Ende verfügbar ist, hängt von Route, Datum und Ticketart ab (Einweg vs. Hin- und Rückflug). So sieht ein einzelner gescrapter Flugdatensatz als JSON aus:
{
"search_date": "2026-04-16",
"route": "SFO-JFK",
"departure_date": "2026-05-15",
"flights": [
{
"airline": "United Airlines",
"flight_number": "UA123",
"departure_time": "08:00",
"departure_airport": "SFO",
"arrival_time": "16:35",
"arrival_airport": "JFK",
"duration_minutes": 335,
"stops": 0,
"price_usd": 287,
"price_level": "low",
"co2_kg": 156,
"co2_vs_typical": "-12%",
"travel_class": "Economy"
}
]
}
Python-Umgebung einrichten
Bevor der Scraping-Code losgeht, brauchst du ein paar Grundlagen.
Voraussetzungen:
- Schwierigkeitsgrad: Mittel
- Benötigte Zeit: ca. 1–2 Stunden für das komplette Tutorial
- Was du brauchst: Python 3.7+, grundlegende Python-Kenntnisse, ein Browser auf Chromium-Basis
Benötigte Bibliotheken installieren
Wir setzen auf Playwright für die Browserautomatisierung (Google Flights wird zu 100 % per JavaScript gerendert — einfache HTTP-Requests liefern nichts Brauchbares), dazu ein paar Helfer:
pip install playwright playwright-stealth pandas
playwright install chromium
- Playwright — Browser-Automatisierung ohne GUI, verarbeitet JavaScript-Rendering, integrierte Warte-Mechanismen
- playwright-stealth — kaschiert typische Bot-Erkennungssignale
- pandas — später für Datenanalyse und CSV-Export
Warum Playwright statt Selenium oder Requests?
Mit requests und BeautifulSoup allein kommst du bei Google Flights nicht weit — der Inhalt entsteht komplett per JavaScript. Du brauchst einen echten Browser.
| Feature | Playwright | Selenium | requests + BS4 |
|---|---|---|---|
| JS-Rendering | Voll unterstützt | Voll unterstützt | Keins |
| Geschwindigkeit | Insgesamt 42 % schneller | Basiswert | Für diesen Anwendungsfall nicht geeignet |
| Async-Support | Nativ | Nur sequenziell | Nicht relevant |
| Speicherverbrauch | 30 % geringer | Höher | Minimal |
| Umgehung von Bot-Erkennung | Gut (mit Stealth) | Leichter erkennbar | Nicht relevant |
Playwright ist schneller, moderner und beim Async-Support deutlich überlegen. Für Google Flights also die klare Wahl.
Schritt für Schritt: Google Flights mit Python scrapen
Jetzt zum Herzstück. Wir setzen den Scraper Stück für Stück zusammen.

Schritt 1: Datenklassen definieren
Den Anfang machen Suchparameter und Flugdaten, sauber strukturiert über Python-Dataclasses. Das hält den Code übersichtlich und erleichtert spätere Erweiterungen.
from dataclasses import dataclass, field
from typing import Optional, List
@dataclass
class SearchParams:
origin: str # z. B. "SFO"
destination: str # z. B. "JFK"
departure_date: str # z. B. "2026-05-15"
return_date: Optional[str] = None
trip_type: str = "one-way" # "one-way" oder "round-trip"
travel_class: str = "economy"
@dataclass
class FlightData:
airline: str = ""
departure_time: str = ""
arrival_time: str = ""
duration: str = ""
stops: str = ""
price: str = ""
co2_emissions: str = ""
Jedes Feld spiegelt unmittelbar wider, was wir von der Seite herausholen. Mit dieser Struktur musst du später keine unübersichtlichen Dictionaries mehr durch den Code schleusen.
Schritt 2: Die URL-Struktur von Google Flights verstehen
Google Flights packt die Suchparameter als Base64-kodierte Protobuf-Daten in den URL-Parameter tfs. Diese Kodierung kannst du reverse-engineeren — oder du nimmst den bequemen Weg und baust eine URL in natürlicher Sprache.
Am einfachsten ist dieses Suchmuster:
https://www.google.com/travel/flights?q=flights+from+SFO+to+JFK+on+2026-05-15&curr=USD
Für mehr Kontrolle lässt sich die URL auch programmatisch erzeugen:
def build_flights_url(origin: str, destination: str, date: str) -> str:
base = "https://www.google.com/travel/flights"
query = f"flights from {origin} to {destination} on {date}"
return f"{base}?q={query.replace(' ', '+')}&curr=USD"
Die Alternative — das Reverse-Engineering der Protobuf-Kodierung — liefert mehr Präzision, fliegt aber in dem Moment auseinander, in dem Google das interne Format ändert. Bibliotheken wie fast-flights auf GitHub dekodieren das Protobuf und umgehen so das HTML-Parsing komplett, doch das ist der Ansatz für Fortgeschrittene.
Schritt 3: Browser starten und zu Google Flights navigieren
Jetzt das Playwright-Setup. Mit playwright-stealth drücken wir das Erkennungsrisiko von vornherein.
import asyncio
from playwright.async_api import async_playwright
from playwright_stealth import Stealth
async def scrape_flights(params: SearchParams) -> List[FlightData]:
async with Stealth().use_async(async_playwright()) as pw:
browser = await pw.chromium.launch(
headless=True,
args=[
"--disable-blink-features=AutomationControlled",
"--disable-dev-shm-usage",
"--no-first-run",
]
)
context = await browser.new_context(
viewport={"width": 1920, "height": 1080},
user_agent=(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/125.0.0.0 Safari/537.36"
),
locale="en-US",
timezone_id="America/New_York",
)
# Cookie für Consent vorab setzen, um das Popup zu überspringen
await context.add_cookies([{
"name": "SOCS",
"value": "CAESHwgBEhJnd3NfMjAyNTAyMjctMF9SQzIaBXpoLUNOIAEaBgiAy6O-Bg",
"domain": ".google.com",
"path": "/"
}])
page = await context.new_page()
Im Produktivbetrieb starten wir headless (zum Debuggen einfach auf headless=False umstellen), legen ein realistisches Viewport- und User-Agent-Profil an und setzen das SOCS-Cookie vorab, damit das Consent-Popup gar nicht erst aufpoppt — mehr dazu im Anti-Bot-Abschnitt.
Schritt 4: Zu den Suchergebnissen navigieren
Lade die erzeugte URL und warte, bis die Flugergebnisse auftauchen:
url = build_flights_url(
params.origin, params.destination, params.departure_date
)
await page.goto(url, wait_until="networkidle")
# Warten, bis die Flugdaten geladen sind
await page.wait_for_selector(
"li.pIav2d", timeout=15000
)
Ein Timeout an dieser Stelle hat meist eine von zwei Ursachen: Entweder blockiert das Consent-Popup die Seite (siehe den Cookie-Fix in Schritt 3) oder Google serviert dir eine CAPTCHA-Seite. Beides nehmen wir uns im Anti-Bot-Abschnitt vor.
Schritt 5: Alle Flugergebnisse laden
Google Flights versteckt zusätzliche Treffer hinter einem Button mit der Aufschrift „More flights anzeigen“. Den musst du so oft klicken, bis alle Flüge sichtbar sind:
# „Show more flights“ klicken, bis alle Ergebnisse geladen sind
while True:
try:
more_button = page.locator(
'button:has-text("Show more flights")'
)
if await more_button.is_visible(timeout=3000):
await more_button.click()
await page.wait_for_timeout(2000)
else:
break
except Exception:
break
Die Schleife klickt den Button, gibt dem Nachladen 2 Sekunden und steigt aus, sobald der Button verschwindet. In meinen Tests hatten die meisten Routen 1–3 Ergebnisseiten.
Schritt 6: Flugdaten mit CSS-Selektoren extrahieren
Jetzt lesen wir die eigentlichen Flugdaten aus der geladenen Seite. Hier die Selektoren (Stand April 2026 verifiziert — warum dieses Datum zählt, erkläre ich gleich im Wartungsabschnitt):
flights = []
cards = await page.query_selector_all("li.pIav2d")
for card in cards:
flight = FlightData()
# Name der Airline
airline_el = await card.query_selector(
"div.sSHqwe span:not([class])"
)
if airline_el:
flight.airline = (await airline_el.inner_text()).strip()
# Abflugzeit
dep_el = await card.query_selector(
'span[aria-label*="Departure time"]'
)
if dep_el:
flight.departure_time = (await dep_el.inner_text()).strip()
# Ankunftszeit
arr_el = await card.query_selector(
'span[aria-label*="Arrival time"]'
)
if arr_el:
flight.arrival_time = (await arr_el.inner_text()).strip()
# Dauer
dur_el = await card.query_selector("div.gvkrdb")
if dur_el:
flight.duration = (await dur_el.inner_text()).strip()
# Stopps
stops_el = await card.query_selector("div.EfT7Ae span")
if stops_el:
flight.stops = (await stops_el.inner_text()).strip()
# Preis
price_el = await card.query_selector(
"div.FpEdX span"
)
if price_el:
flight.price = (await price_el.inner_text()).strip()
# CO2-Emissionen
co2_el = await card.query_selector("div.O7CXue")
if co2_el:
flight.co2_emissions = (
await co2_el.get_attribute("aria-label") or ""
).strip()
flights.append(flight)
await browser.close()
return flights
Wichtig: Klassennamen wie pIav2d, sSHqwe und FpEdX stammen aus Googles Closure Compiler und können sich mit jedem Build verschieben. Die aria-label-Selektoren halten deutlich länger. Eine belastbare Wartungsstrategie folgt weiter unten.
Schritt 7: Ergebnisse als JSON oder CSV speichern
Zum Abschluss sichern wir die gescrapten Daten mit Zeitstempel — unverzichtbar fürs spätere Preis-Tracking:
import json
from datetime import datetime
from dataclasses import asdict
async def main():
params = SearchParams(
origin="SFO",
destination="JFK",
departure_date="2026-05-15"
)
flights = await scrape_flights(params)
output = {
"search_date": datetime.now().isoformat(),
"params": asdict(params),
"flights": [asdict(f) for f in flights],
}
with open("flights.json", "w") as f:
json.dump(output, f, indent=2)
# Zusätzlich als CSV speichern
import pandas as pd
df = pd.DataFrame([asdict(f) for f in flights])
df["search_date"] = datetime.now().isoformat()
df["route"] = f"{params.origin}-{params.destination}"
df.to_csv("flights.csv", index=False)
print(f"{len(flights)} Flüge gescrapt")
asyncio.run(main())
Lass das laufen, und du erhältst flights.json sowie flights.csv mit deinen Ergebnissen. In meinen Tests liefert eine SFO-JFK-Suche typischerweise 30–80 Flugoptionen und braucht etwa 15–20 Sekunden.
Überlebensleitfaden gegen Bot-Erkennung beim Google Flights Scraping
An dieser Stelle hören die meisten Tutorials auf. Und genau hier scheitern die meisten Scraper. Google hat SearchGuard im Januar 2025 eingeführt und damit über Nacht fast jeden SERP-Scraper lahmgelegt. Google selbst spricht von „das Ergebnis von zehntausenden Arbeitsstunden und Investitionen in Millionenhöhe“. Beim Scraping bewerten Experten Google Flights mit Schwierigkeitsgrad 4/5.
Kein Konkurrenzartikel geht hier wirklich in die Tiefe — dabei ist genau das der Hauptgrund, warum Scraper plötzlich stillstehen. Hier siehst du, womit du rechnen musst und wie du damit umgehst.

Zufällige Pausen zwischen Anfragen
Der simpelste Schutz gegen Rate Limiting. Zwei Zeilen Code, ordentlicher Effekt:
import time
import random
time.sleep(random.uniform(3, 7))
Setz das zwischen Seitenaufrufe. Feste Intervalle (immer exakt 5 Sekunden) sind verräterisch — zufällig ist besser.
User-Agent-Rotation
Bei jeder Anfrage denselben User-Agent-String mitzuschicken, fällt sofort auf. Nimm eine rotierende Liste:
import random
USER_AGENTS = [
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/125.0.0.0 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 Safari/605.1.15",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/124.0.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0",
]
user_agent = random.choice(USER_AGENTS)
Headless-Erkennung umgehen
Google klopft das Flag navigator.webdriver und weitere Automatisierungssignale ab. Die Bibliothek playwright-stealth kaschiert viele davon; ergänzend setzt du die in Schritt 3 gezeigten Startargumente. Die wichtigsten Flags:
args=[
"--disable-blink-features=AutomationControlled",
"--disable-dev-shm-usage",
"--no-first-run",
]
Damit umschiffst du die einfache Erkennung. SearchGuard geht weiter — es belauert Mausgeschwindigkeit, Tastatur-Timing und Scrollmuster —, aber für Scraping in moderatem Umfang reichen Stealth-Modus plus realistische Pausen oft aus.
Proxy-Rotation: Datacenter vs. Residential
Für mehr als nur eine Handvoll Abfragen brauchst du Proxies. Der Unterschied ist nicht nebensächlich:
| Feature | Datacenter-Proxies | Residential-Proxies |
|---|---|---|
| Geschwindigkeit | 100–1.000 Mbps | 10–100 Mbps |
| Erfolgsquote bei Google | 20–40 % | 85–95 % |
| Kosten | 0,10–0,50 $/IP/Monat | 5–15 $/GB |
| Erkennungsrisiko | Hoch | Sehr niedrig |
Beim Scrapen geschützter Seiten sind Residential Proxies pro erfolgreicher Anfrage etwa 180-mal günstiger. Preisbeispiele 2026: Smartproxy ab 7 $/GB, Bright Data 8,40 $/GB, Oxylabs 8 $/GB.
So bindest du einen Proxy in Playwright ein:
browser = await pw.chromium.launch(
proxy={"server": "http://proxy-host:port",
"username": "user", "password": "pass"}
)
Umgang mit dem Cookie-Consent-Popup
Nutzer melden es immer wieder: Das Zustimmungs-Popup verstellt die Sicht — „zuerst zeigt Google das Popup ‚I agree to terms and conditions‘ an.“ Am saubersten löst du das mit dem vorab gesetzten SOCS-Cookie (siehe Schritt 3). Reicht das nicht, klickst du es einfach weg:
try:
accept_btn = page.locator('button:has-text("Accept all")')
if await accept_btn.is_visible(timeout=3000):
await accept_btn.click()
await page.wait_for_timeout(1000)
except Exception:
pass # Kein Popup vorhanden
Beachte: Der Button-Text richtet sich nach der Sprache — auf Deutsch heißt er etwa „Alle akzeptieren“, auf Französisch „Tout accepter“.
Schnellübersicht Anti-Bot-Techniken
| Technik | Schwierigkeitsgrad | Wirksamkeit | Code nötig? |
|---|---|---|---|
| Zufällige Pausen (2–7 Sek.) | Niedrig | Mittel | 2 Zeilen |
| User-Agent-Rotation | Niedrig | Mittel | 5 Zeilen |
| Headless-Erkennung umgehen | Mittel | Hoch | Playwright-Startargumente |
| playwright-stealth-Plugin | Mittel | 60–80 % bei einfachen Sites | pip install |
| Proxy-Rotation (Datacenter) | Mittel | Mittel | Konfiguration |
| Proxy-Rotation (Residential) | Mittel | 85–95 % Erfolg | Konfiguration |
| Consent-Cookie vorab setzen (SOCS) | Niedrig | Erforderlich | 1 Zeile |
Empfohlene sichere Raten: 10–20 Sekunden Verzögerung zwischen den Anfragen bei IP-Rotation. Googles Schwellen liegen ungefähr bei 100 Anfragen pro Minute und IP, bevor ein 429 droht; dauerhaft mehr als 1.000 Anfragen pro Tag und IP können zu temporären Sperren führen.
Warum deine Google-Flights-Selektoren ständig kaputtgehen – und wie du das behebst
Das mit Abstand größte Problem. Foren quellen über vor Varianten von „Ich bekomme nur 14 leere Listen zurück.“ Jeder Leitfaden reicht dir Selektoren. Kaum einer erklärt, warum sie zerbrechen.
Warum sich Google-Flights-Selektoren ändern
Dahinter stecken drei Hauptgründe:
-
Obfuskation durch den Closure Compiler. Google erzeugt per Closure Stylesheets über
goog.setCssNameMapping()Klassennamen wieBVAVmfundYMlIz. Die wechseln mit jedem Build — manchmal wöchentlich. -
A/B-Tests. Verschiedene Nutzer bekommen zeitgleich verschiedene HTML-Strukturen serviert. Dein Scraper läuft bei dir tadellos und versagt bei jemand anderem in einer anderen Region.
-
Lokale Unterschiede. Nutzer in der EU sehen andere Begriffe, Layouts und sogar andere Datenfelder als Nutzer in den USA.
Robuste Selektoren schreiben
Setz auf Selektoren, die an Bedeutung statt an Optik hängen:
# Fragil — bricht bei jedem Build
price_el = await card.query_selector("div.BVAVmf > div.YMlIz")
# Robuster — an Accessibility-Labels gebunden
dep_el = await card.query_selector('span[aria-label*="Departure time"]')
# Ebenfalls robust — textbasierte Suche
more_btn = page.locator('button:has-text("Show more flights")')
Hierarchie der Selektor-Stabilität (von stabil nach instabil):
aria-label-Attribute — für Barrierefreiheit gedacht, selten geändertdata-*-Attribute — explizit für Funktionen ergänztrole-Attribute — ARIA-Rollen sind semantisch- textbasierte Selektoren — passen auf sichtbaren Inhalt
- Teilstring-Matching bei Klassen — z. B.
[class*="price"] - Vollständig obfuskierte Klassennamen — wenn möglich vermeiden
Eine Validierungsfunktion hinzufügen
Lass kaputte Selektoren nicht klammheimlich leere Daten produzieren. Fang das früh ab:
import logging
logger = logging.getLogger(__name__)
def validate_flight(data: FlightData) -> bool:
required = ["airline", "price", "departure_time",
"arrival_time", "duration"]
valid = True
for field_name in required:
if not getattr(data, field_name, ""):
logger.warning(
f"Fehlendes Feld '{field_name}' — Selektoren müssen möglicherweise aktualisiert werden"
)
valid = False
return valid
Lass das bei jedem gescrapten Flug mitlaufen. Tauchen Warnungen auf, schaust du dir die Seite an und ziehst deine Selektoren nach.
Strategie zur Wartung von Selektoren
- Selektoren monatlich prüfen oder sofort, wenn die Ausgabequalität sinkt
- Selektoren in einem separaten Konfigurations-Dictionary halten, damit Updates leicht von der Hand gehen
- Die Selektoren in diesem Artikel wurden zuletzt im April 2026 verifiziert
- Als Alternative kommt die fast-flights library in Frage — sie setzt auf Protobuf-Dekodierung statt CSS-Selektoren und umschifft dieses Problem ganz (bleibt aber ebenfalls anfällig, sobald Google interne Datenformate ändert)
Vom einmaligen Scrape zum automatisierten Google-Flights-Preis-Tracker
Die meisten Tutorials enden bei „als JSON speichern“. Im Titel dieses Artikels stehen aber „Preisalarme“. Zeit, das einzulösen.
![]()
Den Scraper automatisch planen
Option 1: Python-Bibliothek schedule (am einfachsten, plattformübergreifend):
import schedule
import time
def run_scraper():
asyncio.run(main())
schedule.every().day.at("06:00").do(run_scraper)
schedule.every().day.at("18:00").do(run_scraper)
while True:
schedule.run_pending()
time.sleep(60)
Option 2: Cron-Job (Linux/Mac):
# Täglich um 6:00 und 18:00 ausführen
0 6,18 * * * cd /path/to/scraper && python scraper.py
Option 3: Windows Aufgabenplanung — leg eine einfache Aufgabe an, die python scraper.py nach deinem Zeitplan startet.
Der Haken: Alle drei Varianten setzen einen Rechner voraus, der durchgehend läuft. Machst du das auf einem Laptop, der in den Standby geht, fallen Läufe aus.
Historische Preisdaten speichern
Statt eine JSON-Datei immer wieder zu überschreiben, kannst du Daten an eine SQLite-Datenbank anhängen:
import sqlite3
from datetime import datetime
def init_db():
conn = sqlite3.connect("flights.db")
conn.execute("""
CREATE TABLE IF NOT EXISTS flights (
id INTEGER PRIMARY KEY AUTOINCREMENT,
scrape_date TEXT NOT NULL,
route TEXT NOT NULL,
airline TEXT,
departure_time TEXT,
arrival_time TEXT,
duration TEXT,
stops TEXT,
price_usd REAL,
co2_emissions TEXT
)
""")
conn.execute(
"CREATE INDEX IF NOT EXISTS idx_route_date "
"ON flights(route, scrape_date)"
)
conn.commit()
return conn
def save_flight(conn, route: str, flight: FlightData):
price_num = float(
flight.price.replace("$", "").replace(",", "")
) if flight.price else None
conn.execute(
"INSERT INTO flights "
"(scrape_date, route, airline, departure_time, "
"arrival_time, duration, stops, price_usd, co2_emissions) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
(datetime.now().isoformat(), route, flight.airline,
flight.departure_time, flight.arrival_time,
flight.duration, flight.stops, price_num,
flight.co2_emissions)
)
conn.commit()
Nach einer Woche mit zwei Scrapes pro Tag hast du genug Daten beisammen, um Trends abzulesen.
Preistrends analysieren und Alarme setzen
Finde aus deinen historischen Daten die günstigste Option heraus:
import pandas as pd
import sqlite3
conn = sqlite3.connect("flights.db")
df = pd.read_sql_query(
"SELECT * FROM flights WHERE route = 'SFO-JFK'", conn
)
summary = df.groupby("scrape_date")["price_usd"].agg(
["min", "max", "mean"]
)
cheapest = df.loc[df["price_usd"].idxmin()]
print(
f"Am günstigsten: ${cheapest['price_usd']:.0f} am "
f"{cheapest['scrape_date']} ({cheapest['airline']})"
)
Stoß eine E-Mail-Benachrichtigung an, sobald ein Preis unter deine Schwelle rutscht:
import smtplib
from email.mime.text import MIMEText
def send_price_alert(route, price, threshold, recipient):
msg = MIMEText(
f"Preisalarm! {route}: ${price:.0f} "
f"(unter deinem Schwellenwert von ${threshold:.0f})"
)
msg["Subject"] = f"Flugangebot: {route} für ${price:.0f}"
msg["From"] = "alerts@example.com"
msg["To"] = recipient
with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
server.login("alerts@example.com", "your_app_password")
server.send_message(msg)
# Nach jedem Scrape nach günstigen Angeboten prüfen
min_price = df["price_usd"].min()
threshold = 250
if min_price < threshold:
send_price_alert("SFO-JFK", min_price, threshold,
"you@email.com")
Empfohlene Scraping-Frequenz: Für privates Preis-Monitoring genügen zwei Läufe pro Tag völlig (zufällige Zeitpunkte drücken das Erkennungsrisiko). Für geschäftliche Überwachung alle 4–6 Stunden. Stündliches Scraping nur während kurzer Rabattaktionen und nur vorübergehend.
Der einfache Weg: Thunderbits geplanter Scraper
Klingen Cron-Jobs, ein dauerhaft laufender Server und Proxy-Konfigurationen nach mehr Infrastruktur, als du pflegen magst, deckt Thunderbits Scheduled Scraper denselben Anwendungsfall ohne diesen Ballast ab. Du beschreibst das Intervall in natürlicher Sprache, fütterst deine Google-Flights-URLs ein, und der Scraper läuft automatisch in Thunderbits Cloud-Infrastruktur — mit eingebauter Anti-Bot-Behandlung und direktem Export nach Google Sheets, Excel oder Airtable. Den vollen Python-Ansatz ersetzt er nicht, weil du weniger Freiheiten hast, aber für den konkreten Fall „Ich will eine Preis-Tracking-Tabelle“ ist er der schnellste Weg. Ausprobieren kannst du ihn mit dem kostenlosen Tarif.
Wenn Python überdimensioniert ist: No-Code-Wege, um Google Flights zu scrapen
Seien wir ehrlich: Das ist eine Menge Technik. Nicht jeder braucht so viel Kontrolle. Selektoren brechen, Proxies wollen rotiert, Cron-Jobs überwacht werden. Lautet dein Ziel schlicht „Flugpreise regelmäßig in eine Tabelle bringen“, gibt es schnellere Wege.
Vergleich: Python selbst bauen vs. API-Dienste vs. Thunderbit
| Ansatz | Einrichtungszeit | Programmierung nötig | Anti-Bot abgedeckt | Planung | Kosten |
|---|---|---|---|---|---|
| DIY mit Playwright (dieses Tutorial) | 1–2 Stunden | Python (Mittelstufe) | Manuelle Konfiguration | Manuell (Cron) | Kostenlos + Proxy-Kosten |
| SerpApi Google Flights Endpoint | 15 Min. | Nur API-Aufrufe | Inbegriffen | Über API | ca. 50 $/Monat oder mehr |
| Thunderbit Chrome Extension | 2 Min. | Keine | Cloud-Scraping | Integrierter Scheduler | Kostenloser Tarif verfügbar |
Ein Wort zu SerpApi: Google reichte im Dezember 2025 eine DMCA-Klage gegen SerpApi ein mit der Behauptung, deren Anfragen seien binnen zwei Jahren um 25.000 % gestiegen. Diese rechtliche Unsicherheit solltest du im Hinterkopf behalten, wenn du API-Anbieter abwägst.
So scrapt Thunderbit Google Flights
Öffne deine Google-Flights-Suchergebnisse in Chrome, klick auf Thunderbits Schaltfläche „KI-Felder vorschlagen“ — die KI liest die Seite aus und schlägt Spalten wie Airline, Preis, Abflugzeit und Stopps vor —, prüf die Vorschläge und klick dann auf „Scrapen“. Die Ergebnisse erscheinen in einer Tabelle, die du nach Excel, Google Sheets, Airtable oder Notion exportierst — alles im kostenlosen Tarif.
Für genau den Preis-Tracking-Fall ersetzen Thunderbits Scheduled Scraper und Cloud Scraping (mit bis zu 50 Seiten parallel) die komplette Cron-/Proxy-/Server-Infrastruktur.
Python gibt dir volle Kontrolle und grenzenlose Anpassbarkeit. Thunderbit liefert Tempo und null Wartungsaufwand. Wähle nach deinem echten Ziel. Magst du mehr über No-Code-Scraping erfahren, lies unseren Leitfaden zu den besten No-Code-Web-Scrapern.

Ist das Scrapen von Google Flights legal? Das solltest du wissen
In Foren kommt das regelmäßig hoch: „Google Flights direkt zu scrapen verstößt gegen Googles AGB.“ Ein berechtigter Einwand — gerade weil die API eingestellt wurde und es keine offiziell gesegnete Alternative gibt.
AGB-Verstoß vs. rechtliche Haftung
Googles Nutzungsbedingungen (aktualisiert am 22. Mai 2024) halten fest, dass Nutzer die Dienste oder Inhalte nicht „mithilfe automatisierter Mittel (z. B. Roboter, Spider oder Scraper) abrufen oder verwenden“ dürfen. Ein AGB-Verstoß ist eine Vertragsverletzung (zivilrechtlich) — und damit nicht dasselbe wie ein Gesetzesverstoß.
Der wichtigste juristische Präzedenzfall ist hiQ v. LinkedIn (Ninth Circuit, 2022): Demnach verstößt das Scraping öffentlich zugänglicher Daten nicht gegen den Computer Fraud and Abuse Act (CFAA). Der Fall endete jedoch mit einem Vergleich, und Googles Klage gegen SerpApi vom Dezember 2025 fußt auf einer anderen juristischen Theorie — DMCA Section 1201 (Umgehung technischer Schutzmaßnahmen) — und wiegt potenziell schwerer.
Best Practices für verantwortungsvolles Scraping
- Anfragen drosseln — 10–20 Sekunden Verzögerung mit IP-Rotation
- Keine personenbezogenen Daten scrapen — Flugpreise sind öffentlich angezeigte, aggregierte Daten
- CAPTCHAs nicht programmgesteuert umgehen (genau hier lauert das DMCA-Risiko)
- Daten für persönliche Recherche nutzen, nicht für ein konkurrierendes kommerzielles Produkt ohne passende Lizenz
- Offizielle APIs prüfen, sofern verfügbar
Alternative Datenquellen
Wirkt Scraping für deinen Fall zu riskant, gibt es legale API-Optionen:
| Anbieter | Kosten | Kostenloser Tarif | Hinweise |
|---|---|---|---|
| SerpApi | 75–3.750 $/Monat oder mehr | 250 Suchen/Monat | Direktes Google-Flights-JSON (rechtlich umstritten) |
| Kiwi Tequila | Kostenlos (Affiliate-Modell) | Unbegrenzt | Gut für Startups und Tests |
| Amadeus | Pay-as-you-go | 2.000 Anfragen/Monat | 400+ Airlines, Buchungsfunktionen |
| Skyscanner | Individuell | Genehmigung erforderlich | 52 Märkte, 30 Sprachen |
Wir haben außerdem einen ausführlicheren Artikel zu den rechtlichen Auswirkungen von Web Scraping, falls du das Thema bis ins Detail aufdröseln willst.
Fazit und wichtigste Erkenntnisse
Das war viel auf einmal. Worauf es ankommt:
- Python + Playwright ist der flexibelste Ansatz für Google Flights Scraping, verlangt aber laufende Wartung
- Anti-Bot-Maßnahmen wie Verzögerungen, User-Agent-Rotation und Residential Proxies sind kein Nice-to-have — sie sind für Stabilität essenziell, gerade nach SearchGuard
- Selektoren brechen häufig — nutze nach Möglichkeit
aria-label- und textbasierte Selektoren, prüfe deine Ergebnisse und halte Wartungsintervalle ein - Automatisiere mit
scheduleoder Cron, um aus einem einmaligen Scrape einen echten Preis-Tracker mit historischen Daten und E-Mail-Warnungen zu formen - Thunderbit bietet die No-Code-Alternative mit integrierter Planung, Cloud-Scraping und Anti-Bot-Unterstützung — ideal, wenn du eine Preis-Tracking-Tabelle statt eines Programmierprojekts willst
- Bleib innerhalb der rechtlichen Grenzen — drossele Anfragen, scrape nur öffentliche Daten und prüfe für kommerzielle Nutzung API-Alternativen
Nimm den Code aus diesem Tutorial oder installier die Thunderbit Chrome Extension für den Schnellweg. So oder so verfolgst du künftig Flugpreise, statt Google Flights pausenlos von Hand zu aktualisieren.
Mehr Python-Scraping-Techniken findest du in unseren Leitfäden zu Web Scraping mit Python und den besten Python-Web-Scraping-Tools.
FAQs
1. Kann ich Google Flights ohne Python scrapen?
Ja. API-Dienste wie SerpApi und Kiwi Tequila liefern strukturierte Flugdaten über API-Aufrufe (ohne Browser-Automatisierung). Für einen komplett No-Code-Ansatz kann die Thunderbit Chrome Extension Google-Flights-Ergebnisse direkt aus dem Browser scrapen — mit KI-gestützten Feldern und Export per Klick.
2. Blockiert Google das Scraping von Flugdaten?
Google setzt Bot-Erkennung (SearchGuard), CAPTCHAs und Rate Limiting ein. Mit den richtigen Anti-Bot-Maßnahmen — zufällige Verzögerungen, User-Agent-Rotation, Residential Proxies und Stealth-Browser-Einstellungen — lässt sich Scraping in moderatem Umfang zuverlässig betreiben. Konkrete Techniken und Schwellenwerte findest du im Anti-Bot-Abschnitt oben.
3. Wie oft sollte ich Google Flights für Preis-Tracking scrapen?
Zweimal täglich zu zufälligen Zeiten reicht fürs private Preis-Monitoring und hält das Erkennungsrisiko niedrig. Für geschäftliche Überwachung alle 4–6 Stunden mit Proxy-Rotation. Stündliches Scraping nur während kurzfristiger Tarifaktionen — sonst steigt die Blockierwahrscheinlichkeit deutlich.
4. Gibt es eine kostenlose Google-Flights-API?
Die offizielle Google QPX Express API wurde im April 2018 eingestellt. Einen kostenlosen offiziellen Ersatz gibt es nicht. Am nächsten kommt die kostenlose Kiwi Tequila API (Affiliate-Modell, unbegrenzte Suchen). SerpApi bietet 250 kostenlose Suchen pro Monat. Für die meisten ist Scraping oder ein No-Code-Tool wie Thunderbit der praktikabelste Weg.
5. Warum liefern meine Google-Flights-CSS-Selektoren immer wieder leere Daten?
Google erzeugt mit dem Closure Compiler obfuskierte Klassennamen, die sich mit jedem Build ändern. A/B-Tests und lokale Unterschiede sorgen zudem dafür, dass die HTML-Struktur zwischen Nutzern variiert. Die Lösung: aria-label-Attribute und textbasierte Selektoren statt Klassennamen verwenden, eine Validierungsfunktion einbauen, um Probleme früh zu erkennen, und die Selektoren monatlich prüfen. Eine ausführliche Strategie findest du im Abschnitt zur Selektor-Wartung.
Mehr erfahren


