# Google Maps Reviews Scraper – Ratings, Text, Owner Replies (`moyadata/google-maps-reviews-scraper`) Actor

Extract Google Maps reviews by place ID, cid, or Maps URL, or by search query: rating, review text (original + translated), author, owner replies and photos.

- **URL**: https://apify.com/moyadata/google-maps-reviews-scraper.md
- **Developed by:** [SERP King](https://apify.com/moyadata) (community)
- **Categories:** Lead generation, Automation, Travel
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 review scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Google Maps Reviews Scraper – Ratings, Review Text, Owner Replies

Extract Google Maps reviews for any business: rating, review text (original and translated), author, whether they're a Local Guide, owner replies, and review photos. Give it a place ID, a `cid`, or a Google Maps URL — or just a search query — and it calls a licensed data provider directly. **No proxies, no headless browser, no IP bans, no CAPTCHAs.**

### What data you get

One dataset row per review:

- `placeId`, `cid` — Google's identifiers for the place the review belongs to
- `placeName`, `placeAddress`, `placeRating`, `placeReviewsCount` — the place's own current stats, attached to every one of its reviews
- `reviewId`, `reviewUrl`
- `author`, `authorUrl`, `isLocalGuide`, `authorReviewsCount`
- `rating` — the reviewer's star rating (0-5) for this review
- `text`, `originalText`, `originalLanguage` — review text in the requested `language`, the untranslated original, and its detected language
- `publishedAt` (ISO 8601 UTC)
- `ownerAnswer`, `ownerAnswerAt` — the business's reply, if any
- `imagesCount`, `imageUrls` — photos attached to the review
- `query` — the search query that surfaced this place, `null` for a place passed directly via `placeIds`

### Why this scraper

- **Fast** — reviews come from a real-time, expedited data pipeline, typically under a minute per batch of places, not minutes of browser automation per place.
- **Cheap** — pay only for reviews actually returned, a flat **$0.0005 per review** regardless of your Apify plan (see Pricing).
- **Up to 4,000 reviews per place** — far past what Google's own UI shows without heavy scrolling.
- **Three ways in** — a `place_id`, a numeric `cid`, or a Google Maps URL (both the `?cid=` and the `!1s0x...:0x...` link formats work); or skip IDs entirely and search by `queries` like the Maps actor does.
- **Original + translated text** — both `text` (in your requested `language`) and `originalText`/`originalLanguage` are returned, so you can tell a translation from a native-language review.

### Pricing

This actor uses Apify's **pay-per-event** pricing, flat across all plans:

| 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 that finds zero reviews costs almost nothing | $0.00005 |
| `apify-default-dataset-item` ("Review scraped" in Console) | Charged once per review written to the output dataset | $0.0005 |

**Worked example:** scraping 1,000 reviews costs `1,000 × $0.0005 + $0.00005 ≈ $0.50`.

### Input

Two ways to tell the actor which places to fetch reviews for — fill one, or both:

**By ID:**

```json
{
  "placeIds": ["ChIJrTLr-GyuEmsRBfy61i59si0"],
  "maxReviewsPerPlace": 100
}
```

**By search query** (used as the prefill; searches Google Maps, takes the top `maxPlacesPerQuery` results):

```json
{
  "queries": ["coffee shops in Austin, TX"],
  "country": "US",
  "maxPlacesPerQuery": 2,
  "maxReviewsPerPlace": 20
}
```

| Field | Type | Default | Description |
|---|---|---|---|
| `placeIds` | array of strings | — | Places to fetch reviews for. Accepts a `place_id` (e.g. `"ChIJrTLr-GyuEmsRBfy61i59si0"`), a numeric `cid` (e.g. `"9542840243817508540"`), or a Google Maps URL containing a `?cid=` query parameter or a `!1s0x...:0x...` feature id. Optional if `queries` is set. |
| `queries` | array of strings | `["coffee shops in Austin, TX"]` | Google Maps search queries; each takes its first `maxPlacesPerQuery` results. Optional if `placeIds` is set. |
| `country` | string (ISO-2) | `"US"` | Used to resolve the location for every review request, and (unless `coordinates` is set) to anchor `queries`. |
| `coordinates` | string | — | Query mode only: anchor `queries` to a map viewport instead of `country`, `"lat,lng,zoom"` (e.g. `"30.2672,-97.7431,14"`). |
| `maxPlacesPerQuery` | integer | `10` (1-100) | How many places to take from each query's search results. |
| `language` | string | `"en"` | Language code for review text and query results. |
| `maxReviewsPerPlace` | integer | `100` (10-4,000) | Reviews to fetch per place. Must be a multiple of 10 — Google's reviews API pages in tens. |
| `reviewsSort` | string | `"newest"` | One of `newest`, `relevant`, `highest_rating`, `lowest_rating`. |
| `reviewsNewerThan` | string (ISO-8601) | — | Keep only reviews published on or after this date/datetime (inclusive), e.g. `"2026-09-01"` or `"2026-09-01T00:00:00Z"`. Applied after fetching, before writing to the dataset — see below. |
| `reviewsOlderThan` | string (ISO-8601) | — | Keep only reviews published on or before this date/datetime (inclusive), e.g. `"2026-09-08"`. Must be on or after `reviewsNewerThan`. |

DataForSEO's reviews API has no date filter of its own, so `reviewsNewerThan`/`reviewsOlderThan` are applied by this actor after fetching up to `maxReviewsPerPlace` reviews and before writing rows to the dataset — only rows inside the window get pushed, and **only pushed rows are charged**, so an out-of-window review costs nothing. The run's `STATS` record (`reviewsFilteredByDate`) reports how many were dropped this way.

A run is capped at **100 places total** (`placeIds` count + `maxPlacesPerQuery x queries`) and **4,000 reviews per place** — so the input fails validation up front, with a message telling you exactly how far over you are, rather than letting a typo turn into a runaway bill. Need more than 100 locations? See [Monitoring at scale](#monitoring-at-scale) below.

### Output

One real dataset row, from an actual run against a public Google Maps place on September 8, 2026:

```json
{
  "placeId": null,
  "cid": "9920705898319708454",
  "placeName": "Cacao Much - Chocolate Café",
  "placeAddress": "Av. Providencia 2400, 7510034 Providencia, Región Metropolitana, Chile",
  "placeRating": 4.5,
  "placeReviewsCount": 3261,
  "reviewId": "Ci9DQUlRQUNvZENodHljRjlvT25WemVXa3pNblpoY1ZOblFsY3llV1ZwTTBZeFVVRRAB",
  "author": "Nahime Mostafá",
  "authorUrl": "https://www.google.com/maps/contrib/102272400702552550088/reviews?hl=es-419",
  "isLocalGuide": true,
  "authorReviewsCount": 54,
  "rating": 5,
  "text": "Personalmente me encantó que ofrezcan el chocolate caliente en su versión espesa y ligera. Opté por el ligero y es ideal para acompañar con los churros.",
  "originalText": null,
  "originalLanguage": "es",
  "publishedAt": "2026-09-08T00:57:25Z",
  "ownerAnswer": "Gracias por tu increíble reseña! Te esperamos nuevamente para más cositas ricas ",
  "ownerAnswerAt": "2026-09-08T02:06:20Z",
  "imagesCount": 3,
  "imageUrls": [
    "https://lh3.googleusercontent.com/grass-cs/ACvplmPyV1Y2boQQd_IiPxCNzcRRj_H4xKqiC5FsyY228kXEfOEz-E9eopWRITHuzN7ZD-t7bTdxXZdq3YBuF2PWFTxZwZ3_xlGOTCZZhaqdCmpWJl9two6UgsXy71WDz0DsIUnk5WuTarNqA_ed=k-no",
    "https://lh3.googleusercontent.com/grass-cs/ACvplmNGHnWahaniFQAoBtOvACPQj15OYxrIRgspNJlecjwXl-ijWm-XxdImmQlX8zlRNEPJ8CCak9Hwzs5VGoHHKayLicxBUMRcf2xWsfCt9D-COS0QGBBl6u3HF8ExN_ui7eQOfMrzKKBqSv1y=k-no",
    "https://lh3.googleusercontent.com/grass-cs/ACvplmN3ATMOr1PfjQM6qaoZuObWplPfurFHqdz7Un2CU3r-7Zg9iQ05YBL10h6-eoDNaHLMiNSTfgMfgN2j1I5tYfWRansMGS9mqcG8ofAPYD-4EMmmwW2zKaYoMybvrGADU0mMReUHk_9RXNAn=k-no"
  ],
  "reviewUrl": "https://www.google.com/maps/reviews/data=!4m8!14m7!1m6!2m5!1sCi9DQUlRQUNvZENodHljRjlvT25WemVXa3pNblpoY1ZOblFsY3llV1ZwTTBZeFVVRRAB!2m1!1s0x0:0x89ad6d73ce780126!3m1!1s2@1:CAIQACodChtycF9oOnVzeWkzMnZhcVNnQlcyeWVpM0YxUUE%7C%7C?hl=es-419",
  "query": "cafeterías en Providencia, Santiago"
}
```

| Field | Type | Description |
|---|---|---|
| `placeId` | string | null | Google's place identifier |
| `cid` | string | null | Google's numeric identifier for the place |
| `placeName` | string | null | Business name |
| `placeAddress` | string | null | The place's address, as shown alongside its reviews |
| `placeRating` | float | null | The place's current overall rating (0–5) |
| `placeReviewsCount` | integer | null | The place's current total review count |
| `reviewId` | string | null | Google's identifier for this specific review |
| `author` | string | null | Reviewer's display name |
| `authorUrl` | string | null | Link to the reviewer's Google Maps profile |
| `isLocalGuide` | boolean | null | Whether the reviewer has Local Guide status |
| `authorReviewsCount` | integer | null | Total reviews the author has written (not just for this place) |
| `rating` | float | null | This review's star rating (0–5) |
| `text` | string | null | Review text, translated to `language` if needed |
| `originalText` | string | null | Untranslated review text |
| `originalLanguage` | string | null | Detected language of `originalText` |
| `publishedAt` | string | null | ISO 8601 UTC |
| `ownerAnswer` | string | null | The business's reply text, if any |
| `ownerAnswerAt` | string | null | ISO 8601 UTC |
| `imagesCount` | integer | null | Number of photos attached to the review |
| `imageUrls` | array of strings | Direct URLs of attached photos (empty if none) |
| `reviewUrl` | string | null | Direct link to the review |
| `query` | string | null | The `queries` entry that surfaced this place, `null` for a `placeIds` entry |

### Limits

- Maximum 4,000 reviews per place, and 100 places per run (see Input) — this actor's own cap, to keep a typo from turning into a runaway bill.
- `maps.app.goo.gl` short links (the ones "Share" in the Google Maps app produces) aren't supported: resolving them needs an HTTP redirect, and this actor never makes requests to Google directly. Expand the link in a browser first and pass the resulting `maps.google.com`/`google.com/maps` URL instead.
- `authorReviewsCount` counts the author's total reviews across Google Maps, not reviews of this specific place.
- Auto-generated vs. manually-written reviews aren't distinguished — Google itself doesn't expose that flag through this data source.
- A place found via `queries` but not yet indexed with any reviews returns zero rows for it (not an error) — check the run's `STATS` key-value record (`placesWithoutReviews`) to see how many.
- Results reflect Google Maps at the moment the run executes; ratings, reply text, and review counts can change between runs.

### Monitoring at scale

Tracking more than 100 locations on a recurring schedule (e.g. a reputation-management agency watching 300 client locations weekly)? Chunk your place list across multiple runs rather than trying to fit it into one:

1. **Chunk `placeIds` into batches of ≤100** — e.g. 300 locations become 3 chunks of 100, each its own scheduled run (3 Apify [Schedules](https://docs.apify.com/platform/schedules), or 3 calls from your own orchestrator to the [Apify API](https://docs.apify.com/api/v2)'s run-actor endpoint).
2. **Prefer `placeIds` over `queries`** for monitoring — a `placeIds` list targets the same locations every run, while a `queries` search can surface a different set of places as Google Maps' own ranking shifts.
3. **Set `reviewsNewerThan` to your last successful run's date, with `reviewsSort: "newest"`** — this keeps a run from re-charging for reviews it already pulled last time (see Input above for how the filter works). An Apify Schedule sends the same fixed input on every trigger, so it can't compute "since last run" by itself: drive `reviewsNewerThan` from your own cron/orchestrator, updating the run's input to the previous run's date (via the Apify API) before each trigger.
4. **Dedupe by `reviewId`** on your side across runs — if you pad `reviewsNewerThan` a day earlier than strictly necessary as a safety margin (clock drift, a review that lands mid-run), you'll see a handful of the same reviews again rather than relying on the filter alone for a perfectly disjoint set.
5. **Watch `reviewsFilteredByDate` and `placeReviewsCount` in `STATS`/each row** — if a place picked up more new reviews than `maxReviewsPerPlace` since your last run, the oldest ones in that window are lost (with `reviewsSort: "newest"`, only the newest `maxReviewsPerPlace` are fetched at all); raise `maxReviewsPerPlace` for that place if this happens.

**Worked weekly example:** 300 locations, chunked into 3 runs of 100 places each, averaging ~20 new reviews per location per week: `300 × 20 × $0.0005 = $3.00` in review charges, plus `3 × $0.00005 = $0.00015` for the 3 run starts — about **$3.00/week** total.

### FAQ

**Is this legal?**
The actor retrieves publicly available review data through a licensed data provider, the same category of source used by reputation-management and local-SEO tools. You're responsible for how you use the extracted data under your local regulations.

**Which languages are supported?**
Any language Google Maps itself supports for reviews. Set `language` to get `text` translated into that language; `originalText`/`originalLanguage` always carry the review as the author wrote it, regardless of `language`.

**Can I pass a `place_id` I got from the Google Maps Places API or another Apify actor?**
Yes — `placeIds` accepts a `place_id` directly, no conversion needed. It also accepts a `cid` or a full Google Maps URL if that's what you have instead.

**Why did I get fewer reviews than `maxReviewsPerPlace`?**
The place may simply not have that many. `placeReviewsCount` on each returned row tells you the place's actual total; if it's lower than `maxReviewsPerPlace`, that's the ceiling regardless of what you requested.

**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](https://docs.apify.com/api/v2), run on a recurring [schedule](https://docs.apify.com/platform/schedules), and wired to webhooks on run events from the Apify Console.

# Actor input Schema

## `placeIds` (type: `array`):

Places to fetch reviews for, one per entry. Accepts a Google `place_id` (e.g. "ChIJrTLr-GyuEmsRBfy61i59si0"), a numeric `cid` (e.g. "9542840243817508540"), or a Google Maps URL containing either a `?cid=` query parameter or a `!1s0x...:0x...` feature id. Optional if `queries` below is set instead.

## `queries` (type: `array`):

One or more Google Maps search queries, e.g. "coffee shops in Austin, TX". Max 700 characters each.

## `country` (type: `string`):

ISO-2 country code (e.g. US, GB, CL, MX). Used to resolve the location for every review request, and (unless `coordinates` is set) to anchor `queries`.

## `coordinates` (type: `string`):

Anchor `queries` to a map viewport instead of `country`: "lat,lng,zoom", e.g. "30.2672,-97.7431,14".

## `maxPlacesPerQuery` (type: `integer`):

How many places to take from each query's Google Maps search results (1-100).

## `language` (type: `string`):

Language code for review text and query results, e.g. "en", "es".

## `maxReviewsPerPlace` (type: `integer`):

How many reviews to fetch per place (10-4000, must be a multiple of 10 -- Google's reviews API pages in tens).

## `reviewsSort` (type: `string`):

Sort order requested from the reviews API.

## `reviewsNewerThan` (type: `string`):

ISO-8601 date or datetime, e.g. "2026-09-01" or "2026-09-01T00:00:00Z". Inclusive: a review published exactly at this instant is kept. A date-only value resolves to midnight UTC that day, so the whole day is kept. Set `reviewsSort` to "newest" so the most recent reviews are the ones fetched within `maxReviewsPerPlace`.

## `reviewsOlderThan` (type: `string`):

ISO-8601 date or datetime, e.g. "2026-09-08" or "2026-09-08T23:59:59Z". Inclusive: a review published exactly at this instant is kept. A date-only value resolves to midnight UTC that day (i.e. the very start of it), so pair it with a datetime or the next day's date to include a full day. Must be on or after `reviewsNewerThan`.

## Actor input object example

```json
{
  "queries": [
    "coffee shops in Austin, TX"
  ],
  "country": "US",
  "maxPlacesPerQuery": 2,
  "language": "en",
  "maxReviewsPerPlace": 20,
  "reviewsSort": "newest"
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `stats` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "queries": [
        "coffee shops in Austin, TX"
    ],
    "country": "US",
    "maxPlacesPerQuery": 2,
    "maxReviewsPerPlace": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("moyadata/google-maps-reviews-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "queries": ["coffee shops in Austin, TX"],
    "country": "US",
    "maxPlacesPerQuery": 2,
    "maxReviewsPerPlace": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("moyadata/google-maps-reviews-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "queries": [
    "coffee shops in Austin, TX"
  ],
  "country": "US",
  "maxPlacesPerQuery": 2,
  "maxReviewsPerPlace": 20
}' |
apify call moyadata/google-maps-reviews-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,moyadata/google-maps-reviews-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/QSIgfYdKa0Q32kchR/builds/D016JYCcJSZU4eDLi/openapi.json
