# Google Maps Scraper & Cross-Platform Review Monitor (`ntriqpro/google-maps-reviews-plus`) Actor

Monitor public Google Maps listings and recent reviews, with optional App Store matching, rating gaps, response rates, and heuristic signals. Not a full-history export; result caps apply. Google Play review extraction is currently unavailable.

- **URL**: https://apify.com/ntriqpro/google-maps-reviews-plus.md
- **Developed by:** [daehwan kim](https://apify.com/ntriqpro) (community)
- **Categories:** Business, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 Scraper & Review Monitor — Reputation Signals

Enter Google Maps search queries, Maps URLs, or place IDs. The Actor returns a structured place
row for each matched business, the Google review rows available from its public page, and optional
App Store review rows matched by business name. Google Play review extraction is currently
unavailable and returns no review rows. Each place row also includes the
implemented reputation signals: cross-platform rating, rating gap, platform coverage, review
pattern heuristics, owner-response rate, and optional public contact details.

This is an incremental review monitor for small-business owners, multi-location operators, and
reputation teams, not a full-history export. Google Maps public pages currently expose around five
recent reviews per business, and source availability can change. Free-account runs deliver at most
25 result rows across place and review rows, then finish normally with a notice. Customer-selected
limits and the public source can reduce the result count further.

> This Actor is an **independent, unofficial tool**. It is **not affiliated with, endorsed by,
> or sponsored by** Google, Apple, or Google Play. It reads only **publicly accessible** listing
> and review pages. Its Google Maps request logic was written with reference to the
> **MIT-licensed** open-source projects
> [gosom/google-maps-scraper](https://github.com/gosom/google-maps-scraper) and
> [omkarcloud/google-maps-scraper](https://github.com/omkarcloud/google-maps-scraper) — no code
> from either project is bundled or redistributed by this Actor. **You are responsible** for
> complying with the target platforms' own terms of service, and **you are the data controller**
> for personal data (reviewer names, profile data) returned by any run you start.

### Who this is for

Small-business owners, multi-location franchise operators, and reputation-management agencies
who want a recurring view of newly available reviews and consistent reputation fields across
their selected businesses. It is not intended to reconstruct a business's complete review history.

### What rides free on every business row

Every business (`place-scraped`) row carries a **judgment layer** at no extra charge — no
separate event, no separate line item:

| Field | What it tells you |
|---|---|
| `crossPlatformRating` | Per-platform ratings plus a plain mean across every platform this run collected |
| `ratingGap` | Google Maps rating minus the mean of the other platforms — a big gap is worth a look |
| `platformCoverage` | How many platforms this business was found on in this run |
| `suspicionSignals` | Same-day review clustering, duplicate review text, single-review-account ratio — heuristics, not a fraud verdict |
| `ownerResponseRate` | Share of this business's **Google** reviews that got an owner reply (`rate`, `respondedCount`, `sampleSize`). **Read `sampleSize` alongside `rate`**: Google Maps' public pages currently return around 5 reviews per business (see [Output](#output)), so this rate is computed over at most that many reviews, not a full history — a gap in a 2-review sample is a different claim than a gap in a 200-review one |
| `contact` | Publicly listed email, phone, and social links read from the business's own website (only when `includeContactDetails` is on) |

These field names are the actual dataset field names — see [Output](#output) below.

### Cost, next to a Google-Maps-only scraper

Basic basket: **100 businesses, 5 Google reviews each** (the actual ceiling, not a wish):

| | Google-Maps-only scraper (e.g. Compass-style) | This Actor |
|---|---:|---:|
| Run start | — | $0.005 |
| 100 business records | ~$0.50 (≈$0.005/business) | $0.30 (100 × $0.003) |
| 500 Google reviews (5/business) | included above or not offered | $0.25 (500 × $0.0005) |
| **Total (Google Maps only)** | **~$0.50** | **$0.555** |

**We are more expensive for Google Maps data alone — if that is genuinely all you need, use a
Google-Maps-only scraper.** What the extra cost buys: cross-platform matching, the judgment
layer above, and an incremental-monitor design meant for repeat runs rather than one-off exports.

Optional: App Store reviews (up to 50 reviews per business):

| | Cost |
|---|---:|
| 100 businesses × 50 cross-platform reviews = 5,000 rows | $1.50 (5,000 × $0.0003) |

This row is **optional** — set `crossPlatforms: []` to skip it and pay only the Google Maps total
above.

### Use this as a review monitor, not a one-time export

#### Incremental mode — only new reviews, every re-run

```json
{
  "queries": ["coffee shop, New York"],
  "sinceTimestamp": "2026-09-01T00:00:00Z"
}
```

Only reviews published after `sinceTimestamp` are collected and charged. Point it at your last
run's start time and a daily re-run costs a fraction of the first one.

#### Negative-review alert preset

```json
{
  "queries": ["coffee shop, New York"],
  "sinceTimestamp": "2026-09-01T00:00:00Z",
  "negativeReviewAlert": 2
}
```

Combined with `sinceTimestamp`, this returns only new 1- and 2-star reviews — a watch-for-trouble
feed instead of a full copy of every review.

#### Webhook — push new reviews out automatically

1. On the Actor's **Integrations** tab (or via API), add a webhook on the
   **`ACTOR.RUN.SUCCEEDED`** event.
2. Point it at Slack, a spreadsheet, or your own endpoint — Apify posts the run's dataset link
   when the run finishes.
3. Schedule the Actor to run daily or weekly with `sinceTimestamp` set to "now minus one
   schedule interval"; each webhook delivery then carries only the new reviews since last time.

### How to use it

1. Enter one or more **queries** — a search phrase, a Google Maps URL, a place ID, or a
   "business name, city" string.
2. (Optional) Set **maxPlacesPerQuery** to control how many businesses are collected per query.
   `maxReviewsPerPlace` is a requested ceiling; Google's public pages typically hand back around
   5 reviews per business regardless of the number requested (see [P3](research/p3-result.md)).
3. (Optional) Enable **crossPlatforms** to also pull App Store reviews for the same businesses.
   Google Play remains selectable for compatibility but currently returns no review rows because
   its response shape has not been verified.
4. (Optional) Set **sinceTimestamp** for incremental runs, **negativeReviewAlert** for a
   bad-review watch, or **includeContactDetails** for the contact layer.
5. Click **Start** and read the results in the **Output** tab, pull them via the API, or wire up
   a webhook for a recurring watch.

### Input

Free-account runs deliver at most **25 result rows** across place and review rows, then finish
normally with a `free-plan-cap` notice. Paid plans receive the full available result set, subject
to your selected limits and public-source availability.

| Field | Type | Description |
|---|---|---|
| `queries` | array (required) | Search queries, Google Maps URLs, place IDs, or "business name, city" strings |
| `maxPlacesPerQuery` | integer | Max businesses collected per query (default 10) |
| `maxReviewsPerPlace` | integer | Requested maximum Google reviews per business (default 100); Google's public pages currently return around 5 regardless of this value |
| `reviewsSort` | enum | `newest`, `relevant`, `highest`, `lowest` (default `newest`) |
| `sinceTimestamp` | string | ISO-8601 timestamp or Unix epoch — keep only reviews published after it (incremental runs) |
| `negativeReviewAlert` | integer | Keep only reviews at or below this star rating (for example `2` for 1- and 2-star only) |
| `crossPlatforms` | array | Which other platforms to check: `appstore`, `googleplay`, `trustpilot`, `tripadvisor` (default `appstore`, `googleplay`). App Store extraction is available up to `maxReviewsPerPlatform`. Google Play currently returns no review rows because its response shape has not been verified. Trustpilot and Tripadvisor sit behind bot-challenge walls and are off by default; enabling them usually returns a notice row instead of reviews. |
| `maxReviewsPerPlatform` | integer | Max reviews collected per business on each cross-platform source (default 50) |
| `language` / `countryCode` | string | Language and country bias for results (default `en` / `us`) |
| `includeContactDetails` | boolean | Collect publicly listed contact info from each business's own website |
| `proxyConfiguration` | object | Apify Proxy settings. Datacenter proxy is the default; residential costs more and is yours to choose. |
| `browserConcurrency` | integer | How many business pages the browser opens at once (default 3) |
| `startUrls` | array | Apify-standard list of Google Maps start URLs |

```json
{
  "queries": ["coffee shop, New York"],
  "maxPlacesPerQuery": 10,
  "maxReviewsPerPlace": 100,
  "crossPlatforms": ["appstore", "googleplay"]
}
```

### Output

Dataset rows cover both business records and review records across every platform enabled,
tagged by `platform` and `rowType` (`place`, `review`, or `notice`). The judgment layer rides on
the `place` row.

```json
{
  "rowType": "place",
  "platform": "google_maps",
  "title": "Blue Bottle Coffee",
  "placeId": "0x89c259af336b3341:0x8f9ab29d8a8cdd6d",
  "address": "1 Rockefeller Plaza, New York, NY",
  "categoryName": "Coffee shop",
  "totalScore": 4.4,
  "reviewsCount": 812,
  "platformCoverage": 3,
  "crossPlatformRating": { "google_maps": 4.4, "appstore": 3.9, "googleplay": 4.1 },
  "ratingGap": 0.5,
  "suspicionSignals": [],
  "ownerResponseRate": { "rate": 0.4, "respondedCount": 2, "sampleSize": 5 }
}
```

**How many Google reviews per business?** Our [P3](research/p3-result.md) and
[P4](research/p4-result.md) measurements (2026-09-10) put the realistic ceiling at **around 5
most-recent reviews per business** — P4's three-business sample returned 5, 5, and 8, and every
review-pagination RPC attempt returned HTTP 403, including gosom's own open-source engine
requesting the identical endpoint. This is why the Actor is built as an incremental monitor
rather than a full-history export tool: request `sinceTimestamp` on a schedule and each run's
newest reviews become a rolling feed instead of a one-time snapshot capped at 5. App Store has a
separate pagination path subject to `maxReviewsPerPlatform`. Google Play review extraction is
currently unavailable and returns no review rows.

You can download the dataset in various formats such as JSON, HTML, CSV, or Excel.

### Data table

| Field group | Example fields |
|---|---|
| Review content | `text`, `appVersion` (App Store rows; Google Play currently returns no review rows) |
| Rating | `stars`, `rating` (review rows), `totalScore`, `reviewsCount` (business rows) |
| Timing | `publishAt`, `publishedAtDate`, `publishedAtDateIsApproximate`, `scrapedAt` |
| Identifiers | `reviewId`, `reviewUrl`, `placeId`, `searchString`, `rowType`, `platform` |
| Reviewer | `name`, `reviewerUrl`, `reviewerPhotoUrl`, `reviewerNumberOfReviews`, `isLocalGuide` |
| Business | `title`, `address`, `categoryName`, `url`, `phone`, `website`, `language`, `countryCode` |
| Engagement | `likesCount` |
| Owner response | `responseFromOwnerText`, `responseFromOwnerDate` |
| Judgment layer (on the business row, no extra charge) | `platformCoverage`, `crossPlatformRating`, `ratingGap`, `suspicionSignals`, `ownerResponseRate`, `contact` |

Google exposes a review's age as a relative phrase ("a month ago"), so `publishedAtDate` is
derived from it and flagged with `publishedAtDateIsApproximate: true`. Rows that are a message
rather than a result — a blocked platform, an invalid query, the free-plan cap — arrive as
`rowType: "notice"` and are never charged.

### Pricing

This Actor uses **pay-per-event** pricing:

| Event | Price | When it's charged |
|---|---:|---|
| Actor start | $0.005 | Once per run |
| Place scraped | $0.003 | Per Google Maps business written to the dataset |
| Google review scraped | $0.0005 | Per Google Maps review written to the dataset |
| Cross-platform review scraped | $0.0003 | Per review row from a supported cross-platform source (currently App Store; Google Play returns no review rows) |

**How `crossPlatformRating` is computed.** Every platform that reported a numeric rating counts
equally: `average` is the plain mean of those ratings, not a review-volume-weighted one, because
review counts are not comparable across platforms. `ratingGap` is the Google Maps rating minus
the mean of the other platforms.

**Where charging stops.** A row that reached your dataset is charged, whatever HTTP status
produced it. Two things are never charged: a `rowType: "notice"` row (a blocked platform, an
invalid query, the free-plan cap — a message, not a result), and a source that produced no row
at all. A run whose query is valid but matches nothing charges the run start only.

**Google Play status.** The integration currently rejects the unverified response shape and
returns no review rows. The input option remains available for compatibility.

Four events, and that is the whole list. The judgment layer — cross-platform rating, rating gap,
platform coverage, suspicion signals, owner response rate, contact details — rides on the
business row you have already paid for. Notice rows are free. Apify platform compute is billed
separately per your plan.

Free-account runs deliver at most **25 result rows** across place and review rows. If that limit is
reached, the Actor keeps the delivered rows, adds an uncharged `free-plan-cap` notice, and finishes
normally. Paid plans receive the full available result set, subject to your selected limits and
public-source availability.

### Tips

- Start with a small `maxPlacesPerQuery` to size a run before scaling up.
- Enable only the `crossPlatforms` sources you need — each one adds review rows and cost.
- For a daily watch, set `sinceTimestamp` to your last run's start time: only reviews newer than
  that are collected, so a repeat run costs a fraction of the first one.
- To watch for trouble only, set `negativeReviewAlert` to `2` and pair it with `sinceTimestamp`.
- Schedule recurring runs and attach a **webhook** on the `ACTOR.RUN.SUCCEEDED` event to push new
  reviews straight into Slack, a spreadsheet, or your own endpoint.

### FAQ, disclaimers, and support

**Is this legal?** This Actor reads only publicly accessible listing and review pages, does not
bypass logins, CAPTCHAs, or anti-bot protections, and identifies itself as an automated client.
You are responsible for using the output in line with the target platforms' own terms of
service and applicable law.

**Privacy and data responsibility.** **You are the data controller** for every run you start
with this Actor; ntriqpro is a **data processor** acting solely on your instructions. You are
responsible for having a lawful basis for each run and for complying with GDPR, PIPA, CCPA, and
other applicable privacy laws, including for any reviewer names or profile data returned.

**Why might a platform be missing from my results?** A source can be unavailable for a given
business (rate limiting, no matching listing, structural changes on the target site). That
platform's rows are simply absent from the output for that business; the run still completes
and returns everything it could collect from the other sources.

**Found a bug or want a feature?** Use the **Issues** tab. Custom solutions available.

# Actor input Schema

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

Google Maps search queries, Maps URLs, place IDs, or 'business name, city' strings. The Actor returns structured place rows, available public Google review rows, optional App Store review rows matched by business name, and implemented reputation signals on each place row. Google Play review extraction is currently unavailable. At least one query is required. Free-account runs deliver at most 25 result rows across place and review rows, then finish normally with a notice. Paid plans receive the full available result set, subject to selected limits and public-source availability.

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

Customer-selected maximum number of Google Maps businesses to collect for each query. Free-account runs still deliver at most 25 total result rows across the run.

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

Customer-selected maximum number of Google Maps reviews to collect per business. Google Maps public pages currently expose around five recent reviews per business, and free-account runs deliver at most 25 total result rows across the run.

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

Order in which Google Maps reviews are fetched. Use Newest together with sinceTimestamp for incremental runs.

## `sinceTimestamp` (type: `string`):

ISO-8601 timestamp (for example 2026-08-01T00:00:00Z) or a Unix epoch. Only reviews published after this point are returned and charged, so a daily or weekly re-run costs a fraction of a full run. Google Maps publishes a relative age ("3 months ago"), so the cutoff is applied to an approximate date.

## `negativeReviewAlert` (type: `integer`):

Set to 2, for example, to return only 1- and 2-star reviews. Combine with sinceTimestamp and a webhook to get an alert whenever a new bad review appears. Leave empty to return every review.

## `crossPlatforms` (type: `array`):

Additional review sources to check for the same business, matched by business name. Trustpilot and Tripadvisor sit behind bot-challenge walls (403) and return a notice row instead of reviews; they are off by default.

## `maxReviewsPerPlatform` (type: `integer`):

Maximum number of reviews to collect per business on each enabled cross-platform source.

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

Language code used to request results (for example en, ko, es).

## `countryCode` (type: `string`):

Two-letter country code used to bias search results (for example us, kr, gb).

## `includeContactDetails` (type: `boolean`):

Read the business's own website (the one listed on its Google profile) for publicly posted email, phone and social links. Returned as a field on the business row at no extra charge.

## `proxyConfiguration` (type: `object`):

Apify Proxy settings. Datacenter proxy is the default and is what we recommend; residential proxy costs far more per gigabyte and is rarely needed for Google Maps.

## `browserConcurrency` (type: `integer`):

How many business pages the single browser opens at once. Higher is faster but uses more memory.

## `startUrls` (type: `array`):

Apify-standard list of Google Maps URLs to start from, as an alternative or addition to queries.

## Actor input object example

```json
{
  "queries": [
    "coffee shop, New York"
  ],
  "maxPlacesPerQuery": 10,
  "maxReviewsPerPlace": 100,
  "reviewsSort": "newest",
  "crossPlatforms": [
    "appstore",
    "googleplay"
  ],
  "maxReviewsPerPlatform": 50,
  "language": "en",
  "countryCode": "us",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": []
  },
  "browserConcurrency": 3,
  "startUrls": []
}
```

# Actor output Schema

## `dataset` (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 shop, New York"
    ],
    "maxPlacesPerQuery": 10,
    "maxReviewsPerPlace": 100,
    "reviewsSort": "newest",
    "crossPlatforms": [
        "appstore",
        "googleplay"
    ],
    "maxReviewsPerPlatform": 50,
    "language": "en",
    "countryCode": "us",
    "includeContactDetails": false,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": []
    },
    "browserConcurrency": 3,
    "startUrls": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("ntriqpro/google-maps-reviews-plus").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 shop, New York"],
    "maxPlacesPerQuery": 10,
    "maxReviewsPerPlace": 100,
    "reviewsSort": "newest",
    "crossPlatforms": [
        "appstore",
        "googleplay",
    ],
    "maxReviewsPerPlatform": 50,
    "language": "en",
    "countryCode": "us",
    "includeContactDetails": False,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": [],
    },
    "browserConcurrency": 3,
    "startUrls": [],
}

# Run the Actor and wait for it to finish
run = client.actor("ntriqpro/google-maps-reviews-plus").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 shop, New York"
  ],
  "maxPlacesPerQuery": 10,
  "maxReviewsPerPlace": 100,
  "reviewsSort": "newest",
  "crossPlatforms": [
    "appstore",
    "googleplay"
  ],
  "maxReviewsPerPlatform": 50,
  "language": "en",
  "countryCode": "us",
  "includeContactDetails": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": []
  },
  "browserConcurrency": 3,
  "startUrls": []
}' |
apify call ntriqpro/google-maps-reviews-plus --silent --output-dataset

```

## MCP server setup

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

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/Pff7BwNrTEsrs7fZw/builds/kbdGYDgKeSj5uP6Fy/openapi.json
