Jak scrapować Google Flights w Pythonie: od kodu do alertów cenowych

Ostatnia aktualizacja: April 16, 2026
Jak scrapować Google Flights w Pythonie: od kodu do alertów cenowych

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żytkownikaZastosowanieNajważniejsza korzyść
Indywidualni podróżniŚledzenie cen dla konkretnych tras w czasieOszczędność średnio $50 na locie
Biura podróżyAnaliza konkurencyjnych cenMonitoring parytetu taryf w czasie rzeczywistym
Zespoły ds. podróży służbowychOptymalizacja kosztów na trasach10–30% oszczędności na podróżach firmowych
ProgramiściTworzenie aplikacji porównujących cenyProgramowy dostęp do danych cenowych
BadaczeAnaliza zmienności cen linii lotniczychBadania 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.

FunkcjaPlaywrightSeleniumrequests + BS4
Renderowanie JSPełna obsługaPełna obsługaBrak
Szybkośćo 42% szybszy ogólniePunkt odniesieniaNie dotyczy w tym zastosowaniu
Obsługa asyncNatywnaTylko sekwencyjnaNie dotyczy
Zużycie pamięcio 30% niższeWyższeMinimalne
Omijanie wykrywania botówDobre (z stealth)Łatwiejsze do wykryciaNie 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.

google-flights-scraping-workflow.webp

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ć.

anti-bot-survival-guide.webp

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:

CechaProxy data centerProxy residential
Szybkość100–1,000 Mbps10–100 Mbps
Skuteczność (Google)20–40%85–95%
Koszt$0.10–$0.50/IP/mies.$5–$15/GB
Ryzyko wykryciaWysokieBardzo 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

TechnikaTrudnośćSkutecznośćWymagany kod?
Losowe opóźnienia (2–7 s)NiskaŚrednia2 linie
Rotacja user-agentaNiskaŚrednia5 linii
Obejście detekcji headlessŚredniaWysokaArgumenty uruchomieniowe Playwright
Wtyczka playwright-stealthŚrednia60–80% na podstawowych stronachpip install
Rotacja proxy (data center)ŚredniaŚredniaKonfiguracja
Rotacja proxy (residential)Średnia85–95% skutecznościKonfiguracja
Ustawienie cookies zgody (SOCS)NiskaWymagane1 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:

  1. Obfuskacja przez Closure Compiler. Google używa Closure Stylesheets, aby generować nazwy klas takie jak BVAVmf i YMlIz przez goog.setCssNameMapping(). Zmieniają się one przy każdym buildzie — czasem co tydzień.

  2. 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.

  3. 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):

  1. Atrybuty aria-label — związane z dostępnością, rzadko się zmieniają
  2. Atrybuty data-* — dodawane celowo do działania funkcji
  3. Atrybuty role — role ARIA są semantyczne
  4. Selektory oparte na tekście — dopasowują widoczną treść
  5. Dopasowanie klas po fragmencie — np. [class*="price"]
  6. 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ę.

scraper-to-price-tracker-sfo-jfk.webp

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ścieCzas konfiguracjiWymagany kodObsługa anty-botHarmonogramKoszt
DIY Playwright (ten tutorial)1–2 godz.Python (średniozaawansowany)Ręczna konfiguracjaRęcznie (cron)Darmowe + koszty proxy
Endpoint Google Flights w SerpApi15 minTylko wywołania APIObsługiwanePrzez APIok. $50+/mies.
Rozszerzenie Chrome Thunderbit2 minBrakScrapowanie w chmurzeWbudowany schedulerDostę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.

flight-data-options-comparison.webp

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:

DostawcaKosztDarmowy planUwagi
SerpApi$75–$3,750+/mies.250 wyszukiwań/mies.Bezpośredni JSON z Google Flights (pod lupą prawną)
Kiwi TequilaDarmowe (model afiliacyjny)Bez limituNajlepsze dla startupów i testów
AmadeusPay-as-you-go2,000 req/mies.Ponad 400 linii lotniczych, możliwość rezerwacji
SkyscannerIndywidualnieWymaga akceptacji52 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-label i selektorów tekstowych, tam gdzie to możliwe, waliduj wynik i trzymaj harmonogram przeglądów
  • Automatyzuj przez schedule lub 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

Ke
Ke
CTO w Thunderbit | Starszy data scientist i ekspert ML Dzięki prawie dziesięciu latom doświadczenia w uczeniu maszynowym i data science, Ke Shen jest absolwentem Columbia University i byłym starszym data scientistą w Walmart Labs. Dysponując dogłębną, uznaną przez branżowych ekspertów wiedzą w zakresie Python, R, Java i statystyki, dzieli się sprawdzonymi w boju spostrzeżeniami na temat wdrażania złożonych algorytmów AI — od teorii po architekturę gotową do produkcji.
Spis treści

Zbierz dane ze strony, po prostu o to prosząc

Powiedz, czego potrzebujesz, prostym angielskim. A najlepiej: nie mów nic.

Wypróbuj Thunderbit darmowe
Wyodrębniaj dane dzięki AI
Łatwo przenoś dane do Google Sheets, Airtable lub Notion
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week