Google Maps Scraper with Emails avatar

Google Maps Scraper with Emails

Pricing

from $0.80 / 1,000 place scrapeds

Go to Apify Store
Google Maps Scraper with Emails

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

Hussein Al-Mansori

Maintained by Community

Actor 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

OptionWhat it doesDefault
searchStringsArrayWhat to look for. One search runs per term.required
locationQueryCity, region, or country. Looked up on OpenStreetMap.
customGeolocationGeoJSON Polygon, MultiPolygon, or Point with radiusKm, instead of a name. Longitude comes first.
maxPlacesPerSearchStop after this many places per term.100
languageTwo-letter code. Affects categories and opening hours.en
restrictToAreaDiscard places Google returns from outside your area.true
scrapePlaceDetailsOpen each place's own page for website, phone, full address, hours and categories. Google does not show these in its results list.false
enrichContactsRead each website for email addresses and social profiles. Turns on the option above, since the website comes from it.false
proxyConfigurationResidential 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. Its worker field records which runtime served the run; output is identical either way.
  • SUMMARY (Key-value store) — cumulative counts for the run.

Pricing

EventWhen it appliesPrice
place-scrapedA business was returned$0.0008
place-detailedIts own page was read too: website, phone, full address, hours$0.0020
place-enrichedIts 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

LimitValue
Search terms per run20
Places per search term10,000
Results per map view (Google's ceiling)~120
Area subdivision depth4
Map views per search term400

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

CodeMeaningWorth retrying?
INVALID_INPUTThe input did not match the schemaNo — fix the input
GEOCODE_FAILEDThe location could not be foundNo — try a more specific name
UPSTREAM_BLOCKEDGoogle throttled the runYes, later, or with residential proxies
UPSTREAM_TIMEOUTA map view took too longYes
PARSE_FAILEDGoogle's result formatting was unreadableYes
NO_RESULTSNothing matched in that areaNo
BUDGET_EXHAUSTEDThe run's charge limit was reachedNo — raise the limit
WORKER_UNAVAILABLEOur harvesting capacity was briefly unreachableYes — nothing was charged
STORAGE_FAILUREA result could not be stored, and was not chargedYes
INTERNAL_ERRORSomething unexpectedYes

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.