Google Flightsin poiminta Pythonilla: koodista hintahälytyksiin

Viimeksi päivitetty April 16, 2026
Google Flightsin poiminta Pythonilla: koodista hintahälytyksiin

Google lopetti Flights API -rajapintansa jo vuonna 2018, mutta lentojen hinnat muuttuvat yhä — jopa 17 kertaa 48 tunnin aikana yhdellä kotimaan reitillä. Jos haluat päästä käsiksi tähän dataan ohjelmallisesti, web scraping on käytännössä ainoa vaihtoehto.

Olen testannut pitkään erilaisia tapoja hakea lentodataa Googlesta, ja tilanne on muuttunut todella paljon — etenkin sen jälkeen, kun Google otti SearchGuardin käyttöön tammikuussa 2025. Tässä oppaassa käyn läpi toimivan Python-pohjaisen Google Flights -scraperin rakentamisen Playwrightilla, näytän miten käsitellä bottisuojaus, joka kaataa useimmat yritykset, ja laajennan kokonaisuuden automaattiseksi hintaseurannaksi hälytyksineen. Jos haluat jättää koodin kokonaan väliin, käyn läpi myös no-code-vaihtoehdon Thunderbitillä, jolla pääset samaan lopputulokseen noin kahdessa minuutissa.

Miksi Google Flights kannattaa poimia Pythonilla?

Google Flights hallitsee lentohakuja. Sen näkyvyys Yhdysvaltain mobiilissa nousi 47,6 prosenttiin ja ohitti kaikki suuret OTA-toimijat. Taustalla oleva travel metasearch -markkina oli arvoltaan $8,33 miljardia vuonna 2024, ja kasvu jatkuu 30,2 %:n vuosivauhtia. Silti sen jälkeen, kun QPX Express API suljettiin pysyvästi 10. huhtikuuta 2018, tähän dataan ei ole virallista ohjelmallista pääsyä.

Samaan aikaan lentohinnat voivat vaihdella jopa 50 % samalla matkalla, ja alin ja ylin hinta eroavat keskimäärin noin 20 dollarilla. Esimerkiksi Delta käyttää dynaamisessa hinnoittelussa 77 hintaluokkaa. Yhdysvaltain keskimääräinen meno-paluu-hinta oli vuoden 2026 alussa 408 dollaria, ja lentohinnat olivat 14,9 % korkeammat kuin vuotta aiemmin.

Valtava alusta, ei API:a, hinnat elävät koko ajan. Siksi Google Flightsin poiminnasta Pythonilla on tullut yksi suosituimmista projekteista GitHubissa ja matkailufoorumeilla.

Näin eri käyttäjät hyötyvät siitä:

KäyttäjätyyppiKäyttötapausKeskeinen hyöty
Yksittäiset matkustajatSeuraa tiettyjen reittien hintoja ajan yliSäästä keskimäärin 50 dollaria lentoa kohden
MatkatoimistotKilpailukykyisen hinnoittelun seurantaReaaliaikainen hintapariteetin valvonta
Yritysmatkat-tiimitKustannusten optimointi reiteittäin10–30 % säästö yritysmatkailussa
KehittäjätRakenna lentohintojen vertailusovelluksiaOhjelmallinen pääsy hintadataan
TutkijatLentoyhtiöiden hintavaihtelun analysointiAkateeminen ja markkinatutkimus

Foorumeilla käyttäjät sanovat asian suoraan: "Google Flights API was discontinued and I should use web scraping instead" -tyyppinen ajatus toistuu jatkuvasti. Ja ROI on ihan oikea — Hopperin mukaan 95 %:n ennustetarkkuus perustuu yli 5 miljardin päivittäisen hintanoteerauksen analysointiin, kun taas Expedia:n vuoden 2026 data kertoo, että 8–15 päivää etukäteen varaamalla säästää noin 25 dollaria kotimaan lennoissa.

Mitä dataa Google Flightsista voi poimia?

Google Flightsin tulossivu sisältää yllättävän rikkaan tietojoukon. Tyypillisesti saatavilla on muun muassa:

  • Lentoyhtiön nimi (ja logo)
  • Lähtöaika ja lentokenttäkoodi
  • Saapumisaika ja lentokenttäkoodi
  • Lennon kokonaiskesto
  • Välilaskujen määrä ja vaihtotiedot (kenttä, kesto, yöpymistieto)
  • Lipun hinta (valuuttakohtainen)
  • CO2-päästöt (kg CO2e, sekä prosenttierot tyypilliseen lentoon verrattuna)
  • Matkustusluokka, lennon numero, konetyyppi
  • Jalkatila
  • Lisäominaisuudet (Wi-Fi, pistorasiat, median suoratoisto)
  • Hintatason ilmaisin (matala / tyypillinen / korkea)
  • Viivevaroitukset ("Often delayed by over 30 min")

Datan saatavuus vaihtelee reitin, päivämäärän ja lipputyypin mukaan (yhdensuuntainen vs. meno-paluu). Tässä miltä yksi poimittu lentorivi näyttää JSON-muodossa:

{
  "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-ympäristön valmistelu

Ennen kuin kirjoitamme yhtään scraping-koodia, muutama asia pitää olla kunnossa.

Vaatimukset:

  • Vaikeustaso: Keskitaso
  • Aikaa kuluu: Noin 1–2 tuntia koko oppaaseen
  • Tarvitset: Python 3.7+, perustaidot Pythonista, Chrome-pohjaisen selaimen

Asenna tarvittavat kirjastot

Käytämme Playwrightia selaimen automaatioon (Google Flights on 100 % JavaScriptillä renderöity — pelkät HTTP-pyynnöt eivät palauta mitään hyödyllistä), sekä muutamaa apukirjastoa:

pip install playwright playwright-stealth pandas
playwright install chromium
  • Playwright — headless-selaimen automaatio, hoitaa JavaScript-renderöinnin ja sisäänrakennetut odotusmekanismit
  • playwright-stealth — paikkaa yleisiä bottitunnistuksen signaaleja
  • pandas — datan analysointiin ja CSV-vientiin myöhemmin

Miksi valita Playwright eikä Selenium tai requests

Google Flights ei toimi pelkällä requests + BeautifulSoup -yhdistelmällä — sivun sisältö renderöidään kokonaan JavaScriptillä. Tarvitset oikean selaimen.

OminaisuusPlaywrightSeleniumrequests + BS4
JS-renderöintiTäysi tukiTäysi tukiEi lainkaan
Nopeus42 % nopeampi kokonaisuutenaVertailutasoEi sovellu tähän
Async-tukiNatiiviVain peräkkäinenEi sovellu
Muistin käyttö30 % vähemmänKorkeampiMinimaalinen
Bottitunnistuksen kiertoHyvä (stealthillä)Helpompi tunnistaaEi sovellu

Playwright on nopeampi, modernimpi ja tukee asynkronisuutta paremmin. Google Flightsia varten se on selkeä ykkönen.

Vaihe vaiheelta: Google Flightsin poiminta Pythonilla

Tämä on oppaan ydin. Rakennamme scraperin pala palalta.

google-flights-scraping-workflow.webp

Vaihe 1: Määritä dataluokat

Aloita jäsentämällä hakuparametrit ja lentodata Pythonin dataclassien avulla. Näin koodi pysyy siistinä ja sitä on helpompi laajentaa myöhemmin.

from dataclasses import dataclass, field
from typing import Optional, List

@dataclass
class SearchParams:
    origin: str          # esim. "SFO"
    destination: str     # esim. "JFK"
    departure_date: str  # esim. "2026-05-15"
    return_date: Optional[str] = None
    trip_type: str = "one-way"  # "one-way" tai "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 = ""

Jokainen kenttä vastaa suoraan sitä, mitä sivulta poimimme. Kun rakenne on valmiina alusta alkaen, sinun ei tarvitse myöhemmin pyöritellä sekavia sanakirjoja.

Vaihe 2: Ymmärrä Google Flightsin URL-rakenne

Google Flights koodaa hakuehdot Base64-koodattuun Protobufiin tfs-URL-parametrissa. Voit joko reverse-engineerata tämän koodauksen tai käyttää yksinkertaisempaa tapaa: rakentaa luonnollisen kielen hakulinkin.

Yksinkertaisin vaihtoehto on käyttää hakulauseketta:

https://www.google.com/travel/flights?q=flights+from+SFO+to+JFK+on+2026-05-15&curr=USD

Jos haluat enemmän kontrollia, voit rakentaa URL-osoitteen ohjelmallisesti:

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"

Vaihtoehtoinen tapa — Protobuf-koodauksen reverse-engineeraus — antaa tarkemman hallinnan, mutta rikkoutuu heti, kun Google muuttaa sisäistä formaattia. Esimerkiksi fast-flights GitHubissa käyttää Protobufin purkua HTML-parsinnan sijaan, mutta se on jo huomattavasti edistyneempi lähestymistapa.

Vaihe 3: Käynnistä selain ja siirry Google Flightsiin

Tässä on Playwright-asennus. Käytämme playwright-stealth-kirjastoa vähentämään tunnistusriskiä heti alusta alkaen.

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",
        )
        # Esiaseta eväste, jotta suostumusikkuna ohitetaan
        await context.add_cookies([{
            "name": "SOCS",
            "value": "CAESHwgBEhJnd3NfMjAyNTAyMjctMF9SQzIaBXpoLUNOIAEaBgiAy6O-Bg",
            "domain": ".google.com",
            "path": "/"
        }])
        page = await context.new_page()

Ajamme headless-tilassa tuotannossa (vaihda headless=False, jos haluat debugata), asetamme realistisen viewportin ja user-agentin, ja esiasetamme SOCS-evästeen suostumusikkunan ohittamiseksi — lisää tästä anti-bot-osiossa.

Vaihe 4: Siirry hakutuloksiin

Lataa rakennettu URL ja odota, että lentotulokset ilmestyvät:

        url = build_flights_url(
            params.origin, params.destination, params.departure_date
        )
        await page.goto(url, wait_until="networkidle")

        # Odota, että lentotulokset latautuvat
        await page.wait_for_selector(
            "li.pIav2d", timeout=15000
        )

Jos tässä tulee aikakatkaisu, syynä on yleensä joko suostumusikkuna, joka estää sivun, tai Google on näyttänyt CAPTCHA:n. Käsittelemme molemmat tilanteet anti-bot-osiossa.

Vaihe 5: Lataa kaikki lentotulokset

Google Flights piilottaa lisää tuloksia "Show more flights" -painikkeen taakse. Sinun pitää klikata sitä toistuvasti, kunnes kaikki lennot näkyvät:

        # Klikkaa "Show more flights" kunnes kaikki tulokset on ladattu
        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

Tämä silmukka klikkaa painiketta, odottaa 2 sekuntia uusien tulosten renderöitymistä ja pysähtyy, kun painike ei enää ole näkyvissä. Testieni mukaan useimmilla reiteillä on 1–3 sivua tuloksia.

Vaihe 6: Poimi lentodataa CSS-valitsimilla

Nyt parsitaan itse lentodata ladatulta sivulta. Tässä valitsimet (varmistettu huhtikuussa 2026 — katso ylläpidosta miksi tämä päivämäärä on tärkeä):

        flights = []
        cards = await page.query_selector_all("li.pIav2d")

        for card in cards:
            flight = FlightData()

            # Lentoyhtiön nimi
            airline_el = await card.query_selector(
                "div.sSHqwe span:not([class])"
            )
            if airline_el:
                flight.airline = (await airline_el.inner_text()).strip()

            # Lähtöaika
            dep_el = await card.query_selector(
                'span[aria-label*="Departure time"]'
            )
            if dep_el:
                flight.departure_time = (await dep_el.inner_text()).strip()

            # Saapumisaika
            arr_el = await card.query_selector(
                'span[aria-label*="Arrival time"]'
            )
            if arr_el:
                flight.arrival_time = (await arr_el.inner_text()).strip()

            # Kesto
            dur_el = await card.query_selector("div.gvkrdb")
            if dur_el:
                flight.duration = (await dur_el.inner_text()).strip()

            # Välilaskut
            stops_el = await card.query_selector("div.EfT7Ae span")
            if stops_el:
                flight.stops = (await stops_el.inner_text()).strip()

            # Hinta
            price_el = await card.query_selector(
                "div.FpEdX span"
            )
            if price_el:
                flight.price = (await price_el.inner_text()).strip()

            # CO2-päästöt
            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

Huomio: luokkanimet kuten pIav2d, sSHqwe ja FpEdX generoidaan Googlen Closure Compilerilla, ja ne voivat muuttua missä tahansa buildissa. aria-label-valitsimet ovat huomattavasti vakaampia. Kerron alla koko ylläpitostrategian.

Vaihe 7: Tallenna tulokset JSON- tai CSV-muotoon

Lopuksi tallenna poimittu data aikaleiman kanssa (tämä on tärkeää myöhempää hintaseurantaa varten):

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)

    # Tallenna myös CSV:nä
    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())

Aja tämä, ja sinun pitäisi saada flights.json ja flights.csv, joissa on tulokset. Testeissäni SFO-JFK-haku palauttaa tyypillisesti 30–80 lentovaihtoehtoa ja kestää noin 15–20 sekuntia.

Bottisuojausopas Google Flightsin poimintaan

Useimmat oppaat päättyvät tähän. Useimmat scraperit kaatuvat tähän. Google otti käyttöön SearchGuardin tammikuussa 2025, mikä rikkoi lähes kaikki SERP-scraperit yhdessä yössä. Google kuvailee sitä "kymmenien tuhansien ihmistyötuntien ja miljoonien dollarien investoinnin tuotteeksi." Google Flightsin arvioitu vaikeustaso on 4/5 scrapingin näkökulmasta.

Yksikään kilpailija-artikkeli ei käsittele tätä kunnolla, vaikka tämä on #1 syy siihen, miksi ihmisten scraperit lakkaavat toimimasta. Tässä, mitä vastaan taistelet ja miten siitä selviät.

anti-bot-survival-guide.webp

Satunnaiset viiveet pyyntöjen välillä

Yksinkertaisin suoja rate limitingiä vastaan. Kaksi riviä koodia, kohtalainen teho:

import time
import random

time.sleep(random.uniform(3, 7))

Lisää tämä sivun siirtymien väliin. Kiinteät aikavälit (esimerkiksi tasan 5 sekuntia joka kerta) ovat punainen lippu — satunnaista.

User-Agentin kierto

Sama user-agent-merkkijono jokaisessa pyynnössä paljastaa helposti automatisoinnin. Kierrätä niitä listasta:

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-tunnistuksen kierto

Google tarkistaa navigator.webdriver-lipun ja muita automaation merkkejä. playwright-stealth hoitaa suurimman osan näistä, mutta sinun kannattaa myös käyttää Vaiheessa 3 esitettyjä käynnistysargumentteja. Keskeiset liput:

args=[
    "--disable-blink-features=AutomationControlled",
    "--disable-dev-shm-usage",
    "--no-first-run",
]

Tällä pääset ohi perusbottitunnistuksesta. SearchGuard menee syvemmälle — se tarkkailee hiiren nopeutta, näppäilyjen rytmiä ja vieritysmallia — mutta keskisuurella volyymilla stealth-tila ja realistiset viiveet riittävät yleensä.

Proxy-kierto: datacenter vs. residential

Jos aiot tehdä muutamaa hakua enemmän, tarvitset proxyt. Ero on tärkeä:

OminaisuusDatacenter-proxytResidential-proxyt
Nopeus100–1 000 Mbps10–100 Mbps
Onnistumisaste (Google)20–40 %85–95 %
Hinta0,10–0,50 $ / IP / kk5–15 $ / GB
TunnistusriskiKorkeaErittäin matala

Residential-proxyt ovat noin 180 kertaa edullisempia per onnistunut pyyntö suojattuja sivustoja poimittaessa. Palveluntarjoajien 2026-hinnastoissa: Smartproxy alkaen 7 $/GB, Bright Data 8,40 $/GB, Oxylabs 8 $/GB.

Lisää proxy Playwrightiin näin:

browser = await pw.chromium.launch(
    proxy={"server": "http://proxy-host:port",
           "username": "user", "password": "pass"}
)

Evästeiden suostumusikkunan käsittely

Käyttäjät raportoivat jatkuvasti "I agree to terms" -ponnahdusikkunan ongelmaksi: "first google will show you the 'I agree to terms and conditions' popup." Siistein ratkaisu on esiasettaa SOCS-eväste (näytetty Vaiheessa 3). Jos se ei toimi, klikkaa ikkuna läpi:

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  # Ei ponnahdusikkunaa

Huom: painikkeen teksti vaihtelee kielen mukaan — saksaksi "Alle akzeptieren", ranskaksi "Tout accepter".

Anti-bot-pikaviite

TekniikkaVaikeusTehoVaatiiko koodia?
Satunnaiset viiveet (2–7 s)MatalaKeskitaso2 riviä
User-Agentin kiertoMatalaKeskitaso5 riviä
Headless-tunnistuksen kiertoKeskitasoKorkeaPlaywrightin launch-argumentit
playwright-stealth-laajennusKeskitaso60–80 % perussivustoillapip install
Proxy-kierto (datacenter)KeskitasoKeskitasoAsetukset
Proxy-kierto (residential)Keskitaso85–95 % onnistuminenAsetukset
Suostumusikkunan esiasetus (SOCS)MatalaPakollinen1 rivi

Suositellut turvalliset nopeudet: pidä 10–20 sekunnin viiveet pyyntöjen välillä IP-kierron kanssa. Googlen kynnys on suunnilleen 100 pyyntöä/minuutti per IP ennen kuin saat 429-virheen, ja yli 1 000 pyyntöä/päivä per IP voi laukaista tilapäisen eston.

Miksi Google Flightsin selektorit rikkoutuvat jatkuvasti — ja miten korjata se

Selvästi suurin kipupiste. Foorumiketjut ovat täynnä variaatioita siitä, että "all I get back is 14 empty lists." Jokainen opas antaa selektorit. Kukaan ei selitä, miksi ne rikkoutuvat.

Miksi Google Flightsin selektorit muuttuvat

Syitä on kolme:

  1. Closure Compilerin obfuskointi. Google käyttää Closure Stylesheets -tekniikkaa, joka generoi luokkanimiä kuten BVAVmf ja YMlIz goog.setCssNameMapping()-mekanismin kautta. Nämä vaihtuvat jokaisessa buildissa — joskus viikoittain.

  2. A/B-testaus. Eri käyttäjille näytetään samanaikaisesti eri HTML-rakenteita. Scraper voi toimia omalla koneellasi mutta epäonnistua toisella alueella.

  3. Kielialue-erot. EU-käyttäjät näkevät eri termejä, asettelua ja jopa eri datakenttiä kuin yhdysvaltalaiset käyttäjät.

Kirjoita kestävät selektorit

Suosi merkitykseen sidottuja valitsimia ulkoasun sijaan:

# Hauras — rikkoutuu jokaisessa buildissa
price_el = await card.query_selector("div.BVAVmf > div.YMlIz")

# Kestävämpi — sidottu saavutettavuuslabeliin
dep_el = await card.query_selector('span[aria-label*="Departure time"]')

# Myös kestävä — tekstipohjainen osuma
more_btn = page.locator('button:has-text("Show more flights")')

Selektorien vakausjärjestys (vakaimmasta vähiten vakaaseen):

  1. aria-label-attribuutit — sidottu saavutettavuuteen, muuttuvat harvoin
  2. data-*-attribuutit — lisätään nimenomaan toiminnallisuutta varten
  3. role-attribuutit — ARIA-roolit ovat semanttisia
  4. Tekstipohjaiset selektorit — vastaavat näkyvää sisältöä
  5. Osittaista luokkanimeä vastaavat selektorit — esim. [class*="price"]
  6. Täydet obfuskoidut luokkanimet — vältä näitä aina kun mahdollista

Lisää validointifunktio

Älä anna rikkinäisten selektorien tuottaa hiljaisesti tyhjää dataa. Tunnista ongelma ajoissa:

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"Puuttuu '{field_name}' — selektorit saattavat kaivata päivitystä"
            )
            valid = False
    return valid

Aja tämä jokaiselle poimitulle lennolle. Jos varoituksia alkaa tulla, on aika tarkistaa sivu ja päivittää selektorit.

Selektorien ylläpitostrategia

  • Tarkista selektorit kuukausittain tai heti, jos datan laatu heikkenee
  • Pidä selektorit erillisessä konfiguraatiossa, jotta niitä on helppo päivittää
  • Tämän artikkelin selektorit on viimeksi varmistettu: huhtikuu 2026
  • Harkitse vaihtoehdoksi fast-flights-kirjastoa — se käyttää Protobufin purkua CSS-selektoreiden sijaan ja kiertää tämän ongelman kokonaan (vaikka siinäkin on oma haavoittuvuutensa, jos Google muuttaa sisäistä datamuotoa)

Yhden kerran poiminnasta automaattiseksi Google Flights -hintaseurannaksi

Useimmat oppaat lopettavat kohtaan "tallenna JSONiin". Tämän artikkelin otsikossa lukee "Price Alerts". Nyt toimitetaan se loppuun.

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

Aja scraper automaattisesti ajastettuna

Vaihtoehto 1: Pythonin schedule-kirjasto (helpoin, toimii kaikilla alustoilla):

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)

Vaihtoehto 2: cron-jobi (Linux/Mac):

# Aja päivittäin klo 6 ja 18
0 6,18 * * * cd /path/to/scraper && python scraper.py

Vaihtoehto 3: Windowsin Tehtävien ajoitus — luo perustoiminto, joka ajaa python scraper.py haluamallasi aikataululla.

Miinuspuoli: kaikki nämä vaativat koneen, joka on aina päällä. Jos ajat tätä läppärillä, joka nukkuu, skräpäykset jäävät väliin.

Tallenna historiallinen hintadata

Vaihda JSON-tiedoston ylikirjoittamisesta SQLite-tietokantaan lisäämiseen:

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()

Viikon kahdesti päivässä ajettujen hakujen jälkeen sinulla on jo tarpeeksi dataa trendien havaitsemiseen.

Analysoi hintatrendejä ja aseta hälytykset

Etsi halvin vaihtoehto historiallisesta datastasi:

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"Halvin: ${cheapest['price_usd']:.0f} päivänä "
    f"{cheapest['scrape_date']} ({cheapest['airline']})"
)

Lähetä sähköposti, kun hinta laskee alle asettamasi rajan:

import smtplib
from email.mime.text import MIMEText

def send_price_alert(route, price, threshold, recipient):
    msg = MIMEText(
        f"Hinta laski! {route}: ${price:.0f} "
        f"(alle asettamasi ${threshold:.0f} rajan)"
    )
    msg["Subject"] = f"Lentotarjous: {route} hintaan ${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)

# Jokaisen haun jälkeen tarkista tarjoukset
min_price = df["price_usd"].min()
threshold = 250
if min_price < threshold:
    send_price_alert("SFO-JFK", min_price, threshold,
                     "you@email.com")

Suositeltu hakutiheys: kaksi kertaa päivässä riittää henkilökohtaiseen hintaseurantaan (satunnainen ajoitus vähentää tunnistusriskiä). Yrityskäytössä 4–6 tunnin välein. Tunti-intervalleja kannattaa käyttää vain lyhyiden tarjouskampanjoiden aikana, ja silloinkin vain tilapäisesti.

Helppo tie: Thunderbitin Scheduled Scraper

Jos cron-jobien, jatkuvasti pyörivän palvelimen ja proxy-asetusten ylläpito kuulostaa liian raskaalta, Thunderbitin Scheduled Scraper hoitaa saman käyttötapauksen ilman tuota infrastruktuurikuormaa. Kuvaat vain hakuvälin luonnollisella kielellä, syötät Google Flights -URL:t, ja scraper toimii automaattisesti Thunderbitin pilvi-infrassa — sisäänrakennetulla bottisuojauksen käsittelyllä ja tulosten viennillä suoraan Google Sheetsiin, Exceliin tai Airtableen. Se ei korvaa täyttä Python-ratkaisua (räätälöitävyys vähenee), mutta juuri siihen tilanteeseen, jossa haluat hintaseurannan taulukkoon, se on nopein reitti. Voit kokeilla sitä ilmaisella tasolla.

Kun Python on liikaa: no-code-tavat Google Flightsin poimintaan

Kun rakensin kaiken tämän, rehellisesti sanottuna: osia on paljon. Kaikki eivät tarvitse näin paljon hallintaa. Selektorit rikkoutuvat, proxyt pitää kierrättää, cron-jobeja täytyy valvoa. Jos tavoitteesi on "saada lentohinnat säännöllisesti taulukkoon", nopeampia vaihtoehtoja on olemassa.

Vertailu: itse rakennettu Python vs. API-palvelut vs. Thunderbit

LähestymistapaKäyttöönottoaikaKoodausta vaaditaanKäsittelee bottisuojauksenAjastusHinta
DIY Playwright (tämä opas)1–2 tuntiaPython (keskitaso)Manuaalinen konfigurointiManuaalinen (cron)Ilmainen + proxy-kulut
SerpApi Google Flights -endpoint15 minVain API-kutsutHoidettuAPI:n kauttaNoin 50 $+/kk
Thunderbit Chrome -laajennus2 minEi lainkaanPilviskräpäysSisäänrakennettu ajastinIlmainen taso saatavilla

Huomio SerpApiista: Google nosti DMCA-kanteen SerpApia vastaan joulukuussa 2025 väittäen, että heidän pyyntönsä kasvoivat 25 000 % kahdessa vuodessa. Tätä oikeudellista epävarmuutta kannattaa pohtia, jos arvioit API-toimittajia.

Miten Thunderbit poimii Google Flightsin

Avaa Google Flightsin hakutulokset Chromessa, klikkaa Thunderbitin "AI Suggest Fields" -painiketta — AI lukee sivun ja ehdottaa sarakkeita, kuten lentoyhtiö, hinta, lähtöaika ja välilaskut — tarkista ehdotetut kentät ja klikkaa "Scrape." Tulokset ilmestyvät taulukkoon, josta voit viedä ne Exceliin, Google Sheetsiin, Airtableen tai Notioniin — kaikki ilmaisella tasolla.

Juuri hintaseurannan käyttötapauksessa Thunderbitin Scheduled Scraper ja Cloud Scraping (joka pystyy käsittelemään 50 sivua yhtä aikaa) korvaavat koko cron + proxy + serveri-infran.

Python antaa täyden hallinnan ja rajattoman muokattavuuden. Thunderbit antaa nopeuden ja käytännössä nollan ylläpidon. Valitse sen mukaan, mikä tavoite sinulla oikeasti on. Jos haluat lukea lisää no-code-skräpäyksestä, tutustu oppaaseemme parhaista no-code-web scrapereista.

flight-data-options-comparison.webp

Onko Google Flightsin poiminta laillista? Mitä sinun pitää tietää

Foorumikäyttäjät nostavat tämän esiin jatkuvasti: "scraping Google Flights directly violates the TOS that Google has." Huoli on ymmärrettävä — etenkin kun API on poistettu eikä virallista vaihtoehtoa ole.

TOS-rikkomus vs. oikeudellinen vastuu

Googlen käyttöehdot (päivitetty 22. toukokuuta 2024) sanovat, että käyttäjien ei pidä "access or use the Services or any content through the use of any automated means (such as robots, spiders or scrapers)." Käyttöehtojen rikkominen on sopimusrikkomus (siviilioikeudellinen asia) — se ei ole sama asia kuin lain rikkominen.

Keskeinen oikeustapaus: hiQ v. LinkedIn (Ninth Circuit, 2022) linjasi, että julkisesti saatavilla olevan datan poiminta ei riko Computer Fraud and Abuse Actia (CFAA). Tapaus päättyi kuitenkin sovintoon, ja Googlen joulukuun 2025 kanne SerpApia vastaan perustuu eri oikeusteoriaan — DMCA:n pykälään 1201 (teknisten suojakeinojen kiertäminen) — mikä voi olla vakavampi.

Hyvät käytännöt vastuulliseen poimintaan

  • Rajoita pyyntömääriä — 10–20 sekunnin viiveet IP-kierron kanssa
  • Älä poimi henkilötietoja — lentohinnat ovat julkisesti näytettyä aggregaattidataa
  • Älä kierrä CAPTCHAa ohjelmallisesti (tämä on DMCA-riskialuetta)
  • Käytä dataa henkilökohtaiseen tutkimukseen, älä kilpailijan kaupallisen tuotteen rakentamiseen ilman asianmukaista lisensointia
  • Harkitse virallisia API-rajapintoja, jos niitä on saatavilla

Vaihtoehtoiset datalähteet

Jos poiminta tuntuu sinun käyttötapauksessasi liian riskialttiilta, tarjolla on myös laillisia API-vaihtoehtoja:

PalveluntarjoajaHintaIlmainen tasoHuomiot
SerpApi75–3 750 $+/kk250 hakua/kkSuora Google Flights -JSON (oikeudellisen tarkastelun alla)
Kiwi TequilaIlmainen (affiliate-malli)RajatonParas startupeille ja testaukseen
AmadeusKäytön mukaan2 000 pyyntöä/kk400+ lentoyhtiötä, varausmahdollisuus
SkyscannerRäätälöityVaatii hyväksynnän52 markkina-aluetta, 30 kieltä

Kirjoitimme myös tarkemman erittelyn web scrapingin oikeudellisista vaikutuksista, jos haluat koko kuvan.

Yhteenveto ja tärkeimmät opit

Tämä oli paljon. Tässä oleellinen:

  • Python + Playwright on joustavin tapa poimia Google Flightsia, mutta se vaatii jatkuvaa ylläpitoa
  • Bottisuojaukset (viiveet, user-agentin kierto, residential-proxyt) eivät ole valinnaisia — ne ovat välttämättömiä luotettavuuden kannalta, erityisesti SearchGuardin jälkeen
  • Selektorit rikkoutuvat usein — käytä mahdollisuuksien mukaan aria-label- ja tekstipohjaisia valitsimia, validoi tulokset ja pidä ylläpitoaikataulu
  • Automatisoi schedulen tai cronin avulla niin saat kertaluonteisesta poiminnasta oikean hintaseurannan historiallisella datalla ja sähköposti-ilmoituksilla
  • Thunderbit tarjoaa no-code-vaihtoehdon, jossa on sisäänrakennettu ajastus, pilviskräpäys ja bottisuojauksen käsittely — ihanteellinen, jos tavoitteesi on hintaseurannan taulukko eikä koodiprojekti
  • Noudata lakirajoja — rajoita pyyntitahtia, poimi vain julkista dataa ja harkitse API-vaihtoehtoja kaupalliseen käyttöön

Ota tämän oppaan koodi käyttöön, tai asenna Thunderbit Chrome -laajennus nopeaa reittiä varten. Joka tapauksessa seuraat lentohintoja etkä enää päivitä Google Flightsia käsin.

Lisää Python-skräpäysvinkkejä löydät oppaistamme miten web scrape tehdään Pythonilla ja parhaat Python-web scraping -työkalut.

Usein kysytyt kysymykset

1. Voinko poimia Google Flightsin ilman Pythonia?

Kyllä. API-palvelut kuten SerpApi ja Kiwi Tequila tarjoavat jäsenneltyä lentodataa API-kutsuilla (selaimen automaatiota ei tarvita). Täysin no-code-lähestymistapaan Thunderbitin Chrome-laajennus voi poimia Google Flightsin tulokset suoraan selaimestasi AI:n ehdottamilla kentillä ja yhden klikkauksen viennillä.

2. Estääkö Google lentojen poimintaa?

Google käyttää bottitunnistusta (SearchGuard), CAPTCHAa ja rate limitingiä. Oikeilla vastatoimilla — satunnaisilla viiveillä, user-agentin kierrolla, residential-proxyilla ja stealth-selaimen asetuksilla — luotettava poiminta onnistuu keskisuurilla volyymeilla. Katso yllä oleva bottisuojausosio tarkkojen tekniikoiden ja kynnysarvojen osalta.

3. Kuinka usein Google Flightsia pitäisi poimia hintaseurantaan?

Kaksi kertaa päivässä (satunnaisiin kellonaikoihin) riittää henkilökohtaiseen hintaseurantaan ja pitää tunnistusriskin matalana. Yrityskäytössä 4–6 tunnin välein proxy-kierron kanssa. Vältä tuntikohtaista poimintaa, ellei kyse ole lyhytaikaisesta tarjouskampanjasta — se kasvattaa estoriskin merkittävästi.

4. Onko olemassa ilmaista Google Flights API:a?

Virallinen Google QPX Express API lakkautettiin huhtikuussa 2018. Ilmaista virallista korvaajaa ei ole. Lähin ilmainen vaihtoehto on Kiwi Tequila API (affiliate-malli, rajattomat haut). SerpApi tarjoaa 250 ilmaista hakua kuukaudessa. Useimmille käyttäjille web scraping tai no-code-työkalu kuten Thunderbit on käytännöllisin reitti.

5. Miksi Google Flightsin CSS-selektorit palauttavat jatkuvasti tyhjää dataa?

Google käyttää Closure Compilertia, joka generoi obfuskoituja luokkanimiä, ja ne vaihtuvat jokaisessa buildissa. A/B-testaus ja kielialue-erot tekevät HTML-rakenteesta myös eri käyttäjille erilaisen. Ratkaisu: käytä aria-label-attribuutteja ja tekstipohjaisia selektoreita luokkanimien sijaan, lisää validointifunktio havaitsemaan rikkoutumiset ajoissa ja tarkista selektorit kuukausittain. Katso yllä oleva selektorien ylläpito-osio tarkkaa strategiaa varten.

Lue lisää

Ke
Ke
Thunderbitin CTO | Senior Data Scientist & ML-asiantuntija Lähes vuosikymmenen kokemuksella koneoppimisesta ja data science -työstä Ke Shen on Columbia Universityn alumni ja entinen Senior Data Scientist Walmart Labsilla. Hänellä on syvällistä, alan kollegoiden tunnustamaa asiantuntemusta Pythonista, R:stä, Javasta ja tilastotieteestä, ja hän jakaa käytännössä koeteltuja oivalluksia siitä, miten monimutkaiset tekoälyalgoritmit viedään teoriasta tuotantokäyttöön sopivaksi arkkitehtuuriksi.
Sisällysluettelo

Poimi verkkosivu pelkällä pyynnöllä

Kerro mitä tarvitset selkeällä englannilla. Tai vielä parempaa, älä kerro mitään.

Kokeile Thunderbitiä ilmaista
Poimi dataa AI:n avulla
Siirrä data helposti Google Sheetiin, Airtableen tai Notioniin
Chrome Store Rating
PRODUCT HUNT#1 Product of the Week