ScreenshotNeo

BlogHow-to

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.

By the ScreenshotNeo team29 September 20269 min read

How to Scrape Google Flights With Python: Fares, Routes, and Times

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:

A route query becomes itinerary-level fare, airport, and time data.
A route query becomes itinerary-level fare, airport, and time data.
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_flights or other_flights can 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.

Consent elements and overlays can be cleared before a clean capture.
Consent elements and overlays can be cleared before a clean capture.

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.