# Google Maps Places Scraper: Hours, Ratings, Websites (`arcadium/google-maps-places`) Actor

Businesses on Google Maps by search term and area: address, coordinates, rating, website, opening hours; $2.00 per 1,000 places. Area split into map cells past one result list, booking links, optional details, free coverage rows.

- **URL**: https://apify.com/arcadium/google-maps-places.md
- **Developed by:** [Arcadium Data](https://apify.com/arcadium) (community)
- **Categories:** Lead generation, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 places

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

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

## What's an Apify Actor?

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 Places Scraper: Hours, Ratings, Websites

Give it a search term and an area (a city, district or postcode, a bounding box, a circle or a GeoJSON polygon) and it returns every business Google Maps shows there as one JSON row each: name, categories, full address and its parts, coordinates, star rating and review count, website, phone, opening hours for the next 7 days, open-now text and booking links. Or give it place links, place IDs or CIDs. $2.00 per 1,000 places; you pay only for rows you get.

**Why this actor**

- **Past one result list.** One Google Maps search stops at about 200 places. The area is split into map cells and every cell that is still full is split again, so a city returns all of its dentists, not the first page.
- **Fast and light.** Google's own data answers, no browser.
- **Listings as Google shows them.** Name, phone and website exactly as the business publishes them on its Google listing. No reviewer names or profiles, nothing behind a login; emails and phone numbers inside free-text descriptions are removed.
- **Know what you got.** One free `coverage` row per search: places delivered, map cells searched, cells that were still full at the smallest size, places filtered out, and why the search stopped. A free `error` row with a `code` for every input that could not be read.
- **Paste your input.** compass/crawler-google-places inputs run as they are for places: `searchStringsArray`, `locationQuery` (or `city`/`state`/`county`/`postalCode`/`countryCode`), `maxCrawledPlacesPerSearch` (missing = all places, as in compass), `language`, `placeMinimumStars`, `categoryFilterWords`, `searchMatching`, `website`, `skipClosedPlaces`, `scrapePlaceDetailPage`, `customGeolocation` (Polygon, MultiPolygon, FeatureCollection of polygons, Point with `radiusKm`), `startUrls` and `placeIds` (place links, place ids, search links). Reviews, images, contacts, leads, social profiles, table reservations, order-online, web results and directories are not part of this actor: those fields are ignored with a warning. Inputs of scraperlink/google-maps-scraper (`query`, `location`, `num`, `hl`, `gl`, coordinates) and vortex_data/google-maps (`locationQueries`, `languages`, `extractPlaceDetails`) also run; several locations use the first.

Built by Arcadium Data: public data, dated and agent-ready.

### Use with AI agents

Agents can call it through the Apify MCP server (`https://mcp.apify.com`) as `arcadium/google-maps-places`; the input schema is read from the actor, the result is a dataset of JSON rows. Filter on `rowType`.

Input example, "all dentists in Austin, TX":

```json
{ "searchTerms": ["dentist"], "location": "Austin, TX", "maxPlacesPerSearch": 0 }
```

"Coffee shops rated 4.5+ within 2 km of a point, with details":

```json
{ "searchTerms": ["coffee shop"], "latitude": 52.52, "longitude": 13.405, "radiusKm": 2, "minRating": 4.5, "includeDetails": true }
```

Cost: 500 places at $2.00 per 1,000 is $1.00 (Gold plans $0.50); details add $0.50 per 1,000.

Links work too: a place link, place id or CID reads that place; a search link such as `https://www.google.com/maps/search/dentist/@30.2672,-97.7431,14z` searches "dentist" in the map area that link shows (the area a 1024 x 768 window shows at that zoom), cell by cell; a satellite or 3D link with `<n>m` instead of `<n>z` is read as a camera altitude of n metres (our assumption from how such links behave, not documented by Google); a place link without an id is looked up by its name near its coordinates. Links to other sites become free `invalid_input` error rows.

Output example (one place row, shortened):

```json
{
  "rowType": "place",
  "placeId": "ChIJCar0f49ZwokR6ozLV-dHNTE",
  "cid": "3545819340560108778",
  "name": "Katz's Delicatessen",
  "personNamed": false,
  "category": "Deli",
  "address": "205 E Houston St, New York, NY 10002",
  "city": "New York",
  "postalCode": "10002",
  "latitude": 40.722233,
  "longitude": -73.987429,
  "rating": 4.5,
  "reviewCount": 56440,
  "website": "https://katzsdelicatessen.com/",
  "phone": "(212) 254-2246",
  "phoneE164": "+12122542246",
  "openingHours": [{ "day": "Monday", "date": "2026-11-02", "hours": ["8 AM–11 PM"] }],
  "closed": false,
  "searchTerm": null,
  "scrapedAt": "2026-11-02T18:00:00.000Z"
}
```

### What you get

**Place row** (`rowType: "place"`, charged as `place`):

| Field | What it holds |
| --- | --- |
| `placeId`, `cid`, `featureId`, `placeUrl` | Google's ids of the place and its Maps link. `placeId` is stable: deduplicate on it between runs. |
| `name`, `personNamed` | Business name as Google shows it; `personNamed: true` when the listing is named after a person (e.g. a sole practitioner's practice, "Bo Stanley, DDS"). |
| `category`, `categories`, `description` | Main and all Google categories; Google's one-line summary. |
| `address`, `street`, `neighborhood`, `city`, `postalCode`, `state`, `countryCode` | Full address and its parts. |
| `latitude`, `longitude`, `timeZone` | Location. |
| `rating`, `reviewCount` | Google stars and number of reviews. |
| `website`, `bookingLinks` | Website the business lists; booking and ordering links (tracking removed). |
| `openingHours`, `openNowText`, `closed` | Hours of the next 7 days (`[{ day, date, hours }]`, holiday-aware), open-now text, closed flag. |
| `phone`, `phoneE164` | Business phone as published on the listing (on by default; switch `includePhones` off to leave them empty). |
| `details` | With `includeDetails` (charged as `place-details`): plus code, long description, amenity and accessibility attributes, review topics with mention counts. `null` (and not charged) when the detail request failed. |
| `searchTerm`, `searchLocation`, `rank`, `scrapedAt` | Which search found it, its position, when it was read. |

**Coverage row** (`rowType: "coverage"`, free): `delivered`, `duplicates` (places an earlier search of the run already delivered), `cellsSearched`, `cellsSplit`, `cellsSaturatedAtMin` (cells still full at the smallest size: places there may be missing; lower `minCellKm` to go deeper), `filteredOut`, `outsideArea`, `personNamed`, `detailsFailed`, `detailsSkipped` (places delivered without a detail request while details were paused after 5 failures in a row; one retry after 25 places turns them back on), `detailsCapped`, `pages` and `maxPages`, `areaSource` (`location`, `coordinates`, `link`), `stopReason` (`limit`: max places reached; `end`: the area is exhausted; `budget`: the page budget or the run's max charge was reached; `error`), `capSource` (what cut the search: `user` for a limit you set or the run's max charge, `default` for a default page budget, `site` when Google's own result cap left places out).

**Page budget.** Filters run on the places Google returns, so a strict filter over a big area could read a whole city and keep almost nothing. Each search therefore stops after `maxPagesPerSearch` search pages (default 3 per 20 places asked for, at least 30; "all places" = 3,000 pages) and the run after `maxPagesPerRun` (default 5,000), with `stopReason: "budget"`.

**Error row** (`rowType: "error"`, free): `input`, `code` (`invalid_input`, `not_found`, `no_match`, `blocked`, `unavailable`), `message`. Retry `blocked` and `unavailable` later.

Listings are published by the businesses for customer contact. A sole trader's name and phone may still be personal data under GDPR; you are responsible for using them lawfully.

Reviews of the places are not part of this actor: pass the `placeId` values to our Google Maps Reviews Scraper.

### Pricing

| Event | Free plan | Bronze | Silver | Gold+ |
| --- | --- | --- | --- | --- |
| Place (per 1,000) | $2.00 | $1.60 | $1.40 | $1.00 |
| Place details (per 1,000, only with `includeDetails`) | $0.50 | $0.50 | $0.50 | $0.50 |

Coverage and error rows are free. Each run adds a $0.001 per GB start fee. Set a maximum charge on the run and the actor stops when it is reached.

### Related actors

- [Yelp Reviews Scraper: All Languages, Owner Replies](https://apify.com/arcadium/yelp-reviews)
- [Review Monitor: Negative Review Alerts and Rating Drift](https://apify.com/arcadium/reputation-monitor)

### Limitations

- Price level and popular times are not in the data Google serves without signing in; they are not delivered.
- `closed` does not tell permanently and temporarily closed apart.
- One location per run; several search terms share it. When both a location and coordinates are given, the coordinates are used (with a warning).
- Run time: each place with details is one more request (~0.5 s). A run reads at most 4,000 place details; places past that come without details (not charged for details, counted as `detailsCapped`). The default run timeout is 1 hour: rows delivered before a timeout are kept and charged, but the coverage row of the search in progress is not written. For more than ~4,000 places with details, raise the timeout and split the run.

# Actor input Schema

## `searchTerms` (type: `array`):

What to look for on Google Maps, one term per line, such as "dentist", "coffee shop" or "plumber". Each term is searched in the area below and returns up to the max places per search. Also accepts searchStringsArray.

## `location` (type: `string`):

Where to search: a city, district, region or postcode as you would type it into Google Maps, such as "Austin, TX" or "Schwabing, Munich". The area is the map Google shows for it, searched cell by cell so results are not capped at one result list. Leave empty when you give coordinates or a polygon below, or to read Google's own result list for the term (up to about 200 places). Also accepts locationQuery.

## `maxPlacesPerSearch` (type: `integer`):

Places to return for each search term (or search link), 0 = all places found in the area (up to 20,000). Default 50. Each place is one charged result. Also accepts maxCrawledPlacesPerSearch; a compass input without it means all places, as in compass.

## `places` (type: `array`):

Places to read directly instead of (or as well as) searching: a Google Maps place link, a place id (ChIJ...), a CID or a place name with its city such as "Katz's Delicatessen New York". A Google Maps search link (/maps/search/dentist/@30.27,-97.74,14z) runs as a search of its text in the map area the link shows. Links to other sites are listed as free error rows. Also accepts startUrls and placeIds.

## `boundingBox` (type: `object`):

Search inside this box instead of a location name: {"south": 30.2, "west": -97.8, "north": 30.35, "east": -97.65} in degrees.

## `latitude` (type: `number`):

Centre of a circle to search (with longitude and radius), such as 30.2672.

## `longitude` (type: `number`):

Centre of a circle to search, such as -97.7431.

## `radiusKm` (type: `number`):

Radius of the circle around the centre, in km, such as 2 or 0.5. Default 5; values above 500 are treated as 500.

## `polygon` (type: `object`):

Search only inside this GeoJSON Polygon, MultiPolygon or FeatureCollection of polygons (or a Point with radiusKm); places outside it are dropped. Rings need at least 4 \[longitude, latitude] positions. Also accepts customGeolocation.

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

Language of names, categories and opening hours as a code such as en, de or pt-BR.

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

Google market as a 2-letter code such as us, de or gb (affects ranking, not the area). Also accepts countryCode.

## `minRating` (type: `number`):

Only places rated at least this many stars, such as 4 or 4.5. Empty = no filter. Also accepts placeMinimumStars (two ... fourAndHalf).

## `categoryFilter` (type: `array`):

Only places with a Google category containing one of these words, such as "dentist" (drops orthodontists listed only as orthodontists). Also accepts categoryFilterWords.

## `website` (type: `string`):

Filter by whether the place lists a website.

## `skipClosed` (type: `boolean`):

Leave out places Google marks as closed. Also accepts skipClosedPlaces.

## `nameMatch` (type: `string`):

Keep only places whose name contains (or equals) the search term, ignoring case and accents. Also accepts searchMatching (all, only_includes, only_exact).

## `includeDetails` (type: `boolean`):

Read each place's detail page too: plus code, long description, amenity and accessibility attributes, review topics. One extra request and one extra place-details charge per place. Also accepts scrapePlaceDetailPage.

## `includePhones` (type: `boolean`):

Add the phone number the business publishes on its Google listing (on by default). Switch off to leave phone and phoneE164 empty.

## `minCellKm` (type: `number`):

Advanced. The area is split into smaller map cells while a cell still returns 100+ places; cells are not split below this size. Default 0.4.

## `maxPagesPerSearch` (type: `integer`):

Advanced. Search pages (20 places each, before filters) one search may read before it stops with stopReason "budget". Default: 3 per 20 places asked for, at least 30 (100 places: 30 pages; all places: 3,000). Keeps a search whose filters drop most places from reading a whole city.

## `maxPagesPerRun` (type: `integer`):

Advanced. Search pages the whole run may read (all searches and place-name lookups together). Default 5,000.

## `includeCoverageRows` (type: `boolean`):

One free row per search: places delivered, map cells searched, places filtered out and why the search stopped.

## `includeErrorRows` (type: `boolean`):

One free row per input that could not be read, with a code and a message.

## `maxRetries` (type: `integer`):

Retries per request after a temporary error.

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

Optional. By default the actor picks the cheapest working connection itself; set your own proxies here only if needed.

## Actor input object example

```json
{
  "searchTerms": [
    "dentist"
  ],
  "location": "Austin, TX",
  "maxPlacesPerSearch": 50,
  "language": "en",
  "country": "us",
  "website": "any",
  "nameMatch": "all",
  "includePhones": true,
  "minCellKm": 0.4,
  "maxPagesPerRun": 5000,
  "includeCoverageRows": true,
  "includeErrorRows": true,
  "maxRetries": 4
}
```

# Actor output Schema

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

All rows of this run in the default dataset.

## `view_overview` (type: `string`):

No description

## `view_coverage` (type: `string`):

No description

## `view_errors` (type: `string`):

No description

## `record_stats` (type: `string`):

Requests, data volume, map cells, retries and cost per result for this run.

# 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 = {
    "searchTerms": [
        "dentist"
    ],
    "location": "Austin, TX",
    "maxPlacesPerSearch": 50,
    "language": "en",
    "country": "us",
    "website": "any",
    "skipClosed": false,
    "nameMatch": "all",
    "includeDetails": false,
    "includePhones": true,
    "minCellKm": 0.4
};

// Run the Actor and wait for it to finish
const run = await client.actor("arcadium/google-maps-places").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 = {
    "searchTerms": ["dentist"],
    "location": "Austin, TX",
    "maxPlacesPerSearch": 50,
    "language": "en",
    "country": "us",
    "website": "any",
    "skipClosed": False,
    "nameMatch": "all",
    "includeDetails": False,
    "includePhones": True,
    "minCellKm": 0.4,
}

# Run the Actor and wait for it to finish
run = client.actor("arcadium/google-maps-places").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 '{
  "searchTerms": [
    "dentist"
  ],
  "location": "Austin, TX",
  "maxPlacesPerSearch": 50,
  "language": "en",
  "country": "us",
  "website": "any",
  "skipClosed": false,
  "nameMatch": "all",
  "includeDetails": false,
  "includePhones": true,
  "minCellKm": 0.4
}' |
apify call arcadium/google-maps-places --silent --output-dataset

```

## MCP server setup

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

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/6JhevgfsbUoK5OjXK/builds/cd5lyq7arojK6FCPJ/openapi.json
