Google Maps Scraper – Places, Phone, Website, Email & Hours
Pricing
from $1.00 / 1,000 place scrapeds
Google Maps Scraper – Places, Phone, Website, Email & Hours
Extract business data from Google Maps by search query or coordinates: name, address, phone, website, rating, hours and more.
Pricing
from $1.00 / 1,000 place scrapeds
Rating
0.0
(0)
Developer
SERP King
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
9 minutes ago
Last modified
Categories
Share
Extract structured business data from Google Maps by search query or by coordinates: name, address, phone, website, rating, review count, opening hours, price level, and more. This actor calls a licensed SERP data provider directly — no proxies, no headless browser, no IP bans, no CAPTCHAs. Results typically come back in a few seconds per query, and the same low, transparent price applies whether you scrape 10 places or 10,000.
What data you get
Each result item includes:
placeId,cid— Google's stable identifiers for the placename,category,additionalCategories— business name and category tagsaddress,addressParts(street, city, zip, region, country code)phone,website,domainrating,reviewsCount,ratingDistribution(1–5 star breakdown)priceLevellatitude,longitudeopeningHours— per weekday, list of{open, close}time rangescurrentStatus(e.g. open now / closed / temporarily closed)isClaimed— whether the business owner has claimed the listingmainImageUrl,totalPhotosgoogleMapsUrl— direct link to the place on Google MapsbookOnlineUrlrank— position in the search results for its queryquery,scrapedAt(ISO 8601 UTC)
Why this scraper
- Fast — results come from a real-time SERP data pipeline, typically seconds per query, not minutes of browser automation.
- Cheap — pay only for places actually scraped, a flat $0.001 per place regardless of your Apify plan (see Pricing).
- Up to 700 results per query — go well past Google's default page of ~20 results.
- Coordinate-based search — target a specific map viewport (
lat,lng,zoom) instead of relying on a text query alone, useful for dense areas orsearch_this_area-style workflows. - Optional reviews, filters, and bulk category x city expansion — see the Reviews, Filters, and Bulk categories x cities sections below.
How it compares
Public Apify Store place prices on the Free plan tier, checked against the Store API on 8 Sep 2026:
| Actor | Place price (Free plan) | Notes |
|---|---|---|
| compass/crawler-google-places | $0.004/place | 594,600 total users, 1,814 reviews (4.70★). |
| vortex_data/google-maps | $0.001/place | 712 users in the last 30 days, 18 reviews (4.84★). |
| This actor (moyadata/google-maps-places-scraper) | $0.001/place | No proxies, no browser, Standby API for synchronous HTTP calls, filters applied before billing (see Filters). |
compass/crawler-google-places also offers GeoJSON polygon-area search and full photo-gallery extraction that this actor does not.
Pricing
This actor uses Apify's pay-per-event pricing, flat across all plans. You are charged for:
| Event | What it means | Price |
|---|---|---|
apify-actor-start | Apify's platform-billed "run started" event, charged once per run automatically -- no code call needed, so a run with zero results costs almost nothing | $0.00005 |
apify-default-dataset-item ("Place scraped" in Console) | Charged once per place written to the output dataset | $0.001 |
review-scraped | Charged once per review fetched, only when includeReviews is on (see Reviews) | $0.0005 |
contact-enriched | Charged once per place, only when enrichContacts found at least one email or social link (see Contact enrichment) | $0.0015 |
Worked example: scraping 1,000 places costs 1,000 × $0.001 + $0.00005 ≈ $1.00. The same 1,000 places with includeReviews on and 10 reviews fetched per place cost 1,000 × $0.001 + 10,000 × $0.0005 + $0.00005 ≈ $1.00 + $5.00 = $6.00.
Apify's free plan includes monthly platform credit that, on its own, covers roughly the first 5,000 places scraped with this actor before you'd need to add a payment method.
Input
Set
countryto the country your queries refer to. Tested example:dentista en Providencia, Santiagowithcountry: "US"returns places from the wrong country; withcountry: "CL"it returns 100 dentists in Providencia.
Minimal example:
{"queries": ["coffee shops in Austin, TX"],"country": "US","language": "en","maxResultsPerQuery": 20}
| Field | Type | Default | Description |
|---|---|---|---|
queries | array of strings | ["coffee shops in Austin, TX"] | One or more search queries, e.g. "dentist in Austin, TX". Max 700 characters each. Optional if both categories and cities are set (see Bulk categories x cities). At least one query must result, from queries and/or expansion. |
country | string (ISO-2) | "US" | Country to search in. Ignored for query matching when coordinates is set, but always used to resolve the location for review requests (see Reviews). |
language | string | "en" | Language code for the results (e.g. "en", "es", "de"). Also selects the categories x cities query template. |
coordinates | string | — | Optional "lat,lng,zoom" (e.g. "30.2672,-97.7431,14"). When set, search is anchored to this map viewport instead of country. |
maxResultsPerQuery | integer | 20 (max 700) | Maximum number of places returned per query. |
deduplicate | boolean | true | Remove duplicate places (by placeId) seen across multiple queries in the same run. |
categories | array of strings | — | Bulk mode: business categories to cross with cities. See Bulk categories x cities. |
cities | array of strings | — | Bulk mode: cities/areas to cross with categories. See Bulk categories x cities. |
includeReviews | boolean | false | Fetch reviews for each place. See Reviews. |
maxReviewsPerPlace | integer | 10 (10-100) | Reviews to fetch per place when includeReviews is on. See Reviews. |
reviewsSort | string | "newest" | One of relevant, newest, highest_rating, lowest_rating. See Reviews. |
minRating | number | — | Keep only places with a rating >= this value (0-5). See Filters. |
minReviews | integer | — | Keep only places with at least this many reviews. See Filters. |
hasWebsite | boolean | — | true/false to require or exclude a website. See Filters. |
hasPhone | boolean | — | true/false to require or exclude a phone number. See Filters. |
openNow | boolean | — | true/false to require or exclude "currently open". See Filters. |
categoriesInclude | array of strings | — | Keep only places matching one of these categories (case-insensitive, exact match). See Filters. |
enrichContacts | boolean | false | Fetch emails and social links from each place's website. See Contact enrichment. |
Output
One real dataset item, from a live run against plumbers in Austin, TX on 8 Sep 2026 (see the measured-yield numbers in Contact enrichment below for the full run this item came from):
{"placeId": "ChIJpxUGhC_LRIYR2g941oH50ow","cid": "10147447246463438810","name": "Beyond Wow Plumbing & Drains","category": "Plumber","additionalCategories": ["Drainage service","Gas installation service","Hot water system supplier","Water filter supplier"],"address": "3432 Greystone Dr, Austin, TX 78731","addressParts": {"street": "3432 Greystone Dr","city": "Austin","zip": "78731","region": "Texas","countryCode": "US"},"phone": "+1512-601-6173","website": "https://beyondwow.com/?utm_campaign=gmb&utm_medium=gbp-austin&utm_source=google","domain": "beyondwow.com","rating": 4.9,"reviewsCount": 2579,"ratingDistribution": { "1": 58, "2": 17, "3": 25, "4": 50, "5": 2429 },"priceLevel": null,"latitude": 30.3578911,"longitude": -97.7482485,"openingHours": {"monday": [{ "open": "07:00", "close": "18:00" }],"tuesday": [{ "open": "07:00", "close": "18:00" }],"wednesday": [{ "open": "07:00", "close": "18:00" }],"thursday": [{ "open": "07:00", "close": "18:00" }],"friday": [{ "open": "07:00", "close": "18:00" }]},"currentStatus": "open","isClaimed": true,"mainImageUrl": "https://lh3.googleusercontent.com/gps-cs-s/AHRPTWmiDLztnnpPJfoaMPrurchultIC5ltxZTId2hco9G7k_JQt-bniJ6Ujl8eMC4IUEmE9JkRzjy28-LKlqRjzNYJ5-lfpiDCCuhaa7oNEBwjLc6YrBqSgiMoPu83s4KuzBnYkmd8=w408-h408-k-no","totalPhotos": 247,"googleMapsUrl": "https://www.google.com/maps?cid=10147447246463438810","bookOnlineUrl": "https://form.recreateai.com/?rai_pak=8b0f7a2c-e37c-4714-bdff-999dc19da556&branch_id=a5321498-4798-41c9-b352-0814630c5cfe&rwg_token=AE37R_h4RLz_FFoarFAgEf7DCB9ApToHo2LMDEU4WbzMvDUtjVTiP69iCQVdIZjuf5BRWfYElxHuzQJIspr7CGNboOSgSOia0Q%3D%3D","rank": 1,"query": "plumbers in Austin, TX","scrapedAt": "2026-09-08T17:08:09Z","reviews": null,"contacts": {"emails": ["info@beyondwow.com"],"socials": {"facebook": "https://www.facebook.com/beyondwowplumbing","instagram": "https://www.instagram.com/beyondwowplumbing","linkedin": "https://www.linkedin.com/company/beyondwowplumbing","twitter": null,"tiktok": "https://www.tiktok.com/@beyondwowplumbing","youtube": "https://www.youtube.com/@beyondwowplumbing","whatsapp": null},"phonesOnSite": []},"contactsStatus": "enriched"}
| Field | Type | Description |
|---|---|---|
placeId | string | Google's place identifier |
cid | string | Google's numeric customer/place ID, used to build googleMapsUrl |
name | string | Business name |
category | string | Primary category |
additionalCategories | array of strings | Secondary category tags |
address | string | Full formatted address |
addressParts | object | street, city, zip, region, countryCode |
phone | string | null | Phone number as shown on the listing |
website | string | null | Business website URL |
domain | string | null | Website domain, extracted from website |
rating | float | null | Average rating (0–5), null if the place has no ratings |
reviewsCount | integer | null | Total number of reviews |
ratingDistribution | object | null | Count of reviews per star, keys "1"–"5" |
priceLevel | string | null | One of inexpensive, moderate, expensive, very_expensive, or null when Google shows no price level |
latitude | float | Latitude |
longitude | float | Longitude |
openingHours | object | null | Per weekday list of {open, close} ranges ("HH:MM") |
currentStatus | string | null | e.g. open, closed, temporarily_closed |
isClaimed | boolean | Whether the owner has claimed the listing |
mainImageUrl | string | null | URL of the listing's main photo |
totalPhotos | integer | null | Total photo count on the listing |
googleMapsUrl | string | Direct Google Maps link for the place |
bookOnlineUrl | string | null | Online booking link, if available |
rank | integer | Position of this place in the results for its query |
query | string | The input query that returned this place |
scrapedAt | string | ISO 8601 UTC timestamp of when the place was scraped |
reviews | array of objects | null | null unless includeReviews is true, in which case it's always an array (possibly empty). See Reviews. |
Reviews
Set includeReviews: true to fetch reviews for each place, in addition to the place data above.
| Field | Type | Default | Description |
|---|---|---|---|
includeReviews | boolean | false | Turns reviews on. |
maxReviewsPerPlace | integer | 10 | Reviews to fetch per place (10-100; multiples of 10 recommended — Google's reviews API pages in tens). |
reviewsSort | string | "newest" | One of relevant, newest, highest_rating, lowest_rating. |
Each entry in the reviews array looks like:
{"reviewId": "ChdDSUhNMG9nS0VJQ0FnSURvd2VYSkR3EAE","author": "Jamie Rivera","authorUrl": "https://www.google.com/maps/contrib/000000000000000000001","isLocalGuide": true,"rating": 5,"text": "Great coffee and friendly staff, will come back.","originalText": "Great coffee and friendly staff, will come back.","publishedAt": "2026-08-24T09:15:00Z","ownerAnswer": "Thanks so much for the kind words, Jamie!","ownerAnswerAt": "2026-08-31T10:00:00Z","imagesCount": 1,"likes": null,"reviewUrl": "https://www.google.com/maps/reviews/data=example1"}
Notes:
likesis alwaysnull— Google's reviews data doesn't expose a like/thumbs-up count.- A place with no
cid, or whose reviews couldn't be fetched (upstream error or timeout), getsreviews: [], notnull— the place itself is still delivered and charged normally. - Reviews are billed with the
review-scrapedevent, once per review actually fetched, charged right after the place that contains them is pushed (see Pricing). If your max charge limit is reached while fetching reviews, this actor stops requesting reviews but keeps delivering places (charged onlyapify-default-dataset-item) until its own limit is reached. - Because each batch of review requests can take up to a minute upstream, a run with
includeReviewson is capped at 2,000 places total (maxResultsPerQuery x queries) — the run fails validation up front if your input would exceed that.
Filters
Six optional fields filter results after scraping and before truncating to maxResultsPerQuery or charging apify-default-dataset-item — you only pay for places that pass every filter you set.
| Field | Type | Behavior |
|---|---|---|
minRating | number (0-5) | Keep places with rating >= minRating. Places with no rating are excluded. |
minReviews | integer | Keep places with reviewsCount >= minReviews. Places with no review count are excluded. |
hasWebsite | boolean | true keeps only places with a website; false keeps only places without one. |
hasPhone | boolean | true keeps only places with a phone number; false keeps only places without one. |
openNow | boolean | true keeps only places with currentStatus == "open"; false keeps only the rest. |
categoriesInclude | array of strings | Keep places whose category or any additionalCategories entry case-insensitively matches one of these (exact match, not substring). |
STATS (placesFiltered in the run's key-value store) reports how many scraped places were dropped by filters.
Bulk categories x cities
Instead of (or in addition to) writing out queries by hand, fill both categories and cities to auto-generate every combination:
{"categories": ["dentist", "coffee shop"],"cities": ["Austin, TX", "Denver, CO"],"language": "en"}
generates, category-major: "dentist in Austin, TX", "dentist in Denver, CO", "coffee shop in Austin, TX", "coffee shop in Denver, CO". The template depends on language: "{category} in {city}" for en, "{category} en {city}" for es, and "{category} {city}" for anything else. Generated queries are concatenated with any explicit queries, then deduplicated together with them. A run is capped at 500 total queries — the input fails validation up front if categories x cities (plus any explicit queries) would exceed that.
Limits
- Google Maps listings themselves don't expose email addresses (review authors are identified by display name only). With
enrichContactson, the actor reads the business's own website to find emails and social profiles — see Contact enrichment. - Paid/ad results (
maps_paid_item) are skipped; only organic listings are returned. - Maximum 700 results per query — this is the data provider's ceiling; most queries have far fewer organic listings anyway.
- Results reflect Google Maps at the moment the query runs; Google's own index and ranking can change between runs.
includeReviewscaps a run at 2,000 places total (see Reviews).
FAQ
Is this legal? The actor retrieves publicly available business listing data through a licensed SERP data provider, the same category of data source used by SEO and local-search tools. You are responsible for how you use the extracted data under your local regulations (e.g. marketing/consent rules for phone numbers).
How is this different from other Google Maps scrapers on Apify? Most Google Maps actors drive a real or headless browser against Google Maps, which means proxy costs, occasional IP blocks/CAPTCHAs, and slower runs. This actor calls a licensed SERP data API directly: no proxies, no browser, no IP bans, and — because there's no proxy or browser compute overhead — a lower price per place.
How do I search by coordinates instead of a text query?
Set the coordinates input field to "lat,lng,zoom" (e.g. "30.2672,-97.7431,14"). When present, it overrides country and anchors the search to that map viewport, similar to using "Search this area" on Google Maps itself.
Why did I get fewer results than maxResultsPerQuery?
Google Maps itself caps how many organic results exist for a given query and area — if a query only has 12 relevant places, that's what you'll get regardless of the requested maximum. Narrowing the query (e.g. adding a neighborhood) or using coordinates with "search this area"-style bounding often surfaces more results than a broad city-wide text query.
Can I run this via API, on a schedule, or trigger it from a webhook? Yes. Any Apify actor can be started, monitored and read back through the Apify API, run on a recurring schedule from the Apify Console, and wired to webhooks on run events (started, succeeded, failed) from the same Console.
Contact enrichment
Set enrichContacts: true (default false) to have the actor visit each
result's website and pull out public contact details: email addresses,
social media links, and phone numbers found on the page.
For every place with a website, the actor fetches the home page and up to
two candidate pages likely to list contact details (/contact, /contacto,
/about, or a link on the home page whose text contains "contact"). Each
result item gets two new fields:
contacts—{ "emails": [...], "socials": { "facebook", "instagram", "linkedin", "twitter", "tiktok", "youtube", "whatsapp" }, "phonesOnSite": [...] }, ornullwhen nothing was fetched.contactsStatus— one of:
| Status | Meaning |
|---|---|
enriched | The site was fetched and contacts was extracted (may still be empty if the site has none of the above). |
no_website | The place had no website field, so no request was made. |
blocked | The site returned an anti-bot response (HTTP 403/503) or its robots.txt disallows the pages this actor requests. |
timeout | The site did not respond within 10 seconds (one retry is attempted, then it's reported as a timeout). |
error | Any other network or HTTP error (e.g. a 500 response, too many redirects). |
unsafe_url | The website value failed a safety check (not http(s), a bare IP address, or an address that resolves to a private/internal network) and was never requested. |
Measured yield. In one test run (plumbers in Austin, TX, 100 places,
8 Sep 2026): 98 places had a website and 2 had none. Of those 98, enrichment
found at least one email for 57 places and at least one social profile for
68; 4 sites blocked the fetch and 5 errored. 78 places were charged the
contact-enriched event (at least one email or social found). The whole
run — 100 places plus enrichment — took 81 seconds. Results vary by
industry and country; this is one data point, not a guarantee.
Pricing. The contact-enriched event is charged once per place, only
when at least one email address or social media link was found — never
merely for having attempted a fetch. Proposed price: $0.0015 per
enriched place, same across all plans.
Limits, by design:
- No JavaScript is executed — sites that render their contact details client-side (JS-only) won't be enriched even if a human visitor could see them.
- Sites behind anti-bot protection return
contactsStatus: "blocked"rather than being retried aggressively or routed through proxies. This actor never uses proxies and never retries more than once. robots.txtis respected: if a site disallows the pages this actor would request, they are skipped.- Each page fetch is capped at 1 MB and 10 seconds; requests use an
identified User-Agent (
GoogleMapsPlacesScraper/0.2).
API mode (Standby)
Standby is enabled on this actor as of September 2026; its base URL is
https://moyadata--google-maps-places-scraper.apify.actor.
This actor can also run as a synchronous HTTP API instead of a queued run, using Apify's Standby mode. Standby must be turned on for this actor in Apify Console (Settings -> Standby) by the developer publishing it — it is off by default and cannot be enabled from the input.
Once Standby is enabled, the actor's Standby URL (shown on the actor's Console page) accepts:
GET /search?query=...&country=...&language=...&coordinates=...&maxResults=...POST /searchwith a JSON body using the same fields as the actor input (only places are returned in Standby mode — no reviews, no contact enrichment).
Both return 200 with { "places": [...], "stats": {...} } on success, a
400 with { "error": "..." } on invalid input, and a 503 with
{ "error": "..." } if the upstream data provider is unavailable (balance
exhausted, circuit breaker open).
Example:
curl "https://moyadata--google-maps-places-scraper.apify.actor/search?query=coffee+shops+in+Austin,+TX&country=US&maxResults=20" \-H "Authorization: Bearer $APIFY_TOKEN"
curl -X POST "https://moyadata--google-maps-places-scraper.apify.actor/search" \-H "Authorization: Bearer $APIFY_TOKEN" \-H "Content-Type: application/json" \-d '{"queries": ["coffee shops in Austin, TX"], "country": "US", "maxResultsPerQuery": 20}'
Pricing. Standby requests are charged the same apify-default-dataset-item
event ("Place scraped" in Console) as a normal run, per place returned in the
response — there is no separate "per request" charge and no dataset is
written (there's nothing to push, so the event is charged directly).
apify-actor-start is billed once by the platform when the Standby run
starts, not per individual request.