# Google Maps Scraper – Places, Ratings & Contacts (`simple.actor/google-maps`) Actor

Scrape Google Maps places for any search: name, rating, review count, address, phone, website, categories, opening hours, coordinates, photo and place ID. Twenty places a request, paged to everything Google lists. No API key, no browser. Unofficial.

- **URL**: https://apify.com/simple.actor/google-maps.md
- **Developed by:** [Simple Actor](https://apify.com/simple.actor) (community)
- **Categories:** Lead generation, Business, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.50 / 1,000 place returneds

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

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

## What's an Apify Actor?

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

## How to integrate an Actor?

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

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

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

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

# README

## Google Maps Scraper – Places, Ratings & Contacts

Scrape Google Maps places as structured data. Type what you would type into
Maps — "coffee shops in austin tx", "dentists in miami fl", "boulangerie
paris 11e" — and every place Google lists comes back as a row: name, star
rating, review count, full address, phone, website, categories, opening
hours, coordinates, lead photo and Google's own Place ID — in Google's order.

Built for lead generation, local SEO, market research and location data:
every business of a kind in an area, with the contact details to reach it and
the Place ID to look it up anywhere else.

**No API key, no login, no browser, no account of yours involved.** Twenty
places arrive per request, so a typical search finishes in seconds.

**$0.50 per 1,000 places** — $0.01 for a typical 20-place search. Apify
platform usage included, nothing else to pay. A search that cannot be read is
never charged.

*Unofficial. Not affiliated with, endorsed by, or sponsored by Google.*

### Features

- **The whole listing, per place** — name, rating, review count, address,
  phone, website, every category, opening hours by weekday, coordinates and a
  photo.
- **Google's Place ID on every row** — the same `ChIJ…` identifier the Places
  API uses, stable across runs: deduplicate on it, or look the place up
  elsewhere. Plus a ready-made Google Maps link.
- **Everything Google lists** — paged automatically to the end of Google's
  own results, typically 120–140 places for a search, with no place twice.
- **Review counts you can rely on** — Google sometimes answers with a lighter
  record that has the rating but not the count; those pages are re-read until
  the counts arrive.
- **Any place, any language** — the search text decides where; `language`
  decides what Google answers in, opening hours included.
- **Many searches in one run** — one per line, each with its own rows.
- **Paste a Maps link** — a search copied from Maps' address bar is read as
  its terms.
- **Runs stay green.** A search that cannot be read becomes an error row, so
  one bad line never costs the rest of the job.

### Input

```json
{
  "queries": ["coffee shops in austin tx", "dentists in miami fl"],  // one per line
  "maxPlaces": 20,          // per search; Google runs out at ~120–140
  "reviewCounts": true,     // re-read a page that came without review counts
  "language": "en",
  "country": "US"
}
```

| Field | Type | Default | What it does |
|---|---|---|---|
| `queries` | array | — | Maps searches, one per line, **with the place in them** (`plumbers in denver co`). Links copied from Maps work too. |
| `maxPlaces` | integer | `20` | Places per search, up to 200. Twenty arrive per request. |
| `reviewCounts` | boolean | `true` | Re-read a page Google answered without review counts. Off is faster; `reviewCount` may then be null on some rows. |
| `language` | string | `en` | What Google answers in: `en`, `de`, `fr`, `es`, `ja`, `pt-BR`… |
| `country` | string | `US` | Two-letter code. Nudges ranking; the place in the search decides where. |

#### Put the place in the search

Google answers a search with no place in it — just "plumbers" — for wherever
it thinks the request came from, which is a data centre, not you. Write the
search the way you would in Maps: **"plumbers in denver co"**, **"coffee near
shibuya station"**, **"hotels in lisbon"**. Any language works.

#### How many places

Google lists 20 places a request and stops at roughly 120–140 for most
searches — the same point where Maps itself stops scrolling. `maxPlaces`
decides how far to read; asking for more than Google has returns everything
it lists. To get *different* places rather than more of them, narrow the
search: a neighbourhood instead of a city, or a more specific category.

### Output

One item per place, in the run's dataset:

```json
{
  "type": "place",
  "position": 1,
  "name": "Relax and Smile Dental Care",
  "placeId": "ChIJ8WVD9Z622YgRTsE_RcBq1Qk",
  "cid": "0x88d9b69ef54365f1:0x9d56ac0453fc14e",
  "address": "1490 Biscayne Blvd Suite 101, Miami, FL 33132",
  "phone": "(305) 555-0134",
  "website": "https://www.example-dental.com/",
  "rating": 4.9,
  "reviewCount": 928,
  "categories": ["Dentist", "Cosmetic dentist", "Dental clinic"],
  "category": "Dentist",
  "latitude": 25.7891,
  "longitude": -80.1886,
  "openingHours": { "Monday": "12–7 PM", "Tuesday": "9 AM–5 PM", "Sunday": "Closed" },
  "imageUrl": "https://lh3.googleusercontent.com/gps-cs-s/…",
  "googleMapsUrl": "https://www.google.com/maps/place/?q=place_id:ChIJ8WVD9Z622YgRTsE_RcBq1Qk",
  "query": "dentists in miami fl",
  "searchUrl": "https://www.google.com/maps/search/dentists+in+miami+fl",
  "page": 1,
  "retrievedAt": "2026-09-11T14:02:11.000Z"
}
```

| Field | What it is |
|---|---|
| `placeId` | Google's Place ID (`ChIJ…`) — stable, and what every other Google surface keys on. |
| `cid` | Google's internal Maps listing identifier, in its hex form. |
| `rating`, `reviewCount` | Star rating and number of reviews. `reviewCount` is null only if every re-read came back without it. |
| `address`, `phone`, `website` | As Google shows them. `phone` is null when the listing has none — common outside the US. |
| `categories`, `category` | Every category Google files the place under, and the primary one. |
| `openingHours` | Hours by weekday in the language asked for. Null for listings that publish none (hotels, for instance). |
| `latitude`, `longitude`, `imageUrl` | Coordinates and the lead photo. |
| `googleMapsUrl`, `searchUrl` | A link that opens the place in Maps, and the search it came from. |
| `position`, `page` | Google's rank for the search, 1 first, and which request it arrived on. |

A search that could not be read is its own row with `type: "error"`, an
`error` code (`bad_input` or `read_failed`) and an `errorDescription`. The
run itself stays green either way, and its status message says how many
searches could not be read — so if you run on a schedule, check for
`type: "error"` rows rather than trusting the run status.

An empty dataset means every search was read and Google Maps lists no places
for it. It never means a search could not be made; that arrives as an error
row instead.

### How to use

#### From Apify Console

1. Open the Actor and click **Try for free** / **Start**.
2. Put one search per line into **Searches**, with the place in it.
3. Set **Places per search** — 20 is one request; Google has 120–140 for
   most searches.
4. Click **Start**, then open the **Dataset** tab — the Places view shows a
   photo of each — and export as JSON, CSV or Excel. The **Contacts** view
   is the website, phone and Maps link per place.

#### From the API

```bash
curl -s "https://api.apify.com/v2/acts/simple.actor~google-maps/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"queries": ["coffee shops in austin tx"], "maxPlaces": 60}'
```

Lead generation with the JavaScript client:

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('simple.actor/google-maps').call({
    queries: ['dentists in miami fl', 'dentists in fort lauderdale fl'],
    maxPlaces: 100,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const leads = items.filter((p) => p.website && p.phone);
// Deduplicate across searches and runs on `placeId`, which is stable.
```

### Use cases

- **Lead generation** — every business of a kind in an area, with website
  and phone, ready for outreach or a contact-details crawler.
- **Local SEO and rank tracking** — where a business sits in Maps for its
  keywords, by `position`, over time.
- **Market research** — how many competitors a category has in a city, how
  they rate, where they cluster.
- **Location datasets** — coordinates, categories and hours for mapping,
  routing or analysis.
- **Data enrichment** — attach a Place ID, rating and hours to a list of
  businesses you already have.

### Pricing

Simple pay-per-result pricing — no subscription and no start fee.

| Event | Price |
|---|---|
| Place returned | $0.0005 |

What that comes to:

- A search at the default depth, 20 places: **$0.01**.
- Everything Google lists for a search, around 140 places: **about $0.07**.
- 1,000 places, however many searches they come from: **$0.50**.

Error rows are never charged, and a search Google has no places for costs
nothing.

### Limits worth knowing

- **Google stops at roughly 120–140 places a search.** That is where Maps
  itself stops scrolling too. Narrow the search for different places.
- **The place goes in the search.** A search without one is answered for the
  data centre's location, not yours.
- **Reviews themselves are not included** — the count and rating are. The
  `googleMapsUrl` opens the place where reviews can be read.
- **Some listings have no phone, website or hours.** Those fields are null
  rather than guessed.

### FAQ

**Is scraping Google Maps legal?**
Results come from Google Maps and are subject to Google's terms — check them,
and your own use, before relying on the data. This Actor reads the same
listing data Maps shows anyone, without logging in.

**Do I need a Google Maps API key?**
No. There is no key, no login, no browser and no quota of your own to manage.

**Why is `reviewCount` null on some rows?**
Google sometimes answers with a lighter record that carries the rating but not
the count. With `reviewCounts` on (the default) such pages are re-read until
the counts arrive, which nearly always works. Null means every re-read came
back light, or you switched the option off.

**How do I know a search failed if the run stays green?**
Check for rows with `type: "error"` — that is the failure channel by design, so
one bad search never costs the rest of the job. The run's status message also
counts how many searches could not be read.

Results come from Google Maps and are subject to Google's terms. Unofficial,
and not affiliated with Google.

# Actor input Schema

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

What to search Google Maps for, one search per line, exactly as you would type it into Maps. Put the place in the search — "plumbers in denver co", "coffee shops near shibuya station", "hotels in lisbon" — because a search with no place in it is answered for wherever Google thinks the request came from, which is not where you are. Any language works.

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

How many places to return per search. Google lists 20 per request and runs out at roughly 120–140 for most searches, the same point where Maps itself stops scrolling — asking for more returns everything Google has. Narrow the search (a neighbourhood instead of a city, or a more specific category) to get different places rather than more of them.

## `reviewCounts` (type: `boolean`):

Google answers some requests with a lighter record that carries the star rating but not the number of reviews. On, a page that comes back that way is re-read (up to four more times) until the counts arrive, which nearly always works and costs a few extra seconds. Off, the first answer is kept and `reviewCount` may be null on some rows.

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

The language Google answers in — place names, categories and opening hours: en, de, fr, es, ja, pt-BR… Defaults to en.

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

The country Google searches from, as a two-letter code: US, GB, DE, FR, JP… It nudges which places rank first; the place named in the search itself decides where. Defaults to US.

## Actor input object example

```json
{
  "queries": [
    "coffee shops in austin tx",
    "dentists in miami fl",
    "boulangerie paris 11e"
  ],
  "maxPlaces": 20,
  "reviewCounts": true,
  "language": "fr",
  "country": "FR"
}
```

# Actor output Schema

## `places` (type: `string`):

One row per place: photo, name, rating, reviews, category, address and the search it answered.

## `contacts` (type: `string`):

Each place's website, phone, Google Maps link and IDs, for outreach or crawling.

## `raw` (type: `string`):

Every field of every item, exactly as pushed.

# API

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

## JavaScript example

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

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

// Prepare Actor input
const input = {
    "queries": [
        "coffee shops in austin tx"
    ]
};

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

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

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

```

## Python example

```python
from apify_client import ApifyClient

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

# Prepare the Actor input
run_input = { "queries": ["coffee shops in austin tx"] }

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

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

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

```

## CLI example

```bash
echo '{
  "queries": [
    "coffee shops in austin tx"
  ]
}' |
apify call simple.actor/google-maps --silent --output-dataset

```

## MCP server setup

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

```

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/6vd6lbIxA0jPN8XW5/builds/sjdfdmDQKegd7RzOO/openapi.json
