I built this actor; it's a paid tool on Apify with a free trial credit.
Google shut down its QPX Express flight API in 2018, and there's no public Google Flights API to replace it. If you want to watch a fare for a trip, compare dates, or feed real itineraries into a travel app, you either click through the site every day or scrape it.
I'm an 18-year-old engineering student, and I built Google Flights Scraper for that. This post covers the Python and Node calls, the output, and a small fare watcher for a trip from Charlotte to Tokyo.
How it works
The actor makes plain HTTP requests (no headless browser) and reads the same results the Google Flights site shows. A one-way search is one request, takes about a second and usually returns 10-40 itineraries. A round trip lists outbound flights, then fetches the returns for the top few (3 by default) and outputs one row per complete outbound + return pair. It runs fine at 256 MB of memory.
Python example
pip install apify-client
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("rel8ble/google-flights-scraper").call(run_input={
"origin": "JFK",
"destination": "LAX",
"departureDate": "2026-11-12",
"adults": 1,
"cabinClass": "economy",
"currency": "USD",
"maxResults": 10,
})
for f in client.dataset(run["defaultDatasetId"]).iterate_items():
print(f["price"], f["airlines"], f["stops"], f["totalDuration"], f["departureDateTime"])
Node example
import { ApifyClient } from "apify-client";
const client = new ApifyClient({ token: "<YOUR_APIFY_TOKEN>" });
const run = await client.actor("rel8ble/google-flights-scraper").call({
origin: "SFO",
destination: "ORD",
departureDate: "2026-10-20",
returnDate: "2026-10-23",
maxStops: "0",
returnFlightsForTopOutbound: 3,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const t of items) {
console.log(t.price, t.flightNumbers.join("+"), "back:", t.returnFlight?.flightNumbers.join("+"));
}
origin and destination also take several airports (JFK,EWR,LGA) or a Google city ID like /m/02_286 for all New York airports.
What comes back
One row per itinerary. A real row from a test run, Charlotte to Tokyo Narita one-way, trimmed:
{
"searchId": "CLT-NRT 2026-12-03",
"tripType": "one_way",
"currency": "USD",
"category": "best",
"price": 1014,
"priceLevel": "typical",
"airlines": ["Air Canada"],
"flightNumbers": ["AC 8746", "AC 9"],
"stops": 1,
"totalDuration": "17 hr 35 min",
"departureDateTime": "2026-12-03T08:55:00-05:00",
"arrivalDateTime": "2026-12-04T16:30:00+09:00",
"arrivalDayOffset": 1,
"layovers": [
{ "airport": "YYZ", "city": "Toronto", "durationMinutes": 98, "durationText": "1 hr 38 min" }
],
"segments": [
{ "flightNumber": "AC 8746", "operatedBy": "Air Canada Express - Jazz", "aircraft": "Embraer 175", "legroom": "31 in", "emissionsKg": 142 },
{ "flightNumber": "AC 9", "aircraft": "Boeing 777", "legroom": "31 in", "emissionsKg": 582 }
],
"emissionsKg": 724,
"typicalEmissionsKg": 817,
"emissionsDifferencePercent": -11,
"googleFlightsUrl": "https://www.google.com/travel/flights/booking?tfs=..."
}
Times are local airport times with the UTC offset, so you can compute real elapsed time across time zones. priceLevel is Google's own low / typical / high verdict for that route and date, which is handy for alerts.
Use case: watch a trip across several dates
Say you're flexible on when you fly Charlotte to Tokyo in December. The searches input takes many routes and dates in one run, so you can check a week of departure dates at once and log the cheapest option for each.
import csv
import datetime
import os
from apify_client import ApifyClient
DATES = [f"2026-12-{d:02d}" for d in range(1, 8)]
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("rel8ble/google-flights-scraper").call(run_input={
"searches": [{"origin": "CLT", "destination": "NRT", "departureDate": d} for d in DATES],
"maxStops": "1",
"currency": "USD",
})
cheapest = {}
for f in client.dataset(run["defaultDatasetId"]).iterate_items():
day = f["searchId"]
if f["price"] is not None and (day not in cheapest or f["price"] < cheapest[day]["price"]):
cheapest[day] = f
today = datetime.date.today().isoformat()
new_file = not os.path.exists("clt-nrt.csv")
with open("clt-nrt.csv", "a", newline="", encoding="utf-8") as out:
w = csv.writer(out)
if new_file:
w.writerow(["checked", "search", "price", "priceLevel", "airlines", "stops", "duration"])
for day, f in sorted(cheapest.items()):
w.writerow([today, day, f["price"], f["priceLevel"], "+".join(f["airlines"]), f["stops"], f["totalDuration"]])
if f["priceLevel"] == "low":
print(f'LOW FARE: {day} ${f["price"]} {f["googleFlightsUrl"]}')
Run it once a day. The CSV gives you a price history per departure date, and the priceLevel == "low" check prints a booking link when Google itself says the fare is below normal.
What it costs
$0.50 per 1,000 results, one result = one itinerary row. A one-way search usually returns 10-40 itineraries, so a single route/date costs about $0.005-$0.02.
- The 7-date watcher above, daily for a month: roughly 7 × 25 × 30 ≈ 5,000 rows ≈ $2.60/month
- Set
maxResultsto 5 and it drops to about $0.50/month - Apify's free plan gives $5 of monthly credit, about 10,000 itineraries
Limits
- One-way and round trip only. No multi-city (3+ legs) yet.
-
No per-agency booking prices (Expedia vs the airline) and no baggage fees.
googleFlightsUrlopens the itinerary on Google Flights, where those options are listed. - Initial results page only. Google sometimes loads extra "other flights" later; in tests I got 36 JFK-LAX options, 18 Madrid-Barcelona and 7 Charlotte-Tokyo.
- Up to 9 passengers, about 11 months ahead (Google's own limits).
-
Fares change constantly.
scrapedAttells you when each row was captured.
If a round-trip return lookup fails after retries you still get the outbound row with returnFlight: null, not a crashed run. Bugs go to the Issues tab on the actor page.
Google Flights Scraper on Apify
This article was drafted with AI and published by me.
Top comments (0)