This guide covers the CLI and library flows that are too detailed for the README.
Scrape a saved list:
uv run gmaps-scraper "https://maps.app.goo.gl/MG2Vd5pWBkL7hXL18"Scrape a place page:
uv run gmaps-scraper \
"https://www.google.com/maps/place/Den/@35.6731762,139.7127216,17z" \
--kind placeWrite JSON to a file:
uv run gmaps-scraper \
"https://maps.app.goo.gl/MG2Vd5pWBkL7hXL18" \
--output saved-list.jsonDownload place images:
uv run gmaps-scraper URL --kind place --download-photo den-photo.jpg
uv run gmaps-scraper URL --kind place --download-main-photo den-main-photo.jpgRun with a visible browser for debugging:
uv run gmaps-scraper URL --kind place --headeduv run gmaps-scraper URL --fetch-mode auto
uv run gmaps-scraper URL --fetch-mode curl
uv run gmaps-scraper URL --fetch-mode browserautousescurl_cffifirst and falls back to the browser when parsing fails.curluses only the HTTP path.browseruses only the browser path.
Place scraping currently uses the browser path.
places.txt is newline-delimited. Blank lines and comments are ignored:
https://www.google.com/maps/place/Den/@35.6731762,139.7127216,17z
# comments are ignored
https://www.google.com/maps/place/Narisawa/@35.6724929,139.7111143,17z
Batch scrape with retries, warmed browser contexts, staggered starts, and screenshots:
uv run gmaps-scraper \
--kind place \
--input places.txt \
--session-dir "$CONDUCTOR_ROOT_PATH/.gmaps-scraper/session" \
--max-concurrency 1 \
--max-retries 2 \
--retry-backoff-ms 2000 \
--stagger-ms 500 \
--screenshot-output-dir .context/places/screenshots \
--output place-results.jsonBrowser sessions use a weighted random desktop viewport by default. Persistent
profile sessions keep a stable viewport for that profile path. Use
--disable-random-window-size when you need CloakBrowser's default viewport,
and --human-mouse when you want CloakBrowser to add humanized mouse, keyboard,
and scroll behavior despite the extra latency.
You can also pass multiple URLs directly or pipe them on stdin:
uv run gmaps-scraper --kind place URL1 URL2 URL3
printf '%s\n' URL1 URL2 URL3 | uv run gmaps-scraper --kind place --input -Parallel workers get separate browser profile subdirectories under
--session-dir and separate HTTP cookie jar paths. Use --max-concurrency 1
when the goal is a single long-lived browser identity. Use higher concurrency
when separate worker session state is acceptable.
Write place debug artifacts and a compact selector recipe:
uv run gmaps-scraper URL \
--kind place \
--debug-output-dir .context/places/example--screenshot-output-dir writes overview and reviews screenshots. Debug dumps
also include raw investigation artifacts under artifacts/ and a compact
selector-recipe.json. Reuse selector recipes across sessions, not full DOM
snapshots.
Reviews and About collection are enabled by default for place scraping. Use
--skip-reviews when you only need overview facts and do not want to open the
Reviews tab. Use --skip-about when About-panel attributes are not needed.
Skipping Reviews still preserves any review-count/topic evidence present on the
overview page, but it will not expand the Reviews tab for more topics or visible
review snippets.
LLM repair is opt-in. Deterministic DOM and preview extraction always run first.
OPENAI_API_KEY=...
LLM_MODEL=gpt-5-mini
uv run gmaps-scraper \
URL \
--kind place \
--llm-repairThe LLM path is split into two generic tasks:
dom_repair: repair missing or suspicious Google Maps facts from sanitized DOM evidence.display_translation: produce English-readableaddress_display_enandcategory_display_enwhen raw Google fields contain non-Latin display text.
By default, --llm-repair enables both tasks. Restrict work with repeated
--llm-task flags:
uv run gmaps-scraper \
URL \
--kind place \
--llm-repair \
--llm-task display_translationConfiguration precedence is:
- Checked-in app defaults
- Worktree-local
llm.local.json - Environment variables
Built-in aliases currently include gpt-5-mini, gpt-4o-mini, and
gpt-4.1-mini. Checked-in defaults intentionally target OpenAI-compatible chat
completions endpoints only. Add other OpenAI-compatible providers through
worktree-local llm.local.json.
Example worktree-local llm.local.json for Fireworks:
{
"models": {
"kimi-k2p6": {
"provider": "fireworks",
"model": "accounts/fireworks/models/kimi-k2p6",
"omit_temperature": true,
"request_options": {
"reasoning_effort": "low",
"max_tokens": 512
}
}
}
}Useful environment variables include LLM_MODEL, LLM_PROVIDER,
LLM_BASE_URL, LLM_API_KEY, LLM_MAX_TOKENS, LLM_TEMPERATURE,
LLM_REASONING_EFFORT, and LLM_OMIT_TEMPERATURE.
Cache optional LLM repairs so unchanged evidence does not call the model again:
uv run gmaps-scraper \
--kind place \
--input places.txt \
--llm-repair \
--llm-cache-dir "$CONDUCTOR_ROOT_PATH/.gmaps-scraper/llm-cache" \
--session-dir "$CONDUCTOR_ROOT_PATH/.gmaps-scraper/session"When --llm-cache-dir is used, the scraper also stores exact typed
translation-memory entries learned from LLM address_display_en and
category_display_en repairs in translation-memory.learned.json. Those
entries are reused from the same cache directory on later runs before calling
the LLM.
Learned memory is limited to category labels, city/country/neighborhood components, floor/building suffixes, and known address tokens. Reviews and review topics are never translated or learned into this cache.
Approved memory also supports typed pattern entries for structural address
tokens. Pattern templates intentionally support only numbered capture
substitution such as {1} and {2}:
{
"kind": "pattern",
"source_pattern": "(?<![A-Za-z0-9])(\\d+)\\s*樓\\s*之\\s*(\\d+)",
"target_template": "{1}F-{2}",
"field_kinds": ["address_component"],
"source_method": "approved",
"confidence": "high"
}See Contributing for promotion rules.
Recommended low-cost refresh flow:
- Scrape with
llm_policy="never"to get fresh deterministic facts. - Reuse prior
address_display_enandcategory_display_enonly when the rawaddressorcategoryis unchanged and the prior display value no longer needs English normalization. - If a raw field changed, or there is no reusable display value, check
needs_display_en(raw_value)or diagnostics flags such asneeds_address_display_en/needs_category_display_en. - Only then run optional LLM repair with a stable cache directory.
from pathlib import Path
from gmaps_scraper import (
cached_place_repairer,
llm_cache_namespace_from_env,
needs_display_en,
openai_compatible_place_repairer_from_env,
repair_place_display_fields,
reuse_place_display_fields,
scrape_place,
)
fresh = scrape_place(place_url, llm_policy="never")
fresh = reuse_place_display_fields(fresh, previous_place)
needs_translation = (
needs_display_en(fresh.address) and fresh.address_display_en is None
) or (
needs_display_en(fresh.category) and fresh.category_display_en is None
)
if needs_translation:
fresh = repair_place_display_fields(
fresh,
repairer=cached_place_repairer(
openai_compatible_place_repairer_from_env(),
cache_dir=Path(".gmaps-scraper/llm-cache"),
cache_namespace=llm_cache_namespace_from_env(),
),
evidence={"city": "Singapore", "country": "Singapore"},
)For mapping-based caches, use
reusable_place_display_fields(current_fields, previous_fields) and merge the
returned keys into the refreshed record.
repair_place_display_fields() is a no-scrape helper. It sends only the current
place fields and optional caller evidence to the display-translation repairer.
Useful caller evidence includes guide/list city, country, or region. Reviews and
review topics should not be used for display translation.
Use repair_place_display_fields() when a downstream cache already has fresh
place facts and only needs English-readable display fields. This avoids opening
Google Maps again.
from pathlib import Path
from gmaps_scraper import (
PlaceDetails,
cached_place_repairer,
llm_cache_namespace_from_env,
openai_compatible_place_repairer_from_env,
repair_place_display_fields,
)
repairer = cached_place_repairer(
openai_compatible_place_repairer_from_env(),
cache_dir=Path(".gmaps-scraper/llm-cache"),
cache_namespace=llm_cache_namespace_from_env(),
)
place = PlaceDetails(
source_url="https://www.google.com/maps/place/Fiamma",
resolved_url="https://www.google.com/maps/place/Fiamma",
name="Fiamma",
category="イタリア料理店",
rating=None,
review_count=None,
address="1 The Knolls, シンガポール 098297",
located_in="Capella Singapore",
)
place = repair_place_display_fields(
place,
repairer=repairer,
evidence={
"city": "Singapore",
"country": "Singapore",
"source": "downstream guide metadata",
},
)
print(place.category_display_en) # "Italian restaurant"
print(place.address_display_en) # "1 The Knolls, Singapore 098297"The repair request includes raw name, secondary_name, category, address,
located_in, address_parts, and google_place_id when present. The model can
only return display translation fields; venue names, reviews, review topics,
tags, neighborhoods, and ranking data are ignored by this helper.
gmaps-scraper does not know a downstream guide's region. The caller should
build a specific query from its own context, then pass the resulting URL to the
scraper.
from gmaps_scraper import build_maps_search_url, localize_maps_url, scrape_place
url = build_maps_search_url("Analogue, Singapore")
place = scrape_place(url)For ambiguous names, include the most specific caller-known context. Prefer a place ID when available, then full address, then city/country:
build_maps_search_url("Analogue, Singapore", gl="sg")
build_maps_search_url("Analogue, 30 Victoria Street, Singapore", gl="sg")If the caller has a Google place ID, include it:
url = build_maps_search_url(
"Ad Astra, Taipei",
place_id="ChIJHeQU2UCpQjQRhNcDeQ1fUMI",
gl="tw",
)The helper defaults to hl="en" and gl="us". Keeping hl=en reduces
localized UI surprises for the scraper and usually gives English-readable
labels. Override gl when the downstream app has a regional bias such as sg,
tw, au, or uk. Override hl only when you intentionally want Google Maps
to render in another language and can tolerate more localized output.
scrape_place() itself preserves the URL it receives. For direct place or CID
URLs, call localize_maps_url(url, hl="en", gl="us") before scraping when you
want the same English-readable bias explicitly:
place = scrape_place(localize_maps_url("https://www.google.co.jp/maps/place/Tokyo+Tower"))gl is a regional search bias, not proof of location. Downstream consumers that
have expected city/country context should still validate the resolved place
address or coordinates before accepting a refresh.
Common top-level imports:
build_maps_search_urlscrape_saved_listscrape_placescrape_placescollect_place_snapshotcached_place_repairerllm_cache_namespace_from_envopenai_compatible_place_repairer_from_envneeds_display_enreusable_place_display_fieldsreuse_place_display_fieldsBrowserSessionConfigHttpSessionConfigPlaceDetailsPlaceScrapeResultPlaceExtractionDiagnostics