Google Maps Scraper - $0.27/1k, Places & Reviews avatar

Google Maps Scraper - $0.27/1k, Places & Reviews

Pricing

from $0.27 / 1,000 place returneds

Go to Apify Store
Google Maps Scraper - $0.27/1k, Places & Reviews

Google Maps Scraper - $0.27/1k, Places & Reviews

The coordinates and IDs of a place sit inside its result link, before you open anything, and a real browser scrolls the feed reading them off, with name, category, address and rating. Phone, website and review count appear only when Google shows them. $0.27 per 1,000 rows.

Pricing

from $0.27 / 1,000 place returneds

Rating

5.0

(2)

Developer

Dami's Studio

Dami's Studio

Maintained by Community

Actor stats

1

Bookmarked

11

Total users

1

Monthly active users

2 days ago

Last modified

Share

Google Maps Scraper

A Google Maps result link tells you more than it looks like it does. The place's latitude, longitude and its two identifiers are all sitting in the /maps/place/... href, so reading the results feed is most of the job.

Two things in one actor. Search queries collect places off that feed. Place URLs open each place and pull the public reviews. It drives a real browser, because Maps doesn't hand this over any other way.

$0.27 per 1,000 rows ($0.00027 each), plus $0.001 when a run starts. Flat on every plan, from your first run.

What a place row actually contains

Here's a real one, unedited:

{
"ok": true,
"type": "place",
"searchQuery": "coffee in Brooklyn",
"name": "Qahwah House Coffee - Williamsburg Brooklyn",
"category": "Coffee shop",
"address": "162 Bedford Ave",
"rating": 4.7,
"reviewCount": null,
"phone": null,
"website": null,
"lat": 40.7185755,
"lng": -73.9571869,
"placeId": "0x89c25953721fc19d:0x185b29b3e7559d6e",
"cid": "1755042332447972718",
"placeUrl": "https://www.google.com/maps/place/Qahwah+House+Coffee…"
}

Look at the nulls in that row. They're normal, not a bad sample.

Reliable on search results: name, category, address, rating, lat, lng, placeId, cid, placeUrl.

Usually null: phone, website, reviewCount. Google only prints these on the results card for some businesses, and a search reads the card rather than opening every place. Counted over 342 place rows from recent runs: phone was null on 99% of them, website on 84%, reviewCount on 43%.

Re-counted 2026-08-29 over 127 place rows, and website is scarcer than that older figure suggests: phone was present on 2 rows and website on 2 — both about 1.6%, so website is null on ~98%, not 84%. Treat this as name, address, rating and coordinates data. If you need contact details, the second pass below is not optional.

So if phone numbers are the point of your project, a plain search will not give you them. Feed the placeUrl values into a second pass, or set includeReviews so every place gets opened.

placeId and cid are always there, which is what you want if you're joining this against anything else.

Reviews

Pass place URLs instead of, or as well as, search queries:

{
"reviewUrls": ["https://www.google.com/maps/place/Example+Business/@40.7,-74,17z/data=..."],
"maxReviewsPerPlace": 100,
"includeOwnerReplies": true
}

It opens the rendered review pane, scrolls until it hits your count or the end of the list, and writes one row per unique review with type: "review", placeId, placeName, placeUrl, reviewId, reviewerName, rating, relativeDate, text, ownerReply and photoUrls. Deduplication uses Google's own data-review-id where it exists and falls back to reviewer + date + text.

Set includeReviews: true alongside searchQueries to discover places and pull their reviews in one run. That opens every place, so it's a lot more browser time than a plain place search.

Dates are Google's relative strings ("3 months ago"), not timestamps. Google doesn't expose the exact date in that pane.

Input

FieldNotes
searchQueriesOne or more searches with the location in the text, like coffee in Brooklyn.
searchQuerySingle-search alternative if you'd rather use one box.
maxItemsPlaces per search. Default 60, max 500.
reviewUrlsPlace URLs to pull reviews from. placeUrls is an accepted alias.
maxReviewsPerPlaceDefault 50, max 500.
includeReviewsAlso open every place found by a search. Default false.
includeOwnerRepliesInclude the owner's visible reply. Default true.
languageGoogle language code for the Maps interface, en by default. Any code Google accepts works, not just the common few.
countryCodeTwo-letter country to exit from. Leave empty for the actor's US pool, which is the most reliable lane.

Billing

EventPrice
Actor start$0.001 per run
Row returned$0.00027

A review row costs the same $0.00027 as a place row. They're the same billing event, so a run that pulls 100 reviews from one place is billed for 101 rows, not 1. Worth knowing before you set maxReviewsPerPlace to 500 across a long list of URLs.

Nothing is charged for blocked runs, bad input, or a place that yields no reviews — those write an ok: false diagnostic row instead. An entirely empty input emits one _sample row without even launching a browser.

Proxy, and where this breaks

The actor brings its own egress: static addresses Google tolerates, with rotating datacenter behind them. When it sees a captcha it rebuilds the browser and moves to the next address. Metered Apify groups (RESIDENTIAL, GOOGLE_SERP) aren't offered and get swapped for datacenter if requested, so a run can't quietly bill you per gigabyte. Your own proxy servers are used exactly as given.

Review extraction depends on Google's rendered pane, so it can come back BLOCKED when Google challenges the browser. No review is charged when that happens. This reads rendered markup, so Google changing its layout will need selector updates from time to time.

If you want the rows in Notion rather than a dataset, authorize a Notion MCP connector once under Settings → API & Integrations, then set notionConnector and notionParentId and the run writes them there when it finishes.