# Google Maps Reviews Scraper - Places & AI Sentiment (`harvestlab/google-maps-reviews-scraper`) Actor

Scrape Google Maps reviews at $0.0003/review - 33% cheaper than alternatives. 3 modes: keyword search, direct URL, place details. AI sentiment + topic analysis via 5 LLM providers. Residential proxy included. n8n-ready.

- **URL**: https://apify.com/harvestlab/google-maps-reviews-scraper.md
- **Developed by:** [Nick](https://apify.com/harvestlab) (community)
- **Categories:** Marketing, Business, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, NaN 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/platform/actors/running/actors-in-store#pay-per-usage

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use 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 - Places & AI Sentiment

> **Part of the harvestlab MCP suite** - 36 RAG-ready, AI-agent-payment-ready Apify actors covering ecommerce, social, travel, news, jobs, EU B2B, dev-tools, and government data. [See the full suite](https://apify.com/harvestlab)

Stop paying $299/mo for reputation management platforms that lock you into dashboards you barely use. Get Google Maps reviews at **$0.0003 each** - 33% cheaper than the leading Apify competitor - with AI sentiment analysis, topic extraction, and owner-reply capture built in. No monthly minimum. Pay only for what you scrape.

---

### Key Features

- **3 scraping modes**: keyword search to find places, direct place URL for targeted review extraction, and full place details with enriched metadata
- **AI sentiment analysis** (optional): per-review sentiment scores, topic tags, and per-place aggregated insight summaries via 5 LLM providers
- **Full place data**: name, rating, review count, address, phone, website, opening hours, price level, GPS coordinates, place ID
- **Rich review data**: reviewer name, star rating, full text (with "More" expansion), date, owner reply text and date, Local Guide level, photo and like counts
- **Flexible sorting**: newest, most relevant, highest rating, lowest rating - configurable per run
- **Residential proxy built in**: pre-configured for Apify Residential proxy to bypass Google's datacenter IP blocks
- **Webhook-ready**: connect n8n, Make, or Zapier to fire alerts when runs complete

---

### Competitor Comparison

Why pay a monthly subscription for data you can pull on demand?

| Tool | Pricing | AI Sentiment | Search Mode | Full Place Detail |
|------|---------|-------------|------------|-------------------|
| **This actor** | **$0.0003/review** | **Yes** | **Yes** | **Yes** |
| Leading Apify competitor | $0.00045/review | No | Yes | Partial |
| ReviewTrackers | ~$49/mo (limited reviews) | Basic | No | No |
| Podium | $289/mo | No | No | No |
| Birdeye | $299/mo | Add-on cost | No | No |
| Reputation.com | $500+/mo | Add-on cost | No | No |
| Yotpo | $199/mo | Limited | No | No |
| Google Maps Platform API | $0.017/request | No | Yes | Yes |
| DataForSEO | $1.50/1,000 pages | No | Yes | Partial |
| Outscraper | $0.004-$0.008/review | No | Yes | Partial |

**The math**: 10 places x 100 reviews = 1,000 reviews. This actor: **$0.30**. Podium: **$289/mo** for the same data locked in their dashboard.

---

### Use Cases

#### Local SEO Agency - Replace $289/mo Podium Subscription

A local SEO agency managing 50 clients monitors each client's Google Maps reviews weekly. At 200 reviews per client per week: 50 x 200 = 10,000 reviews/week. Cost with this actor: **$3.00/week** vs **$289/mo** for Podium. Annual saving: **$3,456**.

The AI sentiment layer surfaces which clients are trending negative before ratings drop - enabling proactive outreach rather than reactive crisis management.

#### Competitive Intelligence - Find Where Competitors Fail

Pull 500 reviews from every nail salon in a target city. Feed them through AI topic extraction. Surface recurring complaints - "parking," "wait times," "rude staff" - with statistical frequency. Identify the service gaps your business can fill before spending on ads.

Run this monthly. Track competitor sentiment trends over time. When a previously 4.2-star competitor drops to 3.8, that is a window for a targeted campaign.

#### Reputation Monitoring - Early Warning Before Ratings Drop

Schedule weekly runs against your own Google Maps listing. Connect the `webhookUrl` output to a Slack notification via n8n. Get alerted the moment a 1-star review lands. The AI analysis flags which topic it belongs to (food quality, service, wait times) so the right team member gets the alert.

Set up a dashboard that tracks your 7-day rolling sentiment score alongside your weekly revenue. Correlate review sentiment with bookings to quantify the financial impact of reputation.

#### Lead Generation - Negative Reviews as Sales Signals

Businesses with declining ratings are exactly the customers who need reputation management, review generation software, or agency services. Scrape 4.0-3.5 star businesses in your vertical. Extract their phone numbers and websites. Score leads by review volume and sentiment trajectory.

A reputation management agency running this monthly on "restaurants [city]" can build a pipeline of warm leads who have documented, public evidence of the problem your product solves.

#### Market Research - Validate Before You Invest

Before entering a new city or launching a product category, scrape the top 20 businesses in your target niche. AI analysis surfaces what customers consistently love and hate. Combined with rating distribution, this gives you the clearest possible signal of unmet demand - before you spend on lease deposits or inventory.

Use the `lowest_rating` sort to systematically surface the most critical feedback. These reviews contain the most specific, actionable insight about what the market is missing.

#### Training Data - Sentiment and NER Datasets

Google Maps reviews are one of the richest public datasets of real-world consumer sentiment. Researchers and ML engineers use them for sentiment classification training, named-entity recognition (business names, locations, products), aspect-based sentiment analysis (reviews mention specific features), and service quality benchmarking studies.

Scrape by category (hotels, hospitals, schools, transport hubs) to build domain-specific training corpora. The AI analysis layer pre-annotates sentiment - giving you silver-labeled data for fine-tuning smaller models.

#### Hospitality and Travel - Benchmark Before Pricing

Hotels, restaurants, and attractions live and die by Google reviews. Before a pricing or renovation decision, scrape the competitive set. Map star ratings against price levels. Identify which amenities drive 5-star reviews in your comp set and which trigger complaints.

The owner reply data reveals how actively competitors manage their reputation. Low owner responsiveness combined with declining ratings is a competitive moat opportunity.

#### Real Estate - Neighbourhood Intelligence

Real estate professionals use Google Maps reviews to research neighbourhoods at scale. Scrape reviews of local schools, hospitals, restaurants, gyms, and transport hubs within a defined area. Run AI sentiment to generate a neighbourhood quality score. Build this into property valuation models or buyer briefing packs.

---

### Input Parameters

#### Mode and Targets

| Parameter | Type | Default | Description | Example |
|-----------|------|---------|-------------|---------|
| `mode` | string | `search` | `search` - find places by keyword then extract reviews. `reviews` - scrape reviews from provided place URLs directly. | `"search"` |
| `searchQuery` | string | - | Keyword + location for search mode. Alias: `q`. | `"coffee shops Berlin"` |
| `startUrls` | array | - | Google Maps place URLs for reviews mode. | `[{"url": "https://www.google.com/maps/place/..."}]` |

#### Scraping Options

| Parameter | Type | Default | Description | Example |
|-----------|------|---------|-------------|---------|
| `maxPlaces` | integer | 10 | Max places to scrape in search mode (max: 50). Alias: `maxResults`. | `20` |
| `maxReviewsPerPlace` | integer | 50 | Max reviews per place (max: 500). | `200` |
| `sortReviewsBy` | string | `newest` | Review order: `newest`, `relevant`, `highest_rating`, `lowest_rating`. | `"lowest_rating"` |
| `language` | string | `en` | Language code for reviews. | `"de"` |

#### AI Analysis (optional)

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `enableAiAnalysis` | boolean | `false` | Run AI sentiment and topic analysis on reviews. Charged $0.005 per place analysed. |
| `llmProvider` | string | `openrouter` | AI provider: `openrouter`, `anthropic`, `google`, `openai`, `ollama`. |
| `llmModel` | string | - | Override the default model. Leave empty for recommended default per provider. |
| `openrouterApiKey` | string | - | Set here or via `OPENROUTER_API_KEY` env var. Get at openrouter.ai/keys |
| `anthropicApiKey` | string | - | Set here or via `ANTHROPIC_API_KEY` env var. |
| `googleApiKey` | string | - | Set here or via `GOOGLE_API_KEY` env var. |
| `openaiApiKey` | string | - | Set here or via `OPENAI_API_KEY` env var. |
| `ollamaBaseUrl` | string | - | Ollama server URL (default: `http://localhost:11434`). Self-hosted, no API key. |

#### Proxy

| Parameter | Type | Description |
|-----------|------|-------------|
| `proxyConfiguration` | object | Residential proxies strongly recommended. Defaults to Apify Residential. |

---

### Output Schema

Three item types are pushed to the dataset.

#### Place item (`type: "place"`)

```json
{
  "type": "place",
  "url": "https://www.google.com/maps/place/...",
  "placeId": "ChIJ1234abcd5678",
  "name": "Cafe Einstein Stammhaus",
  "category": "Cafe",
  "rating": 4.2,
  "reviewCount": 1847,
  "address": "Kurfurstenstrasse 58, 10785 Berlin, Germany",
  "phone": "+49 30 2639192",
  "website": "https://www.cafeeinstein.com",
  "priceLevel": "$$",
  "openingHours": ["Monday: 7:00 AM - 10:00 PM", "Tuesday: 7:00 AM - 10:00 PM"],
  "coordinates": {"lat": 52.5074, "lng": 13.3553},
  "scrapedAt": "2026-04-27T14:22:00Z"
}
````

#### Review item (`type: "review"`)

```json
{
  "type": "review",
  "url": "https://www.google.com/maps/place/...",
  "placeName": "Cafe Einstein Stammhaus",
  "reviewId": "ChIJabc123...",
  "reviewer": "Maria S.",
  "reviewerLocalGuideLevel": 5,
  "reviewerReviewCount": 142,
  "rating": 5,
  "reviewText": "Wonderful old-Vienna atmosphere. The apfelstrudel is the best in Berlin. Staff are friendly and attentive.",
  "reviewDate": "2026-03-15",
  "photosCount": 3,
  "likesCount": 12,
  "ownerReply": "Thank you so much, Maria! We look forward to welcoming you again.",
  "ownerReplyDate": "2026-03-16",
  "aiSentiment": "positive",
  "aiSentimentScore": 0.94,
  "aiTopics": ["atmosphere", "food quality", "service"],
  "llmProvider": "openrouter",
  "llmModel": "google/gemini-2.0-flash-001",
  "scrapedAt": "2026-04-27T14:22:05Z"
}
```

#### AI analysis item (`type: "ai_analysis"`)

Emitted once per place when `enableAiAnalysis: true`, after all reviews for that place are scraped:

```json
{
  "type": "ai_analysis",
  "url": "https://www.google.com/maps/place/...",
  "placeName": "Cafe Einstein Stammhaus",
  "reviewsAnalysed": 50,
  "analysis": {
    "overall_sentiment": "positive",
    "average_sentiment_score": 0.81,
    "sentiment_distribution": {"positive": 74, "neutral": 18, "negative": 8},
    "top_positive_topics": ["atmosphere", "food quality", "location"],
    "top_negative_topics": ["wait times", "pricing"],
    "key_themes": ["historic ambiance", "traditional pastries", "weekend crowding"],
    "owner_responsiveness": "high",
    "key_insights": [
      "Highly regarded for historic ambiance and traditional Viennese pastries",
      "Weekend wait times are the primary friction point for otherwise loyal customers",
      "Price point slightly above Berlin average but accepted by most regulars"
    ],
    "competitive_strengths": ["unique historic setting", "consistent food quality"],
    "improvement_areas": ["weekend reservation system", "table turnover speed"]
  },
  "scrapedAt": "2026-04-27T14:22:10Z"
}
```

***

### Pricing

This actor uses **pay-per-event** pricing - you pay only for what you scrape.

| Event | Price | Trigger |
|-------|-------|---------|
| `place-scraped` | $0.0002 | Each place with name, rating, address extracted |
| `review-scraped` | $0.0003 | Each review extracted (text, rating, owner reply) |
| `ai-analysis-completed` | $0.005 | Per place when AI analysis is run |

**Cost examples**:

- 5 places x 100 reviews: 5 place events ($0.001) + 500 review events ($0.15) = **$0.151**
- 20 places x 50 reviews: 20 place events ($0.004) + 1,000 review events ($0.30) = **$0.304**
- 20 places x 50 reviews + AI analysis: $0.304 + 20 x $0.005 = **$0.404**
- Full category sweep - 50 places x 200 reviews: 50 place events ($0.010) + 10,000 review events ($3.00) = **$3.01**

No monthly minimum. No seat licenses. No dashboard fees.

***

### AI Sentiment Analysis

When `enableAiAnalysis` is enabled, the actor processes reviews through your chosen LLM to extract:

- **Per-review**: sentiment label (`positive`/`neutral`/`negative`), numerical score (0.0-1.0), topic tags
- **Per-place aggregate**: sentiment distribution, top positive and negative themes, owner responsiveness score, competitive strengths, improvement areas, and a human-readable insight summary

#### Supported AI Providers

| Provider | Default Model | Setup |
|----------|--------------|-------|
| **OpenRouter** (recommended) | `google/gemini-2.0-flash-001` | Set `openrouterApiKey` or `OPENROUTER_API_KEY` env var. Access to 300+ models including GPT-4o, Claude, Gemini. |
| **Anthropic** | `claude-sonnet-4-20250514` | Set `anthropicApiKey` or `ANTHROPIC_API_KEY` env var. Highest accuracy on nuanced reviews. |
| **Google AI** | `gemini-2.0-flash` | Set `googleApiKey` or `GOOGLE_API_KEY` env var. Fast and cost-efficient. |
| **OpenAI** | `gpt-4o-mini` | Set `openaiApiKey` or `OPENAI_API_KEY` env var. |
| **Ollama** | `llama3.1` | Set `ollamaBaseUrl` (default: `http://localhost:11434`). Self-hosted, no API key required. |

**Cost tip**: OpenRouter with Gemini Flash processes thousands of review batches per dollar. For production pipelines where accuracy matters most, use Anthropic Claude.

***

### Technical Details

#### Playwright Architecture

Google Maps is a fully JavaScript-rendered SPA. The actor uses **Playwright** (headless Chromium) to interact with the live page - scrolling the results list, clicking review tabs, expanding truncated review text, and capturing dynamically loaded content. This approach handles the JavaScript rendering that simple HTTP scrapers cannot.

Key implementation details:

- **Consent banner handling**: Automatically dismisses the EU GDPR consent overlay when present - no manual intervention needed for EU IP ranges
- **Dynamic selectors**: Uses multiple fallback selectors (ARIA labels, `data-*` attributes, role attributes) because Google Maps CSS class names change frequently without notice
- **Review scrolling**: Scrolls the reviews panel progressively until `maxReviewsPerPlace` reviews are captured or the end of the list is reached
- **"More" button expansion**: Automatically clicks the "More" link to expand truncated review texts before capturing
- **Sort support**: Navigates to the correct sort order before scraping so you get the newest (or lowest-rated) reviews first
- **Deduplication**: Uses review IDs and text hashing to prevent duplicate reviews across scroll rounds

#### Rate Limiting and Proxy

The actor uses `max_concurrency=1` to avoid triggering Google's per-IP rate limits. Concurrent requests from the same IP are reliably blocked. With residential proxy rotation enabled, the actor receives a fresh IP per page navigation, largely mitigating this constraint.

**Success rate**: With residential proxies enabled, typical success rates are 95%+. Without proxies or with datacenter IPs, expect CAPTCHAs and consent-page redirects.

#### Review Date Handling

Google Maps displays relative dates ("2 months ago", "a year ago") rather than exact timestamps. The actor converts these to approximate ISO 8601 dates based on the scrape timestamp. Exact publication dates are not available via the public web interface. Use the `reviewDate` field as an approximation with +/- 15 days accuracy for recent reviews.

#### Language Filtering

The `language` parameter sets the `hl=` query parameter on Google Maps requests, prompting Google to return reviews in that language where available. It does not filter out reviews in other languages - use the `language` field on output items to filter downstream if strict language filtering is required.

***

### Limitations

- **Maximum 500 reviews per place**: The Google Maps web interface does not paginate beyond approximately 500 reviews per sort order. To get broader coverage, run the actor multiple times with different `sortReviewsBy` values (`newest`, `lowest_rating`) and deduplicate by `reviewId`.
- **Review dates are approximate**: Google shows relative dates, not exact timestamps. Dates are approximated from the scrape time.
- **Search results capped at ~20 organic results**: Google Maps search returns approximately 20 place cards per query. The `maxPlaces` cap applies on top of this.
- **Residential proxy required**: Datacenter IPs are blocked by Google. Plan proxy costs accordingly - Apify Residential proxy is billed per GB transferred.
- **Dynamic selectors**: Google Maps updates its frontend regularly. If you encounter empty results or partial data, check for an updated actor version.

***

### Example Runs

#### Search mode - find cafes in Amsterdam and scrape reviews

```json
{
  "mode": "search",
  "searchQuery": "coffee shops Amsterdam",
  "maxPlaces": 5,
  "maxReviewsPerPlace": 100,
  "sortReviewsBy": "newest",
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

Expected output: 5 place items + up to 500 review items.

#### Reviews mode - scrape a specific restaurant with AI analysis

```json
{
  "mode": "reviews",
  "startUrls": [
    { "url": "https://www.google.com/maps/place/Noma/@55.6882607,12.5994....." }
  ],
  "maxReviewsPerPlace": 200,
  "sortReviewsBy": "relevant",
  "enableAiAnalysis": true,
  "llmProvider": "openrouter",
  "openrouterApiKey": "sk-or-...",
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

Expected output: 1 place item + up to 200 review items + 1 AI analysis item.

#### Competitive analysis - scrape lowest-rated reviews to find complaints

```json
{
  "mode": "search",
  "q": "pizza restaurants New York",
  "maxResults": 20,
  "maxReviewsPerPlace": 50,
  "sortReviewsBy": "lowest_rating",
  "enableAiAnalysis": true,
  "llmProvider": "openrouter",
  "openrouterApiKey": "sk-or-...",
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

Note: `q` is an alias for `searchQuery`, `maxResults` is an alias for `maxPlaces` - both work identically.

#### CLI usage (Apify CLI)

```bash
apify call YOUR_ACTOR_ID -i '{
  "mode": "search",
  "q": "hotels Barcelona",
  "maxResults": 10,
  "maxReviewsPerPlace": 50
}'
```

***

### Scheduling and Webhooks

Schedule weekly review-pulse runs in Apify Console under Schedules to track rating trends over time. Connect a `webhookUrl` in n8n or Make to fire an alert into Slack, PagerDuty, or your CRM the moment a run finishes.

For reputation monitoring workflows, set up a run that fires when ratings drop below a threshold. The `ai_analysis` items carry the pre-aggregated sentiment summary with numerical scores - easy to threshold in a downstream webhook handler without further processing.

***

### Pair with Other Actors

This actor slots naturally into multi-source reputation and intelligence pipelines:

- **[Trustpilot Scraper](https://apify.com/harvestlab/trustpilot-scraper)** - cross-platform review pair: run both on the same business to compare Google Maps sentiment vs. Trustpilot ratings and surface where scores diverge - often the earliest signal of a PR crisis
- **[google-maps-eu-scraper](https://apify.com/harvestlab/google-maps-eu-scraper)** - EU counterpart: when you need place discovery across multiple European cities without US-biased results, use the EU actor's OpenStreetMap backend and then enrich with this actor's review pipeline
- **[Contact Extractor](https://apify.com/harvestlab/contact-extractor)** - local lead enrichment: extract the owner/manager email from the business `website` field and combine with AI sentiment scores for outreach prioritisation (contact the 4-star business losing ground, not the stable 5-star)
- **[News Monitor](https://apify.com/harvestlab/news-monitor)** - reputation context: pair review sentiment with Google News coverage on the same business name to distinguish organic reputation shifts from media-driven spikes

***

### Use with AI Agents

This actor is **x402-ready** - pay-per-event pricing is compatible with HTTP 402 micropayment flows for autonomous AI agents that self-fund API calls.

The output items are structured for RAG pipelines. The `ai_analysis` items (one per place) are ideal as document chunks - they carry the pre-aggregated summary with sentiment scores, themes, and insights in a single JSON object that can be embedded and retrieved without further preprocessing.

```python
from apify_client import ApifyClient
from langchain.tools import Tool

client = ApifyClient("YOUR_APIFY_TOKEN")

def get_google_maps_reviews(args: dict) -> list[dict]:
    run = client.actor("harvestlab/google-maps-reviews-scraper").call(run_input={
        "mode": "search",
        "searchQuery": args["query"],
        "maxPlaces": args.get("maxPlaces", 5),
        "maxReviewsPerPlace": args.get("maxReviews", 50),
        "enableAiAnalysis": True,
        "llmProvider": "openrouter",
        "openrouterApiKey": args.get("openrouterApiKey"),
    })
    return list(client.dataset(run["defaultDatasetId"]).iterate_items())

maps_review_tool = Tool(
    name="google_maps_reviews",
    description="Get Google Maps reviews and AI sentiment for places matching a keyword + location query.",
    func=get_google_maps_reviews,
)
```

For a LangGraph reputation-monitoring agent, the actor slots in as a `fetch_reviews_node` that fires on a schedule, feeds sentiment deltas to a `summarize_node`, and posts alerts to Slack or email when ratings drop below a threshold.

***

### Legal and Compliance

This actor scrapes publicly available data from Google Maps. Users are responsible for:

- Complying with Google's Terms of Service (google.com/intl/en/policies/terms/)
- Complying with applicable data protection laws (GDPR, CCPA, etc.) when processing personal data (reviewer names, review text)
- Ensuring their use case is lawful in their jurisdiction

**GDPR notice**: Reviewer names and review texts may constitute personal data under GDPR. If you operate in the EU/EEA, ensure you have a lawful basis for processing this data and handle it accordingly (data minimisation, retention limits, right to erasure requests).

This actor is intended for legitimate research, business intelligence, and competitive analysis. Users bear sole responsibility for how they use the data collected.

***

### Support

If you encounter issues or have feature requests, open an issue on the actor page or contact us via Apify support.

**Common issues**:

- Empty results: enable residential proxy, verify `proxyConfiguration` is set correctly
- CAPTCHA redirect: try a different proxy group or verify residential proxy is active
- Missing reviews: Google may show fewer reviews than `reviewCount` indicates for some sort orders; this is a Google-side limitation
- AI analysis fails: verify your API key is set correctly in both the input field and the corresponding environment variable
- Partial place data: Google Maps updates its HTML structure regularly; check for a newer actor version if fields are missing

# Actor input Schema

## `mode` (type: `string`):

search — find places by keyword then extract reviews. reviews — scrape reviews from provided place URL(s) directly.

## `searchQuery` (type: `string`):

Keyword + location to search Google Maps. E.g. 'pizza restaurants Amsterdam' or 'hotels Barcelona center'.

## `q` (type: `string`):

Alias for searchQuery — short form for CLI use.

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

Google Maps place URLs to scrape reviews from. Used in reviews mode. E.g. https://www.google.com/maps/place/...

## `maxPlaces` (type: `integer`):

Maximum number of places to scrape in search mode (default: 10, max: 50).

## `maxResults` (type: `integer`):

Alias for maxPlaces — short form for CLI use.

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

Maximum reviews to scrape per place (default: 50, max: 500).

## `sortReviewsBy` (type: `string`):

Order of reviews to scrape.

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

Language code for reviews (default: en). Google will return reviews in this language where available.

## `enableAiAnalysis` (type: `boolean`):

Run AI sentiment scoring and topic extraction on reviews. Requires an LLM API key. Charged as ai-analysis-completed ($0.005) per place analysed.

## `llmProvider` (type: `string`):

AI provider for sentiment analysis.

## `llmModel` (type: `string`):

Override the default model for your chosen provider. Leave empty to use the recommended default.

## `openrouterApiKey` (type: `string`):

OpenRouter API key. Get one at https://openrouter.ai/keys — or set OPENROUTER\_API\_KEY env var.

## `anthropicApiKey` (type: `string`):

Anthropic API key. Get one at https://console.anthropic.com/settings/keys — or set ANTHROPIC\_API\_KEY env var.

## `googleApiKey` (type: `string`):

Google AI Studio API key. Get one at https://aistudio.google.com/app/apikey — or set GOOGLE\_API\_KEY env var.

## `openaiApiKey` (type: `string`):

OpenAI API key. Get one at https://platform.openai.com/api-keys — or set OPENAI\_API\_KEY env var.

## `ollamaBaseUrl` (type: `string`):

Ollama server URL. Default: http://localhost:11434. Install Ollama at https://ollama.com/download.

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

Residential proxies strongly recommended — Google Maps blocks datacenter IPs aggressively.

## Actor input object example

```json
{
  "mode": "search",
  "searchQuery": "coffee shops in Berlin",
  "maxPlaces": 10,
  "maxReviewsPerPlace": 50,
  "sortReviewsBy": "newest",
  "language": "en",
  "enableAiAnalysis": false,
  "llmProvider": "openrouter",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `datasetOutput` (type: `string`):

Dataset containing scraped Google Maps place and review records plus optional AI sentiment analysis records.

# 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 = {
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("harvestlab/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 = { "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    } }

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

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

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

```

## CLI example

```bash
echo '{
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call harvestlab/google-maps-reviews-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=harvestlab/google-maps-reviews-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Google Maps Reviews Scraper - Places & AI Sentiment",
        "description": "Scrape Google Maps reviews at $0.0003/review - 33% cheaper than alternatives. 3 modes: keyword search, direct URL, place details. AI sentiment + topic analysis via 5 LLM providers. Residential proxy included. n8n-ready.",
        "version": "0.1",
        "x-build-id": "HTvIcaSDl1xeOVUao"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/harvestlab~google-maps-reviews-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-harvestlab-google-maps-reviews-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/harvestlab~google-maps-reviews-scraper/runs": {
            "post": {
                "operationId": "runs-sync-harvestlab-google-maps-reviews-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/harvestlab~google-maps-reviews-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-harvestlab-google-maps-reviews-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "mode"
                ],
                "properties": {
                    "mode": {
                        "title": "Mode",
                        "enum": [
                            "search",
                            "reviews"
                        ],
                        "type": "string",
                        "description": "search — find places by keyword then extract reviews. reviews — scrape reviews from provided place URL(s) directly.",
                        "default": "search"
                    },
                    "searchQuery": {
                        "title": "Search query",
                        "type": "string",
                        "description": "Keyword + location to search Google Maps. E.g. 'pizza restaurants Amsterdam' or 'hotels Barcelona center'."
                    },
                    "q": {
                        "title": "Search query (alias)",
                        "type": "string",
                        "description": "Alias for searchQuery — short form for CLI use."
                    },
                    "startUrls": {
                        "title": "Place URLs",
                        "type": "array",
                        "description": "Google Maps place URLs to scrape reviews from. Used in reviews mode. E.g. https://www.google.com/maps/place/...",
                        "items": {
                            "type": "object",
                            "properties": {
                                "url": {
                                    "type": "string",
                                    "title": "URL",
                                    "description": "Google Maps place URL"
                                }
                            }
                        }
                    },
                    "maxPlaces": {
                        "title": "Max places",
                        "minimum": 1,
                        "maximum": 50,
                        "type": "integer",
                        "description": "Maximum number of places to scrape in search mode (default: 10, max: 50).",
                        "default": 10
                    },
                    "maxResults": {
                        "title": "Max results (alias)",
                        "minimum": 1,
                        "type": "integer",
                        "description": "Alias for maxPlaces — short form for CLI use."
                    },
                    "maxReviewsPerPlace": {
                        "title": "Max reviews per place",
                        "minimum": 1,
                        "maximum": 500,
                        "type": "integer",
                        "description": "Maximum reviews to scrape per place (default: 50, max: 500).",
                        "default": 50
                    },
                    "sortReviewsBy": {
                        "title": "Sort reviews by",
                        "enum": [
                            "newest",
                            "relevant",
                            "highest_rating",
                            "lowest_rating"
                        ],
                        "type": "string",
                        "description": "Order of reviews to scrape.",
                        "default": "newest"
                    },
                    "language": {
                        "title": "Review language",
                        "type": "string",
                        "description": "Language code for reviews (default: en). Google will return reviews in this language where available.",
                        "default": "en"
                    },
                    "enableAiAnalysis": {
                        "title": "Enable AI sentiment analysis",
                        "type": "boolean",
                        "description": "Run AI sentiment scoring and topic extraction on reviews. Requires an LLM API key. Charged as ai-analysis-completed ($0.005) per place analysed.",
                        "default": false
                    },
                    "llmProvider": {
                        "title": "LLM provider",
                        "enum": [
                            "openrouter",
                            "anthropic",
                            "google",
                            "openai",
                            "ollama"
                        ],
                        "type": "string",
                        "description": "AI provider for sentiment analysis.",
                        "default": "openrouter"
                    },
                    "llmModel": {
                        "title": "LLM model override",
                        "type": "string",
                        "description": "Override the default model for your chosen provider. Leave empty to use the recommended default."
                    },
                    "openrouterApiKey": {
                        "title": "OpenRouter API key",
                        "type": "string",
                        "description": "OpenRouter API key. Get one at https://openrouter.ai/keys — or set OPENROUTER_API_KEY env var."
                    },
                    "anthropicApiKey": {
                        "title": "Anthropic API key",
                        "type": "string",
                        "description": "Anthropic API key. Get one at https://console.anthropic.com/settings/keys — or set ANTHROPIC_API_KEY env var."
                    },
                    "googleApiKey": {
                        "title": "Google AI API key",
                        "type": "string",
                        "description": "Google AI Studio API key. Get one at https://aistudio.google.com/app/apikey — or set GOOGLE_API_KEY env var."
                    },
                    "openaiApiKey": {
                        "title": "OpenAI API key",
                        "type": "string",
                        "description": "OpenAI API key. Get one at https://platform.openai.com/api-keys — or set OPENAI_API_KEY env var."
                    },
                    "ollamaBaseUrl": {
                        "title": "Ollama base URL",
                        "type": "string",
                        "description": "Ollama server URL. Default: http://localhost:11434. Install Ollama at https://ollama.com/download."
                    },
                    "proxyConfiguration": {
                        "title": "Proxy configuration",
                        "type": "object",
                        "description": "Residential proxies strongly recommended — Google Maps blocks datacenter IPs aggressively."
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
