繁體中文 | English
A lightweight Google Flights client that actually works for small and regional airports. Uses a fast SSR path for popular routes; falls back to a Playwright-based stage for regional airports (e.g. Taichung RMQ, Kumamoto KMJ).
Existing libraries like fast-flights silently return empty results for low-traffic airports (e.g. Taichung RMQ, Kumamoto KMJ). The root cause is an incomplete protobuf URL encoding: Google receives a malformed request and skips on-demand calculation, returning data[3] = null.
gf-search reverse-engineered the exact protobuf format Chrome sends, with three critical fixes:
| Field | fast-flights | gf-search |
|---|---|---|
Airport.field_1 (entity type) |
missing | 1 = IATA airport, 2 = city entity ID |
Info.field_1, Info.field_2 |
missing | 28, 2 (query type flags) |
Info.field_16 |
missing | INT64_MAX — triggers on-demand calculation for small airports |
Result: RMQ → KMJ returns full flight data including Starlux Airlines (JX) direct flights, whereas fast-flights returns data[3] = null.
pip install google-flights-searchpip install "google-flights-search[playwright]"
playwright install chromium # download browser binary (~130 MB), one-time
gf-search-setup # one-time Google sign-in → saves sessiongf-search-setup opens a browser window. Sign into your Google account; the session is auto-detected and saved to ~/.flight_agent/session_cookies.json. All subsequent searches use it automatically — no further setup needed.
When Chrome is not running, the session can be extracted directly from Chrome's cookie store (no manual sign-in needed):
pip install "google-flights-search[playwright,windows]"
playwright install chromiumpip install "google-flights-search[full]"
playwright install chromium
gf-search-setupgit clone https://github.com/NYCU-Chung/google-flights-search
cd google-flights-search
pip install -e ".[playwright,windows]"
playwright install chromium
gf-search-setupfrom gf_search import search
# Search flights from Taoyuan (TPE) to Tokyo Narita (NRT)
results = search("TPE", "NRT", "2026-08-08")
for r in results:
print(r["airlines"], r["price"], r["stops"], "stop(s)")# Small airport example — this is where gf-search shines
# fast-flights returns nothing; gf-search returns Starlux JX direct flights
results = search("RMQ", "KMJ", "2026-08-08")
for r in results:
print(r["airlines"], r["price"])from gf_search import search
results = search(
origin="TPE", # IATA departure airport code
destination="NRT", # IATA arrival airport code
departure_date="2026-08-08", # "YYYY-MM-DD"
return_date=None, # "YYYY-MM-DD" for round-trip; None for one-way
adults=1, # number of adult passengers
travel_class="economy", # "economy" | "premium-economy" | "business" | "first"
max_results=5, # maximum number of results to return
currency="TWD", # price currency code (e.g. "TWD", "USD", "JPY")
max_stops=None, # max layovers: 0=direct only, 1=max 1 stop, None=any
)Returns: list[dict], each dict has the shape:
{
"airlines": ["JX"], # IATA carrier code(s) — one per operating carrier
"price": "TWD 8900", # price string, or "" if unavailable
"stops": 0, # number of layovers
"segments": [
{
"from": "RMQ",
"to": "KMJ",
"flight_no": "JX317", # carrier + flight number (e.g. "JX317", "CI002")
"departure": "2026-08-08 15:00",
"arrival": "2026-08-08 18:15",
"duration_min": 95,
"plane": "Airbus A321neo",
}
],
"source": "gf_search",
}Returns [] if no results are found after retries.
Check the health of the saved Google session (used by Stage 5 Playwright fallback).
from gf_search import session_status
status = session_status()
print(status)
# {
# "valid": True, # True if session exists and is fresh
# "exists": True, # True if session_cookies.json exists
# "age_hours": 72.5, # hours since last setup (or -1)
# "stale": False, # True if session is older than 72h
# "cookie_count": 10, # number of session cookies
# "message": "Google session 有效(10 cookies,72 小時前更新)。"
# }Builds the raw tfs URL parameter (URL-safe base64-encoded protobuf) for the Google Flights search endpoint. Useful if you want to construct URLs manually or inspect the encoding.
from gf_search import build_tfs
tfs = build_tfs(
origin="RMQ",
destination="KMJ",
departure_date="2026-08-08",
return_date="2026-08-15", # optional
seat=1, # 1=economy 2=premium-economy 3=business 4=first
adults=1,
)
url = f"https://www.google.com/travel/flights/search?tfs={tfs}&tfu=EgIIACIA&hl=zh-TW"
print(url)A dict mapping IATA codes to Google's city/metro entity IDs. Regular airports use entity_type=1 (handled automatically). Airports that Google indexes at the city level need entity_type=2 with a special entity ID.
from gf_search import CITY_ENTITIES
print(CITY_ENTITIES)
# {
# "RMQ": "/m/01r8pt", # Taichung (city entity)
# "KHH": "/m/0h7h6", # Kaohsiung
# "TSA": "/m/02kg86", # Taipei Songshan
# }
# Add your own:
CITY_ENTITIES["OKA"] = "/m/0h7r_" # Okinawa NahaTo find an entity ID: open Google Flights in Chrome DevTools, trigger a search for the target airport, and inspect the tfs parameter in the network request.
gf-search ships a built-in MCP server. Once published to PyPI, anyone can add it to Claude Desktop with a single config entry — no pre-installation required.
Claude Desktop config (%APPDATA%\Claude\claude_desktop_config.json on Windows, ~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"google-flights": {
"command": "uvx",
"args": ["--from", "google-flights-search", "gf-search-mcp"]
}
}
}Restart Claude Desktop. Claude will have access to four tools:
search_flights— single origin-destination search (supportsmax_stopsfilter; returns helpful hints when results are empty or priceless)search_multi_city_flights— multi-city / open-jaw / 4-leg itinerariessearch_cheapest_dates— search a date range and return results sorted by cheapest price (great for flexible-date travellers)generate_search_urls— generate a Google Flights URL that can be opened in a browser (useful when API results are incomplete for niche routes)
If you prefer installing manually first:
pip install "google-flights-search[mcp]"{
"mcpServers": {
"google-flights": {
"command": "gf-search-mcp"
}
}
}gf-search uses a multi-stage pipeline, stopping as soon as results are found:
| Stage | Method | Requires |
|---|---|---|
| 0 | Chrome-authenticated cache (~/.gf_search/chrome_cache.json) |
Pre-populated cache file |
| 1–3 | primp SSR + tfu/batchexecute fallbacks |
Nothing (pure HTTP) |
| 5 | Playwright: real Chrome/Chromium, network interception | playwright + gf-search-setup |
| 4 | Supplemental schedules (schedules.json) |
Nothing |
Stages 1–3 (fast path): Google Flights renders flight data server-side into a <script class="ds:1"> tag. gf-search:
- Builds a correctly-encoded protobuf
tfsparameter — three fields missing from other libraries are the key fix - Fetches via
primp(Rust HTTP client that impersonates Chrome's TLS fingerprint) - Retries up to 3×; if still empty, tries a
tfu-based return-leg fetch and abatchexecutechain
Stage 5 (regional airports & round-trips): For routes where Google's SSR cache is empty (e.g. RMQ→KMJ), or when all SSR results lack prices, a real Chrome/Chromium session is launched via Playwright. For round-trip queries, Stage 5 first attempts the native round-trip URL to preserve round-trip pricing; if Playwright fails, the search is decomposed into two one-way legs (results include a direction field and _price_note). Network responses (GetShoppingResults) are intercepted and parsed directly — no airline-specific code, works for any route Google has indexed. Session cookies from session_cookies.json are automatically injected into both Playwright and SSR requests.
- Non-official API: Google may change the response format at any time.
- SSR non-determinism: Even with the correct protobuf, flight data sections are occasionally
nullon a cold cache hit. The built-in 3-retry logic handles most cases. - Regional airports need Playwright: Routes where Google's SSR cache is empty (small airports) require
pip install "google-flights-search[playwright]"+playwright install chromium+gf-search-setup. - Google session: Stage 5 works without a session but returns fewer results. Run
gf-search-setuponce for full coverage. Setup mainly affects niche routes (Stage 5); popular routes return the same results with or without a session via SSR. - Price currency: Configurable via the
currencyparameter (default"TWD"). - No seat map / availability API: Search results only; no booking-level availability.
PRs are welcome! The most impactful contributions right now:
- More city entity IDs in
CITY_ENTITIES(any airport where Google uses a city-level entity rather than an IATA code directly) - Expanded
_SEAT_MAPaliases
To add a city entity ID, find it via Chrome DevTools as described above, then add it to gf_search/builder.py.
MIT — see LICENSE.