Google Maps Scraper with Emails
Pricing
from $0.80 / 1,000 place scrapeds
Google Maps Scraper with Emails
Extract Google Maps business listings with website, phone, email and social profiles - and a coverage report that says when an area could not be read exhaustively.
Pricing
from $0.80 / 1,000 place scrapeds
Rating
0.0
(0)
Developer
Hussein Al-Mansori
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
13 days ago
Last modified
Categories
Share
Verified Google Maps Harvester
Extract business listings from Google Maps — names, addresses, categories, ratings, review counts, price bands and opening status — for any city, region, or map area you draw yourself. Optionally open each place's own page for its website, phone number and full address, and read that website for email addresses and social profiles.
What makes this one different: it tells you when it did not find everything.
Every scraper hits Google's hard ceiling of roughly 120 results per map view. Most quietly return what they got and let you assume that was all there was. This one subdivides the area and searches again — and when an area is still too dense to read exhaustively, it says so in a coverage report instead of handing you a confident-looking partial list.
What you get
One row per business.
With scrapePlaceDetails and enrichContacts turned on, a row also carries the fields Google
does not show in its results list, and the contacts from the business's own website:
{"title": "Bwè Coffee - Washington St.","website": "http://bwekafe.com/","phone": "+1 201-683-0045","phoneUnformatted": "+12016830045","street": "1002 Washington St","city": "Hoboken","state": "NJ","postalCode": "07030","countryCode": "United States","emails": [{ "address": "info@bwekafe.com", "kind": "role", "foundIn": "mailto" }],"socials": [{ "platform": "instagram", "url": "https://instagram.com/bwekafe" }],"reviewsDistribution": { "fiveStar": 17677, "fourStar": 5124 },"fieldSources": { "title": "card", "website": "detail", "emails": "website" }}
Every row records where each value came from in fieldSources — the results list, the place's own
page, or its website. Nobody else publishes that, and it is what separates "this business has no
website" from "something broke".
A plain run, with neither option on, returns what the results list carries:
{"title": "Joe's Pizza Broadway","categoryName": "Pizza","address": "1435 Broadway","location": { "lat": 40.7546795, "lng": -73.9870291 },"totalScore": 4.4,"reviewsCount": 26457,"priceRange": "$10–20","description": "Classic NYC slice shop","openState": "Closed · Opens 10 AM","serviceOptions": ["Dine-in"],"placeId": "ChIJifIePKtZwokRVZ-UdRGkZzs","cid": "4280570365733019477","fid": "0x89c259ab3c1ef289:0x3b67a41175949f55","kgmid": "/g/11bw4ws2mt","url": "https://www.google.com/maps/place/...","searchString": "pizza","rank": 1,"status": "verified","scrapedAt": "2026-08-11T08:43:22Z"}
Four identifiers come back for every place — placeId, cid, fid, and kgmid — so results
join cleanly against the Google Places API, your CRM, or a previous run.
Why this one
Real boundaries, not a square around a point. Ask for "Jersey City, New Jersey" and the area is looked up on OpenStreetMap, so the search follows the actual administrative outline. Places in the next town are discarded rather than padding your row count.
Honest coverage. Every run writes a COVERAGE record saying how many map tiles were read,
whether the area was fully covered, and if not, why. If Google throttled the run or an area was
too dense to exhaust, that is stated plainly.
Status you can trust. A row is verified only when the listing was read cleanly. If Google's
result formatting changes and details go missing, the row is downgraded to warning rather than
published as though nothing happened.
You draw the area. Give a place name, or supply GeoJSON — a polygon, several polygons, or a point with a radius.
Quick start
{"searchStringsArray": ["coffee shop", "bakery"],"locationQuery": "Jersey City, New Jersey","maxPlacesPerSearch": 200}
Use search terms that overlap as little as possible. ["restaurant", "bar", "cafe"] finds far
more than ["restaurant", "restaurants", "chinese restaurant"], because near-duplicate terms
return near-identical results and simply cost more.
Input
| Option | What it does | Default |
|---|---|---|
searchStringsArray | What to look for. One search runs per term. | required |
locationQuery | City, region, or country. Looked up on OpenStreetMap. | — |
customGeolocation | GeoJSON Polygon, MultiPolygon, or Point with radiusKm, instead of a name. Longitude comes first. | — |
maxPlacesPerSearch | Stop after this many places per term. | 100 |
language | Two-letter code. Affects categories and opening hours. | en |
restrictToArea | Discard places Google returns from outside your area. | true |
scrapePlaceDetails | Open each place's own page for website, phone, full address, hours and categories. Google does not show these in its results list. | false |
enrichContacts | Read each website for email addresses and social profiles. Turns on the option above, since the website comes from it. | false |
proxyConfiguration | Residential addresses reach more results before Google throttles. | Apify Proxy |
Provide either locationQuery or customGeolocation. Without an area, Google answers from
wherever it guesses you are, which produces a confident dataset for the wrong city.
Reading the result
- Dataset — one row per place. The Places view shows the columns most people want; the Location view shows coordinates and all four identifiers.
COVERAGE(Key-value store) — whether the area was read completely, how many tiles were searched, and any reason it stopped short. Check this before treating a run as exhaustive. Itsworkerfield records which runtime served the run; output is identical either way.SUMMARY(Key-value store) — cumulative counts for the run.
Pricing
| Event | When it applies | Price |
|---|---|---|
place-scraped | A business was returned | $0.0008 |
place-detailed | Its own page was read too: website, phone, full address, hours | $0.0020 |
place-enriched | Its website was read as well: emails and social profiles | $0.0035 |
One event per row, and only the tier actually delivered. A place whose page failed to load is
charged as place-scraped, not as something richer. A website that publishes no email address and
no social profile is charged as place-detailed, even though reading it cost the same. Failed rows
are never charged at all.
1,000 places costs about $0.80; with websites and phone numbers, $2.00; with email addresses too, $3.50.
Prices are set from measured platform cost on real runs, not estimated.
Limits
| Limit | Value |
|---|---|
| Search terms per run | 20 |
| Places per search term | 10,000 |
| Results per map view (Google's ceiling) | ~120 |
| Area subdivision depth | 4 |
| Map views per search term | 400 |
When an area holds more places than subdivision can reach, the run says so in COVERAGE rather
than pretending the list is complete. Searching a smaller area is how you get the rest.
Errors
| Code | Meaning | Worth retrying? |
|---|---|---|
INVALID_INPUT | The input did not match the schema | No — fix the input |
GEOCODE_FAILED | The location could not be found | No — try a more specific name |
UPSTREAM_BLOCKED | Google throttled the run | Yes, later, or with residential proxies |
UPSTREAM_TIMEOUT | A map view took too long | Yes |
PARSE_FAILED | Google's result formatting was unreadable | Yes |
NO_RESULTS | Nothing matched in that area | No |
BUDGET_EXHAUSTED | The run's charge limit was reached | No — raise the limit |
WORKER_UNAVAILABLE | Our harvesting capacity was briefly unreachable | Yes — nothing was charged |
STORAGE_FAILURE | A result could not be stored, and was not charged | Yes |
INTERNAL_ERROR | Something unexpected | Yes |
Privacy and lawful use
This Actor does not collect reviews or reviewer profiles, which is where most of the personal data on Google Maps lives. Ratings are returned only in aggregate — a score and a count, never who gave them.
enrichContacts is different, and worth understanding before you turn it on. It reads each
business's own website and returns the addresses published there. Most are role addresses like
info@ or sales@, but some are named individuals, and a named individual's work address is
personal data under the GDPR and similar laws. Every address is labelled role, personal or
unknown in the kind field so you can filter on it, and foundIn records where on the page it
came from so any claim can be re-checked by hand.
Business contact details are still subject to data-protection law in many jurisdictions. If you use this data for outreach, you are the data controller: you need a lawful basis, and recipients need a way to opt out. Marketing rules such as GDPR, CAN-SPAM, and PECR apply to how you use the output, not to how it was gathered.
FAQ
Why did I get fewer places than I asked for?
Check the COVERAGE record. Either the area genuinely holds fewer businesses, or the run stopped
early and it will tell you why. Fewer honest rows beats more padded ones.
Can I search several cities at once?
Run the Actor once per city, or supply a MultiPolygon covering all of them in
customGeolocation.
Why are some ratings empty? New businesses often have no rating yet. The field is null rather than guessed.
Does it get emails and phone numbers from business websites?
Yes. enrichContacts reads each business's own website and returns the email addresses and social
profiles published there; scrapePlaceDetails gets the phone number from the Google listing.
About four sites in ten publish an email address — the rest come back empty rather than guessed,
and fieldSources tells you which is which.
How many places can one run return?
As many as the area holds, up to your per-term limit. Google shows roughly 120 per map view, so
this subdivides the area and searches again. Where even that is not enough, COVERAGE says so.
Why did a run fail with WORKER_UNAVAILABLE?
Our harvesting capacity was briefly unreachable. Nothing was charged. Retry in a minute.