How to Scrape Google Flights With Python: Fares, Routes, and Times
Build a structured Google Flights search in Python, parse fares and flight times safely, and understand parameters, limits, and alternatives.

To scrape Google Flights with Python, use a service that documents Google Flights result retrieval, send a route and date query, validate the JSON response, and extract itinerary-level fields such as price, duration, airports, airlines, and departure times. The example below uses the documented SerpApi interface. It is a third-party integration, not a Google-published Flights API, and you should replace the example route, dates, and API key with your own values.
Google Flights pages do not provide a stable, supported page-scraping schema. A direct requests plus BeautifulSoup script or browser automation can break when page markup or access behavior changes. Automated access must also respect Google’s applicable machine-readable instructions and terms. Google’s Terms of Service prohibit using automated means to access content in violation of those instructions and prohibit bypassing protective measures.
What data can you collect?
A search response is organized around itineraries. An itinerary can include a total price, total duration, carbon-emissions information, and one or more flight legs. Each leg can contain airline details, departure and arrival airport identifiers, and local departure and arrival times. A round trip normally has outbound and return legs, while a one-way result has only the outbound journey.
| Data | Typical use |
|---|---|
| Price | Sort or display the current search price for an itinerary |
| Total duration | Compare elapsed travel time |
| Flight legs | Show each segment, connection, airline, and airport |
| Airport IDs | Build route labels and filters |
| Departure and arrival times | Display schedules in the returned local time fields |
| Carbon emissions | Expose the value when the provider returns it |
How do I scrape Google Flights in Python?
1. Create a small project and keep the key outside source control
Install the documented Python client and put your provider key in an environment variable:

python -m pip install serpapi
export SERPAPI_KEY="your-provider-key"
Do not commit the key, place it in a notebook shared publicly, or print it in logs. In production, inject it through your secret manager or deployment environment.
2. Send a route and date query
This example calculates an outbound date 30 days from today and a return date seven days after that. It requests a round trip from New York (JFK) to London (LHR), asks for US English localization, and reads either best_flights or other_flights:
import os
from datetime import date, timedelta
import serpapi
def search_flights():
api_key = os.environ["SERPAPI_KEY"]
outbound = date.today() + timedelta(days=30)
inbound = outbound + timedelta(days=7)
client = serpapi.Client(api_key=api_key)
response = client.search(
engine="google_flights",
departure_id="JFK",
arrival_id="LHR",
type=1, # round trip
outbound_date=outbound.isoformat(),
return_date=inbound.isoformat(),
currency="USD",
hl="en",
gl="us",
)
# A successful HTTP response can still contain an API-level error.
if response.get("error"):
raise RuntimeError(response["error"])
itineraries = response.get("best_flights") or response.get("other_flights") or []
if not itineraries:
raise LookupError("No flight itineraries were returned")
return response, itineraries
def print_itineraries(itineraries):
for number, itinerary in enumerate(itineraries, start=1):
price = itinerary.get("price", "unknown")
duration = itinerary.get("total_duration", "unknown")
print(f"{number}. price={price}, duration={duration}")
for leg in itinerary.get("flights", []):
departure = leg.get("departure") or {}
arrival = leg.get("arrival") or {}
print(
" "
f"{departure.get('airport', 'unknown')} "
f"{departure.get('time', 'unknown')} -> "
f"{arrival.get('airport', 'unknown')} "
f"{arrival.get('time', 'unknown')}"
)
if __name__ == "__main__":
_, itineraries = search_flights()
print_itineraries(itineraries)
The field names and accepted parameter values are provider details. Check the live Google Flights parameter reference before production deployment because vendors can change supported values or response fields.
3. Use ordinary HTTP when you need direct JSON handling
The provider also documents a normal HTTP GET pattern. This makes status handling, timeouts, retries, and response logging explicit:
import os
from datetime import date, timedelta
import requests
endpoint = "https://serpapi.com/search.json"
outbound = date.today() + timedelta(days=30)
inbound = outbound + timedelta(days=7)
params = {
"engine": "google_flights",
"api_key": os.environ["SERPAPI_KEY"],
"departure_id": "JFK",
"arrival_id": "LHR",
"type": 1,
"outbound_date": outbound.isoformat(),
"return_date": inbound.isoformat(),
"currency": "USD",
"hl": "en",
"gl": "us",
}
try:
result = requests.get(endpoint, params=params, timeout=60)
result.raise_for_status()
data = result.json()
except requests.Timeout as exc:
raise RuntimeError("The flight search timed out; retry later") from exc
except requests.RequestException as exc:
raise RuntimeError(f"The provider request failed: {exc}") from exc
if data.get("error"):
raise RuntimeError(data["error"])
itineraries = data.get("best_flights") or data.get("other_flights") or []
if not itineraries:
raise LookupError("The response was valid but contained no itineraries")
for itinerary in itineraries:
print(itinerary.get("price"), itinerary.get("total_duration"))
The wrapper documentation describes HTTP and timeout exceptions, while the endpoint example demonstrates checking both HTTP status and an API-level error field. A 200 response means the request was accepted; it does not guarantee that useful flight results exist.
Which parameters do I need?
For an airport-pair search, start with these fields:
| Parameter | Purpose |
|---|---|
departure_id |
Origin airport identifier, such as JFK |
arrival_id |
Destination airport identifier, such as LHR |
type |
Trip type: round trip, one way, or multi-city according to the provider’s values |
outbound_date |
Outbound date in YYYY-MM-DD format |
return_date |
Required for a round trip |
Some providers also accept supported place identifiers instead of plain airport codes. Confirm accepted identifiers in the current documentation.
Localization and passenger controls
gl: country or market used for localization.hl: language of returned display text.currency: currency used for prices.- Travel class: economy, premium economy, business, or first, using the provider’s accepted values.
- Passenger counts: adults, children, infants, and other supported passenger categories.
Localization can change displayed currency, language, and available presentation. A price in USD for the US market should not be treated as identical to a price shown for another country.
Stops, sorting, airlines, and time windows
Additional controls can limit stops, include or exclude airlines, select a sort order, and constrain outbound or return departure times. Apply these filters before parsing so the provider does the expensive search work. Store the complete query with each result set so you can explain why two searches differ.
One-way and multi-city searches
For a one-way trip, omit the return date and use the one-way trip type. Multi-city searches use a JSON list of legs, with each leg containing its departure, arrival, and date, rather than top-level outbound and return dates. The exact serialization and trip-type values are vendor-specific; validate them against the current parameter reference.
How do I parse fares, routes, and times safely?
Never assume that every itinerary has every optional field. Use .get(), check types, and preserve the raw response for debugging. A defensive normalizer can produce a stable internal record:
def normalize_itinerary(item):
legs = []
for flight in item.get("flights") or []:
departure = flight.get("departure") or {}
arrival = flight.get("arrival") or {}
legs.append({
"airline": flight.get("airline"),
"flight_number": flight.get("flight_number"),
"from": departure.get("airport"),
"departure_time": departure.get("time"),
"to": arrival.get("airport"),
"arrival_time": arrival.get("time"),
})
return {
"price": item.get("price"),
"duration_minutes": item.get("total_duration"),
"carbon_emissions": item.get("carbon_emissions"),
"legs": legs,
}
Keep the returned time strings with their date and timezone context when supplied. Do not convert them to UTC unless you know the provider’s timezone semantics. A midnight arrival, daylight-saving transition, or overnight flight can otherwise be displayed on the wrong calendar day.
Reliability, freshness, and cost considerations
- Refresh before acting. Flight offers are time-sensitive. Refresh and confirm current airline details before a booking decision.
- Retry narrowly. Retry transient network failures with exponential backoff and a limit. Do not blindly retry authentication or invalid-parameter errors.
- Cache by query. A short-lived cache keyed by route, dates, passengers, cabin, filters, currency, language, and country reduces duplicate calls. Label cached data with its retrieval time.
- Record provenance. Store the provider, query parameters, response timestamp, and a request identifier if supplied.
- Budget provider usage. Count searches, retries, and pagination against your provider plan. Avoid polling faster than your application needs.
- Expect incomplete groups. Either
best_flightsorother_flightscan be absent, and an itinerary can lack optional emissions or airline fields.
Do not present a search-time price as guaranteed at purchase. Duffel’s offer documentation makes the same distinction for airline offer APIs: prices and service details can change, and supplier results can be incomplete within a timeout.
When should I use an airline offers API instead?
If your application needs airline offers, orderable services, or a booking flow rather than a Google Flights-style comparison view, evaluate an airline offers API. Duffel’s documented pattern creates an offer request containing passengers and journey slices, then returns offers from a range of airlines. It is not a drop-in Google Flights replica and does not guarantee identical route coverage.
Compare the options on Google-specific coverage, airline sourcing, booking support, route and passenger filters, integration effort, and refresh behavior. Recheck offer details when the traveler is ready to book.
Or skip the browser setup
If your goal is a clean image of a flight-results page for documentation, monitoring, or an agent workflow, ScreenshotNeo provides a single screenshot request without maintaining browser automation:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://www.google.com/travel/flights -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://www.google.com/travel/flights"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://www.google.com/travel/flights' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
const image = Buffer.from(await res.arrayBuffer());
await fs.promises.writeFile('shot.webp', image);
See the ScreenshotNeo API documentation for request options. ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the page verdict and billing status in X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000.
Create a free ScreenshotNeo account and get 1,000 screenshots each month without adding a card.
Troubleshooting common errors
| Symptom | Likely cause | Fix |
|---|---|---|
| 401 or authentication error | Missing, expired, or incorrectly named key | Set SERPAPI_KEY in the running process and verify the provider account. |
| 400 invalid parameter | Wrong date format, trip type, airport ID, or filter value | Use YYYY-MM-DD, valid IATA codes, and the current parameter reference. |
| HTTP 200 but no flights | No matching itinerary group or an API-level error | Check error, then test a future date, broader stops, or another airport. |
| KeyError while parsing | Optional field is absent | Use .get() and defaults as in the normalization example. |
| Timeout | Provider or upstream search exceeded your limit | Set a finite timeout, retry transient failures with backoff, and show a recoverable error. |
| Unexpected currency or language | Localization parameters differ from the user’s market | Set gl, hl, and currency explicitly and display them with the result. |
| Stale fare | Search result was cached or the offer changed | Refresh immediately before presenting booking or payment actions. |
FAQ
Is there an official Google Flights API?
This workflow uses a third-party provider that documents Google Flights result retrieval. It should not be described as a Google-published Flights API.

Can I use airport names instead of IATA codes?
Use the identifiers accepted by your provider. IATA airport codes are the straightforward, portable starting point; some providers also support place identifiers.
Why did two searches return different prices?
Prices and availability are time-sensitive, and country, language, currency, passengers, cabin, filters, and cache state can change the response.
Should I scrape the rendered Google Flights HTML directly?
There is no stable, supported page schema established by the reviewed sources. Prefer a documented structured-result integration and respect Google’s machine-readable instructions and terms.
How do I show a flight-results page in a report?
Use ScreenshotNeo to capture the page after consent elements and popups are removed, or use its PDF tool when a document output is more useful than an image.


