# 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
- **Features:** Limited permissions
- **Stats:** 42 total users, 24 monthly users, 96.5% 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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

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

## How to integrate an Actor?

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

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

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

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

# README

## 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 review count or the opening hours,
  on every place of a page or only on some. Such a page is read again until a
  complete one arrives.
- **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 on any of its places. Off is faster; `reviewCount` and `openingHours` 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: the country Google reads the search from. Set it to the place's country — it decides which city a shared name means. |

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

**Name the country when a city's name exists in more than one.** Google
reads "dentists in manchester" through `country`, which defaults to US, so it
can come back as Manchester, New Hampshire, as Manchester, England, or as a
mix of the two that changes from one page of results to the next. Measured
with the default: 8 of 20 pages were all New Hampshire and 10 mixed both;
with `country` set to `GB`, all 20 were England. Birmingham (Alabama) and
London (Ontario, Ohio) mixed in the same way. Write "dentists in manchester
uk", or set `country` to the place's country. Every row's `address` says where
that place is.

#### 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), and on a row whose `reviewCount` is null for the reason above. |
| `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`, `read_failed`, `timed_out` or `parse_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.

#### Runs that reach their time limit

A run stops reading shortly before its own timeout, keeps every place it has
already found, and gives each search it never reached a row with
`error: "timed_out"`. The run still succeeds, so a long batch returns what it
managed instead of being killed with nothing. A search that stopped partway
keeps the places it read and says so in the log. If you see those rows, raise
the run's timeout or put fewer searches in one run — a search of 20 places
takes a few seconds, and one paged to Google's full depth can take a couple of
minutes.

When a run does fail, this Actor reports the failure to its author so it can be
fixed: the error and where in the code it happened, how long the run had been
going and what it had delivered, and the shape of the input — how many
searches, how deep, which language and country. Your search terms and your
results are never part of that report.

The one thing that fails a run is the Actor itself being broken. An error it
did not expect on a search is a row too, with `error: "parse_failed"`, and the
other searches carry on. Only when every search in a run ends that way does
the run fail, with the reason in its status message and in each row.

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, as you would type it into Maps. Put the place in the search — "plumbers in denver co", "coffee shops near shibuya station" — because a search with no place in it is answered for wherever Google thinks the request came from, which is not where you are, and name the country when a city's name exists in several ("dentists in manchester uk"). 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 or the opening hours, on every place of a page or only on some. On, a page that comes back that way is re-read (up to eight more times) until a complete one arrives, which nearly always works and costs a few extra seconds. Off, the first answer is kept and `reviewCount` and `openingHours` 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… Set it to the country of the place you search: a city name shared by several countries is read for this one, so with the default US, "dentists in manchester" comes back as Manchester, New Hampshire, or mixed with Manchester, England, page by page. It also nudges which places rank first. 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/hClxShn4asDSB4mGX/openapi.json
