Google Maps Scraper
Pricing
from $1.00 / 1,000 place scrapeds
Google Maps Scraper
Stop wasting your budget on slow, resource-heavy browser-based scrapers. This is the fastest, most cost-effective, and data-rich Google Maps scraper on Apify, designed for high-scale lead generation and market research.
Pricing
from $1.00 / 1,000 place scrapeds
Rating
5.0
(5)
Developer
VortexData
Maintained by CommunityActor stats
8
Bookmarked
233
Total users
80
Monthly active users
0.49 hours
Issues response
15 hours ago
Last modified
Categories
Share
🗺️ Google Maps Scraper
Extract structured Google Maps place data for lead generation, local market research, review monitoring, and competitor analysis.
This Actor searches Google Maps by keyword and location, returns one dataset row per unique place, and can optionally enrich each place with website contacts, social profiles, and reviews.
✅ No Google Maps API key is required.
📦 What You Can Extract
| Data group | Example fields |
|---|---|
| 🏷️ Place identity | title, categoryName, categories, description, subTitle |
| 📍 Address and location | address, street, city, state, postalCode, countryCode, location |
| ☎️ Contact data | phone, phoneUnformatted, website, websiteDisplay |
| ✉️ Website enrichment | emails, additionalPhones, facebooks, instagrams, linkedIns, twitters, youtubes, tiktoks, pinterests, whatsapps |
| ⭐ Ratings and status | totalScore, reviewsCount, currentStatus, openingHoursToday, permanentlyClosed, temporarilyClosed |
| 💬 Reviews | reviews, reviewsScrapedCount, review text, author metadata, dates, photos when available |
| 🔗 Google identifiers | placeId, fid, cid, kgmid, canonical Google Maps url |
| 🏨 Hotel fields | hotelStars, hotelPrice, hotelAmenities, hotelCheckInDate, hotelCheckOutDate |
The dataset includes ready-made Apify views for Overview, Lead generation, Reviews, and Hotels. Apify also provides the built-in All fields view.
🎯 Best For
- Building local B2B lead lists with phone numbers, websites, emails, and social profiles.
- Finding competitors in a city, region, ZIP code, or custom area.
- Collecting Google Maps reviews for reputation analysis.
- Monitoring business categories, ratings, opening status, and addresses.
- Enriching CRM, SEO, sales, or local-market datasets.
🚀 Quick Start
For most runs, fill in only these fields:
searchStringsArray- what to search for, one term per line.locationQueries- one or more cities, regions, neighborhoods, or countries, one per line.maxCrawledPlacesPerSearch- how many places to keep for each search term and location.
Example input:
{"searchStringsArray": ["coffee shop"],"locationQueries": ["Brooklyn, New York, United States"],"maxCrawledPlacesPerSearch": 200}
This returns up to 200 coffee shops around Brooklyn with core Google Maps fields such as name, category, address, phone, website, rating, coordinates, Place ID, and Maps URL.
🧪 Input Examples
✉️ Lead Generation With Emails
Use website enrichment when you need emails and social profiles from business websites.
{"searchStringsArray": ["dental clinic"],"locationQueries": ["Austin, Texas, United States"],"maxCrawledPlacesPerSearch": 500,"extractContactsFromWebsite": true}
The Actor saves all scraped places and fills emails, social profiles, and
extra phones when they are found. After the run, filter the dataset by
emails.length > 0 if you need an email-only export.
⭐ Reviews
Set maxReviewsPerPlace to collect reviews for each scraped place.
{"searchStringsArray": ["pizza"],"locationQueries": ["Brooklyn, New York, United States"],"maxCrawledPlacesPerSearch": 100,"maxReviewsPerPlace": 100,"reviewsSort": "newest"}
The Actor can fetch up to 1000 reviews per place. Higher values take longer and use more requests.
🔎 Multiple Search Terms
{"searchStringsArray": ["restaurant","coffee shop","bakery"],"locationQueries": ["Helsinki, Finland"],"maxCrawledPlacesPerSearch": 300,"language": "fi","additionalLanguages": ["en"]}
Each search term has its own result limit. Extra languages run additional passes and can improve coverage in multilingual regions.
📍 Multiple Locations
Use multiple entries in locationQueries when you want to run the same search
terms across several independent areas in one run. Keep one complete location
per line; do not write two cities in one line.
{"searchStringsArray": ["plumber", "electrician"],"locationQueries": ["Seddon, Victoria, Australia","Footscray, Victoria, Australia"],"maxCrawledPlacesPerSearch": 100}
maxCrawledPlacesPerSearch applies per search term per location. The example
above can save up to about 400 places before deduplication: 2 terms × 2
locations × 100.
🔗 Google Maps URLs Or Place IDs
Use this when you already know exact places, or when you copied a Google Maps search result page with its visible map viewport.
{"startUrls": [{"url": "https://www.google.com/maps/search/?api=1&query=Example+Business&query_place_id=ChIJreV9aqYWdkgROM_boL6YbwA","userData": { "externalId": "crm-123" }}],"maxReviewsPerPlace": 50}
Direct URLs and raw Google Place IDs do not require searchStringsArray or
locationQueries. Raw Place IDs such as ChIJreV9aqYWdkgROM_boL6YbwA are
resolved through Google's exact query_place_id place endpoint and accepted
only when the returned placeId matches the input. Short /g/... identifiers,
CIDs, and fids are different Google identifiers; pass a full Google Maps URL
for those.
Copied search result URLs such as
https://www.google.com/maps/search/coffee+shop/@30.2711286,-97.7436995,13z
are treated as searches in the visible map area from the URL. The Actor parses
the search text and @lat,lng,zoom viewport. Google may also copy URLs with a
meter-scale viewport such as @lat,lng,7094m; those are accepted too. The Actor
does not guess one exact place. Search URLs without @lat,lng,zoom or
@lat,lng,meters are ignored because no search area is available.
How to choose the right direct input:
- Best option: open the place in Google Maps, click Share, click Copy link,
and paste the full link into
startUrls. - Use
placeIdsonly for raw Google Place IDs from Google Places API, Google's Place ID Finder, or a Maps URL parameter namedquery_place_id. - Paste
/maps/search/.../@lat,lng,zoomor/maps/search/.../@lat,lng,metersURLs when you want to scrape the visible Google Maps search area from an existing map page. - Do not paste
/g/...values, short IDs such as11lmncvyn7, fids such as0x317067ffdca15d1b:0x367befc8c87c6fef, numeric CIDs, or full URLs intoplaceIds. If you have any of those, paste the full Google Maps URL intostartUrls.
🧭 Custom Search Area
Use customGeolocation when a city boundary is too small, too large, or not accurate enough.
{"searchStringsArray": ["plumber"],"maxCrawledPlacesPerSearch": 1000,"customGeolocation": {"type": "Point","coordinates": [-74.006, 40.7128],"radiusKm": 20}}
Supported custom areas include GeoJSON Point with radiusKm, Polygon, and MultiPolygon.
For normal keyword + location searches, the Actor adds the resolved location
text to Google Maps queries so broad categories stay focused on the requested
city or region before geographic filtering is applied.
By default, the Actor keeps all places Google returns for the search area. Set
geoStrictMatch=true when you need the final dataset to include only places
whose coordinates are inside the resolved location or custom GeoJSON area.
GeoJSON Polygon and MultiPolygon inputs are checked against the exact
polygon. Small geocoded areas such as suburbs and neighborhoods use the exact
OpenStreetMap boundary when available; broader geocoded areas and GeoJSON
Point + radiusKm areas use the resolved bounding box.
Strict mode requires a real area boundary. If the location resolves only to a
point, the Actor skips keyword search with a warning instead of returning rows
that cannot be checked against the selected area.
📍 When strict matching is off, rows include insideSearchArea so you can filter
nearby or out-of-area Google results without losing the scraped row.
⚙️ Configuration Reference
| Input field | What it does | Recommended use |
|---|---|---|
searchStringsArray | Google Maps search terms. | Add one business category or keyword per line. |
locationQueries | One or more independent search locations. | Add one complete city/suburb/region per line. One line works for normal single-location runs; multiple lines run the same search terms across several areas. |
maxCrawledPlacesPerSearch | Maximum kept places per search term per location, or per direct target. | Use 50 for tests, 500+ for city-scale lists. |
language | Primary Google Maps language. | Use the local language for local categories when needed. |
additionalLanguages | Extra language passes. | Useful in multilingual countries, but increases requests. |
extractContactsFromWebsite | Visits business websites and extracts emails, phones, and social links. | Enable for lead generation. |
contactsFetchContactPage | If the homepage has no email, checks up to five likely contact/about/location/press/booking pages. | Keep enabled for better email yield. |
contactsProxyFallback | Retries only failed website/contact-page fetches through residential proxy after a direct attempt. | Keep enabled for better contact coverage; turn off only to minimize proxy bandwidth. |
contactsFilterEmailsByWebsiteDomain | Keeps only emails that look related to the business website. | Enabled by default for cleaner leads; turn off to save every valid-looking public email found on the visited homepage/contact pages, including parent-company, agency, vendor, or platform domains. |
maxReviewsPerPlace | Number of reviews to attach to each place. | Use 0 for fastest runs, higher values for review analysis. |
reviewsSort | Requested review order. Google can ignore it for some places. | newest is best-effort, not a guarantee. |
skipClosedPlaces | Removes temporarily or permanently closed places. | Enable for active-business lead lists. |
geoStrictMatch | 📍 Drops places outside the resolved location or custom GeoJSON area. | Leave off for broad discovery; enable for strict city, neighborhood, or exact polygon exports. |
enableSubdivision | Splits dense map areas into smaller viewports. | Keep enabled for full city coverage. |
maxSubdivisionDepth | Maximum depth for automatic area splitting. | Increase for dense cities; decrease for faster runs. |
multiZoomDelta | Runs nearby zoom levels around the resolved location. | Use only when you need extra coverage; it increases requests. |
concurrency | Parallel scraping workers. | Higher is faster but can use more proxy bandwidth. |
Legacy post-fetch filters such as categoryFilterWords, placeMinimumStars,
and searchMatching are no longer applied. They could remove valid rows only
after Google Maps data had already been fetched, which made some runs expensive
with little or no output. Use the dataset fields (categories, totalScore,
title, searchString) to filter after the run instead.
📤 Output
The main result is the default dataset. Each item is one unique Google Maps place.
Example output item:
{"searchString": "coffee shop","title": "Bird & Branch Coffee Roasters","categoryName": "Coffee shop","categories": ["Coffee shop", "Cafe"],"address": "359 W 45th St, New York, NY 10036, United States","city": "New York","state": "New York","countryCode": "US","location": { "lat": 40.7602998, "lng": -73.9907758 },"insideSearchArea": true,"phone": "+1 917-265-8444","website": "https://www.birdandbranch.com/","emails": ["hello@example.com"],"instagrams": ["https://www.instagram.com/example"],"totalScore": 4.6,"currentStatus": "Closed","placeId": "ChIJTVhsxFNYwokRXgPwYnY0vgI","url": "https://www.google.com/maps/search/?api=1&query=Bird+%26+Branch+Coffee+Roasters&query_place_id=ChIJTVhsxFNYwokRXgPwYnY0vgI","reviewsScrapedCount": 5,"reviews": [{"author": "Alex Smith","text": "Great coffee and friendly service.","publishedAt": "3 months ago","publishedAtTimestamp": "2026-02-04T14:22:09Z"}],"scrapedAt": "2026-05-29T10:30:00Z"}
Website enrichment fields such as emails and social profiles are present only when extractContactsFromWebsite is enabled. Review fields are present only when maxReviewsPerPlace is greater than 0.
🩺 Run Summary And Diagnostics
At the end of each run, the Actor writes an OUTPUT record to the default key-value store. It contains:
status-SUCCEEDED,PARTIAL, orINPUT_NEEDS_ATTENTION.inputWarning- human-readable explanation when the run needs input changes.diagnosticHints- concrete reasons for low or zero output.totalPlaces- number of pushed dataset rows.searchStats- raw counters for returned, filtered, out-of-area, and dropped places.searchStartUrlsAccepted- number of Google Maps search URLs accepted fromstartUrls.unsupportedStartUrls- number of unsupported URL entries ignored fromstartUrls.stoppedByChargeLimit- true when the run stopped because Apify's max total charge limit was reached.chargeLimitEvent- last pricing event reported when the charge limit was reached.datasetUrl- direct API link to the dataset.
The same important diagnostics are also printed in the Apify log as Run diagnosis.
Dataset rows also include insideSearchArea when a search-area boundary and
place coordinates were available.
Example:
{"status": "INPUT_NEEDS_ATTENTION","inputWarning": "Google Maps returned 80 raw place(s), but no rows matched the final criteria: 📍 80 outside the resolved search area.","totalPlaces": 0,"diagnosticHints": ["Google returned only out-of-area results for 'restaurant'. Check Location spelling or use customGeolocation for an exact area."]}
🛠️ Troubleshooting
🔍 The run succeeded but returned 0 places
Open the OUTPUT record or the Apify log and look for Run diagnosis.
Common causes:
- The location was misspelled or resolved to the wrong area.
- Google returned places outside the requested area and
geoStrictMatch=trueremoved them. skipClosedPlaces=trueremoved places that Google marks as temporarily or permanently closed.- The search term is too narrow for the selected location.
Try a broader search term, a clearer location such as City, Country, or disable strict/closed-place filtering if those options are too narrow for the run.
📍 Location results look wrong
Use simple location text first:
Los Angeles, California, United StatesHelsinki, FinlandBrooklyn, New York, United States
If you need an exact area, use customGeolocation and enable geoStrictMatch.
🧩 I need more than the visible Google Maps limit
Keep enableSubdivision=true. The Actor automatically splits dense areas into smaller viewports and deduplicates places by Google identifiers.
✉️ I need emails only
Use:
{"extractContactsFromWebsite": true}
The Actor saves all scraped places. Filter the finished dataset by the emails
field to export only rows where website enrichment found an email.
⚡ Performance And Cost Tips
- Start with
maxCrawledPlacesPerSearch=50for a quick test. - Keep
maxReviewsPerPlace=0unless reviews are needed. - Enable contact enrichment only when you need emails or social profiles.
- Extra languages and extra zoom passes increase request volume.
- Higher concurrency is faster, but can use more proxy bandwidth.
- If you set a low Apify
maxTotalChargeUsdrun limit, the Actor stops gracefully when the limit is reached and writes a partialOUTPUTsummary instead of continuing until the platform aborts the run. - Check the Apify Pricing tab for current per-event pricing. You are charged according to the pricing events shown there.
- Pricing events are designed around delivered value: one base charge per saved place, one optional contact-details event when website enrichment finds emails or social profiles, and one optional review event per review actually attached.
🔌 Integrations
You can use the results through:
- Apify dataset exports: JSON, CSV, Excel, XML, RSS, and HTML.
- Apify API and webhooks.
- Scheduled runs.
- Make, Zapier, n8n, Google Sheets, Airtable, Slack, and other Apify integrations.
- Apify CLI or SDKs for automated workflows.
ℹ️ Notes And Limitations
- Google Maps may return different rankings depending on language, location, and availability.
- Some businesses do not publish websites, emails, phone numbers, or reviews.
- Review metadata depends on what Google exposes for each place.
- Website enrichment extracts public contact details from the business website; it cannot create missing emails.
- This Actor is not affiliated with Google.
💬 Support
If a run does not return what you expected, check OUTPUT.inputWarning, OUTPUT.diagnosticHints, and the Apify log first. They usually explain whether the issue is location resolution, strict/closed-place filtering, out-of-area results, or an empty Google response.
For bugs or feature requests, use the Actor's Issues tab on Apify.
See ./CHANGELOG.md for version history.