# Google Maps Reviews Scraper — Sorted Reviews (`meka.im/google-maps-reviews-scraper`) Actor

Google Maps reviews scraper with newest, most relevant, highest, and lowest rating sort orders. Extract review text, ratings, authors, images, replies, and timestamps from a Maps URL or place ID. $0.50 per 1,000 delivered reviews.

- **URL**: https://apify.com/meka.im/google-maps-reviews-scraper.md
- **Developed by:** [Meka.im](https://apify.com/meka.im) (community)
- **Categories:** Business, Marketing, Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.50 / 1,000 reviews

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

Use this **Google Maps reviews scraper** to extract review text, ratings, reviewers, photos, owner replies, and timestamps from Google Maps URLs or place IDs. Collect reviews using Google's real most relevant, newest, highest rating, or lowest rating order without a login.

- **Four real sort orders** — most relevant, newest, highest rating, lowest rating. Google's orders, not a re-sort on our side
- **Four ways to name a place** — Maps URL, `ChIJ…` place ID, numeric CID, or `0x…:0x…` feature ID
- **Truncated responses are flagged, never passed off as the full list**
- **$0.50 per 1,000 delivered reviews**, no start fee
- **No login, no cookies, no browser**

### Quick start: scrape Google Maps reviews

```json
{
  "places": ["https://www.google.com/maps?cid=7590626642755549500"],
  "sort": "newest",
  "maxReviews": 50
}
```

All four of these name the same place and return the same reviews:

```json
{
  "places": [
    "https://www.google.com/maps?cid=7590626642755549500",
    "ChIJ_bMOIClFVVMRPHkP-LZSV2k",
    "7590626642755549500",
    "0x53554529200eb3fd:0x695752b6f80f793c"
  ]
}
```

Given all four at once, the place is scraped and charged **once**.

### Google Maps review scraper input

| Field | Type | Default | Notes |
|---|---|---|---|
| `places` | array | | One place per line, any of the four forms. Required |
| `sort` | string | `mostRelevant` | `mostRelevant` / `newest` / `highest` / `lowest` |
| `maxReviews` | integer | 100 | Per place |
| `language` | string | `en` | Google interface language. Does **not** filter reviews, see below |
| `requireComplete` | boolean | `true` | Flag truncated responses instead of accepting them |
| `placeConcurrency` | integer | 2 | Advanced |
| `sessionRetries` | integer | 3 | Advanced |
| `retryDelayMs` | integer | 1500 | Advanced |

Unrecognised places are skipped, reported in the run summary, and never charged. One bad entry does not stop the batch. Links that are not on a Google domain are rejected before any request is made.

### Google Maps reviews data output

One row per unique review, ordered by input, then page, then position on the page.

| Field | Meaning |
|---|---|
| `reviewId` | Google's own id. Stable across sessions and sort orders |
| `text`, `language`, `rating` | The review as written. Not a machine translation |
| `time` | When the review was **first posted** |
| `updatedTime` | When it was **last edited**. Equals `time` when never edited |
| `isEdited` | Whether those two differ |
| `relativeTime` | Google's own wording, e.g. `2 months ago` |
| `author` | `name`, `profileUrl`, `profileImageUrl`, `publicId`, `isLocalGuide`, `reviewerNumberOfReviews` |
| `ownerResponse` | The business reply, when there is one |
| `images` | Photo URLs attached to the review |
| `place` | `input`, `inputType`, `featureId`, `cid`, `url` |
| `sort`, `page`, `rank` | Where this row sits in the requested order |
| `requestedLanguage` | The interface language this run asked for |
| `variant`, `incomplete`, `expected` | Completeness, see below |

#### Two timestamps, on purpose

`time` is when the review was posted. `updatedTime` is when it was last edited.

**Google's `newest` order follows the edit time, not the post time.** A review posted in March and edited in June sorts as a June review. Tools that expose a single timestamp have to pick one, and picking the edit time silently rewrites when the review was written.

We give both and let you choose. Measured over 80 reviews: ordering by `time` looks out of order in 7 places, ordering by `updatedTime` in none.

### Pricing

| Event | Price |
|---|---|
| `review-delivered` | $0.0005 — **$0.50 per 1,000 delivered reviews** |

No start fee. Platform usage is included.

Not charged: unrecognised places, places that return nothing, failed fetches, and any review already delivered in this run or a previous one.

### Limits

Stated here rather than discovered later.

**Google truncates some anonymous sessions.** A truncated response is exactly five reviews with no next page — which is indistinguishable from a place that genuinely has five reviews. This Actor retries with fresh sessions; if the response is still short, every row for that place carries `incomplete: true` and the run summary names the place. **We do not pass five reviews off as a full list.** The one case we cannot resolve is a place that really does have exactly five reviews: it will be flagged as incomplete even though it is not.

**This can happen on your first run.** Retries clear it most of the time, but not every time — in our own acceptance runs one attempt in roughly eight came back truncated even after six fresh sessions. When it does, you get five rows with `incomplete: true` rather than a silent short list. Run it again; already-delivered reviews are not refetched or recharged.

**`language` does not filter reviews.** It is Google's interface language: it changes the relative time wording (`3 weeks ago` → `vor 3 Wochen`) and the language Google would translate into. **Review text comes back in the language the reviewer wrote it in, never a translation** — asking for `ja` on a French place still returns French. Each row reports both: `language` is the review's own language, `requestedLanguage` is what you asked for.

**Owner replies have no absolute timestamp.** Google only exposes a relative one (`3 weeks ago`) for the reply itself.

**Sorted orders are Google's.** `mostRelevant` reflects Google's ranking on the day you run it.

Not included: review sentiment, AI summaries, reviewer profile enrichment, emails or contact details, and anything behind a login.

### Completeness

Each row carries:

| Field | Meaning |
|---|---|
| `variant` | `full` when Google returned a complete page, `teaser` when short |
| `incomplete` | `true` when we know the place was not fully delivered |
| `expected` | The completeness threshold the run used |

`OUTPUT` also reports `placesComplete` / `placesIncomplete` and a `perPlace` breakdown, plus target-site errors, local back-pressure and parse failures **counted separately** — so a failure tells you whose problem it is.

### API and automation

```bash
curl "https://api.apify.com/v2/datasets/DATASET_ID/items?format=csv&clean=true" \
  -H "Authorization: Bearer APIFY_TOKEN"
```

A run that is interrupted and restarted picks up where it stopped. The Dataset is the source of truth for what was already delivered, so nothing is written or charged twice.

### Local business research workflow

- Start with [Google Maps Business Scraper](https://apify.com/meka.im/google-maps-business-scraper) to discover businesses and their Google Maps identifiers.
- Use this Actor to collect and sort their customer reviews.
- Send the businesses' websites to [Website Email Finder](https://apify.com/meka.im/website-email-finder) to find public contact addresses with source evidence.

### FAQ

**Why do two rows have the same star rating but different order under `newest`?** Order follows `updatedTime`. Check `isEdited`.

**A place came back with `incomplete: true` but it really does have five reviews.** That is the one case the public protocol cannot resolve. `variant` and the run summary let you confirm by hand.

**Can I get reviews in English for a non-English place?** No. Reviews come back in the original language.

**My run stopped early.** If `stoppedOnBudget` is true, the run hit the maximum charge you allowed. Raise it and start again; delivered reviews are not refetched or recharged.

**Why did a place I listed twice only get scraped once?** All four input forms resolve to the same place. Duplicates are collapsed and charged once.

### Legal

This Actor is independently developed and is not affiliated with, endorsed by, or sponsored by Google.

It reads public pages. It does not sign in, does not use cookies or credentials, and does not reach anything behind a login. You are responsible for how you use the data, including any applicable terms and data-protection rules.

# Actor input Schema

## `places` (type: `array`):

One place per line. Four forms are accepted and all resolve to the same place: a Google Maps link (https://www.google.com/maps?cid=… or /maps/place/…), a ChIJ… place ID, a numeric CID, or a 0x…:0x… feature ID. Anything else is skipped, reported in the run summary, and never charged.

## `sort` (type: `string`):

All four are Google's own orders, not a re-sort on our side. Newest follows Google and orders by last edit time, which is what updatedTime carries.

## `maxReviews` (type: `integer`):

Upper bound per place. A place with fewer reviews returns what it has. Reviews are fetched 20 per page.

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

BCP-47 code such as en, de, ja, pt-BR. This is Google's interface language: it changes the relative time wording ("3 weeks ago" vs "vor 3 Wochen") and the language Google would translate into. It does NOT filter reviews — review text always comes back in the language the reviewer wrote it in. Anything unrecognised falls back to en.

## `requireComplete` (type: `boolean`):

Google sometimes answers an anonymous session with a short five-review response that has no next page, which looks exactly like a genuine last page. On: retry with fresh sessions and, if the response is still short, mark every row incomplete: true. Off: accept the short response silently.

## `placeConcurrency` (type: `integer`):

How many places to scrape at the same time. Delivery order is preserved regardless.

## `sessionRetries` (type: `integer`):

How many fresh sessions to try when Google returns a truncated response, before the Actor re-queues the whole place and starts over after a longer pause. The truncation is bound to the session, and swapping sessions within a couple of seconds tends to land on the same one, so a low number here plus the re-queue is both faster and more reliable than a high number.

## `retryDelayMs` (type: `integer`):

Pause before opening a fresh session. Retrying immediately lands on the same truncated variant more often.

## Actor input object example

```json
{
  "places": [
    "https://www.google.com/maps?cid=7590626642755549500"
  ],
  "sort": "mostRelevant",
  "maxReviews": 50,
  "language": "en",
  "requireComplete": true,
  "placeConcurrency": 2,
  "sessionRetries": 3,
  "retryDelayMs": 1500
}
```

# Actor output Schema

## `reviews` (type: `string`):

No description

## `runSummary` (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 = {
    "places": [
        "https://www.google.com/maps?cid=7590626642755549500"
    ],
    "maxReviews": 50,
    "language": "en"
};

// Run the Actor and wait for it to finish
const run = await client.actor("meka.im/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 = {
    "places": ["https://www.google.com/maps?cid=7590626642755549500"],
    "maxReviews": 50,
    "language": "en",
}

# Run the Actor and wait for it to finish
run = client.actor("meka.im/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 '{
  "places": [
    "https://www.google.com/maps?cid=7590626642755549500"
  ],
  "maxReviews": 50,
  "language": "en"
}' |
apify call meka.im/google-maps-reviews-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,meka.im/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/L6w6bqCLW09clJH9t/builds/MqxXP4HjrzDFlPu0j/openapi.json
