Google Maps Scraper
Pricing
$24.99/month + usage
Google Maps Scraper
Easily extract structured data from Google Maps for research or analysis. Pull info like ratings, reviews count, and business categories to support location-based studies or build market insights dashboards.
Pricing
$24.99/month + usage
Rating
5.0
(2)
Developer
Scrapier
Maintained by CommunityActor stats
2
Bookmarked
557
Total users
25
Monthly active users
23 days ago
Last modified
Categories
Share
Google Maps Scraper — Businesses with Geo-Verified Locations
Google Maps Scraper extracts business listings from Google Maps by keyword and location, or from pasted Google Maps search URLs, and returns them as clean, structured rows — name, address, phone, website, rating, review count, category tags, coordinates and opening hours. Every response is structured JSON, ready to pass directly to an LLM, load into a spreadsheet, or feed a monitoring pipeline. Results stream into the dataset live as each place is found, deduplicated by Google's own place_id, and every row is checked against the search area's bounding box before it's saved — so what comes back is what you actually asked for, not whatever a datacenter proxy happened to be closest to. Point it at a city, a keyword, or a bookmarked map link, and start pulling business data in minutes.
What is Google Maps Scraper?
Google Maps Scraper is an Apify Actor that queries Google Maps by location and keyword — or by a direct Google Maps search URL — and returns every matching business as a structured row. It requires no Google account, no Google Cloud project, and no Google API key; it talks to Google's public map search endpoint the same way a browser does. What makes it worth a second look in a category full of near-identical listings is that it doesn't just collect whatever Google's servers hand back: it geocodes your search area, tiles it into a search grid, and drops any result whose coordinates fall outside that area — filtering out the silent "geo soft-block" where Google returns results near the scraping proxy's IP instead of the location you actually asked for.
- 📍 Location + keyword search — combine any number of cities, regions, or neighborhoods with any number of keywords; every combination is searched
- 🔗 Direct Google Maps URLs — paste search links you've already bookmarked instead of re-typing a location and keyword
- 🗺️ Grid search coverage — each location is geocoded and tiled into a search grid so results aren't capped by a single map view
- 🧭 Geo-verified results — coordinates outside the requested area's bounding box are dropped before the row is ever saved or charged
- 🔁 Automatic dedup — results are deduplicated by Google's
place_idacross every location, keyword, and URL in the run - 🌐 Automatic proxy fallback — starts on a direct connection and escalates to Apify Proxy (datacenter, then sticky residential) only if Google starts blocking
What data can you get with Google Maps Scraper?
Google Maps Scraper returns one result type — a business listing (a "place") — with every field described below on every row. There's no second dataset, no locked-behind-an-add-on field set, and no separate view you have to switch to for the full picture.
| Result Type | Extracted Fields | Primary Use Case |
|---|---|---|
| Business listing (place) | name, website, avg_rating, total_reviews, street_address, city, state, zip, country_code, full_address, tags, notes, place_id, phone, lat, long, hours, success | Lead generation, local SEO audits, competitor mapping, market and territory research |
Every place — a coffee shop, a hotel, a hair salon, a parking garage — comes back through the same row shape above. There's no separate schema branch for lodging or dining places the way some larger Google Maps scrapers document (extra hotel-specific or restaurant-specific fields); if you need hotel star ratings, menus, or table-reservation links, this Actor's output won't have them, and the tags array is your signal for what kind of place a row represents.
Geo-verified place data
Before any tile of results is accepted, the Actor runs a preflight check: it fetches one sample page for the search center, measures how many of the returned coordinates sit within 155 miles of that center, and if too few do, it escalates the connection (direct → Apify Proxy datacenter → Apify Proxy residential, sticky) and tries again. Independently, every individual result that does get saved is checked against the search area's bounding box, padded by at least 0.4° of latitude/longitude — places with coordinates outside that padded box are silently dropped rather than saved. Places with no usable coordinates are kept rather than guessed away. The practical effect: lat and long on every row you receive should sit inside (or just outside the padded edge of) the area you searched, not scattered around wherever the requesting IP happened to be.
{"name": "Blue Bottle Coffee","lat": 30.2711,"long": -97.7437,"street_address": "1108 S Congress Ave","city": "Austin","state": "TX","full_address": "1108 S Congress Ave Austin TX 78704 US"}
Opening hours structure
Each place carries a hours field: an array of one object per day the Google response includes, not a single formatted string. Each entry has a day label, a human-readable hours string (Google's own text, with narrow no-break spaces and en-dashes normalized to plain characters), and open24Hour / close24Hour values pulled from Google's structured time data when present.
"hours": [{ "day": "Monday", "hours": "7 AM-6 PM", "open24Hour": 700, "close24Hour": 1800 },{ "day": "Tuesday", "hours": "7 AM-6 PM", "open24Hour": 700, "close24Hour": 1800 },{ "day": "Sunday", "hours": "Closed", "open24Hour": null, "close24Hour": null }]
Not every place exposes structured 24-hour values even when it shows a text schedule — open24Hour/close24Hour come back null in that case while hours (the text) is still populated.
How does Google Maps Scraper differ from the official Google Places API?
Google's own Places API (New) — Text Search is the official, supported way to query Google Maps programmatically, and it's a genuinely different product to reach for depending on what you need. According to Google's current documentation, checked on the Apify Store analysis date of 2026-07-25, Text Search (New) requires an API key sent via the X-Goog-Api-Key header, requires a response field mask on every request (there is no default field set — omitting it returns an error), returns a maximum of 20 results per page and caps out at 60 results total across all pages, and prices different field groups on separate billing SKUs (Essentials, Pro, Enterprise, and Enterprise + Atmosphere), so requesting ratings or reviews costs more than requesting a name and address. Source: developers.google.com/maps/documentation/places/web-service/text-search.
| Feature | Google Places API (Text Search, New) | Google Maps Scraper |
|---|---|---|
| Access requirement | Google Cloud project, billing account, API key | Apify account only — no Google account or key |
| Results per query | Max 60 total (20 per page, paginated) | Up to maxResults (1–10,000), merged across your full location/keyword/URL set |
| Field access | Split across 4 billing SKUs; ratings/hours cost more than basic fields | Every field on every row, one flat pay-per-event charge |
| Wide-area coverage | Single query, no built-in area tiling | Geocodes and grids the search area for broader coverage than one map view |
| Setup time | Cloud project + billing + key generation | Enter a location and keyword, run |
| Response format | JSON, field-masked per request | JSON, full row on every result |
The official API is the right call if you're building a production integration that needs Google's own first-party support contract and a guaranteed, versioned schema. Google Maps Scraper is the faster path when you need a bulk export of business listings across many locations without provisioning Google Cloud billing first, and without writing your own pagination loop — a single run merges every page of every location and keyword into one dataset, rather than handing back a nextPageToken you have to follow yourself.
What is the difference between a single-view Google Maps search and grid search extraction?
A single-view search opens one Google Maps screen and scrolls it to the end — and Google caps a single map view at roughly 120 results, a limit independently documented by several Google Maps scrapers on the Apify Store as of 2026-07-25. Grid search instead splits your search area into a grid of smaller tiles and runs a separate search centered on each tile, so the 120-per-view ceiling applies per tile instead of per city.
This matters the moment your target area has more businesses of a given type than a single map screen can show — "coffee shops" in a large metro, for example. A location-only or keyword-only search silently truncates at Google's per-view ceiling with no warning; a grid search keeps tiling until it hits your maxResults target or runs out of tiles.
Google Maps Scraper always grid-searches when you provide a locations entry: it geocodes the location, then sizes a grid (6×6 for targets up to 50 results, 8×8 up to 100, 10×10 up to 200, and 12×12 above that — up to 144 tiles, each requesting up to 120 results) around the geocoded bounding box. Pasted urls are geocoded from their embedded coordinates or search text and grid-searched the same way. The place_id-based dedup step then collapses any place that shows up in more than one adjacent tile.
How to scrape Google Maps with Google Maps Scraper?
- Open Google Maps Scraper on its Apify Store listing and click Try for free (or Start, if you already have it saved) to open the run console. No Google account or sign-in is needed for this step or any later one.
- Enter at least one value in
keywords— this is the only field the input schema marks as required. Addcoffee shops,dentists,hotels near airport, or any description of what you're looking for; add several keywords to multiply your searches in a single run. - Add at least one
locationsentry (a city, region, or neighborhood name) or at least oneurlsentry (a Google Maps search link) — the run needs one or the other to know where to search, even though the schema itself doesn't mark either as required. - Set
maxResultsto the number of unique places you want across the whole run, and optionally turn onproxyConfigurationif you expect a heavier or more block-prone run. Click Start. - Watch results populate the output table live as each place is found, or download the finished dataset as JSON, CSV, Excel, XML, or HTML from the Storage tab once the run finishes.
{"locations": ["Austin, Texas"],"keywords": ["coffee shops"],"maxResults": 200}
How to run multiple queries in one job
locations and keywords are both arrays, and the Actor searches every combination — three locations with two keywords runs six searches in one job, all counted against the same maxResults ceiling and deduplicated together. urls is a separate array processed after the locations/keywords set is either exhausted or has already reached maxResults; if you need URLs guaranteed to run, keep locations and keywords focused or leave them empty and use urls alone.
⬇️ Input
Google Maps Scraper accepts a location-and-keyword search, a list of direct Google Maps URLs, or both together. keywords is the only field the schema marks as required, but the Actor's own runtime check also requires at least one locations entry or at least one urls entry — the schema doesn't enforce that second part, so it's worth setting explicitly.
| Parameter | Required | Type | Description | Example Value |
|---|---|---|---|---|
locations | No | array (string list) | Cities, regions, or neighborhoods to search — add several for a wider sweep. Mix and match freely (e.g. New York, Texas, Downtown Chicago). | ["New York", "Texas"] |
keywords | Yes | array (string list) | What you're looking for — coffee shops, dentists, coworking, hotels near airport, etc. Every keyword is combined with every location. | ["coffee shops", "dentists"] |
urls | No | array (string list) | Direct Google Maps search links, for when you've already got the exact search you want bookmarked. Not required if you're using locations + keywords. | [] |
maxResults | No | integer | Total unique places to collect across all locations, keywords, and URLs combined (duplicates merged automatically by place_id). Minimum 1, maximum 10000. | 20 (default) |
proxyConfiguration | No | object (proxy editor) | Optional Apify Proxy configuration. Leave unset for simple runs — the Actor tries a direct connection first and escalates automatically if Google starts blocking. Turn it on for heavier jobs or picky networks. Default is {"useApifyProxy": false}. | {"useApifyProxy": true} |
Example JSON input, combining a location/keyword search with a wider result cap:
{"locations": ["Austin, Texas", "Denver, Colorado"],"keywords": ["coffee shops", "coworking spaces"],"urls": [],"maxResults": 500,"proxyConfiguration": { "useApifyProxy": true }}
Common pitfall: keywords: [] satisfies the input schema's required check (the key exists, even if the array is empty) but fails the Actor's own runtime validation. When that happens — or when both locations and urls are empty — the Actor logs an error and exits immediately with zero rows and zero charges, but the run still finishes with a normal, successful status rather than an explicit failure. If a run comes back empty, check the run log for "Input requires at least one keyword" or "Provide at least one location or one Google Maps URL" before assuming Google returned nothing.
⬆️ Output
Results are typed, structured JSON with a consistent schema on every row — no fields appear or disappear based on what type of place was found. Results are pushed to the Apify dataset live during the run (visible under the Storage tab, or streaming in the run console's output table) and downloadable as JSON, CSV, Excel, XML, or HTML once the run finishes. A second, merged copy of the full result list is also written to the Actor's Key-Value Store as the maps.json record.
Scraped results
[{"name": "Blue Bottle Coffee","website": "https://bluebottlecoffee.com","avg_rating": 4.5,"total_reviews": 812,"street_address": "1108 S Congress Ave","city": "Austin","state": "TX","zip": "78704","country_code": "US","full_address": "1108 S Congress Ave Austin TX 78704 US","tags": ["Coffee shop", "Cafe"],"notes": "Cozy spot known for pour-over coffee and pastries.","place_id": "ChIJrTLr-GyuEmsRBfy61i59si0","phone": "(512) 555-0148","lat": 30.2711,"long": -97.7437,"hours": [{ "day": "Monday", "hours": "7 AM-6 PM", "open24Hour": 700, "close24Hour": 1800 },{ "day": "Sunday", "hours": "8 AM-4 PM", "open24Hour": 800, "close24Hour": 1600 }],"success": true},{"name": "Downtown Dental Care","website": "https://downtowndentalaustin.com","avg_rating": 4.8,"total_reviews": 231,"street_address": "500 Colorado St","city": "Austin","state": "TX","zip": "78701","country_code": "US","full_address": "500 Colorado St Austin TX 78701 US","tags": ["Dentist"],"notes": null,"place_id": "ChIJP3Sa8ziYEmsRUKgyFmh9AQM","phone": "(512) 555-0199","lat": 30.2679,"long": -97.7431,"hours": [{ "day": "Monday", "hours": "8 AM-5 PM", "open24Hour": 800, "close24Hour": 1700 }],"success": true},{"name": "Hyatt Place Austin Downtown","website": "https://hyatt.com/en-US/hotel/texas/hyatt-place-austin-downtown","avg_rating": 4.2,"total_reviews": 1904,"street_address": "211 E 3rd St","city": "Austin","state": "TX","zip": "78701","country_code": "US","full_address": "211 E 3rd St Austin TX 78701 US","tags": ["Hotel"],"notes": null,"place_id": "ChIJQR7gzTiYEmsRP3jz0GVj5Rk","phone": "(512) 555-0177","lat": 30.2657,"long": -97.7413,"hours": [],"success": true}]
Every key above is written for every row — notes and phone come back null rather than omitted when Google's page doesn't expose them, and hours comes back an empty array ([]) rather than being dropped when no schedule data is present. The success key is present on every row and always true: places that fail to parse or fall outside the search area are dropped before they're ever pushed, so success currently reflects "this row exists" rather than a mix of pass/fail states — treat its presence as a schema constant, not a field to filter on.
⚠️ Limitations and things to know before you run it
maxResultstops out at 10,000 per run, and it's a shared ceiling across every location, keyword, and URL combination, not a per-search limit — a run with three locations and five keywords still stops at yourmaxResultstotal, not five times that.- The geo-consistency preflight check runs once per run, not once per location. It fires before the very first location/keyword pair (or the first URL, if no locations are set) and stops escalating the proxy tier as soon as one sample check passes. If Google geo-blocks a later location in a multi-location run on the same proxy tier that passed the first check, that specific batch isn't re-escalated — the final bounding-box filter on every individual result is the safety net in that case.
- Location resolution uses OpenStreetMap's Nominatim service, not Google's own geocoder, and each
(location, keyword)combination triggers its own geocode lookup — searching the same location against five keywords geocodes it five times rather than once. Nominatim is a shared, free community service with no service guarantee of its own, so a very largelocationslist is also a very large number of geocode lookups against a third-party dependency this Actor doesn't control. - Interface language is fixed to English (
hl=en); only the country/region code (gl) adapts automatically, based on the country Nominatim resolves your location to. - Rating and review count are read from a fixed position in Google's response, using a heuristic (the first number between 0 and 5 in a specific data block for
avg_rating, a fixed array index fortotal_reviews). This is fast, but it means a page shaped even slightly differently than expected can come back withavg_ratingortotal_reviewsasnullrather than an error. - No reviews, no review text, and no reviewer identities are extracted at all. This Actor returns business-level data only — nothing that identifies the people who left a review.
- Each Google request runs with a 30-second timeout and up to 3 retries (roughly a 1-second pause between attempts), and the underlying HTTP connection pool is capped at 12 concurrent connections. This is an implementation detail, not a tunable input — there's no
concurrencyormaxConcurrencyparameter to raise it, so a very largemaxResultstarget across many tiles finishes as fast as that fixed connection pool allows, not faster. tagsis Google's own raw category array (for example["Coffee shop", "Cafe"]), not a single normalizedcategorystring — if you're matching on business type, expect to check membership in the array rather than equality against one field.
How can I use the data extracted with Google Maps Scraper?
- 🏢 Sales and lead-gen teams: filter the dataset on
websiteandphonebeing non-null to build a prospect list scoped to a specifictagscategory and city, ready to import into a CRM. - 🤖 AI engineers and LLM developers: wire the Actor into an agent as a callable tool — the agent issues a location + keyword query, receives structured JSON back, and grounds its answer in real
name,avg_rating, andfull_addressvalues instead of a model's memorized guess. - 📊 Market researchers: run the same keyword across multiple
locationsin one job to compare business density, averageavg_rating, andtotal_reviewsdistribution city by city. - 🗺️ Local SEO and agency teams: audit a client's own listing alongside every competitor
tags-matched business in the samelocationssearch, checking for missinghours,website, orphonedata that could be costing them visibility.
📈 How do you monitor businesses on Google Maps over time?
Monitoring is the practice of running the same location-and-keyword search on a schedule and diffing the results against the previous run, rather than treating each run as a one-off snapshot. Because every row carries a stable place_id, you can key each run's output on that field and detect three kinds of change: a place_id that's new since the last run (a competitor opened or was newly indexed), a place_id that's missing from the current run (closed, delisted, or moved outside your search area), and a place_id present in both runs with a changed avg_rating, total_reviews, or hours value.
A practical loop looks like this: schedule the Actor to run weekly against a fixed set of locations and keywords, load each run's dataset keyed by place_id, diff avg_rating and total_reviews against the previous week's values for the same key, and alert when a tracked competitor's rating drops or their review count jumps sharply — both are leading indicators worth acting on before a client or your own listing falls behind.
Google Maps Scraper has no built-in scheduler of its own; use the Apify platform's Schedules feature to trigger runs on a cron-style interval, and pull each run's dataset via the Apify API or apify-client afterward to do the diff.
Integrate Google Maps Scraper and automate your workflow
Google Maps Scraper works with any language or tool that can send an HTTP request through the Apify API — the examples below use Python, but the same run-and-fetch pattern works from any HTTP client.
REST API with Python
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run_input = {"locations": ["Austin, Texas"],"keywords": ["coffee shops"],"maxResults": 200,}run = client.actor("<YOUR_USERNAME>/google-maps-scraper").call(run_input=run_input)for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item["name"], item["full_address"], item["avg_rating"])
Downloading results without a client library
If you'd rather not install apify-client, the finished dataset is a plain REST resource. After a run completes, GET https://api.apify.com/v2/datasets/{datasetId}/items?format=json returns the same rows as the console's export button, using your Apify API token as a token query parameter or a standard Authorization: Bearer header — no separate signup, and no custom auth scheme beyond your normal Apify token.
Scheduled monitoring and delivery
There's no dedicated webhook or MCP integration built into this Actor, but both are available as platform-wide Apify features: attach an Apify Schedule to run it on a recurring cron interval, and add an Apify webhook to fire on ACTOR.RUN.SUCCEEDED so a downstream system is notified — or pulls the fresh dataset — the moment each scheduled run finishes.
Is it legal to scrape Google Maps results?
Scraping publicly accessible web data is generally lawful — Google Maps Scraper retrieves only what any visitor sees when they run the same search in a browser, without logging in or bypassing any access control. Because this Actor does not extract review text or reviewer profiles, it sidesteps the personal-data exposure that comes with scraping the people who wrote reviews, not just the businesses they reviewed; what it does collect (names, addresses, phone numbers, ratings) is predominantly business and public-record data, governed more by Google's Terms of Service and ordinary database-rights considerations than by GDPR or CCPA, which are built around personal data. Scraping for a one-off research pull and scraping to build a bulk, continuously stored dataset carry different risk profiles — the latter deserves more legal care, especially for commercial use. Consult your legal team before storing bulk data at scale or reselling extracted business data commercially.
❓ Frequently asked questions
Does Google Maps Scraper support other languages or regions?
Partially. The gl (region) parameter is set automatically from the country code that OpenStreetMap's Nominatim resolves for each locations entry, so a search for a French city returns results with Google's France-specific ranking. The interface language (hl) is hardcoded to English, though — there's no input parameter to request results with non-English place names or descriptions.
Does maxResults apply per keyword, or across the whole run?
Across the whole run. maxResults is a shared, deduplicated ceiling on unique places across every locations × keywords combination plus every urls entry — it is not multiplied by the number of keywords or locations you provide.
How does Google Maps Scraper handle Google's anti-bot measures?
It starts on a direct connection and watches every response for HTTP 429/403/503 status codes or block-page text patterns (phrases like "unusual traffic," "verify you are not a robot," or "captcha"). On a detected block it escalates the connection tier — direct → Apify Proxy datacenter → Apify Proxy residential (sticky once reached) — and retries up to 3 times per request with a short backoff between attempts.
Does Google Maps Scraper verify that results are actually inside my requested location?
Yes. Every result's lat/long is checked against the search area's bounding box (padded by at least 0.4°) before it's saved, and a separate preflight check measures whether a sample of results lands within 155 miles of the search center before the main crawl begins, escalating the proxy tier if too many don't. Results with no coordinates at all are kept rather than dropped, since there's nothing to check them against.
How many results does Google Maps Scraper return per run?
Up to whatever you set maxResults to, from 1 to 10,000 — there's no separate platform-imposed cap beyond that input. In practice, the run stops early and returns fewer than maxResults if a sparse search area simply doesn't contain that many matching businesses.
Does Google Maps Scraper extract reviews, images, or emails?
No. The output is business-listing data only — name, address, phone, website, rating, review count, category tags, coordinates, and opening hours. It does not fetch individual review text, reviewer profiles, photos, or contact-enrichment data (emails, social profiles) from business websites.
How do I use Google Maps Scraper to monitor businesses over time?
Run the same locations/keywords set on an Apify Schedule, key each run's dataset by place_id, and diff avg_rating, total_reviews, and hours against the previous run. A dropped place_id usually means the business closed or moved outside your search area; a new one means a competitor opened or was newly indexed.
Does Google Maps Scraper work with Claude, ChatGPT, and AI agent frameworks?
There's no dedicated MCP server for this Actor, but it's callable as a standard HTTP endpoint through the Apify API from any agent framework that can issue an HTTP request — an agent calls the Actor with a query, receives structured JSON back, and grounds its answer in real place data instead of guessing.
How does Google Maps Scraper compare to other Google Maps scrapers?
Checked on the Apify Store, 2026-07-25: compass/crawler-google-places (listed with 496,647 users) extracts a much larger field set — reviews, images, popular-times histograms, menus — through a base scrape plus several paid add-on events, and its own README documents additionalInfo as a deeply nested object of category-grouped amenity flags rather than flat fields. compass/google-maps-extractor is described by its own listing as "a stripped-down version of the original Google Maps Scraper," omitting images and reviews entirely. lukaskrivka/google-maps-with-contact-details focuses specifically on contact and email enrichment as its primary add-on, with company-contact and business-leads objects as separate enrichment payloads. Google Maps Scraper returns a fixed, complete field set on every row for a single row_result charge, with no add-on tiers to configure, and a flatter hours array (day/hours/open24Hour/close24Hour) instead of a nested openingHours plus additionalOpeningHours structure — the tradeoff is that it doesn't offer the deeper enrichment (reviews, images, emails, amenity flags) those three do.
Can I use Google Maps Scraper without managing proxies or a Google API key?
Yes. The Actor starts on a direct connection by default and escalates to Apify Proxy automatically if Google starts blocking — you only need to supply proxyConfiguration if you want to opt into proxy usage from the start. No Google account, Google Cloud project, or Google API key is required at any point.
💬 Your feedback
Found a bug, or a field that's coming back empty when it shouldn't? Let Scrapier know through the Actor's Issues tab on the Apify Store listing, or via the Apify Console's built-in support chat on the run page. Bug reports and missing-field reports are what keep this Actor's parsing logic current as Google changes its response format.