Google Maps Scraper: Places, Contacts & Coordinates
Pricing
from $1.40 / 1,000 place scrapeds
Google Maps Scraper: Places, Contacts & Coordinates
Search Google Maps by keyword and area. Names, categories, addresses, coordinates, phones, websites, ratings and opening hours, each with a stable Google place ID. No API key, no billing account.
Pricing
from $1.40 / 1,000 place scrapeds
Rating
0.0
(0)
Developer
Arman Hossain
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
2 days ago
Last modified
Categories
Share

Google Maps Scraper searches Google Maps by keyword and area and returns one clean row per business: name, categories, a split address, coordinates, phone in two formats, website, rating, opening hours and accessibility attributes.
Every row carries placeId, Google's stable identifier, so results join across runs and against your own records. No API key, no billing account, no proxy setup and no browser.
Agent skill: SKILL.md
https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/google-maps-scraper.md
What you get
| Output field | Meaning |
|---|---|
placeId | Google's stable place ID, ChIJ…. Use it as your join key |
cid, fid | Google's internal identifiers for the same place |
kgmid | Knowledge Graph ID, /g/… or the legacy /m/…, joins to Google's entity graph |
title | Business name |
categoryName, categories | Primary category, and every category Google files it under |
address | Formatted single-line address |
street, city, postalCode, countryCode | Address components as Google publishes them, not split out of the display string |
neighborhood, locality | Mitte, and Berlin, Germany |
latitude, longitude | Coordinates |
timezone | IANA zone, e.g. Europe/Berlin |
phone, phoneNational | E.164 and the locally formatted number |
website, domain | The listed website and its host |
rating | Star rating, always a number |
reviewsCount, priceLevel | Always null. Google does not publish either in search results |
permanentlyClosed, temporarilyClosed | Closure flags |
openingHours | Per day: the display text plus a machine-comparable interval of hour/minute pairs |
attributes | {group, name, available}. Read available — Google publishes absence as its own entry |
editorialSummary | Google's one-line blurb, where it has written one |
bookingLinks | Reservation URLs, where the place has them |
imageUrl | Thumbnail |
url | Google Maps link built from placeId |
searchTerm, gridCell, position | Which input produced the row, which grid cell it came from, and where it ranked |
scrapedAt | Run timestamp |
A RUN_SUMMARY record in the key-value store holds the resolved centre point, places saved, what each filter removed, duplicates collapsed, whether each term exhausted its area, and any field that failed its shape check.
Input
| Field | Type | Default | Notes |
|---|---|---|---|
searchTerms | array | required | What you would type into the Maps search bar. Each term is searched independently across the whole area |
location | string | Free text, e.g. Berlin, Germany. Resolved once per run to a centre point, which is reported in RUN_SUMMARY | |
latitude, longitude | number | Explicit centre point. Overrides location. Use when a place name is ambiguous | |
radiusKm | integer | 5 | How far around the centre to search. Smaller is denser and more local; larger covers more ground with less depth |
maxPlacesPerTerm | integer | 200 | Cap per term. The main cost control, since charging is per place |
gridSize | integer | 1 | Split the area into N×N cells and search each. 2 finds substantially more places at four times the requests |
minRating | number | Keep only places at or above this rating. Also removes unrated places | |
skipClosed | boolean | false | Drop permanently and temporarily closed places |
websiteFilter | string | all | all, withWebsite, or withoutWebsite |
Either location or a latitude/longitude pair is required. There is no global Maps search, so a run without an area fails with a named error rather than returning nothing.
{"searchTerms": ["specialty coffee"],"location": "Berlin, Germany","radiusKm": 10,"maxPlacesPerTerm": 200,"gridSize": 2,"minRating": 4.0}
Output
One record per unique place. Every field is present on every record; null means Google does not publish it for that place, never that it was skipped.
{"placeId": "ChIJnS0AQjdRqEcRJk8cf-B4y1M","cid": "0x47a8513742002d9d:0x53cb78e07f1c4f26","kgmid": "/g/11s5wzp9md","title": "Fauve Coffee Berlin","categoryName": "Coffee shop","categories": ["Coffee shop", "Cafe", "Coffee roastery"],"address": "Fauve Coffee Berlin, Neue Schönhauser Str. 8, 10178 Berlin, Germany","street": "Neue Schönhauser Str. 8","city": "Berlin","postalCode": "10178","countryCode": "DE","neighborhood": "Mitte","locality": "Berlin, Germany","latitude": 52.5247417,"longitude": 13.404067,"timezone": "Europe/Berlin","phone": null,"phoneNational": null,"website": null,"domain": null,"rating": 4.7,"reviewsCount": null,"priceLevel": null,"permanentlyClosed": false,"temporarilyClosed": false,"openingHours": [{ "day": "Saturday", "hours": "11 AM–6:30 PM", "interval": [[11], [18, 30]] }],"attributes": [{ "group": "Accessibility", "name": "Wheelchair accessible parking lot", "available": false }],"editorialSummary": null,"bookingLinks": null,"imageUrl": "https://lh5.googleusercontent.com/-OaxK-r1h6zM/…/photo.jpg","url": "https://www.google.com/maps/place/?q=place_id:ChIJnS0AQjdRqEcRJk8cf-B4y1M","searchTerm": "specialty coffee","gridCell": "r1c1","position": 1,"scrapedAt": "2026-08-08T21:04:11.000Z"}
Use cases
- Local lead lists. Every business of a category in a metro area, with phone and website attached.
- Prospecting for businesses without a website.
websiteFilter: "withoutWebsite"is the whole pitch for a web agency, and it is one field. - Geocoding and enrichment. Turn a category into rows with coordinates, a timezone and a stable ID that joins to anything else keyed on Google places.
- Coverage and reputation tracking. Run on a schedule and watch ratings, hours and new entrants move.
- Territory planning.
gridSizewith a wideradiusKmgives even coverage of a city rather than whatever ranks for one centre point.
Limits and behaviour
- Results churn about 10% between runs. The same query returns a roughly 90% stable set of place IDs; Google reorders its own results. Join on
placeIdand treat a single absence as noise, not a closure. - How many places a term yields depends on density, not a fixed cap. A dense term in a large city paginates into the hundreds; a quiet suburb returns a handful. When a term exhausts its area,
RUN_SUMMARY.ceilingHitsays so — raiseradiusKmorgridSizerather thanmaxPlacesPerTerm. reviewsCountandpriceLevelare alwaysnull. Google does not publish them in search results. They are declared so the column exists rather than appearing later and breaking your pipeline.- Attributes can be negative.
{"name": "Wheelchair accessible entrance", "available": false}means the place does not have it. Readingnamewithoutavailableinverts the meaning. - Grid cells overlap. Roughly a sixth to a third of what a grid fetches is a repeat. Duplicates are collapsed on
placeIdand charged once;RUN_SUMMARY.duplicatesCollapsedreports the count. minRatingalso removes unrated places. Leave it unset to keep them.- Address components are
nullwhere Google does not publish them, never guessed.stateis absent in most of the world and is not included. - No reviews, photo galleries or email enrichment. Those are separate products.
- Public data only. No authentication, and nothing that gets around access controls.
FAQ
Do I need a Google API key or a billing account? No. You supply no credentials.
Why did I get fewer places than maxPlacesPerTerm? The area ran out of matches. Check ceilingHit in RUN_SUMMARY: if it is true, widen the area instead of raising the cap.
What is placeId for? It is Google's stable identifier for a business. Use it to join runs, deduplicate against your own records, and build a Maps link.
How do I cover a whole city? Raise radiusKm to cover it and set gridSize to 2 or 3. Cells overlap, and duplicates are charged once.
Why do two runs return slightly different places? Google reorders results per request. See Limits and behaviour.
Can I filter by number of reviews? No. The review count is not in the data this Actor reads, so such a filter would silently match nothing.
Can I plug it into something else? Yes. Apify API, the client libraries, webhooks, scheduled runs, dataset exports to JSON, CSV or Excel, or MCP. The output is structured JSON.
API example
curl -X POST "https://api.apify.com/v2/acts/arman-bd~google-maps-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"searchTerms": ["dentist"],"location": "Austin, TX","radiusKm": 15,"gridSize": 2,"websiteFilter": "withoutWebsite"}'
JavaScript example
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: 'YOUR_TOKEN' });const run = await client.actor('arman-bd/google-maps-scraper').call({searchTerms: ['specialty coffee'],location: 'Berlin, Germany',radiusKm: 8,minRating: 4.0,});const { items } = await client.dataset(run.defaultDatasetId).listItems();for (const p of items) console.log(`${p.title}, ${p.rating}, ${p.city} — ${p.website ?? 'no website'}`);