# Google Maps Reviews Scraper & Pain Point Analyzer (`egeusta/google-maps-review-pain-point-miner`) Actor

Scrape Google Maps reviews and identify recurring complaints, sentiment, customer pain points and actionable business opportunities.

- **URL**: https://apify.com/egeusta/google-maps-review-pain-point-miner.md
- **Developed by:** [Ege Usta](https://apify.com/egeusta) (community)
- **Categories:** SEO tools
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## 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 Review Pain-Point Miner

Turn Google Maps reviews into customer insight and sales opportunities. Find
recurring complaints, service issues, sentiment patterns and positive
differentiators for local businesses, locations and competitors.

Mines Google Maps reviews for **recurring complaints**, positive differentiators
and an **explainable** business opportunity score.

### Input

| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `placeUrls` | string\[] | `[]` | Google Maps **place** URLs (direct route) or **search** URLs (search route). |
| `searchQueries` | string\[] | `[]` | Google Maps search queries (search route). |
| `businessTargets` | string\[] | `[]` | Business name (+ optional location) looked up via the search route. |
| `maxReviewsPerPlace` | integer | `120` | Reviews analysed per place. |
| `maxTotalReviews` | integer | `2000` | Global cap on reviews analysed across the run. |
| `language` | string | `"en"` | `hl` hint. |
| `requestTimeoutSecs` | integer | `30` | Per-request timeout. |
| `concurrency` | integer | `3` | Places fetched in parallel. |
| `minReviewsForConfidence` | integer | `15` | Reviews needed for `dataConfidence` = 1.0; fewer reviews dampen the score. |
| `emitReviewRows` | boolean | `false` | Also emit one row per analysed review (topic tags + sentiment). |

At least one of `placeUrls` / `searchQueries` / `businessTargets` is required.
Invalid Google Maps hosts raise an actionable `ConfigError`. The **direct place
route** and the **search route** are tracked separately and reported per place.

### Output

`type: "place_analysis"` — one per place (see `.actor/dataset_schema.json`):

- rating: `ratingReported`, `ratingFromReviews`, `ratingDistribution` (1–5).
- `ownerResponseRate` — share of analysed reviews with an owner response.
- `topicFindings` — per recurring topic (waiting time, pricing, service,
  cleanliness, staff, availability, quality, location): `mentions`,
  `negativeMentions`, `positiveMentions`, `negativeShare`, `sentiment`,
  `confidence` (`min(1, mentions / saturationHits)`), `sampleQuotes`.
- `painPoints` — topics with enough mentions (`≥ max(2, 10 % of reviews)`) and
  `negativeShare ≥ 0.4`, most negative first.
- `positiveDifferentiators` — topics reviewers consistently praise.
- `opportunityScore` (0–100) / `opportunityGrade` / `scoreTerms` /
  `scoreExplanation` / `recommendedActions`.
- `dataConfidence` (0–1).

Optional `type: "review"` rows. A `SUMMARY` record with per-place reports,
counts and `failuresByReason` is written to the key-value store.

### The score formula (explainable)

```
opportunityScore = round( dampen × weightedAverage[
    low-rating(w3)         = inverse of rating vs a 2.5–4.7 benchmark
    pain-breadth(w2)       = number of distinct pain points, scaled 0–4
    pain-intensity(w2)     = negative share of the worst pain point
    owner-neglect(w1)      = inverse of owner response rate vs 0–0.6
    negative-sentiment(w2) = (1 − mean review sentiment) / 2
] )
dampen = 0.5 + 0.5 × dataConfidence
dataConfidence = min(1, reviewsAnalyzed / minReviewsForConfidence)
```

The exact term values are echoed back in every item's `scoreExplanation` and
`scoreTerms`.

### No fabrication

If a place returns **no rating and no reviews**, `opportunityScore` is `null`,
`opportunityGrade` is `"N/A"`, and `recommendedActions` says to gather data
first. A high reputation opportunity is never invented from missing data.
`placesWithoutReviewData` in the summary counts these.

### Safety

All fetches use the shared `safeFetch` (http/https only, DNS checked against
private ranges, manual redirects, content-type allow-list, byte ceiling, timeout
cleared in `finally`). One failing place never aborts the run; failures are
counted by reason. No CAPTCHA solving, no login, no private endpoints.

### Known limitations

- Google serves reviews through a JS app / internal RPC. This Actor issues a
  `GET` and parses framed JSON or an embedded state blob heuristically; an
  interstitial or a layout change yields fewer / no reviews and a warning, never
  a crash. Review `reviewId` and absolute dates are usually unavailable via this
  path (`relativeDate` is kept when present). All aggregation, topic mining,
  sentiment, scoring and resilience logic is fully implemented and
  fixture-tested.

### Commands

```bash
npm ci                                          # monorepo root
npm run check -w google-maps-review-pain-point-miner
npm run smoke -w google-maps-review-pain-point-miner   # offline
npx apify validate-schema
```

### Deployment

Not deployed here. `apify push` is intentionally not run — the lead engineer
handles deployment.

# Actor input Schema

## `placeUrls` (type: `array`):

Google Maps direct place URLs or search URLs. Place URLs take the direct route; search URLs take the search route.

## `searchQueries` (type: `array`):

Google Maps search queries, e.g. "dentist Austin TX".

## `businessTargets` (type: `array`):

Business name (optionally with a location) to look up via the search route, e.g. "Blue Bottle Coffee, Oakland".

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

Upper bound on reviews analysed for a single place.

## `maxTotalReviews` (type: `integer`):

Global cap on reviews analysed across all places in this run.

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

Google Maps UI language hint (BCP-47 code).

## `requestTimeoutSecs` (type: `integer`):

Per-request timeout.

## `concurrency` (type: `integer`):

Number of places fetched in parallel.

## `minReviewsForConfidence` (type: `integer`):

Reviews needed before dataConfidence reaches 1.0. Fewer reviews dampen the opportunity score.

## `emitReviewRows` (type: `boolean`):

Also write one dataset row per analysed review (with topic tags and sentiment).

## `maxResponseBytes` (type: `integer`):

Hard ceiling on bytes read from a single Google Maps response.

## Actor input object example

```json
{
  "placeUrls": [],
  "searchQueries": [],
  "businessTargets": [],
  "maxReviewsPerPlace": 120,
  "maxTotalReviews": 2000,
  "language": "en",
  "requestTimeoutSecs": 30,
  "concurrency": 3,
  "minReviewsForConfidence": 15,
  "emitReviewRows": false,
  "maxResponseBytes": 4000000
}
```

# Actor output Schema

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

Structured review themes, sentiment and customer pain-point insights.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("egeusta/google-maps-review-pain-point-miner").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("egeusta/google-maps-review-pain-point-miner").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 '{}' |
apify call egeusta/google-maps-review-pain-point-miner --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,egeusta/google-maps-review-pain-point-miner"
        }
    }
}

```

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/qwKnmY9SKYyUYgCsv/builds/fh50vesyhgbDZh9VH/openapi.json
