Google Flights Scraper — Fares, Flight Numbers, Layovers, CO2 avatar

Google Flights Scraper — Fares, Flight Numbers, Layovers, CO2

Pricing

$0.30 / 1,000 per flight itineraries

Go to Apify Store
Google Flights Scraper — Fares, Flight Numbers, Layovers, CO2

Google Flights Scraper — Fares, Flight Numbers, Layovers, CO2

Search Google Flights by route and date and get every itinerary on the board: price, airline, per-leg flight numbers, layovers, stops, duration, baggage and CO2. One-way and round-trip. Pay only for itineraries returned — $0.30 per 1,000.

Pricing

$0.30 / 1,000 per flight itineraries

Rating

0.0

(0)

Developer

Scrapers Delight

Scrapers Delight

Maintained by Community

Actor stats

1

Bookmarked

1

Total users

1

Monthly active users

10 days ago

Last modified

Share

✈️ Google Flights Scraper — fares, per-leg flight numbers, layovers and CO2

Give it a route and a date. Get back every itinerary Google Flights puts on that fare board — the price, the airline, the actual flight number of every leg, each layover with its airport and duration, stops, total duration, baggage allowance and the full CO2 block.

No API key. No login. No browser. One HTTP request per board, parsed out of Google's own server-rendered HTML.

$0.30 per 1,000 itineraries. You pay per itinerary returned — nothing else.


🎯 The two things this does that the lane does not

1. Per-leg flight numbers, without a second request

Most Google Flights scrapers hand you "1 stop, Alaska, 10 hr 12 min" and stop there. This one gives you the itinerary Google itself files with the Travel Impact Model:

"legs": [
{ "origin": "JFK", "destination": "SEA", "carrierCode": "AS", "flightNumber": "21", "date": "2026-10-15" },
{ "origin": "SEA", "destination": "LAX", "carrierCode": "AS", "flightNumber": "487", "date": "2026-10-15" }
],
"flightNumbers": ["AS21", "AS487"],
"layovers": [
{ "ordinal": "1 of 1", "airportCode": "SEA", "airportName": "Seattle-Tacoma International Airport",
"city": "Seattle", "durationText": "58 min", "durationMinutes": 58 }
]

That is a real row from a real board. Flight numbers make a fare row joinable — to schedules, to OAG/Cirium data, to your own inventory — instead of just readable.

2. You are not billed twice for the same flight

Google renders every fare board twice. A JFK→LAX board contains 196 itinerary <li> elements for 98 real itineraries — same ssk, same data-id, same aria-label, same layovers, rendered into two sets of containers. Measured on four boards: 196→98, 178→89, 114→57, 96→48.

The obvious parser (cheerio → li.pIav2d → push) emits all 196 and bills you for all 196. This actor deduplicates before anything is delivered or charged. On the validation run below: 400 raw rows read, 75 delivered, 75 billed.


📦 What you get on every row — 65 fields

Search context

itineraryId · section (Google's own "Top flights" vs "Other flights" bucket) · rankInSection · searchOrigin · searchDestination · searchDepartureDate · searchReturnDate · tripType · cabinClass · passengers · currency · currencyName · searchUrl · tfsToken · scrapedAt

Price

price (numeric) · priceFormatted ("$204", "£1,455") · priceType (one way / round trip total) · priceUnavailable · bookingToken · priceLevel (low/typical/high from Google's Price-insights panel) · priceDeltaVsUsual (signed, e.g. -46) · bookingTip · resultsOnBoard

Airlines

airlines[] · airlineCodes[] (IATA, derived per leg — works on multi-carrier rows where the logo is absent) · airlineLogoUrl · operatedBy ("Operated by Republic Airways as American Eagle") · isCodeshare · mixedCabin ("Business Class + Premium Economy")

Schedule

departureTime · departureDate · departureDateTimeIso · arrivalTime · arrivalDate · arrivalDateTimeIso · arrivalDayOffset (+1 / +2) · durationText · durationMinutes · originCode · originName · destinationCode · destinationName

Routing

stops · stopsText · layovers[] (ordinal, airportCode, airportName, city, durationText, durationMinutes, transferFrom, transferTo) · longLayover · changeOfAirport · legs[] · flightNumbers[]

Baggage

carryOnBagsIncluded · checkedBagsIncluded · baggageNote

Emissions (the full block Google exposes)

co2Grams · co2Kg · co2TypicalGrams · co2SavingsGrams · co2PercentVsTypical · emissionsLabel · relativeEmissions (1 = low, 3 = high) · environmentalRanking · contrailsImpact · easaSafDiscountGrams · travelImpactModelUrl · seatingClassCode


📊 Measured field fill — from a real Apify run, not an estimate

Run t17MbzlTYmjUVhwdm, 2026-09-03. 3 boards, 75 itineraries delivered, SUCCEEDED in 11.4 s, peak memory 415 MB, chargedEventCounts = 75. Searches: JFK→LAX 2026-10-15 one-way (97 on the board), JFK→BKK 2026-11-10 one-way (58), JFK↔LAX 2026-11-10/17 round-trip (45), each capped at 25 rows.

65 fields on every row. 56 of them at 100% fill.

Field groupFill
ids, search context, currency, currencyName, searchUrl, tfsToken, scrapedAt100%
price, priceFormatted, priceType, priceUnavailable, bookingToken100%
airlines, airlineCodes, airlineLogoUrl, isCodeshare100%
all 8 schedule fields + arrivalDayOffset, durationText, durationMinutes100%
originCode/Name, destinationCode/Name, stops, stopsText100%
legs, flightNumbers, longLayover, changeOfAirport100%
all 12 emissions fields incl. travelImpactModelUrl100%
priceLevel, resultsOnBoard100%
layovers56.0% — nonstop rows have none, by definition
carryOnBagsIncluded, checkedBagsIncluded, baggageNote66.7% — Google omits the baggage line on some international fares
searchReturnDate, priceDeltaVsUsual, bookingTip33.3% — only on the round-trip search / only when Google shows that panel
operatedBy1.3% — a codeshare marker; it is true data, not a gap
mixedCabin0% on this run — economy boards don't mix cabins (measured 12 of 89 rows on a business board)

Nothing above is padding: layovers is empty on nonstop rows because a nonstop flight has no layover, and operatedBy is rare because codeshares are rare (12 of 98, 15 of 89, 2 of 48 and 1 of 57 rows on the four validation boards).

Also validated offline against 7 captured boards — exact row counts

BoardRaw <li>Unique itinerariesParsed
JFK→LAX one-way (residential proxy)1969898 ✅
JFK→LAX one-way (datacenter proxy)1969898 ✅
JFK→LAX one-way (direct)1969898 ✅
JFK↔LAX round-trip964848 ✅
JFK→BKK one-way1145757 ✅
JFK→LHR business, GBP, 2 pax1788989 ✅
LWS→BFF (no such route)000 ✅ clean empty

488 rows, every itineraryId unique, £1,455 parsed as price: 1455 + currencyName: "British pounds".


🚀 How to use it

Simplest — one route

{ "origin": "JFK", "destination": "LAX", "departureDate": "2026-10-15" }

Evergreen (for a schedule — never goes stale)

{ "origin": "JFK", "destination": "LAX", "daysFromNow": 30, "maxResultsPerSearch": 25 }

Leave departureDate blank and the actor computes the date at run time, so a saved task or a nightly schedule never starts searching a date in the past.

Round trip

{ "origin": "JFK", "destination": "LHR", "departureDate": "2026-11-10", "returnDate": "2026-11-17" }

Many routes and dates in one run

{
"searches": [
{ "origin": "JFK", "destination": "LAX", "departureDate": "2026-10-15" },
{ "origin": "JFK", "destination": "LHR", "departureDate": "2026-10-16", "returnDate": "2026-10-23" },
{ "origin": "SFO", "destination": "NRT", "departureDate": "2026-10-20" }
],
"cabinClass": "business",
"currency": "USD"
}

Every input

InputDefaultWhat it does
origin / destinationJFK / LAXIATA airport or city codes
departureDateYYYY-MM-DD; blank uses daysFromNow
returnDateset it and the search becomes a round trip
daysFromNow30used when departureDate is blank
tripTypeone-wayone-way or round-trip
searches[]bulk list; each entry overrides the single-route fields
adults / children / infantsInSeat / infantsOnLap1 / 0 / 0 / 0passenger mix
cabinClasseconomyeconomy, premium-economy, business, first
maxStopsany0 nonstop only, 1, 2 — applied by Google, so the response is smaller and faster
currency / country / languageUSD / US / enpoint of sale; fares genuinely differ by it
maxResultsPerSearch0 (whole board)cap applied before delivery, so you are never billed past it
skipPriceUnavailablefalsedrop rows Google has no sellable price for
proxyConfigurationApify Proxy, automatic poolswitch to RESIDENTIAL or bring your own

💵 Pricing

Pay per event: flight-scraped — $0.0003 per itinerary ($0.30 per 1,000). No start fee, no per-run fee, no per-page fee, no usage surcharge. A duplicate render is never billed; a row dropped by maxResultsPerSearch or skipPriceUnavailable is never billed.

Board (measured)ItinerariesYou pay
JFK→LAX one-way98$0.029
JFK→LHR business, 2 pax89$0.027
JFK→BKK one-way57$0.017
JFK↔LAX round-trip48$0.014
30 routes × 60 departure dates ≈ 126,000 rows≈ $38

Rows with priceUnavailable: true (2–8% of a board in testing) are real itineraries with full schedule, per-leg and CO2 data, and they are billed. Set skipPriceUnavailable: true if you do not want them.


⚠️ Honest limits — what this does NOT do

  • No multi-city. Not a shortcut — a measured wall. Google accepts a multi-city request and renders the search form with every leg filled in correctly, then ships zero server-rendered result rows: HTTP 200, ~1.89 MB, 0 itinerary elements, across trip-type values 3, 4 and 5 and five tfu parameter variants. Those boards are built client-side, so no HTTP-only scraper can read them. One-way and round-trip are fully supported.
  • A round-trip board is the DEPARTING slice. This is how Google works, not a shortcut here. Every row on a round-trip board is an outbound itinerary priced as the round-trip total — measured 48 of 48 rows carrying "From 357 US dollars round trip total". The return leg is a second selection Google does not put on this board. Rows say so explicitly: priceType: "round trip total", and legs[] covers the outbound slice only.
  • No booking-option / OTA list. The per-itinerary list of who sells the fare and at what price is behind a click; this actor reads the board.
  • No price calendar / date grid and no Explore. Different pages, deliberately out of scope.
  • departureDateTimeIso and arrivalDateTimeIso carry no timezone. They are local airport clock times, exactly as Google shows them. arrivalDayOffset gives you the +1 / +2 day roll.
  • Prices are what Google displays, not a booked fare. They move, and they differ by country and currency.
  • English only. The parser reads Google's English accessibility sentences. Leave language: en; currency and country still control pricing and point of sale.

Anti-bot and proxy — measured, not guessed

Verified on 2026-09-02 by running the same JFK→LAX board three ways:

RouteStatusBytesItinerariesTime
Direct (no proxy)2007,638,34998
Apify Proxy RESIDENTIAL + country-US2007,644,199986,482 ms
Apify Proxy automatic (datacenter)2007,644,082983,431 ms

No CAPTCHA, no challenge, no JS execution needed. The datacenter pool is the default because it returned identical data about twice as fast and at effectively no proxy cost; residential runs about $0.0026 per search at the measured 329 KB of compressed traffic. One residential request during testing returned an Apify-proxy 590 UPSTREAM504 with 0 bytes while others succeeded — transient, so the actor retries up to 4 times with a fresh session each time.

A blocked run never reports "no flights found." If no board can be read, the run exits cleanly with a status message that names it as a transport failure. A genuinely empty board is a different, distinguishable case (LWS→BFF returned 200 / 1.79 MB / 0 rows) and is reported as such.

robots.txt

https://www.google.com/robots.txt line 176, under User-agent: *, reads verbatim:

Disallow: /travel/flights/search

That is the path this actor reads. It is disclosed here so you can make your own call. Use the output in line with Google's terms and the law in your jurisdiction; you are responsible for how you use it.


🛠 How it works

One GET https://www.google.com/travel/flights/search?tfs=…&hl=…&gl=…&curr=…&tfu=EgYIABABGAA per board. tfs is a base64url protobuf encoding the route, dates, passenger mix, cabin and stop cap — hand-built by this actor, with a self-check that asserts the encoder still reproduces a known-good token before any request goes out (a wrong token would return a perfectly valid page for the wrong route).

tfu=EgYIABABGAA is not optional: with it a JFK→LAX board carries 97–98 itineraries for 328,862 compressed bytes; without it, 32 itineraries for 270,067 bytes.

Parsing is anchored on the stable semantic layer — Google's accessibility sentences and the data-* attributes of the Travel Impact Model block, which is a public contract — rather than on obfuscated class names, which Google rotates without notice. Class selectors are used only as the fast path, always with an aria/attribute fallback, and the run fails loudly rather than silently if itinerary elements are present but none parse.


❓ FAQ

Do I need a Google API key? No. No key, no login, no cookies.

How many itineraries come back per search? The whole board. Measured: 98 (JFK→LAX one-way), 89 (JFK→LHR business), 58 (JFK→BKK), 45–48 (JFK↔LAX round-trip). resultsOnBoard on every row tells you the true board size even when you cap the rows you keep.

Why is the price on the row different from the visible text on Google's page? It isn't — the visible price text is font-obfuscated, so the number is read from Google's own accessibility label ("214 US dollars"), which is why currencyName comes back too.

Can I get the return flights of a round trip? Not from this board — see honest limits. You get the departing itineraries priced as the round-trip total.

Can I search many dates? Yes — put one entry per date in searches. That is the normal way to build a fare-history or fare-alert dataset.

Is priceUnavailable a bug? No. Google lists some itineraries with no sellable price. They keep full schedule, per-leg and CO2 data. Set skipPriceUnavailable: true to drop them.

How fast is it? 3–7 seconds per board including the fetch. The 3-board validation run finished in 11.4 seconds.

What happens on an impossible route? A clean run with zero rows and a status message saying so — never a failure, and never confused with a block.