Google wyłączył API Flights już w 2018 roku, ale ceny biletów nadal się zmieniają — nawet do 17 razy w ciągu 48 godzin na jednej trasie krajowej. Jeśli chcesz mieć programowy dostęp do tych danych, scrapowanie jest w praktyce jedyną realną opcją.
Spędziłem sporo czasu testując różne sposoby pobierania danych lotniczych z Google i ten obszar mocno się zmienił — zwłaszcza po wdrożeniu SearchGuard przez Google w styczniu 2025 roku. W tym poradniku pokażę Ci, jak zbudować działający scraper Google Flights w Pythonie z użyciem Playwright, jak poradzić sobie z zabezpieczeniami anty-bot, które wykładają większość osób, a następnie jak rozwinąć to w automatyczny tracker cen z alertami. Jeśli wolisz całkiem pominąć kod, pokażę też prostą ścieżkę no-code z Thunderbit, która pozwala dojść do tego samego efektu w około dwie minuty.
Dlaczego warto scrapować Google Flights w Pythonie?
Google Flights dominuje w wyszukiwaniu lotów. Jego widoczność na urządzeniach mobilnych w USA wzrosła do 47,6%, wyprzedzając wszystkie duże OTA. Rynek metawyszukiwarek turystycznych, który za nim stoi, był wyceniany na $8,33 mld w 2024 roku i rośnie w tempie 30,2% CAGR. A jednak od momentu, gdy API QPX Express zostało trwale wyłączone 10 kwietnia 2018 roku, nie ma oficjalnego sposobu, by pobierać te dane programowo.
Jednocześnie ceny biletów na tę samą trasę potrafią się wahać nawet o 50%, a średnia różnica między najniższą i najwyższą ceną to około 20 dolarów. Linie takie jak Delta stosują 77 przedziałów taryfowych do dynamicznego ustalania cen. Średnia cena biletu w obie strony w USA na początku 2026 roku wynosi 408 USD, a ceny lotów są o 14,9% wyższe rok do roku.
Dominująca platforma, brak API, zmienne ceny. Nic dziwnego, że scrapowanie Google Flights w Pythonie stało się jednym z najczęściej omawianych projektów zarówno na GitHubie, jak i na forach podróżniczych.
Oto, kto na tym zyskuje i w jaki sposób:
| Typ użytkownika | Zastosowanie | Najważniejsza korzyść |
|---|---|---|
| Indywidualni podróżni | Śledzenie cen dla konkretnych tras w czasie | Oszczędność średnio $50 na locie |
| Biura podróży | Analiza konkurencyjnych cen | Monitoring parytetu taryf w czasie rzeczywistym |
| Zespoły ds. podróży służbowych | Optymalizacja kosztów na trasach | 10–30% oszczędności na podróżach firmowych |
| Programiści | Tworzenie aplikacji porównujących ceny | Programowy dostęp do danych cenowych |
| Badacze | Analiza zmienności cen linii lotniczych | Badania akademickie i rynkowe |
Użytkownicy forów nie przebierają w słowach, gdy tłumaczą, czemu zaczęli scrapować: „Google Flights API zostało wycofane i powinienem zamiast tego użyć web scrapingu” — taki komentarz powraca regularnie. A ROI jest realne — Hopper twierdzi, że osiąga 95% skuteczności prognoz, analizując ponad 5 miliardów wycen dziennie, a dane Expedia z 2026 roku pokazują, że rezerwacja 8–15 dni wcześniej pozwala zaoszczędzić około $25 na lotach krajowych.
Jakie dane można scrapować z Google Flights?
Strona wyników Google Flights zawiera zaskakująco bogaty zestaw pól. Zwykle dostępne są:
- Nazwa linii lotniczej (oraz logo)
- Godzina wylotu i kod lotniska
- Godzina przylotu i kod lotniska
- Łączny czas lotu
- Liczba przesiadek oraz szczegóły międzylądowań (lotnisko, czas trwania, status nocny)
- Cena biletu (zależna od waluty)
- Emisja CO2 (kg CO2e, z procentową różnicą względem typowych lotów)
- Klasa podróży, numer lotu, model samolotu
- Miejsce na nogi
- Udogodnienia (Wi‑Fi, gniazdka, streaming multimediów)
- Wskaźnik poziomu ceny (niska / typowa / wysoka)
- Ostrzeżenia o opóźnieniach („Często opóźniony o ponad 30 min”)
Dostępność danych zależy od trasy, daty i typu biletu (w jedną stronę vs. w obie strony). Oto, jak może wyglądać pojedynczy rekord lotu po scrapowaniu w formacie JSON:
{
"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"
}
]
}
Konfiguracja środowiska Python
Zanim napiszemy jakikolwiek kod do scrapowania, potrzebujesz kilku rzeczy na miejscu.
Wymagania wstępne:
- Poziom trudności: średni
- Czas potrzebny: około 1–2 godzin na pełny tutorial
- Czego potrzebujesz: Python 3.7+, podstawowa znajomość Pythona, przeglądarka oparta na Chrome
Zainstaluj wymagane biblioteki
Do automatyzacji przeglądarki używamy Playwright (Google Flights jest w 100% renderowane przez JavaScript — zwykłe żądania HTTP nic sensownego nie zwracają), plus kilka pomocniczych bibliotek:
pip install playwright playwright-stealth pandas
playwright install chromium
- Playwright — automatyzacja przeglądarki headless, obsługa renderowania JavaScript, wbudowane mechanizmy oczekiwania
- playwright-stealth — maskuje typowe sygnały wykrywania bota
- pandas — późniejsza analiza danych i eksport do CSV
Dlaczego Playwright zamiast Selenium albo requests
Google Flights nie zadziała samym requests + BeautifulSoup — treść strony jest renderowana całkowicie przez JavaScript. Potrzebujesz prawdziwej przeglądarki.
| Funkcja | Playwright | Selenium | requests + BS4 |
|---|---|---|---|
| Renderowanie JS | Pełna obsługa | Pełna obsługa | Brak |
| Szybkość | o 42% szybszy ogólnie | Punkt odniesienia | Nie dotyczy w tym zastosowaniu |
| Obsługa async | Natywna | Tylko sekwencyjna | Nie dotyczy |
| Zużycie pamięci | o 30% niższe | Wyższe | Minimalne |
| Omijanie wykrywania botów | Dobre (z stealth) | Łatwiejsze do wykrycia | Nie dotyczy |
Playwright jest szybszy, nowocześniejszy i lepiej wspiera asynchroniczność. W przypadku Google Flights to zdecydowany zwycięzca.
Krok po kroku: jak scrapować Google Flights w Pythonie
To jest sedno poradnika. Zbudujemy scraper kawałek po kawałku.

Krok 1: Zdefiniuj klasy danych
Zacznij od uporządkowania parametrów wyszukiwania i danych lotu przy użyciu dataclass w Pythonie. Dzięki temu kod będzie czystszy i łatwiejszy do rozbudowy.
from dataclasses import dataclass, field
from typing import Optional, List
@dataclass
class SearchParams:
origin: str # np. "SFO"
destination: str # np. "JFK"
departure_date: str # np. "2026-05-15"
return_date: Optional[str] = None
trip_type: str = "one-way" # "one-way" lub "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 = ""
Każde pole odpowiada bezpośrednio temu, co będziemy wyciągać ze strony. Taka struktura na starcie sprawia, że później nie trzeba przekazywać chaotycznych słowników po całym kodzie.
Krok 2: Zrozum strukturę URL Google Flights
Google Flights koduje parametry wyszukiwania z użyciem Protobuf zakodowanego w Base64 w parametrze URL tfs. Możesz albo odwracać ten mechanizm, albo wybrać prostsze podejście: zbudować adres URL na bazie zapytania w naturalnym języku.
Najprostsza metoda to format wyszukiwania:
https://www.google.com/travel/flights?q=flights+from+SFO+to+JFK+on+2026-05-15&curr=USD
Jeśli chcesz większej kontroli, możesz generować URL programowo:
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"
Alternatywa — odtwarzanie kodowania Protobuf — daje większą precyzję, ale psuje się, gdy Google zmienia wewnętrzny format. Biblioteki takie jak fast-flights na GitHubie używają dekodowania Protobuf, by całkiem ominąć parsowanie HTML, ale to bardziej zaawansowane podejście.
Krok 3: Uruchom przeglądarkę i przejdź do Google Flights
Oto konfiguracja Playwright. Używamy playwright-stealth, aby od początku zmniejszyć ryzyko wykrycia.
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",
)
# Ustaw ciasteczko zgody z góry, żeby ominąć popup
await context.add_cookies([{
"name": "SOCS",
"value": "CAESHwgBEhJnd3NfMjAyNTAyMjctMF9SQzIaBXpoLUNOIAEaBgiAy6O-Bg",
"domain": ".google.com",
"path": "/"
}])
page = await context.new_page()
Uruchamiamy headless w środowisku produkcyjnym (przełącz na headless=False, jeśli debugujesz), ustawiamy realistyczny viewport i user-agent, a także prekonfigurujemy cookie SOCS, żeby ominąć okno zgody — więcej o tym w sekcji anty-bot.
Krok 4: Przejdź do wyników wyszukiwania
Załaduj zbudowany adres i poczekaj, aż pojawią się wyniki lotów:
url = build_flights_url(
params.origin, params.destination, params.departure_date
)
await page.goto(url, wait_until="networkidle")
# Poczekaj, aż załadują się wyniki lotów
await page.wait_for_selector(
"li.pIav2d", timeout=15000
)
Jeśli tutaj pojawi się timeout, zwykle oznacza to, że albo popup zgody zablokował stronę (patrz poprawka z cookie w Kroku 3), albo Google wyświetla CAPTCHA. Oba przypadki omówimy w sekcji anty-bot.
Krok 5: Załaduj wszystkie wyniki lotów
Google Flights ukrywa dodatkowe wyniki pod przyciskiem „Pokaż więcej lotów”. Trzeba klikać go wielokrotnie, aż wszystkie wyniki staną się widoczne:
# Klikaj "Pokaż więcej lotów", aż wszystkie wyniki się załadują
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
Ta pętla klika przycisk, czeka 2 sekundy na wyrenderowanie kolejnych wyników i kończy działanie, gdy przycisk przestaje być widoczny. W moich testach większość tras ma od 1 do 3 stron wyników.
Krok 6: Wyciągnij dane lotów za pomocą selektorów CSS
Teraz parsujemy rzeczywiste dane z załadowanej strony. Oto selektory (zweryfikowane na kwiecień 2026 — dlatego niżej omawiam, czemu ta data ma znaczenie):
flights = []
cards = await page.query_selector_all("li.pIav2d")
for card in cards:
flight = FlightData()
# Nazwa linii lotniczej
airline_el = await card.query_selector(
"div.sSHqwe span:not([class])"
)
if airline_el:
flight.airline = (await airline_el.inner_text()).strip()
# Godzina wylotu
dep_el = await card.query_selector(
'span[aria-label*="Departure time"]'
)
if dep_el:
flight.departure_time = (await dep_el.inner_text()).strip()
# Godzina przylotu
arr_el = await card.query_selector(
'span[aria-label*="Arrival time"]'
)
if arr_el:
flight.arrival_time = (await arr_el.inner_text()).strip()
# Czas trwania
dur_el = await card.query_selector("div.gvkrdb")
if dur_el:
flight.duration = (await dur_el.inner_text()).strip()
# Przesiadki
stops_el = await card.query_selector("div.EfT7Ae span")
if stops_el:
flight.stops = (await stops_el.inner_text()).strip()
# Cena
price_el = await card.query_selector(
"div.FpEdX span"
)
if price_el:
flight.price = (await price_el.inner_text()).strip()
# Emisja CO2
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
Uwaga: klasy takie jak pIav2d, sSHqwe i FpEdX są generowane przez Google Closure Compiler i mogą zmienić się przy dowolnej nowej wersji. Selektory oparte na aria-label są znacznie stabilniejsze. Pełną strategię utrzymania opisuję poniżej.
Krok 7: Zapisz wyniki do JSON lub CSV
Na koniec zapisz wyciągnięte dane z timestampem (to kluczowe później przy śledzeniu cen):
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)
# Zapisz też jako CSV
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"Scraped {len(flights)} flights")
asyncio.run(main())
Uruchom to, a powinieneś otrzymać pliki flights.json i flights.csv z wynikami. W moich testach wyszukiwanie SFO-JFK zwykle zwraca od 30 do 80 opcji lotów i zajmuje około 15–20 sekund.
Przewodnik przetrwania anty-bot przy scrapowaniu Google Flights
Większość poradników kończy się tutaj. Większość scraperów — też. Google wdrożył SearchGuard w styczniu 2025, co z dnia na dzień zablokowało niemal wszystkie scrapery SERP. Google opisuje to jako „efekt dziesiątek tysięcy roboczogodzin i milionów dolarów inwestycji”. Google Flights jest oceniane jako 4/5 trudności do scrapowania.
Żaden konkurencyjny artykuł nie omawia tego tak szczegółowo, a to właśnie ten punkt najczęściej powoduje, że scraper przestaje działać. Oto, z czym się zmagasz i jak sobie z tym radzić.

Losowe opóźnienia między żądaniami
Najprostsza obrona przed limitowaniem. Dwie linie kodu, średnia skuteczność:
import time
import random
time.sleep(random.uniform(3, 7))
Dodaj to między nawigacjami między stronami. Stałe interwały (np. dokładnie 5 sekund za każdym razem) to czerwona flaga — losuj je.
Rotacja user-agenta
Wysyłanie za każdym razem tego samego ciągu user-agent to łatwy sygnał rozpoznawczy. Rotuj z listy:
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)
Obejście wykrywania trybu headless
Google sprawdza flagę navigator.webdriver i inne sygnały automatyzacji. Biblioteka playwright-stealth obsługuje większość z nich, ale warto też ustawić argumenty uruchomieniowe pokazane w Kroku 3. Najważniejsze flagi:
args=[
"--disable-blink-features=AutomationControlled",
"--disable-dev-shm-usage",
"--no-first-run",
]
To pozwala ominąć podstawowe wykrywanie. SearchGuard idzie dalej — monitoruje prędkość ruchu myszy, czas reakcji klawiatury i wzorce przewijania — ale przy średnich wolumenach scrapowania stealth mode plus realistyczne opóźnienia zwykle wystarczą.
Rotacja proxy: data center vs residential
Przy czymś więcej niż kilka wyszukiwań potrzebujesz proxy. Różnica ma znaczenie:
| Cecha | Proxy data center | Proxy residential |
|---|---|---|
| Szybkość | 100–1,000 Mbps | 10–100 Mbps |
| Skuteczność (Google) | 20–40% | 85–95% |
| Koszt | $0.10–$0.50/IP/mies. | $5–$15/GB |
| Ryzyko wykrycia | Wysokie | Bardzo niskie |
Proxy residential są około 180 razy tańsze na skuteczne żądanie przy scrapowaniu chronionych serwisów. Ceny dostawców w 2026 roku: Smartproxy od $7/GB, Bright Data $8.40/GB, Oxylabs $8/GB.
Dodaj proxy do Playwright w ten sposób:
browser = await pw.chromium.launch(
proxy={"server": "http://proxy-host:port",
"username": "user", "password": "pass"}
)
Obsługa popupu zgody na ciasteczka
Użytkownicy regularnie wskazują popup „I agree to terms” jako blokadę: „najpierw Google pokaże popup z 'I agree to terms and conditions'.” Najczystsza poprawka to wcześniejsze ustawienie cookie SOCS (pokazane w Kroku 3). Jeśli to nie zadziała, kliknij przez popup:
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 # Brak popupu
Uwaga: tekst przycisku różni się zależnie od języka — po niemiecku „Alle akzeptieren”, po francusku „Tout accepter”.
Szybka ściąga anty-bot
| Technika | Trudność | Skuteczność | Wymagany kod? |
|---|---|---|---|
| Losowe opóźnienia (2–7 s) | Niska | Średnia | 2 linie |
| Rotacja user-agenta | Niska | Średnia | 5 linii |
| Obejście detekcji headless | Średnia | Wysoka | Argumenty uruchomieniowe Playwright |
| Wtyczka playwright-stealth | Średnia | 60–80% na podstawowych stronach | pip install |
| Rotacja proxy (data center) | Średnia | Średnia | Konfiguracja |
| Rotacja proxy (residential) | Średnia | 85–95% skuteczności | Konfiguracja |
| Ustawienie cookies zgody (SOCS) | Niska | Wymagane | 1 linia |
Zalecane bezpieczne tempo: trzymaj opóźnienia 10–20 sekund między żądaniami przy rotacji IP. Szacunkowe limity Google to około 100 żądań/minutę na IP, zanim pojawi się 429, a stały wolumen powyżej 1,000 żądań dziennie na IP może wywołać tymczasowe blokady.
Dlaczego Twoje selektory Google Flights ciągle się psują (i jak to naprawić)
To zdecydowanie najczęstszy problem. Wątków na forach pełno jest wariacji na temat: „zwraca mi tylko 14 pustych list”. Każdy poradnik daje selektory. Żaden nie tłumaczy, czemu przestają działać.
Dlaczego selektory Google Flights się zmieniają
Powody są trzy:
-
Obfuskacja przez Closure Compiler. Google używa Closure Stylesheets, aby generować nazwy klas takie jak
BVAVmfiYMlIzprzezgoog.setCssNameMapping(). Zmieniają się one przy każdym buildzie — czasem co tydzień. -
Testy A/B. Różni użytkownicy widzą równocześnie różne struktury HTML. Twój scraper może działać u Ciebie, a u kogoś w innym regionie już nie.
-
Różnice lokalizacyjne. Użytkownicy z UE widzą inne nazwy, układ, a nawet pola danych niż użytkownicy z USA.
Jak pisać odporniejsze selektory
Wybieraj selektory oparte na znaczeniu, a nie na wyglądzie:
# Kruche — psuje się przy każdym buildzie
price_el = await card.query_selector("div.BVAVmf > div.YMlIz")
# Odporniejsze — oparte na etykietach dostępności
dep_el = await card.query_selector('span[aria-label*="Departure time"]')
# Równie odporne — dopasowanie po tekście
more_btn = page.locator('button:has-text("Show more flights")')
Hierarchia stabilności selektorów (od najbardziej do najmniej stabilnych):
- Atrybuty
aria-label— związane z dostępnością, rzadko się zmieniają - Atrybuty
data-*— dodawane celowo do działania funkcji - Atrybuty
role— role ARIA są semantyczne - Selektory oparte na tekście — dopasowują widoczną treść
- Dopasowanie klas po fragmencie — np.
[class*="price"] - Pełne obfuskowane nazwy klas — unikać, jeśli to tylko możliwe
Dodaj funkcję walidacji
Nie pozwól, by uszkodzone selektory cicho produkowały puste dane. Wykryj problem od razu:
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"Brakuje pola '{field_name}' — selektory mogą wymagać aktualizacji"
)
valid = False
return valid
Uruchamiaj to dla każdego zeskrobanego lotu. Jeśli zaczną pojawiać się ostrzeżenia, czas sprawdzić stronę i zaktualizować selektory.
Strategia utrzymania selektorów
- Sprawdzaj selektory raz w miesiącu albo natychmiast, gdy spada jakość danych
- Trzymaj selektory w osobnym słowniku konfiguracyjnym, aby łatwo je aktualizować
- Selektory w tym artykule zostały ostatnio zweryfikowane: kwiecień 2026
- Rozważ bibliotekę fast-flights jako alternatywę — używa dekodowania Protobuf zamiast selektorów CSS, całkowicie omijając ten problem (choć nadal ma własną kruchość, gdy Google zmienia wewnętrzne formaty danych)
Od jednorazowego scrapowania do automatycznego trackera cen Google Flights
Większość poradników kończy się na „zapisz do JSON”. Tytuł tego artykułu mówi o „alertach cenowych”. Czas dostarczyć końcówkę.
![]()
Zaplanuj uruchamianie scrapera automatycznie
Opcja 1: biblioteka Python schedule (najprostsza, działa na wielu platformach):
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)
Opcja 2: cron (Linux/Mac):
# Uruchamiaj codziennie o 6:00 i 18:00
0 6,18 * * * cd /path/to/scraper && python scraper.py
Opcja 3: Harmonogram zadań Windows — utwórz podstawowe zadanie, które uruchamia python scraper.py według wybranego harmonogramu.
Minus: wszystkie te opcje wymagają maszyny, która jest stale włączona. Jeśli uruchamiasz to na laptopie, który usypia się sam, przegapisz zaplanowane scrapowania.
Zapisuj historyczne dane cenowe
Zamiast nadpisywać plik JSON, dopisuj dane do bazy SQLite:
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()
Po tygodniu scrapowania dwa razy dziennie będziesz mieć już dość danych, by zacząć zauważać trendy.
Analizuj trendy cenowe i ustaw alerty
Znajdź najtańszą opcję w danych historycznych:
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"Najtańszy: ${cheapest['price_usd']:.0f} w dniu "
f"{cheapest['scrape_date']} ({cheapest['airline']})"
)
Wyślij alert e-mail, gdy cena spadnie poniżej Twojego progu:
import smtplib
from email.mime.text import MIMEText
def send_price_alert(route, price, threshold, recipient):
msg = MIMEText(
f"Alert spadku ceny! {route}: ${price:.0f} "
f"(poniżej Twojego progu ${threshold:.0f})"
)
msg["Subject"] = f"Okazja na lot: {route} za ${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)
# Po każdym scrapowaniu sprawdzaj okazje
min_price = df["price_usd"].min()
threshold = 250
if min_price < threshold:
send_price_alert("SFO-JFK", min_price, threshold,
"you@email.com")
Rekomendowana częstotliwość scrapowania: dwa razy dziennie wystarczy do osobistego śledzenia cen (losowe godziny zmniejszają ryzyko wykrycia). Co 4–6 godzin, jeśli monitorujesz to biznesowo. Co godzinę tylko w czasie dużych wyprzedaży i wyłącznie tymczasowo.
Łatwa droga: Scheduled Scraper w Thunderbit
Jeśli zarządzanie cronem, stale działającym serwerem i konfiguracją proxy brzmi jak większa infrastruktura, niż chcesz utrzymywać, Scheduled Scraper w Thunderbit rozwiązuje ten sam problem bez całego tego narzutu. Opisujesz interwał scrapowania prostym językiem, podajesz adresy URL Google Flights, a scraper działa automatycznie w chmurze Thunderbit — z wbudowaną obsługą anty-bot i eksportem wyników bezpośrednio do Google Sheets, Excela lub Airtable. To nie zastępuje w pełni podejścia w Pythonie (tracisz część personalizacji), ale dla konkretnego przypadku „chcę arkusz do śledzenia cen” to najszybsza droga. Możesz wypróbować go w darmowym planie.
Gdy Python to przesada: no-code sposoby na scrapowanie Google Flights
Po zbudowaniu całego tego rozwiązania powiem wprost: to sporo ruchomych części. Nie każdy potrzebuje takiego poziomu kontroli. Selektory się psują, proxy trzeba rotować, cron wymaga nadzoru. Jeśli Twoim celem jest po prostu „regularnie wrzucać ceny lotów do arkusza”, są szybsze opcje.
Porównanie: Python DIY vs. usługi API vs. Thunderbit
| Podejście | Czas konfiguracji | Wymagany kod | Obsługa anty-bot | Harmonogram | Koszt |
|---|---|---|---|---|---|
| DIY Playwright (ten tutorial) | 1–2 godz. | Python (średniozaawansowany) | Ręczna konfiguracja | Ręcznie (cron) | Darmowe + koszty proxy |
| Endpoint Google Flights w SerpApi | 15 min | Tylko wywołania API | Obsługiwane | Przez API | ok. $50+/mies. |
| Rozszerzenie Chrome Thunderbit | 2 min | Brak | Scrapowanie w chmurze | Wbudowany scheduler | Dostępny darmowy plan |
A propos SerpApi: Google wniósł w grudniu 2025 pozew DMCA przeciw SerpApi, twierdząc, że liczba ich żądań wzrosła o 25 000% w ciągu dwóch lat. Ta niepewność prawna jest warta uwzględnienia, jeśli oceniasz dostawców API.
Jak Thunderbit scrapuje Google Flights
Otwórz wyniki Google Flights w Chrome, kliknij w Thunderbit przycisk „AI Suggest Fields” — AI analizuje stronę i proponuje kolumny, takie jak airline, price, departure time, stops — sprawdź sugerowane pola i kliknij „Scrape”. Wyniki pojawią się w tabeli, którą możesz wyeksportować do Excela, Google Sheets, Airtable lub Notion — wszystko w ramach darmowego planu.
W przypadku śledzenia cen szczególnie przydatne są Scheduled Scraper oraz Cloud Scraping, które potrafią obsługiwać 50 stron równocześnie i zastępują całą infrastrukturę cron + proxy + serwer.
Python daje pełną kontrolę i niemal nieograniczoną personalizację. Thunderbit daje szybkość i brak utrzymania. Wybierz rozwiązanie pod swój rzeczywisty cel. Jeśli chcesz dowiedzieć się więcej o podejściach no-code, sprawdź nasz poradnik o najlepszych no-code web scraperach.

Czy scrapowanie Google Flights jest legalne? Co warto wiedzieć
Użytkownicy forów często to podnoszą: „bezpośrednie scrapowanie Google Flights narusza regulamin Google”. To zasadne zastrzeżenie — szczególnie że API zostało wyłączone i nie ma autoryzowanej alternatywy.
Naruszenie regulaminu a odpowiedzialność prawna
Warunki korzystania z usług Google (zaktualizowane 22 maja 2024) stanowią, że użytkownicy nie mogą „uzyskiwać dostępu do Usług lub treści ani z nich korzystać za pomocą środków automatycznych (takich jak roboty, pająki czy scrapery)”. Naruszenie ToS to złamanie umowy (sprawa cywilna) — to nie to samo, co łamanie prawa.
Kluczowy precedens prawny: hiQ v. LinkedIn (Dziewiąty Okręg, 2022) ustalił, że scrapowanie publicznie dostępnych danych nie narusza Computer Fraud and Abuse Act (CFAA). Sprawa zakończyła się jednak ugodą, a pozew Google przeciw SerpApi z grudnia 2025 opiera się na innej teorii prawnej — sekcji 1201 DMCA (obejście technologicznych środków ochrony) — która może być poważniejsza.
Najlepsze praktyki odpowiedzialnego scrapowania
- Ograniczaj tempo żądań — 10–20 sekund opóźnienia przy rotacji IP
- Nie scrapuj danych osobowych — ceny lotów to publicznie wyświetlane dane zbiorcze
- Nie obchodź CAPTCHA programowo (to właśnie obszar ryzyka DMCA)
- Używaj danych do własnych analiz, a nie do budowy konkurencyjnego produktu komercyjnego bez odpowiedniej licencji
- Rozważ oficjalne API, jeśli są dostępne
Alternatywne źródła danych
Jeśli scrapowanie wydaje się zbyt ryzykowne w Twoim przypadku, istnieją legalne opcje API:
| Dostawca | Koszt | Darmowy plan | Uwagi |
|---|---|---|---|
| SerpApi | $75–$3,750+/mies. | 250 wyszukiwań/mies. | Bezpośredni JSON z Google Flights (pod lupą prawną) |
| Kiwi Tequila | Darmowe (model afiliacyjny) | Bez limitu | Najlepsze dla startupów i testów |
| Amadeus | Pay-as-you-go | 2,000 req/mies. | Ponad 400 linii lotniczych, możliwość rezerwacji |
| Skyscanner | Indywidualnie | Wymaga akceptacji | 52 rynki, 30 języków |
Jeśli chcesz pełniejszy obraz, napisaliśmy też bardziej szczegółowy materiał o prawnych aspektach web scrapingu.
Podsumowanie i najważniejsze wnioski
To było sporo informacji. Oto najważniejsze rzeczy:
- Python + Playwright to najbardziej elastyczne podejście do scrapowania Google Flights, ale wymaga stałego utrzymania
- Zabezpieczenia anty-bot (opóźnienia, rotacja user-agenta, proxy residential) nie są opcjonalne — są niezbędne dla stabilności, szczególnie po SearchGuard
- Selektory często się psują — używaj
aria-labeli selektorów tekstowych, tam gdzie to możliwe, waliduj wynik i trzymaj harmonogram przeglądów - Automatyzuj przez
schedulelub cron, żeby z jednorazowego scrapowania zrobić prawdziwy tracker cen z historią i alertami e-mail - Thunderbit oferuje alternatywę no-code z wbudowanym harmonogramem, scrapingiem w chmurze i obsługą anty-bot — idealną, jeśli Twoim celem jest arkusz do śledzenia cen, a nie projekt programistyczny
- Szanuj granice prawne — ograniczaj tempo, scrapuj wyłącznie publiczne dane i rozważ alternatywy API przy zastosowaniach komercyjnych
Pobierz kod z tego tutorialu albo zainstaluj rozszerzenie Chrome Thunderbit, jeśli chcesz szybką ścieżkę. Tak czy inaczej, zamiast odświeżać Google Flights ręcznie, będziesz śledzić ceny lotów automatycznie.
Po więcej technik scrapowania w Pythonie zajrzyj do naszych poradników: jak web scrapować w Pythonie oraz najlepsze narzędzia do web scrapingu w Pythonie.
FAQ
1. Czy mogę scrapować Google Flights bez Pythona?
Tak. Usługi API, takie jak SerpApi i Kiwi Tequila, udostępniają ustrukturyzowane dane lotów przez wywołania API (bez potrzeby automatyzacji przeglądarki). Jeśli chcesz podejście całkowicie no-code, rozszerzenie Chrome Thunderbit potrafi scrapować wyniki Google Flights bezpośrednio z przeglądarki, z polami sugerowanymi przez AI i eksportem jednym kliknięciem.
2. Czy Google blokuje scrapowanie lotów?
Google stosuje wykrywanie botów (SearchGuard), CAPTCHA i limitowanie liczby żądań. Przy odpowiednich zabezpieczeniach anty-bot — losowych opóźnieniach, rotacji user-agenta, proxy residential i ustawieniach stealth przeglądarki — możesz utrzymać stabilne scrapowanie przy umiarkowanym wolumenie. Szczegółowe techniki i progi znajdziesz w sekcji anty-bot powyżej.
3. Jak często powinienem scrapować Google Flights do śledzenia cen?
Dwa razy dziennie (o losowych porach) wystarczy do osobistego monitorowania cen i utrzymuje ryzyko wykrycia na niskim poziomie. Dla monitoringu biznesowego — co 4–6 godzin z rotacją proxy. Unikaj scrapowania co godzinę, chyba że trwa krótkoterminowa wyprzedaż taryf — znacząco zwiększa to ryzyko blokady.
4. Czy istnieje darmowe API Google Flights?
Oficjalne API Google QPX Express zostało wyłączone w kwietniu 2018. Nie ma darmowego, oficjalnego zamiennika. Najbliższą darmową opcją jest Kiwi Tequila API (model afiliacyjny, nielimitowane wyszukiwania). SerpApi oferuje 250 darmowych wyszukiwań miesięcznie. Dla większości użytkowników najbardziej praktyczne jest scrapowanie albo narzędzie no-code, takie jak Thunderbit.
5. Dlaczego moje selektory CSS dla Google Flights ciągle zwracają puste dane?
Google używa Closure Compiler do generowania obfuskowanych nazw klas, które zmieniają się przy każdym buildzie. Testy A/B i różnice lokalizacyjne też powodują, że struktura HTML różni się między użytkownikami. Rozwiązanie: używaj atrybutów aria-label i selektorów tekstowych zamiast nazw klas, dodaj funkcję walidacji, żeby wcześnie wykryć uszkodzenia, i sprawdzaj selektory co miesiąc. Zobacz sekcję o utrzymaniu selektorów po szczegółową strategię.
Dowiedz się więcej


