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ätyyppi | Käyttötapaus | Keskeinen hyöty |
|---|---|---|
| Yksittäiset matkustajat | Seuraa tiettyjen reittien hintoja ajan yli | Säästä keskimäärin 50 dollaria lentoa kohden |
| Matkatoimistot | Kilpailukykyisen hinnoittelun seuranta | Reaaliaikainen hintapariteetin valvonta |
| Yritysmatkat-tiimit | Kustannusten optimointi reiteittäin | 10–30 % säästö yritysmatkailussa |
| Kehittäjät | Rakenna lentohintojen vertailusovelluksia | Ohjelmallinen pääsy hintadataan |
| Tutkijat | Lentoyhtiöiden hintavaihtelun analysointi | Akateeminen 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.
| Ominaisuus | Playwright | Selenium | requests + BS4 |
|---|---|---|---|
| JS-renderöinti | Täysi tuki | Täysi tuki | Ei lainkaan |
| Nopeus | 42 % nopeampi kokonaisuutena | Vertailutaso | Ei sovellu tähän |
| Async-tuki | Natiivi | Vain peräkkäinen | Ei sovellu |
| Muistin käyttö | 30 % vähemmän | Korkeampi | Minimaalinen |
| Bottitunnistuksen kierto | Hyvä (stealthillä) | Helpompi tunnistaa | Ei 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.

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.

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ä:
| Ominaisuus | Datacenter-proxyt | Residential-proxyt |
|---|---|---|
| Nopeus | 100–1 000 Mbps | 10–100 Mbps |
| Onnistumisaste (Google) | 20–40 % | 85–95 % |
| Hinta | 0,10–0,50 $ / IP / kk | 5–15 $ / GB |
| Tunnistusriski | Korkea | Erittä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
| Tekniikka | Vaikeus | Teho | Vaatiiko koodia? |
|---|---|---|---|
| Satunnaiset viiveet (2–7 s) | Matala | Keskitaso | 2 riviä |
| User-Agentin kierto | Matala | Keskitaso | 5 riviä |
| Headless-tunnistuksen kierto | Keskitaso | Korkea | Playwrightin launch-argumentit |
| playwright-stealth-laajennus | Keskitaso | 60–80 % perussivustoilla | pip install |
| Proxy-kierto (datacenter) | Keskitaso | Keskitaso | Asetukset |
| Proxy-kierto (residential) | Keskitaso | 85–95 % onnistuminen | Asetukset |
| Suostumusikkunan esiasetus (SOCS) | Matala | Pakollinen | 1 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:
-
Closure Compilerin obfuskointi. Google käyttää Closure Stylesheets -tekniikkaa, joka generoi luokkanimiä kuten
BVAVmfjaYMlIzgoog.setCssNameMapping()-mekanismin kautta. Nämä vaihtuvat jokaisessa buildissa — joskus viikoittain. -
A/B-testaus. Eri käyttäjille näytetään samanaikaisesti eri HTML-rakenteita. Scraper voi toimia omalla koneellasi mutta epäonnistua toisella alueella.
-
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):
aria-label-attribuutit — sidottu saavutettavuuteen, muuttuvat harvoindata-*-attribuutit — lisätään nimenomaan toiminnallisuutta vartenrole-attribuutit — ARIA-roolit ovat semanttisia- Tekstipohjaiset selektorit — vastaavat näkyvää sisältöä
- Osittaista luokkanimeä vastaavat selektorit — esim.
[class*="price"] - 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.
![]()
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ähestymistapa | Käyttöönottoaika | Koodausta vaaditaan | Käsittelee bottisuojauksen | Ajastus | Hinta |
|---|---|---|---|---|---|
| DIY Playwright (tämä opas) | 1–2 tuntia | Python (keskitaso) | Manuaalinen konfigurointi | Manuaalinen (cron) | Ilmainen + proxy-kulut |
| SerpApi Google Flights -endpoint | 15 min | Vain API-kutsut | Hoidettu | API:n kautta | Noin 50 $+/kk |
| Thunderbit Chrome -laajennus | 2 min | Ei lainkaan | Pilviskräpäys | Sisäänrakennettu ajastin | Ilmainen 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.

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:
| Palveluntarjoaja | Hinta | Ilmainen taso | Huomiot |
|---|---|---|---|
| SerpApi | 75–3 750 $+/kk | 250 hakua/kk | Suora Google Flights -JSON (oikeudellisen tarkastelun alla) |
| Kiwi Tequila | Ilmainen (affiliate-malli) | Rajaton | Paras startupeille ja testaukseen |
| Amadeus | Käytön mukaan | 2 000 pyyntöä/kk | 400+ lentoyhtiötä, varausmahdollisuus |
| Skyscanner | Räätälöity | Vaatii hyväksynnän | 52 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ää


