Google Maps Places & Reviews Scraper avatar

Google Maps Places & Reviews Scraper

Pricing

from $3.00 / 1,000 results

Go to Apify Store
Google Maps Places & Reviews Scraper

Google Maps Places & Reviews Scraper

Extract Google Maps business listings and their individual reviews in one run — address, phone, website, categories, price band, opening hours, popular times, rating breakdown, photos and reviews with owner replies, all in the same record. Search many terms and cities at once.

Pricing

from $3.00 / 1,000 results

Rating

0.0

(0)

Developer

ITclan BD

ITclan BD

Maintained by Community

Actor stats

0

Bookmarked

7

Total users

4

Monthly active users

9 days ago

Last modified

Categories

Share

Scrape business listings and their reviews from Google Maps. Say what to look for and where — search terms with a city, an exact point and radius, Google Maps URLs, or place IDs — and every business comes back as one dataset record: name, address, phone, website, categories, rating, price band, coordinates, opening hours, popular times, photos, the star breakdown, and its reviews with owner replies nested inside the same record.

{
"searchTerms": ["restaurant"],
"locationQuery": ["Austin, Texas"],
"maxPlacesPerSearch": 10,
"maxReviewsPerPlace": 10
}

This is an unofficial tool and is not affiliated with Google. See Is this Actor affiliated with Google?

Use it for lead lists, competitor and market research, review monitoring on a schedule, and location enrichment. Running it on Apify adds scheduling, a REST API, JSON, CSV and Excel exports, and integrations.

Why use this Google Maps scraper

  • Places and reviews in one run. Opening hours, popular times, the rating breakdown, photos and reviews arrive in the same record — there is no separate detail step to switch on.
  • Four ways to target what you want — a location name, an exact circle on the map, Google Maps URLs, or place IDs — combinable in a single run.
  • A stable record shape. There are 44 fields on every place and 22 fields on every review; a value Google does not publish comes back as null or [] rather than as a missing column, so CSV headers never shift.
  • Provenance on every row. searchTerm, searchLocation and scrapedAt say which input produced each record, and when.
  • No headless browser. Plain HTTP requests at Apify's 512 MB memory floor, which keeps compute usage low.

Quick start

  1. In Search terms, type what to look for — restaurant, dentist. One per line.
  2. In Locations, type where — Austin, Texas. Every term runs in every location.
  3. Set Max places per search term and Max reviews per place (0 collects business details only, and is much faster).
  4. Click Start. Results appear on the Output tab as they arrive.

The example at the top of this page returns 10 Austin restaurants with 10 reviews each. Terms and locations cross-multiply, so on a bigger job set maxPlacesTotal to bound the whole run: three terms in two cities at 100 each can otherwise reach 600 places.

Ways to target what you scrape

All four can be combined in one run; results are merged and deduplicated on Google's own place identifiers.

Search terms with a location name

searchTerms + locationQuery. The location is a human-readable name; the search is anchored there and widens outward. Both fields are lists, and every term runs in every location.

{
"searchTerms": ["dentist", "orthodontist"],
"locationQuery": ["Portland, Oregon", "Boise, Idaho"],
"maxPlacesPerSearch": 50,
"maxPlacesTotal": 100
}

An exact area: coordinates and a radius

latitude + longitude + radiusKm, for when "near Dhaka" is not precise enough — a delivery zone, a catchment area, a sales territory. The radius is a real constraint, not a hint: every result is measured against your point by geographic distance and dropped if it falls outside, the check is repeated against the coordinates that finally ship, and no requests are spent searching ground outside the circle.

{
"searchTerms": ["restaurant"],
"latitude": 23.8103,
"longitude": 90.4125,
"radiusKm": 5
}

A radius needs both coordinates. Next to a location name it is ignored with a warning, because a name has no single point to measure from.

Exact businesses by place ID

placeIds. No search step runs, so nothing else comes back alongside them. These are the same IDs Google's own Places API returns, and the placeId of every record this Actor produces, so a previous run's output feeds straight back in.

{
"placeIds": ["ChIJfbedxbZZwokRpbnR5BBdz1c", "ChIJQXXzJoNbwokRZirX7jSW1i0"],
"maxReviewsPerPlace": 20
}

Unrecognised IDs are skipped with a warning rather than failing the run, and a place named twice — by ID and by URL — is still scraped once.

Google Maps URLs

startUrls accepts Google Maps links, pasted or uploaded as a file. A /maps/search/… link runs that search at the viewport it carries; a /maps/place/… link is fetched as that business, so a pinned link never returns a nearby branch; a place_id:ChIJ… entry — including the /maps/place/?q=place_id:… form this Actor emits as mapsUrl — names one exact place.

{
"startUrls": [
{ "url": "https://www.google.com/maps/place/?q=place_id:ChIJQXXzJoNbwokRZirX7jSW1i0" }
],
"maxReviewsPerPlace": 50
}

This box takes Google Maps URLs only — it is not a general web crawler, and any other link is skipped with a warning. ?cid= share links are rejected with a message saying what to paste instead: a cid carries only half of Google's identifier and cannot be resolved back to a place.

Input

Configure this on the Input tab, or pass JSON through the API.

FieldTypeDefaultWhat it does
searchTermsarrayWhat to look for, one term per line. Every term runs in every location; results are merged and deduplicated, so overlapping terms are safe
locationQueryarrayWhere to search. A place name such as Berlin, Germany, or a point written as 40.758, -73.9855. Names and points can be mixed
latitudenumberSearch around an exact point. Needs longitude too; one on its own is ignored with a warning
longitudenumberThe other half of the point, in decimal degrees
radiusKmnumberHard geographic bound on latitude/longitude: nothing further away is returned, and ground outside it is not searched. Maximum 500
startUrlsarrayGoogle Maps URLs. /maps/search/… runs a search, /maps/place/… scrapes that exact business, place_id:ChIJ… names an exact place
placeIdsarrayExact businesses by Google place ID, one per line. No search step runs
maxPlacesPerSearchinteger20How many places to collect per search term. Also decides how far the search widens, so a lower number searches a smaller area
maxPlacesTotalinteger0Ceiling for the whole run across every term and location. 0 means no limit. Counts unique places, applied before any details are fetched, and shared evenly across your searches so every city is represented
maxReviewsPerPlaceinteger0Reviews per place. 0 issues no review requests at all. Maximum 500
reviewsSortstringmostRelevantmostRelevant is Google's own ordering; newest returns the most recently added or edited reviews first. A reviewsStartDate overrides this
reviewsStartDatestringOnly reviews added or edited on or after this date. Accepts 2026-08-01, 2026-08-01T09:30:00Z, or a span such as 7 days or 25 hours
maxImagesPerPlaceinteger50Photo URLs kept per place. 0 omits them. Maximum 100
languagestringenLanguage for names, categories, addresses and hours — 74 languages. Google localises this server-side, so it changes the data, not just the labels

A run needs at least one complete instruction: a Google Maps URL, a place ID, or a search term paired with a location. Anything half-specified is skipped with a warning, so search terms with no location still let your URLs and place IDs run. The run fails only when nothing at all is runnable.

Output

One dataset item per place, with its reviews nested inside. Export as JSON, CSV, Excel, HTML or XML from the Output tab or the API.

{
"name": "ARVACI Coffee",
"placeId": "ChIJQXXzJoNbwokRZirX7jSW1i0",
"featureId": "0x89c25b8326f37541:0x2dd69634eed72a66",
"address": "167 Rogers Ave, Brooklyn, NY 11216, United States",
"phone": "+1 518-709-4680",
"phoneUnformatted": "+15187094680",
"website": null,
"isClaimed": true,
"rating": 4.7,
"reviewCount": 384,
"reviewsDistribution": { "oneStar": 14, "twoStar": 4, "threeStar": 16, "fourStar": 26, "fiveStar": 324 },
"categories": ["Coffee shop"],
"priceRange": "$1–20",
"coordinates": { "lat": 40.6716473, "lng": -73.9529333 },
"openingHours": { "monday": "7 AM–5 PM" },
"openingHoursPeriods": { "monday": [{ "open": "07:00", "close": "17:00" }] },
"popularTimes": { "monday": [{ "hour": 7, "occupancyPercent": 38 }] },
"additionalInfo": { "Service options": ["Delivery", "Takeaway", "Dine-in"] },
"mapsUrl": "https://www.google.com/maps/place/?q=place_id:ChIJQXXzJoNbwokRZirX7jSW1i0",
"searchTerm": "coffee shop",
"searchLocation": "Brooklyn, New York",
"scrapedAt": "2026-09-02T06:28:37Z",
"responseTruncated": false,
"reviewsExtractedCount": 1,
"reviews": [
{
"reviewId": "Ci9DQUlRQUNvZENodHljRjlvT2xKYWVGVjZlV00zTUZkSE9FeGxlRUZyUnpSVWFWRRAB",
"author": "Kara Lechonsito",
"authorIsLocalGuide": true,
"rating": 5.0,
"publishedAtDate": "2026-04-23T04:44:10Z",
"lastEditedAtDate": "2026-04-23T04:44:10Z",
"text": "I did not expect much when I ordered my coffee, but the first sip caught my attention.",
"textTranslated": null,
"reviewDetailedRating": { "Food": 5.0, "Service": 5.0, "Atmosphere": 5.0 },
"responseFromOwnerText": "That is exactly what I feel, thank you so much.",
"images": []
}
]
}

Place fields

GroupFields
Identityname, address, street, phone, phoneUnformatted (E.164), website, description, editorialSummary
Ratingsrating, reviewCount, reviewsDistribution (reviews per star, summing to reviewCount)
Classificationcategories, categoryId (Google's own key, identical in every language), priceRange, isClaimed
Locationcoordinates, plusCode, plusCodeGlobal, timezone
Hours and trafficopeningHours, openingHoursPeriods, popularTimes, timeSpent
Rich detailadditionalInfo (amenities as Google's About tab groups them), imageCategories, images, imagesCount, fuelPrices, evCharging, hotel
IdentifiersplaceId, featureId, cid, kgmid, mapsUrl
Reviewsreviews, reviewsExtractedCount, reviewsCappedByLimit, reviewsStartDateApplied
Run metadatasearchTerm, searchLocation, scrapedAt, responseTruncated, reviewsError

Which optional fields are filled depends on the business, not on the run. Names, addresses, categories, coordinates and ratings are effectively always present. priceRange is largely food and drink, popularTimes needs enough foot traffic for Google to model it, and fuelPrices, hotel, evCharging and editorialSummary apply to a minority of listings. An absent value is null, not an error.

Three fields deserve a note:

  • isClaimed is three-state: true when Google shows the listing has an owner, false when Google is offering it to be claimed, null when neither signal was present or both were. null means "not known", never "not claimed" — it is never guessed from absence. Owner identifiers are not published.
  • images holds photos from the place's own Google gallery only. Reviewer avatars, review photos and other businesses' photos are excluded — a review's photos sit on that review, the reviewer's avatar in authorPhotoUrl. imagesCount is the gallery size Google reports, usually far larger than the length of images, which maxImagesPerPlace caps.
  • openingHoursPeriods is the machine-readable form of openingHours: 24-hour time, independent of language. A close at or before the open means the interval runs into the next day.
The businessopeningHoursopeningHoursPeriods
Open 9 to 5"9 AM–5 PM"[{"open": "09:00", "close": "17:00"}]
Bar, noon until 2 AM"12 PM–2 AM"[{"open": "12:00", "close": "02:00"}]
Open 24 hours"Open 24 hours"[{"open": "00:00", "close": "00:00"}]
Closed that day"Closed"[]

An empty list means no interval starts that day — a venue open across midnight may already be covered by the previous day's.

Review fields

GroupFields
The reviewreviewId, reviewUrl, rating, text, textTranslated, images
DatespublishedAtDate, lastEditedAtDate (both ISO-8601 UTC), timeAgo
Reviewerauthor, authorId, authorProfileUrl, authorPhotoUrl, authorReviewCount, authorIsLocalGuide
Owner replyresponseFromOwnerText, responseFromOwnerDate, responseFromOwnerEditedDate
Structured extrasreviewDetailedRating (per-aspect scores), reviewContext (guided visit answers), originalLanguage, translatedLanguage

text is the review as its author wrote it, in the original language whatever language the run asked for. When Google machine-translated it for display, that translation is in textTranslated, with the two languages named in originalLanguage and translatedLanguage. The translation is never appended to text. Where the review is already in the run's language, textTranslated is null.

Three views on the same data

ViewOne row perUse it for
Overviewplacethe fields most runs are about
Reviews (one per row)reviewreview analysis, sentiment, CSV and Excel exports
All fieldsplaceeverything, including identifiers and diagnostics

Pick one on the Output tab, or add &view=reviews to the dataset URL:

https://api.apify.com/v2/datasets/{DATASET_ID}/items?clean=true&format=csv&view=reviews

That flattens the nested reviews array so each review is its own row with its business repeated alongside. In that view rating and images belong to the review; the business total stays in reviewCount.

If you also narrow the columns with &fields=, that list must include reviews. Apify applies fields before the unwind, so leaving it out silently returns one row per business — no error, and the symptom is a row with no reviewId. The same applies to &unwind=reviews.

# wrong - ...items?view=reviews&fields=name,reviewId,author,rating
# right - ...items?view=reviews&fields=name,reviews,reviewId,author,rating

Reviews

Set maxReviewsPerPlace above 0 and reviews arrive inside the same record as the business. reviewsSort picks the order: mostRelevant is what the place's own Google Maps page shows, newest puts the most recently added or edited first.

Monitoring new and edited reviews

Set reviewsStartDate and each place returns only the reviews added or edited since then, newest first. The Actor stops as soon as it reaches older reviews rather than paging the whole list, which is what makes a daily or weekly monitoring run cheap.

{
"placeIds": ["ChIJfbedxbZZwokRpbnR5BBdz1c"],
"maxReviewsPerPlace": 100,
"reviewsStartDate": "25 hours"
}

Three things to know about the date filter:

  • A start date always sorts newest-first, overriding reviewsSort: stopping early depends on the list being ordered by last-edited time, and no other ordering provides that. The log says so when it happens.
  • The comparison uses lastEditedAtDate, not publishedAtDate — that is the order Google returns reviews in, and the only field a run can safely stop on. A review written years ago but edited yesterday is returned, which is usually what you want. Both dates are on every review, so filter on publishedAtDate in your export if you only want newly written ones.
  • The date decides which reviews qualify; maxReviewsPerPlace decides how many come back. The cap wins: when more qualify than it allows you get the newest, flagged reviewsCappedByLimit: true. Each row also carries reviewsStartDateApplied, the UTC instant a relative span resolved to.

On a daily schedule, 25 hours rather than 24 overlaps the previous run so a review arriving mid-run is not missed; deduplicate on reviewId and the overlap costs nothing.

Limits and known behaviour

  • A single search point returns roughly 170 results. That is Google's behaviour, not a setting. Ask for more and the Actor widens outward across neighbouring areas, stopping after 60 search centres per term or once it stops finding anything new. Extra search terms usually find more businesses over the same ground than a bigger number does.
  • How much ground one point covers depends on how dense the area is. One restaurant query reached 0.52 km in Manhattan, 2.10 km in Berlin Mitte, 3.30 km in suburban Plano, 18.05 km on Maui and 101.42 km in rural Bangladesh. Very large regions are better split by city or category than asked for in one run.
  • Results stay near where you asked. When an area has no matches, Google can answer with businesses from an unrelated region rather than an empty page. Results further than 500 km from the requested point are discarded, so an empty area returns nothing instead of somewhere else's businesses. With radiusKm set, your radius applies instead.
  • Very large runs can lose three detail fields. An incomplete response is retried, and if it is still incomplete the row is flagged responseTruncated: true rather than dropped. Such a row keeps name, address, coordinates, identifiers, categories, rating, phone, website and hours, and loses only description, priceRange and popularTimes. It is a property of the run, not of the place — the same places re-fetched alone come back complete. Runs of a few hundred places flag nothing; it appears on runs of many hundreds, so split those by city or category if these three fields matter.
  • Up to 500 reviews per place per run. Google also stops issuing review pages for some places before their stated reviewCount is reached, so a place can legitimately return fewer reviews than it claims to have.
  • Up to 100 photos per place, 50 by default. Most places carry far fewer, so a short list is normal rather than a problem.
  • Optional fields are absent for some places, depending on category, country and what Google publishes. Missing values stay null; nothing is inferred from another source.
  • No email enrichment. It returns the website a business lists, but it does not visit that website and does not extract email addresses.
  • There is no proxy setting. Google ties a session to the IP that obtained its cookies, so a rotating exit IP causes silently incomplete responses. Every run uses the configuration measured to return the most complete data.
  • Runtime is not deterministic. Google paces and varies its responses, so two identical runs can differ noticeably in how long they take.

A place that fails does not fail the run: the row arrives flagged with responseTruncated, reviewsError or reviewsCappedByLimit, and a completeness summary is written to the run log.

Tips

  • For review work use the Reviews view (&view=reviews) — exporting the place view to CSV puts the whole review array into one cell.
  • To find unanswered reviews, export that view and filter on an empty responseFromOwnerText.
  • For recurring monitoring, discover the businesses once, keep their placeId values, then re-run against those IDs with reviewsStartDate set slightly longer than the schedule interval.
  • Set maxReviewsPerPlace: 0 when you only need business details — the single largest reduction in work a run can make.
  • Set maxImagesPerPlace: 0 if you do not need photos. They cost no extra requests either way, but they are a large share of a listings-only record, so dropping them makes exports much smaller.
  • Split a multi-city run back into per-city lists using searchTerm and searchLocation.

FAQ

Can I scrape several cities in one run?

Yes. Search terms and locations are both lists, every term runs in every location, and results are deduplicated on Google's place identifiers. Use maxPlacesTotal to bound the total, and see Output for what each row contains.

Can I scrape one specific business, or re-scrape a known list?

Yes. Paste a /maps/place/… URL into Google Maps URLs, or the placeId into Place IDs — both route to that exact business, never a nearby branch. Since placeId and mapsUrl are on every record, a previous run's output feeds straight back in.

Can I scrape a whole city or region?

Within limits — see Limits and known behaviour. Adding search terms is usually more effective than raising the number, and very large areas are best split into several runs.

Why did an old review appear in a recent-review run?

Because it was edited inside your window. The filter compares lastEditedAtDate, which is the order Google returns reviews in. Filter on publishedAtDate in your export if you only want newly written ones.

Why do some places return fewer reviews than their review count?

Google stops issuing review pages for some places. That is Google's behaviour rather than a limit of this Actor; reviewsExtractedCount always says how many were collected.

Does it collect email addresses, and do I need a proxy?

No to both. It returns what Google publishes, including the business website when one is listed, but it does not visit that website. There is no proxy option to configure.

Is this Actor affiliated with Google?

No. This is an unofficial tool. It is not affiliated with, sponsored by, endorsed by or connected to Google LLC or Google Maps in any way, and it is not built on the Google Maps Platform or any Google API. "Google" and "Google Maps" are marks of Google LLC, used here only to describe where the data comes from.

Data source and personal data

All data comes from Google Maps' own public listings, in the language you select. Nothing is inferred or enriched from another source: where Google publishes no value, the field is null or empty.

Support

Found a problem, or need something this Actor does not do? Open an issue on the Issues tab of this Actor's Apify page.