Google ha spento la sua API Flights nel 2018, ma i prezzi dei biglietti non hanno smesso un attimo di muoversi — per una singola tratta nazionale possono cambiare fino a 17 volte in 48 ore. Vuoi mettere le mani su quei dati in modo programmatico? A oggi lo scraping è di fatto l’unica porta aperta.
In questi mesi ho provato e riprovato un bel po’ di approcci diversi per tirare giù i dati dei voli da Google, e ti assicuro che lo scenario è cambiato parecchio — in particolare da quando, a gennaio 2025, Google ha tirato fuori SearchGuard. In questa guida ti faccio vedere come si costruisce un crawler Python funzionante per Google Flights con Playwright, come si tengono a bada le difese anti-bot che mandano in tilt quasi tutti, e come trasformare il tutto in un tracker automatico dei prezzi con tanto di avvisi. E se il codice proprio non ti va, ti mostro anche una scorciatoia no-code con Thunderbit che arriva allo stesso risultato in un paio di minuti scarsi.
Perché estrarre Google Flights con Python?
Quando si tratta di cercare voli, Google Flights fa la parte del padrone. Negli Stati Uniti la sua visibilità su mobile è salita al 47,6%, lasciandosi alle spalle tutti i principali OTA. Il mercato del metasearch travel che gli gira attorno valeva 8,33 miliardi di dollari nel 2024 e corre a un CAGR del 30,2%. Eppure, da quando la QPX Express API è stata chiusa per sempre il 10 aprile 2018, un modo ufficiale per accedere a questi dati via codice semplicemente non esiste più.
Intanto i prezzi dei voli ballano fino al 50% sullo stesso identico itinerario, con una forbice media di una ventina di dollari tra il minimo e il massimo. Compagnie come Delta gestiscono il dynamic pricing su 77 fasce tariffarie. All’inizio del 2026 un A/R medio negli Stati Uniti costa 408 dollari, con tariffe più alte del 14,9% rispetto a un anno prima.
Piattaforma che domina, API che non c’è, prezzi che non stanno fermi un attimo. Ecco perché lo scraping di Google Flights con Python è finito tra i progetti più gettonati su GitHub e nei forum di viaggio.
Vediamo chi ci guadagna, e come:
| Tipo di utente | Caso d'uso | Vantaggio principale |
|---|---|---|
| Viaggiatori singoli | Monitorare nel tempo i prezzi di tratte specifiche | Risparmiare in media $50 per volo |
| Agenzie di viaggio | Intelligence sui prezzi della concorrenza | Monitoraggio in tempo reale della parità tariffaria |
| Team travel aziendali | Ottimizzazione dei costi sulle tratte | Risparmi del 10–30% sui viaggi business |
| Sviluppatori | Creare app di confronto tariffe | Accesso programmatico ai dati sui prezzi |
| Ricercatori | Analisi della volatilità dei prezzi delle compagnie aeree | Ricerca accademica e di mercato |
Chi bazzica i forum non gira troppo intorno al motivo per cui ha scelto lo scraping: "Google Flights API was discontinued and I should use web scraping instead" è un commento che spunta di continuo. E il ritorno c’è davvero — Hopper dichiara un’accuratezza del 95% nelle previsioni macinando oltre 5 miliardi di quotazioni al giorno, mentre i dati Expedia del 2026 raccontano che prenotare con 8–15 giorni di anticipo fa risparmiare circa $25 sui voli domestici.
Quali dati puoi estrarre da Google Flights?
Una pagina dei risultati di Google Flights è una miniera più ricca di quanto ci si aspetti. Ecco cosa trovi di solito a disposizione:
- Nome della compagnia aerea (e logo)
- Orario di partenza e codice aeroporto
- Orario di arrivo e codice aeroporto
- Durata totale del volo
- Numero di scali e dettagli sulle coincidenze (aeroporto, durata, overnight sì/no)
- Prezzo del biglietto (in base alla valuta)
- Emissioni di CO2 (kg CO2e, con differenza percentuale rispetto ai voli tipici)
- Classe di viaggio, numero del volo, modello dell’aereo
- Dati sullo spazio per le gambe
- Servizi aggiuntivi (Wi‑Fi, prese di corrente, streaming multimediale)
- Indicatore del livello di prezzo (basso/normale/alto)
- Avvisi di ritardo ("Often delayed by over 30 min")
Quali dati siano effettivamente disponibili dipende dalla tratta, dalla data e dal tipo di biglietto (solo andata oppure andata e ritorno). Ecco come si presenta in JSON un singolo record di volo estratto:
{
"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"
}
]
}
Configurare l’ambiente Python
Prima di buttare giù una sola riga di codice di scraping, c’è qualche pezzo da mettere a posto.
Prerequisiti:
- Difficoltà : Intermedia
- Tempo richiesto: circa 1–2 ore per l’intero tutorial
- Cosa ti serve: Python 3.7+, qualche nozione di base di Python, un browser basato su Chrome
Installa le librerie necessarie
Per l’automazione del browser ci affidiamo a Playwright (Google Flights è renderizzato al 100% in JavaScript — le richieste HTTP statiche non ti restituiscono niente di utile), più un paio di strumenti di supporto:
pip install playwright playwright-stealth pandas
playwright install chromium
- Playwright — automazione browser headless, si occupa del rendering JavaScript e ha i meccanismi di attesa già integrati
- playwright-stealth — smorza i segnali di rilevamento bot più comuni
- pandas — per l’analisi dei dati e l’export CSV che vedremo più avanti
Perché Playwright invece di Selenium o requests
Con requests + BeautifulSoup da soli, Google Flights non si lascia scrapare — il contenuto della pagina viene tirato su tutto via JavaScript. Qui serve un browser vero.
| Funzione | Playwright | Selenium | requests + BS4 |
|---|---|---|---|
| Rendering JS | Supporto completo | Supporto completo | Nessuno |
| Velocità | 42% più veloce in generale | Base | N/D per questo caso d'uso |
| Supporto async | Nativo | Solo sequenziale | N/D |
| Uso memoria | 30% in meno | Più alto | Minimo |
| Elusione anti-bot | Buona (con stealth) | Più facile da rilevare | N/D |
Playwright è più rapido, più moderno e gestisce l’async molto meglio. Su Google Flights, nello specifico, è la scelta che dà meno grattacapi.
Passo dopo passo: come estrarre Google Flights con Python
Eccoci al cuore del tutorial. Costruiamo lo scraper un mattone alla volta.

Passo 1: definisci le tue classi dati
Si parte dando una struttura ai parametri di ricerca e ai dati dei voli con le dataclass di Python. Così il codice resta ordinato e allargarlo in seguito diventa molto più semplice.
from dataclasses import dataclass, field
from typing import Optional, List
@dataclass
class SearchParams:
origin: str # e.g., "SFO"
destination: str # e.g., "JFK"
departure_date: str # e.g., "2026-05-15"
return_date: Optional[str] = None
trip_type: str = "one-way" # "one-way" or "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 = ""
Ogni campo combacia esattamente con ciò che andremo a estrarre dalla pagina. Avere questa impalcatura fin da subito ti risparmia di doverti trascinare in giro dizionari disordinati più avanti.
Passo 2: capire la struttura dell’URL di Google Flights
Google Flights impacchetta i parametri di ricerca usando Protobuf codificato in Base64 dentro il parametro URL tfs. Puoi provare a decodificare questo formato, oppure imboccare la strada più comoda: costruire un URL in linguaggio naturale.
Il sistema più immediato è sfruttare il formato della query di ricerca:
https://www.google.com/travel/flights?q=flights+from+SFO+to+JFK+on+2026-05-15&curr=USD
Se vuoi avere più controllo, gli URL puoi anche generarli in modo programmatico:
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"
L’altra via — il reverse engineering della codifica Protobuf — ti dà un controllo più fine, ma si spacca ogni volta che Google ritocca il formato interno. Librerie come fast-flights su GitHub adottano la decodifica Protobuf per saltare del tutto il parsing dell’HTML, ma è un approccio decisamente più avanzato.
Passo 3: avvia il browser e apri Google Flights
Ecco la configurazione di Playwright. Sfruttiamo playwright-stealth per abbassare fin da subito il rischio di farci beccare.
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",
)
# Pre-imposta il cookie di consenso per saltare il popup
await context.add_cookies([{
"name": "SOCS",
"value": "CAESHwgBEhJnd3NfMjAyNTAyMjctMF9SQzIaBXpoLUNOIAEaBgiAy6O-Bg",
"domain": ".google.com",
"path": "/"
}])
page = await context.new_page()
In produzione gira tutto in headless (per il debug passa a headless=False), con viewport e user agent realistici, e con il cookie SOCS preimpostato per scavalcare il popup del consenso — ci torniamo sopra nella sezione anti-bot.
Passo 4: vai alla pagina dei risultati
Carica l’URL che hai costruito e aspetta che compaiano i risultati dei voli:
url = build_flights_url(
params.origin, params.destination, params.departure_date
)
await page.goto(url, wait_until="networkidle")
# Attendi il caricamento dei risultati dei voli
await page.wait_for_selector(
"li.pIav2d", timeout=15000
)
Se a questo punto va in timeout, di norma vuol dire una di due cose: o il popup di consenso ha bloccato la pagina (rivedi il fix con il cookie al Passo 3), oppure Google ti sta servendo un CAPTCHA. Nella sezione anti-bot affrontiamo entrambi i casi.
Passo 5: carica tutti i risultati dei voli
Google Flights tiene nascosti altri risultati dietro il pulsante "Show more flights". Tocca cliccarlo a ripetizione finché tutti i voli non sono in vista:
# Clicca "Show more flights" finché tutti i risultati non sono caricati
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
Il ciclo clicca il pulsante, dà 2 secondi al rendering dei nuovi risultati e si ferma quando il pulsante sparisce. Nei miei test, sulla maggior parte delle tratte si arriva a 1–3 pagine di risultati.
Passo 6: estrai i dati dei voli con i selettori CSS
Adesso tiriamo fuori i dati veri dei voli dalla pagina ormai caricata. Ecco i selettori (verificati ad aprile 2026 — più sotto, nella sezione manutenzione, ti spiego perché questa data conta):
flights = []
cards = await page.query_selector_all("li.pIav2d")
for card in cards:
flight = FlightData()
# Nome compagnia
airline_el = await card.query_selector(
"div.sSHqwe span:not([class])"
)
if airline_el:
flight.airline = (await airline_el.inner_text()).strip()
# Orario di partenza
dep_el = await card.query_selector(
'span[aria-label*="Departure time"]'
)
if dep_el:
flight.departure_time = (await dep_el.inner_text()).strip()
# Orario di arrivo
arr_el = await card.query_selector(
'span[aria-label*="Arrival time"]'
)
if arr_el:
flight.arrival_time = (await arr_el.inner_text()).strip()
# Durata
dur_el = await card.query_selector("div.gvkrdb")
if dur_el:
flight.duration = (await dur_el.inner_text()).strip()
# Scali
stops_el = await card.query_selector("div.EfT7Ae span")
if stops_el:
flight.stops = (await stops_el.inner_text()).strip()
# Prezzo
price_el = await card.query_selector(
"div.FpEdX span"
)
if price_el:
flight.price = (await price_el.inner_text()).strip()
# Emissioni di 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
Una raccomandazione: classi come pIav2d, sSHqwe e FpEdX le genera il Closure Compiler di Google e cambiano da una build all’altra. I selettori che si appoggiano a aria-label reggono molto meglio. Più avanti vediamo una strategia di manutenzione completa.
Passo 7: salva i risultati in JSON o CSV
Per finire, salva i dati estratti con tanto di timestamp (è cruciale per il price tracking che facciamo dopo):
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)
# Salva anche in 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())
Lanciando questo script dovresti ritrovarti con flights.json e flights.csv pieni di risultati. Nei miei test una ricerca SFO-JFK restituisce di solito 30–80 opzioni di volo e ci mette sui 15–20 secondi a completarsi.
Guida di sopravvivenza anti-bot per lo scraping di Google Flights
Quasi tutti i tutorial si fermano qui. E quasi tutti gli scraper falliscono qui. Google ha rilasciato SearchGuard a gennaio 2025, e dall’oggi al domani ha fatto fuori praticamente ogni scraper SERP. Google stesso lo descrive come "il risultato di decine di migliaia di ore-uomo e milioni di dollari di investimento". Google Flights ha un livello di difficoltà 4/5 per lo scraping.
Nessun articolo concorrente entra nel dettaglio su questo punto, eppure è il motivo numero uno per cui gli scraper smettono di girare. Ecco con cosa devi vedertela e come gestirla.

Ritardi casuali tra le richieste
La difesa più elementare contro il rate limiting. Due righe di codice, efficacia media:
import time
import random
time.sleep(random.uniform(3, 7))
Infilale tra una navigazione e l’altra. Intervalli fissi (tipo sempre 5 secondi esatti) sono una bandiera rossa — meglio randomizzare.
Rotazione dello User-Agent
Mandare sempre la stessa stringa user-agent è un indizio che si individua al volo. Falla ruotare pescando da una lista:
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)
Bypass del rilevamento headless
Google va a controllare il flag navigator.webdriver e altri segnali di automazione. La libreria playwright-stealth ne copre già gran parte, ma conviene comunque impostare anche gli argomenti di avvio visti al Passo 3. I flag che contano:
args=[
"--disable-blink-features=AutomationControlled",
"--disable-dev-shm-usage",
"--no-first-run",
]
Ti basta a superare il rilevamento di base. SearchGuard scava più a fondo — tiene d’occhio velocità del mouse, tempi di pressione dei tasti e pattern di scrolling — ma per uno scraping a volume moderato la modalità stealth, abbinata a ritardi realistici, di solito basta e avanza.
Rotazione proxy: datacenter vs residential
Per qualsiasi cosa che vada oltre un pugno di ricerche, ti servono dei proxy. E la differenza pesa:
| Funzione | Proxy datacenter | Proxy residential |
|---|---|---|
| Velocità | 100–1.000 Mbps | 10–100 Mbps |
| Tasso di successo (Google) | 20–40% | 85–95% |
| Costo | $0,10–$0,50/IP/mese | $5–$15/GB |
| Rischio di rilevamento | Alto | Molto basso |
Quando estrai siti protetti, i proxy residential costano circa 180 volte meno per richiesta riuscita. I prezzi dei provider nel 2026: Smartproxy da $7/GB, Bright Data $8,40/GB, Oxylabs $8/GB.
Per agganciare un proxy a Playwright fai così:
browser = await pw.chromium.launch(
proxy={"server": "http://proxy-host:port",
"username": "user", "password": "pass"}
)
Gestione del popup di consenso cookie
Il popup "I agree to terms" salta fuori di continuo nelle segnalazioni degli utenti come blocco fastidioso: "first google will show you the 'I agree to terms and conditions' popup." La soluzione più pulita è preimpostare il cookie SOCS (come al Passo 3). Se non dovesse bastare, clicca direttamente sul 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 # Nessun popup presente
Tieni presente: il testo del pulsante cambia con la lingua — "Alle akzeptieren" in tedesco, "Tout accepter" in francese.
Riferimento rapido anti-bot
| Tecnica | Difficoltà | Efficacia | Richiede codice? |
|---|---|---|---|
| Ritardi casuali (2–7s) | Bassa | Media | 2 righe |
| Rotazione user-agent | Bassa | Media | 5 righe |
| Bypass rilevamento headless | Media | Alta | Argomenti di avvio Playwright |
| Plugin playwright-stealth | Media | 60–80% su siti base | pip install |
| Rotazione proxy (datacenter) | Media | Media | Configurazione |
| Rotazione proxy (residential) | Media | 85–95% di successo | Configurazione |
| Preimpostazione consenso cookie (SOCS) | Bassa | Richiesto | 1 riga |
Sulle frequenze ritenute sicure: tieniti su intervalli di 10–20 secondi tra le richieste, con rotazione IP. Le soglie di Google si aggirano sulle 100 richieste/minuto per IP prima che scatti un 429, e volumi prolungati oltre le 1.000 richieste/giorno per IP rischiano di far partire blocchi temporanei.
Perché i selettori di Google Flights continuano a rompersi (e come risolvere)
È il problema n. 1, senza paragoni. I thread nei forum traboccano di varianti del tipo "all I get back is 14 empty lists." Ogni tutorial ti sgancia dei selettori. Nessuno ti spiega perché poi smettono di funzionare.
Perché i selettori di Google Flights cambiano
Dipende da tre cose:
-
Offuscamento tramite Closure Compiler. Google usa Closure Stylesheets per sfornare nomi di classe come
BVAVmfeYMlIzattraversogoog.setCssNameMapping(). Cambiano a ogni build — a volte settimana dopo settimana. -
A/B testing. Nello stesso momento, utenti diversi vedono strutture HTML diverse. Il tuo scraper può girare benissimo sulla tua macchina e fallire per qualcuno in un’altra regione.
-
Differenze di localizzazione. Gli utenti UE si trovano davanti termini, layout e perfino campi dati diversi rispetto a quelli statunitensi.
Scrivi selettori più robusti
Privilegia i selettori legati al significato, non all’aspetto:
# Fragile — si rompe a ogni build
price_el = await card.query_selector("div.BVAVmf > div.YMlIz")
# Più resistente — legato alle etichette di accessibilitÃ
dep_el = await card.query_selector('span[aria-label*="Departure time"]')
# Anch'esso resistente — matching basato sul testo
more_btn = page.locator('button:has-text("Show more flights")')
Gerarchia di stabilità dei selettori (dal più stabile al meno stabile):
- Attributi
aria-label— legati all’accessibilità , li toccano di rado - Attributi
data-*— messi lì apposta per la funzionalità - Attributi
role— i ruoli ARIA hanno valore semantico - Selettori basati sul testo — combaciano con il contenuto a schermo
- Matching parziale delle classi — ad es.
[class*="price"] - Nomi di classe completi offuscati — da evitare finché si può
Aggiungi una funzione di validazione
Non lasciare che dei selettori rotti ti producano dati vuoti sotto il naso senza che te ne accorga. Intercettali subito:
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"Missing '{field_name}' — selectors may need updating"
)
valid = False
return valid
Falla girare su ogni volo che estrai. Appena cominciano a spuntare i warning, è il segnale che è ora di ispezionare la pagina e aggiornare i selettori.
Strategia di manutenzione dei selettori
- Ricontrolla i selettori ogni mese, o subito appena la qualità dei risultati comincia a calare
- Tieni i selettori in un dizionario di configurazione a parte, così aggiornarli è una passeggiata
- I selettori di questo articolo sono stati verificati l’ultima volta: aprile 2026
- Dai un’occhiata alla libreria fast-flights come alternativa — usa la decodifica Protobuf al posto dei selettori CSS, scansando del tutto il problema (anche se resta comunque vulnerabile quando Google cambia i formati dati interni)
Da scraping una tantum a tracker automatico dei prezzi di Google Flights
La gran parte dei tutorial chiude con "salva in JSON". Il titolo di questo articolo, però, promette "Price Alerts". È il momento di mantenere la parola.
![]()
Pianifica l’esecuzione automatica dello scraper
Opzione 1: libreria schedule di Python (la più semplice, multipiattaforma):
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)
Opzione 2: cron job (Linux/Mac):
# Esegui alle 6:00 e alle 18:00 ogni giorno
0 6,18 * * * cd /path/to/scraper && python scraper.py
Opzione 3: Utilità di pianificazione di Windows — crea un’attività di base che lancia python scraper.py alla frequenza che preferisci.
Il rovescio della medaglia è che tutte queste soluzioni vogliono una macchina sempre accesa. Se lo fai girare su un laptop che va in sospensione, qualche esecuzione la perdi.
Conserva lo storico dei prezzi
Smetti di sovrascrivere un file JSON e passa all’inserimento progressivo in un database 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()
Dopo una settimana di scraping due volte al giorno avrai dati a sufficienza per iniziare a cogliere qualche trend.
Analizza l’andamento dei prezzi e imposta avvisi
Pesca l’opzione più conveniente nei dati storici:
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"Più economico: ${cheapest['price_usd']:.0f} il "
f"{cheapest['scrape_date']} ({cheapest['airline']})"
)
Fai partire un avviso email quando il prezzo scende sotto la soglia che hai fissato:
import smtplib
from email.mime.text import MIMEText
def send_price_alert(route, price, threshold, recipient):
msg = MIMEText(
f"Price drop alert! {route}: ${price:.0f} "
f"(below your ${threshold:.0f} threshold)"
)
msg["Subject"] = f"Flight Deal: {route} at ${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)
# Dopo ogni scraping, controlla se ci sono offerte
min_price = df["price_usd"].min()
threshold = 250
if min_price < threshold:
send_price_alert("SFO-JFK", min_price, threshold,
"you@email.com")
Quanto spesso conviene scrapare? Due volte al giorno bastano per il monitoraggio personale dei prezzi (e la tempistica casuale tiene basso il rischio di rilevamento). Ogni 4–6 ore se stai monitorando per un’azienda. Ogni ora solo nei periodi di forte promozione, e solo per un po’.
La strada più semplice: lo Scheduled Scraper di Thunderbit
Se mettere in piedi cron job, un server sempre acceso e configurazioni proxy ti sembra più infrastruttura di quella che hai voglia di mantenere, lo Scheduled Scraper di Thunderbit copre lo stesso caso d’uso senza tutto quel peso operativo. Descrivi in linguaggio naturale ogni quanto vuoi scrapare, incolli gli URL di Google Flights, e lo scraper parte da solo sull’infrastruttura cloud di Thunderbit — con gestione anti-bot già integrata ed export diretto verso Google Sheets, Excel o Airtable. Non rimpiazza in toto l’approccio Python (rinunci alla personalizzazione), ma per il caso d’uso preciso "voglio un foglio di calcolo che mi tenga d’occhio i prezzi" è la via più rapida. Lo puoi provare nel piano gratuito.
Quando Python è troppo: modi no-code per estrarre Google Flights
Dopo aver costruito tutto questo, lo dico senza giri di parole: di pezzi in ballo ce ne sono parecchi. Non a tutti serve un livello di controllo così spinto. I selettori si rompono, i proxy vanno ruotati, i cron job vanno tenuti d’occhio. Se l’obiettivo è "portare con regolarità i prezzi dei voli in un foglio di calcolo", esistono strade più veloci.
Confronto: Python fai-da-te vs servizi API vs Thunderbit
| Approccio | Tempo di configurazione | Serve codice | Gestione anti-bot | Pianificazione | Costo |
|---|---|---|---|---|---|
| Playwright fai-da-te (questo tutorial) | 1–2 ore | Python (intermedio) | Configurazione manuale | Manuale (cron) | Gratis + costi proxy |
| Endpoint Google Flights di SerpApi | 15 min | Solo chiamate API | Gestita | Via API | circa $50+/mese |
| Estensione Chrome di Thunderbit | 2 min | Nessuno | Scraping cloud | Scheduler integrato | Piano gratuito disponibile |
Una nota su SerpApi: Google ha avviato una causa DMCA contro SerpApi a dicembre 2025, sostenendo che le loro richieste siano cresciute del 25.000% in due anni. Vale la pena tenere a mente questa incertezza legale se stai valutando provider API.
Come Thunderbit estrae Google Flights
Apri i risultati di ricerca di Google Flights in Chrome, clicca il pulsante "AI Suggest Fields" di Thunderbit — l’AI legge la pagina e propone colonne come compagnia, prezzo, orario di partenza, scali — passi in rassegna i campi suggeriti e clicchi "Scrape." I risultati arrivano in una tabella che puoi esportare in Excel, Google Sheets, Airtable o Notion — il tutto nel piano gratuito.
Per il caso d’uso del price tracking, in particolare, lo Scheduled Scraper di Thunderbit e il Cloud Scraping (in grado di macinare 50 pagine in parallelo) rimpiazzano tutta l’infrastruttura fatta di cron, proxy e server.
Python ti dà controllo totale e personalizzazione senza limiti. Thunderbit ti dà velocità e zero manutenzione. Scegli in base a quello che ti serve davvero. Se vuoi approfondire gli approcci no-code allo scraping, dai un’occhiata alla nostra guida sui migliori web scraper no-code.

È legale fare scraping di Google Flights? Cosa devi sapere
Nei forum questo punto torna spesso a galla: "scraping Google Flights directly violates the TOS that Google has." Preoccupazione più che legittima — soprattutto perché l’API è stata dismessa e non c’è un’alternativa ufficialmente autorizzata.
Violazione dei TOS vs responsabilità legale
I Termini di servizio di Google (aggiornati il 22 maggio 2024) dicono che gli utenti non devono "accedere o utilizzare i Servizi o qualsiasi contenuto tramite mezzi automatizzati (come robot, spider o scraper)." Violare i ToS è un inadempimento contrattuale (materia civile) — non vuol dire automaticamente infrangere la legge.
Il precedente legale chiave: hiQ v. LinkedIn (Nono Circuito, 2022) ha stabilito che fare scraping di dati pubblicamente disponibili non viola il Computer Fraud and Abuse Act (CFAA). Va detto però che il caso si è chiuso con un accordo, e la causa di dicembre 2025 contro SerpApi poggia su una teoria legale diversa — la Sezione 1201 del DMCA (elusione delle misure tecnologiche di protezione) — potenzialmente più pesante.
Best practice per uno scraping responsabile
- Limita la frequenza delle richieste — ritardi di 10–20 secondi con rotazione IP
- Non estrarre dati personali — i prezzi dei voli sono dati aggregati mostrati pubblicamente
- Non aggirare i CAPTCHA in modo programmatico (è qui che entra in gioco il rischio DMCA)
- Usa i dati per ricerca personale, non per tirare su un prodotto commerciale concorrente senza una licenza adeguata
- Valuta le API ufficiali dove ci sono
Fonti dati alternative
Se per il tuo caso d’uso lo scraping ti pare troppo rischioso, ci sono opzioni API legittime:
| Provider | Costo | Piano gratuito | Note |
|---|---|---|---|
| SerpApi | $75–$3.750+/mese | 250 ricerche/mese | JSON di Google Flights diretto (sotto esame legale) |
| Kiwi Tequila | Gratis (modello affiliate) | Illimitato | Ottimo per startup e test |
| Amadeus | Pay-as-you-go | 2.000 richieste/mese | Oltre 400 compagnie, possibilità di prenotazione |
| Skyscanner | Personalizzato | Richiede approvazione | 52 mercati, 30 lingue |
Se vuoi un quadro completo, abbiamo scritto un approfondimento dedicato alle implicazioni legali dello web scraping.
Conclusione e punti chiave
È stata roba tanta, lo so. Ecco ciò che conta davvero:
- Python + Playwright è l’approccio più flessibile per estrarre Google Flights, ma chiede manutenzione costante
- Le difese anti-bot (ritardi, rotazione user-agent, proxy residential) non sono un optional — sono il cuore dell’affidabilità , ancora di più dopo SearchGuard
- I selettori si rompono spesso — usa
aria-labele selettori basati sul testo quando puoi, valida l’output e tieni viva una routine di manutenzione - Automatizza con
scheduleo cron per trasformare uno scraping una tantum in un vero tracker dei prezzi con storico e avvisi email - Thunderbit è l’alternativa no-code con pianificazione integrata, scraping cloud e gestione anti-bot — perfetta se quello che vuoi è un foglio di calcolo per il tracking dei prezzi più che un progetto di programmazione
- Rispetta i limiti legali — modera la frequenza, estrai solo dati pubblici e valuta le alternative API per usi commerciali
Scarica il codice di questo tutorial oppure installa l’estensione Chrome di Thunderbit per la via più rapida. In un modo o nell’altro, passerai dal ricaricare a mano Google Flights al monitorare per davvero i prezzi dei voli.
Per altre tecniche di scraping in Python, leggi le nostre guide su come fare web scraping con Python e sui migliori strumenti Python per il web scraping.
FAQ
1. Posso estrarre Google Flights senza Python?
Sì. Servizi API come SerpApi e Kiwi Tequila restituiscono dati strutturati sui voli tramite chiamate API (senza alcuna automazione browser). Per un approccio del tutto no-code, l’estensione Chrome di Thunderbit estrae i risultati di Google Flights direttamente dal browser, con campi suggeriti dall’AI ed export con un clic.
2. Google blocca lo scraping dei voli?
Google si affida a sistemi di rilevamento bot (SearchGuard), CAPTCHA e rate limiting. Con le giuste misure anti-bot — ritardi casuali, rotazione user-agent, proxy residential e impostazioni stealth del browser — puoi tenere uno scraping affidabile a volumi moderati. Per tecniche e soglie precise, vedi la sezione anti-bot più sopra.
3. Con che frequenza dovrei estrarre Google Flights per il price tracking?
Due volte al giorno (a orari casuali) bastano per il monitoraggio personale dei prezzi e tengono basso il rischio di rilevamento. Per il monitoraggio business, ogni 4–6 ore con rotazione dei proxy. Evita lo scraping orario, tranne brevi finestre di promozione delle tariffe — fa schizzare la probabilità di blocco.
4. Esiste una API gratuita di Google Flights?
L’API ufficiale Google QPX Express è stata dismessa nell’aprile 2018. Un sostituto ufficiale gratuito non c’è. L’opzione gratuita che ci si avvicina di più è la Kiwi Tequila API (modello affiliate, ricerche illimitate). SerpApi offre 250 ricerche gratuite al mese. Per la maggioranza degli utenti, lo scraping o uno strumento no-code come Thunderbit resta la strada più pratica.
5. Perché i selettori CSS di Google Flights continuano a restituire dati vuoti?
Google usa il Closure Compiler per generare nomi di classe offuscati che cambiano a ogni build. Ci si mettono anche gli A/B test e le differenze di localizzazione a far variare la struttura HTML da utente a utente. La soluzione: usare attributi aria-label e selettori basati sul testo al posto dei nomi di classe, aggiungere una funzione di validazione per intercettare presto i problemi e ricontrollare i selettori ogni mese. Vedi la sezione sulla manutenzione dei selettori per una strategia nel dettaglio.
Scopri di più


