Google Maps Scraper - User friendly avatar

Google Maps Scraper - User friendly

Pricing

from $2.00 / 1,000 results

Go to Apify Store
Google Maps Scraper - User friendly

Google Maps Scraper - User friendly

Pricing

from $2.00 / 1,000 results

Rating

0.0

(0)

Developer

yourlocalhost

yourlocalhost

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

2 days ago

Last modified

Share

Easy Google Maps Places Scraper

Scrape Google Maps places — businesses, points of interest, their ratings, contact details and more — by search term, location, Google Maps URL or place ID. Built to be the easiest Maps scraper to pick up, with the features other scrapers charge extra for included for free.

First run in 10 seconds: type a search term, type a location, hit Start. Everything else has sensible defaults.


Why this actor

CapabilityThis actorTypical Maps scrapers
Beat the ~120-results-per-area limit✅ Automatic adaptive tiling⚠️ Manual GeoJSON required
Many locations in one run✅ Built in, one merged dataset💰 Paid orchestrator add-on
Cross-search de-duplication✅ Built in, free💰 Often a paid step
Incremental / delta runs✅ Built in❌ Rare
Contact enrichment (emails, socials)✅ Bundled💰 Separate paid actor
Interactive results mapresults-map.html
Beginner-safe cost caps✅ Hard maxTotalPlaces cap⚠️ Easy to overspend

Quick start

  1. Open the actor in the Apify Console.
  2. Fill only the three default fields:
    • Search terms — e.g. coffee shop
    • Location — e.g. Boulder, Colorado, USA
    • Max places per search term — keep it small (e.g. 50) for your first run.
  3. Click Start. Results appear in the Dataset tab; an interactive map is saved to the Storage → Key-value store as results-map.

Everything else lives in collapsible Advanced sections — open them only when you need reviews, enrichment, incremental mode or custom areas.

Presets

Pick a Preset to auto-configure the actor for a common job. A preset only fills fields you have not set yourself.

PresetWhat it does
Lead generationContact enrichment ON, larger limits — turn places into leads.
Review analysisReviews ON (100/place), balanced limits.
Quick scanTiny, fast, cheap run — listing data only.

Input

Always visible

FieldTypeDefaultDescription
searchTermsstring[]What to look for, e.g. ["dentist","orthodontist"].
locationstringFree-text place. Resolved automatically — no polygon needed.
maxPlacesPerSearchinteger50Cap per search term.

Advanced (collapsed by default)

  • Location targetinglocations[] (native multi-location), structured country / state / city / postalCode, startUrls[] (direct Google Maps URLs), placeIds[], and customGeolocation (optional GeoJSON / circle override).
  • Search limits & languagemaxTotalPlaces (hard cost cap), language, regionCode.
  • ReviewsmaxReviews, reviewsSort.
  • Contact enrichmentscrapeContactDetails, maxEnrichmentPagesPerSite.
  • Incremental / delta modeincrementalMode, onlyNewOrChanged, stateStoreName.
  • Personal data & compliancescrapePersonalData (master opt-in, see below).
  • Performance & browserlistingMode (auto / http / browser), maxConcurrency, maxRequestRetries, minTileResultsToSubdivide, maxTileDepth.
  • ProxyproxyConfiguration (defaults to Apify RESIDENTIAL proxy).

You can provide any combination of searchTerms + locations, startUrls and placeIds in a single run.


Output

Each dataset item is one place:

{
"title": "Ozo Coffee Roasters",
"categoryName": "Coffee shop",
"categories": ["Coffee shop", "Cafe"],
"address": "1015 Pearl St, Boulder, CO 80302, USA",
"street": "1015 Pearl St",
"city": "Boulder",
"state": "CO",
"postalCode": "80302",
"countryCode": "US",
"location": { "lat": 40.0176, "lng": -105.2797 },
"plusCode": "...",
"phone": "(303) 544-0500",
"phoneUnformatted": "+13035440500",
"website": "https://ozocoffee.com/",
"totalScore": 4.6,
"reviewsCount": 1234,
"reviewsDistribution": { "oneStar": 10, "twoStar": 12, "threeStar": 40, "fourStar": 300, "fiveStar": 872 },
"price": "$$",
"openingHours": [{ "day": "Monday", "hours": "7 AM–6 PM" }],
"permanentlyClosed": false,
"temporarilyClosed": false,
"placeId": "ChIJ...",
"cid": "123456789012345678",
"fid": "0x...:0x...",
"url": "https://www.google.com/maps?cid=123456789012345678",
"imageUrl": "https://lh5.googleusercontent.com/...",
"additionalInfo": { "Service options": [{ "Dine-in": true }, { "Takeaway": true }] },
"searchString": "coffee shop",
"locationQuery": "Boulder, Colorado, USA",
"scrapedAt": "2026-05-21T10:00:00.000Z",
"changeStatus": "new",
"emails": [],
"additionalPhones": [],
"socialProfiles": {},
"reviews": []
}
  • emails, socialProfiles and review reviewerName/reviewerId are only populated when scrapePersonalData is enabled (see Compliance).
  • changeStatus / changes are populated in incremental mode.
  • A results-map HTML file (interactive Leaflet map of every result) is written to the run's key-value store.

How adaptive tiling works

Google Maps only returns ~120 results for any single map view. To get every place in a city, this actor:

  1. Resolves your location to a bounding box (OpenStreetMap Nominatim).
  2. Searches that area as one tile.
  3. Only if the tile hits the ~120 cap, it splits the tile into 4 quadrants and searches each one — recursively, up to maxTileDepth.
  4. Sparse tiles are never subdivided, so dense city centres get fine-grained coverage while quiet suburbs stay cheap.

You never draw a polygon. For full manual control, the advanced customGeolocation field accepts a GeoJSON Polygon / MultiPolygon, or { "type": "circle", "coordinates": [lng, lat], "radiusKm": 5 }.

Incremental / delta mode

Enable incrementalMode to remember places between runs (state is stored in a named key-value store — set stateStoreName to reuse it across a monitoring job). Each run then classifies every place as new, changed or unchanged, and writes a change-report to the key-value store listing new places, rating changes, status changes and closures. Set onlyNewOrChanged to push just the deltas to the dataset.

Contact enrichment

Enable scrapeContactDetails to visit each place's website and collect emails, extra phone numbers and social-media profiles (Facebook, Instagram, LinkedIn, X/Twitter, YouTube, TikTok). This is bundled at no extra charge.


Personal data is OFF by default. Reviewer names/IDs, scraped emails and social-media profiles are only collected when you explicitly enable scrapePersonalData. With it off, those fields are omitted even if reviews or enrichment are enabled.

You are responsible for ensuring your use complies with Google's Terms of Service and all applicable laws (including the GDPR and CCPA). Only scrape personal data when you have a lawful basis to do so, and never use it for unsolicited bulk contact. This actor is a tool; lawful use is the operator's responsibility.


Local development

# 1. Install dependencies
npm install
# 2. Set your test input (already provided at the path below)
# storage/key_value_stores/default/INPUT.json
# 3. Run locally with the Apify CLI
apify run --purge
# Results: storage/datasets/default/
# Map: storage/key_value_stores/default/results-map.html

Install the Apify CLI with npm install -g apify-cli if you do not have it. A residential proxy is strongly recommended — Google blocks datacenter IPs. Running locally without Apify proxy will likely be blocked; the actor will then try the browser fallback automatically (listingMode: "auto").

Deploy to the Apify platform

apify login
apify push

Run via the Apify API

curl -X POST "https://api.apify.com/v2/acts/YOUR_USERNAME~easy-google-maps-scraper/runs?token=YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"searchTerms": ["coffee shop"],
"location": "Boulder, Colorado, USA",
"maxPlacesPerSearch": 50
}'

Example inputs

Multi-location lead generation

{
"searchTerms": ["dentist"],
"locations": ["Austin, Texas, USA", "Round Rock, Texas, USA"],
"preset": "lead-generation",
"scrapePersonalData": true
}

Monitor a city for changes

{
"searchTerms": ["restaurant"],
"location": "Camden, London, UK",
"incrementalMode": true,
"onlyNewOrChanged": true,
"stateStoreName": "camden-restaurants"
}

Scrape specific places by ID

{
"placeIds": ["ChIJ4z2Gx...", "123456789012345678"]
}

Listing modes (how results are fetched)

Google Maps loads its result list through an internal endpoint whose request format it rotates frequently. The actor therefore supports three modes via the advanced listingMode field:

ModeHow it worksNotes
auto (default)Tries the cheap HTTP endpoint first, automatically falls back to a headless browser if HTTP returns nothing.Recommended. Always produces results.
httpHTTP endpoint only.Fastest/cheapest when it works, but depends on Google's current internal format — treat as experimental.
browserHeadless Chromium only — drives the real Google Maps UI.Most robust. This is what auto falls back to.

In auto / browser mode each place carries the core fields (name, category, address, rating, coordinates, place IDs, Maps URL). Fields that only exist on the place's own page — phone, website, openingHours — are not filled by the browser listing path; such records have partialData: true. The HTTP path's parser (src/parsers.js) extracts the full field set, so once the HTTP format is current again every field is populated automatically.

Pricing notes

  • auto makes one cheap HTTP attempt per area, then uses a headless browser for the actual listing. The browser is reused across tiles, not per place.
  • maxTotalPlaces is a hard ceiling on the whole run — set it to control spend.
  • Contact enrichment adds one (or a few) lightweight HTTP requests per website.
  • Proxy traffic (residential, recommended for Maps) is billed by your Apify plan.

Limitations & troubleshooting

  • Zero results / "Blocked by Google" — use Apify RESIDENTIAL proxy. auto mode already retries with a browser; persistent blocking is almost always a proxy-quality issue.
  • partialData: true means the record came from the browser listing path and lacks phone / website / openingHours (see Listing modes above).
  • Google Maps' internal data format is undocumented and changes occasionally. Every field is parsed defensively, so a format change degrades individual fields rather than crashing the run. If many HTTP-path fields come back empty, the array indices in src/parsers.js (parsePlaceArray) are the place to update.
  • Reviews extraction returns the batch embedded in the place page (best-effort). For deep, paginated review scraping use a dedicated reviews actor.
  • Results are held in memory until the run finishes (bounded by maxTotalPlaces).