1.Place data fields worth extracting
The value of a Maps scrape depends entirely on which fields you actually capture. Local SEO teams typically need consistent NAP data — name, address, and phone — to audit citations across directories. Market researchers layer on ratings, review volume, price level, and category tags to benchmark competitors. Mapping and logistics applications need precise lat/lng coordinates. Knowing your target fields upfront determines which CSS selectors you build and how you structure your output schema.
Some fields are always visible in the initial render; others require user interaction (expanding hours, loading reviews) and are harder to extract reliably at scale. Prioritize stable, always-visible fields for production pipelines and treat interactive fields as best-effort.
- Business name and place_id (the only truly stable identifier across updates)
- Formatted address, plus individual components: street, city, postal code, country
- Phone number — note that international formatting varies significantly by region
- Website URL and the canonical Google Maps share link for the place
- Star rating (1–5) and total review count
- Opening hours per weekday, including special hours for holidays
- Primary business category and attribute tags (e.g., wheelchair accessible, outdoor seating)
- Latitude and longitude — extractable from the URL or embedded JSON state
- Price level indicator ($ to $$$$) when present
- Popular times histogram when visible in the panel
- Photos count and thumbnail URLs for visual verification
2.Google Maps URL patterns
Targeting the right URL type is the first decision in any Maps scraping project. Place detail URLs are the stable, canonical unit — they represent a single business and contain the place_id encoded in the data= segment. Avoid reverse-engineering internal RPC endpoints like batchexecute or the /maps/api/js tile calls; these are undocumented, change without notice, and are far more aggressively rate-limited than the web UI.
For search-style queries, Maps uses /maps/search/ URLs that return a scrollable list panel. These are useful for discovery — finding all businesses of a given type in a geographic area — but extracting more than the first ~20 results requires scroll simulation, since Maps uses infinite scroll rather than paginated query parameters.
- Place detail (coordinate form): https://www.google.com/maps/place/Starbucks/@40.7580,-73.9855,17z/data=!3m1!4b1!4m6!3m5!1s0x89c25855c6480299:0x55194ec5a1ae072e
- Place by text query: https://www.google.com/maps/search/coffee+shops+in+austin+tx
- Place by CID: https://maps.google.com/?cid=12345678901234567890 — the CID is a legacy numeric identifier, still widely used in embeds
- Short link: https://goo.gl/maps/... — always resolves via redirect; use data.final_url from the API response to get the canonical URL with coordinates
- Embed URL: https://www.google.com/maps/embed?pb=... — renders a stripped-down panel, fewer fields available
3.Where listings hide in the rendered DOM
Once Maps renders in a headless browser, the place panel appears inside div[role="main"]. The business name is in an h1 element — historically classed DUwDvf, though Google rotates obfuscated class names periodically. Relying purely on class names will break; prefer combining role attributes, data-item-id attributes, and aria-label values, which are more stable because they serve accessibility purposes Google is less likely to rename.
Address data appears as a button with data-item-id="address" — the full formatted address is in its aria-label. Phone numbers use button[data-item-id^="phone:tel:"], where the tel: prefix makes them reliably selectable. The website link is an anchor with data-item-id="authority". These data-item-id values have been stable across multiple Maps UI refreshes and are your most reliable extraction hooks.
Opening hours are collapsed by default behind an expandable button. A single-pass fetch typically returns only today's hours or a summary. To get the full weekly schedule you either need to simulate a click on the hours button (possible with BaaS session control) or accept partial data. Coordinates appear in the canonical URL's @lat,lng,zoom segment after JavaScript navigation completes — check data.final_url in the API response. They also appear in APP_INITIALIZATION_STATE or INITIAL_DATA JSON blobs embedded in script tags, but that structure changes frequently and is fragile to parse.
4.Maps anti-bot protections and access restrictions
Google Maps applies several layers of protection that make naive scraping unreliable at any meaningful volume. Session tokens are generated per map load and tied to JavaScript execution — without them, requests to data endpoints return errors or empty responses. Datacenter IP ranges trigger aggressive throttling and CAPTCHAs on search queries far sooner than residential IPs. EU users see a consent banner on first load that blocks the place panel until accepted, which means your headless browser must handle that interstitial before the actual content renders.
Beyond technical protections, Google's Terms of Service for Maps Platform explicitly restrict bulk extraction, caching of place data beyond short windows, and creating competing databases from Maps content. For production commercial use cases, the official Places API is both the compliant path and often the more cost-predictable one. Scraping the web UI is more defensible for small-scale internal research, competitive monitoring, or one-time data collection where API costs are prohibitive.
One practical implication: CSS class names on Maps are obfuscated and rotated. Build your selectors around structural attributes (role, data-item-id, aria-label) rather than class strings, and plan for selector maintenance as part of your pipeline operations.
- Full JavaScript execution required — no data is present in the initial HTTP response body
- Session tokens tied to map initialization; raw API endpoint calls fail without them
- Datacenter IPs throttled aggressively on search queries; use residential proxies
- EU consent interstitials block place panel on first render
- Obfuscated, rotating CSS class names — prefer data-item-id and aria-label selectors
- Infinite scroll on search results — no simple page=N pagination parameter
- place_id is stable across updates; all other identifiers are not
- Attribution and caching restrictions in Google's Maps Platform Terms of Service
5.Scrape a place detail page
For a single place page, use mode js_rendering with a js_wait_selector targeting the business name h1. This ensures the scraper waits until the place panel has fully rendered before extracting — without it, you risk capturing an empty or partially loaded DOM. Set js_wait_timeout to at least 12 seconds; Maps initialization is slow, especially on first load from a cold session.
Match your proxy country to the market you are cataloging. A US business scraped through a European IP may show different hours formats, phone number formatting, or trigger the EU consent flow. The css_selectors map below targets the most stable DOM attributes available as of the current Maps UI. Expect to revisit the rating and review_count selectors most frequently, as those class names change more often than the data-item-id attributes.
123456789101112131415161718{
"url": "https://www.google.com/maps/place/Blue+Bottle+Coffee/@37.7763,-122.4237,17z",
"mode": "js_rendering",
"output_format": "css_extractor",
"proxy": "residential:us",
"js_wait_selector": "h1",
"js_wait_timeout": 12000,
"css_selectors": {
"name": "h1[class*=\"DUwDvf\"], h1",
"rating": "div.F7nice span[aria-hidden=\"true\"]",
"review_count": "div.F7nice span span[aria-label]",
"address": "button[data-item-id=\"address\"]",
"phone": "button[data-item-id^=\"phone:tel:\"]",
"website": "a[data-item-id=\"authority\"]",
"category": "button.DkEaL",
"price_level": "span[aria-label*=\"price\"]"
}
}
6.Scrape local search results
Search result URLs return a scrollable list panel rendered inside div[role="feed"]. Each business card is a div.Nv2PK containing the name, rating, address snippet, and a link to the place detail page. The most reliable extraction strategy is to collect place detail URLs from the search results, then scrape each individual place page for complete structured data — search cards contain abbreviated information only.
Pagination on Maps search is infinite scroll, not query-string based. A single fetch captures the first batch of results visible in the viewport — typically 10 to 20 listings. For broader coverage, use more specific geographic queries (neighborhood-level rather than city-level) to keep each result set small and complete, rather than trying to scroll through hundreds of results programmatically.
Set js_wait_selector to div[role="feed"] and give it a generous timeout. The feed container renders after the map tiles and initial JavaScript initialization complete, so 15 seconds is a reasonable minimum on residential proxies.
123456789101112131415{
"url": "https://www.google.com/maps/search/plumbers+near+denver+co",
"mode": "js_rendering",
"output_format": "css_extractor",
"proxy": "residential:us",
"js_wait_selector": "div[role=\"feed\"]",
"js_wait_timeout": 15000,
"css_selectors": {
"business_names": "div.Nv2PK div.qBF1Pd",
"ratings": "div.Nv2PK span.MW4etd",
"review_counts": "div.Nv2PK span.UY7F9",
"address_snippets": "div.Nv2PK span.W4Efsd:nth-child(2)",
"place_links": "div.Nv2PK a.hfpxzc"
}
}
7.Deduplicate by place_id, not business name
"Starbucks" appears tens of thousands of times across Maps. "McDonald's" more. Deduplication by name alone is useless. The place_id is Google's own stable identifier for a physical location — it persists across business name changes, address corrections, and Maps UI updates. Extract it from the data=!... segment of the place URL, specifically the hex string following 1s0x in the encoded data blob, or from links within the rendered page that contain the full place URL.
Store place_id as your primary key. When you re-scrape a location for updated hours or ratings, use place_id to match the existing record and update in place rather than creating a duplicate. This keeps your dataset clean across incremental refresh cycles.
When merging Maps data with other sources — Yelp, Apple Maps, Foursquare, or your own CRM — fuzzy name matching alone will create both false positives (different businesses with similar names) and false negatives (the same business with slightly different name spellings). Use coordinate proximity (within ~50 meters) combined with phone number match as secondary deduplication keys. Phone number is particularly reliable because it is business-controlled and changes infrequently.
8.Google Places API vs scraping the web UI
Google's Places API (New) returns structured JSON with explicit field-level pricing, SLA guarantees, and terms that permit commercial use with attribution. For production pipelines where data quality, reliability, and legal clarity matter, the Places API is the right tool. It handles pagination, returns place_id natively, and does not require you to maintain CSS selectors against a UI that changes without notice.
Scraping the Maps web UI is technically feasible for small-scale internal projects — competitive monitoring, one-time research, or augmenting existing datasets where API costs are prohibitive for the volume needed. The tradeoffs are real: selector maintenance overhead, higher per-request cost due to mandatory js_rendering, and ToS exposure for any commercial redistribution of the data. Evaluate both paths against your actual use case volume and legal requirements before committing to an architecture.
For a detailed breakdown of why Maps always requires browser-mode rendering and how to configure it efficiently, see scrape JavaScript rendered pages. For combining Maps data with other local sources in a lead generation context, see lead generation web scraping.
9.Storage, attribution, and legal considerations
Google's Maps Platform Terms of Service impose specific restrictions on what you can do with Maps content beyond the moment of display. Caching place data beyond short windows, bulk exporting to external databases, and building products that compete with or substitute for Google Maps are all restricted. Even data that is technically public — a business's phone number or address — carries contractual limitations when extracted from Maps specifically.
Attribution requirements apply when displaying Maps-derived content: you must show the Google logo and comply with the attribution guidelines in the Maps Platform Terms. Storing lat/lng coordinates extracted from Maps URLs is a grey area; coordinates themselves are not copyrightable, but the database as a whole may be subject to database rights in some jurisdictions.
The practical guidance: for internal analytics, competitive research, or one-time data collection at low volume, the risk profile is manageable. For building a sellable local business directory, a public API, or any product where Maps data is a core commercial asset, get legal review before proceeding. The Places API with its explicit commercial terms is the lower-risk path for those use cases.
Frequently asked questions
Can I use mode 'auto' instead of 'js_rendering' for Google Maps?
You can set mode to 'auto' and OmniScrape will attempt a fast HTTP fetch first, then escalate to js_rendering if the response looks like an empty shell. In practice, Maps almost always triggers the escalation, so you will be billed at js_rendering rates regardless. Setting mode to 'js_rendering' directly avoids the wasted fast-lane attempt and gets you to the rendered content faster. Always pair it with js_wait_selector on 'h1' or 'div[role="feed"]' to ensure the panel is fully loaded before extraction runs.
How do I extract coordinates from a Maps page?
The most reliable source is the canonical URL after JavaScript navigation completes. Check data.final_url in the API response — it will contain the @lat,lng,zoom segment even if the input URL was a short link or CID URL. Parse the segment with a regex like /@(-?\d+\.\d+),(-?\d+\.\d+)/. Embedded JSON state blobs (APP_INITIALIZATION_STATE, INITIAL_DATA) in script tags also contain coordinates but their structure changes frequently and requires fragile parsing — treat them as a fallback only.
Why are opening hours incomplete or missing from my scrape?
Hours are collapsed behind an expandable button in the Maps UI. A single-pass fetch typically captures only today's hours or a brief summary line. To get the full weekly schedule, you need to simulate a click on the hours expansion button during the browser session — this requires BaaS session control or a custom automation layer on top of the rendered page. For monitoring use cases where full hours are not critical, the summary line (e.g., 'Open until 9 PM') is usually present in the initial render and sufficient.
What is the best way to handle EU consent banners on Maps?
Google shows a consent interstitial on first load for users in EU/EEA regions. If your proxy routes through a European IP, the headless browser will hit this banner before the place panel renders, and your js_wait_selector will time out. The fix is to use a US or non-EU residential proxy when scraping non-EU businesses, or to handle the consent click in your automation flow for EU-region scraping. OmniScrape's enable_solver option handles some cookie consent dialogs automatically — set enable_solver: true as a first attempt.
How many results does a Maps search URL return, and can I paginate?
A single fetch of a Maps search URL captures the listings visible in the initial viewport — typically 10 to 20 results. Maps uses infinite scroll, not URL-based pagination, so there is no ?page=2 or &start=20 parameter. For broader coverage, the most practical approach is to break your query into smaller geographic areas (neighborhood or ZIP code level) so each result set is complete within the first load. Alternatively, use BaaS to programmatically scroll the feed container and trigger additional result loads.
How do I get a place_id from the Maps URL?
The place_id is encoded in the data= query parameter of a full Maps place URL. Look for the hex string following '1s0x' in the encoded blob — for example, '1s0x89c25855c6480299:0x55194ec5a1ae072e' decodes to place_id ChIJmVm8xFVYwokRbi6hWsVEFVU. You can also find it in links within the rendered page: any anchor pointing to a Maps place URL will contain the same data= segment. Store place_id as your primary key — it is the only identifier that remains stable across business name changes, address corrections, and Maps UI updates.
Is scraping Google Maps legal for commercial use?
Technical feasibility and legal permissibility are separate questions. Google's Maps Platform Terms of Service prohibit bulk extraction, caching beyond short windows, and building competing databases from Maps content. The ToS apply regardless of how the data is accessed — web scraping or API. For commercial use cases involving redistribution or productization of Maps-derived data, the official Places API with its explicit commercial terms is the appropriate path. Low-volume internal research carries a different risk profile than building a sellable directory. Get legal counsel before committing to any architecture where Maps data is a core commercial asset.
Related guides