# Google Maps Place Scraper — Business by Link or ID (`thenetaji/google-maps-place-scraper`) Actor

For Google Maps links and place ids already in hand. Each one returns a finished business record: name, address, coordinates, phone in two forms, website, categories, rating, opening status, attributes and photos. Built for refreshing a saved list or a column of pasted links.

- **URL**: https://apify.com/thenetaji/google-maps-place-scraper.md
- **Developed by:** [The Netaji](https://apify.com/thenetaji) (community)
- **Categories:** Lead generation, Travel, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 businesses

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/platform/actors/running/actors-in-store#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 Place Scraper

For a list of Google Maps links or place ids that already exists: a stored list being refreshed, a
spreadsheet of links collected by hand, or a link somebody sent. Each one comes back as a finished
business record with the name, the full address, the coordinates, the phone number in two forms, the
website, the categories, the star rating, whether the place is open right now, what it offers, and its
photos.

### What every record carries

| Field | What it holds |
| --- | --- |
| `name` | Business name, as Google files it |
| `cid` | Google's own id for the place, and the value to store for later use |
| `place_id`, `feature_id` | Google's `ChIJ…` id and its Knowledge Graph id, for joining outward |
| `address`, `address_lines`, `address_components` | The address as one line, as display lines, and as Google's own structured list |
| `neighborhood`, `city`, `country_code` | Where Google files the business |
| `latitude`, `longitude`, `timezone` | Where it actually is, and the zone its hours are stated in |
| `categories` | What Google says it is, most specific first |
| `rating` | Star rating out of 5 |
| `phone`, `phone_e164` | The number as displayed, and in the form a dialer or CRM takes |
| `website`, `website_domain` | The site, and the bare domain as a join key |
| `description` | Google's editorial blurb, where it has written one |
| `hours_status`, `hours_today` | Google's own open/closed sentence, and the current day's spans |
| `attributes` | Accessibility, amenities, payment and service options, in Google's own groups |
| `photos` | Up to ten photo links, cover first |
| `maps_url` | Google's own link to the place |

Counted across 20 businesses: `name`, `cid`, `place_id`, `address`, `latitude`, `longitude`,
`rating`, `categories`, `photos` and `timezone` were present on 20 of 20; `website` and `attributes`
on 19 of 20; `phone` on 15 of 20.

### Accepted input

`places` is required and takes one or more places. A place is either Google's own identifier in
`0x…:0x…` form or a Maps link containing one; both are accepted, they may be mixed in one list, and a
link whose colon arrived percent-encoded from a copy-paste still resolves. A place listed twice under
two spellings is fetched once.

`maxItems` caps how many records the run saves and defaults to 100. A list longer than the cap stops
at the cap rather than running to the end of the list, so a list of more than 100 places needs the
value raised or set to `0`.

```json
{
  "places": [
    "0x808587fb77f5f64d:0x25d564f177f99946",
    "https://www.google.com/maps/place/Caffe+Trieste/data=!4m2!3m1!1s0x8085808ca8a3b1a7%3A0xa8a3b1a70cbf0e35"
  ],
  "maxItems": 0
}
```

### Response fields

```json
{
  "cid": "0x808587fb77f5f64d:0x25d564f177f99946",
  "name": "The Coffee Movement",
  "categories": ["Coffee shop", "Cafe"],
  "rating": 4.8,
  "address": "1030 Washington St, San Francisco, CA 94108",
  "neighborhood": "Chinatown",
  "city": "San Francisco, CA",
  "country_code": "US",
  "latitude": 37.7936,
  "longitude": -122.4116,
  "timezone": "America/Los_Angeles",
  "phone": "(415) 780-1902",
  "phone_e164": "+14157801902",
  "website": "https://www.thecoffeemovement.com/",
  "website_domain": "thecoffeemovement.com",
  "hours_status": "Closed · Opens 8 AM Mon",
  "hours_today": ["9 AM–2 PM"],
  "attributes": { "Amenities": ["Restroom", "Wi-Fi"] },
  "photos": ["https://lh3.googleusercontent.com/p/AF1QipN-example=w408-h306-k-no"],
  "place_id": "ChIJ_2f19_uHhYARRpn5d_Fk1SU",
  "maps_url": "https://www.google.com/maps/place/?q=place_id:ChIJ_2f19_uHhYAR"
}
```

Fields Google states nothing for are returned as null rather than omitted, so every record has the
same shape. The list is processed in order, one record per place.

### Questions

**Why did a list of 50 places return fewer than 50 rows?**
Some identifiers did not resolve to a place, or resolved to one whose identity did not match the
request. Both are reported in the run log, one line per place, naming the value that produced no row.
An unknown place produces no row and the run continues to the next identifier, so one dead link in a
list of two hundred does not cost the other 199 records.

**What happens on an identifier that does not resolve?**
It is reported as nothing at all rather than as a business, and that check matters more than it looks.
Google does not always answer a bad identifier with nothing: it can return a DIFFERENT business,
complete and confidently wrong. Every record is therefore verified against the identifier it was
requested for, and a record that does not match is dropped.

**Should this run after a search, to fill in the details?**
No, and this is worth stating because the usual shape of a Maps scraper makes that second step look
compulsory. On Google Maps a search result and a place-detail response are the same record, verified
field by field across both surfaces, so
[Google Maps Scraper](https://apify.com/thenetaji/google-maps-scraper) already returns everything this
Actor returns, twenty businesses at a time. This Actor is for identifiers that arrive from somewhere
other than a search.

**Is a Maps link taken apart before the request?**
No. The link is passed through as it was given and reduced to the identifier at the point where the
place is read, which keeps a single definition of what an identifier is. This also means an unusual
link shape fails with a message naming the value rather than being silently mangled first.

**Which identifier should be stored for later use?**
`cid`. It is the value this Actor and
[Google Maps Reviews Scraper](https://apify.com/thenetaji/google-maps-reviews-scraper) both take, and
every row from [Google Maps Scraper](https://apify.com/thenetaji/google-maps-scraper) publishes it.
`place_id` is published as well, but it is for joining outward to Google's own API rather than for
use here.

**Why does `hours_today` only cover one day?**
Because the full week is not in this payload. Measured across 8 businesses, the hours block held
exactly one day every time, always the current one, and no slot anywhere in the record carried four or
more weekday names. `hours_status` carries Google's own sentence about the business at the moment of
reading, such as `Closed · Opens 8 AM Mon`.

**Why is there no review count beside `rating`?**
Because the integer that resembles one disagrees with the count Maps displays, reading 1,324 where
Maps said 1,723 and 10,337 where Maps said 5,950, so it is not published. Counting the rows
[Google Maps Reviews Scraper](https://apify.com/thenetaji/google-maps-reviews-scraper) returns
reproduces the displayed number instead.

**Why is `attributes` an object of varying keys rather than fixed columns?**
Because Google's own groups vary by business and by category. A fixed set of columns would invent
some and drop others, so the map is saved as it arrives.

**Why does the record for a chain look identical across branches?**
Because each branch is a separate place with its own `cid`, and identical fields across branches
means the branches genuinely carry the same data, most often the same `website` and `categories`.
`address`, `latitude`, `longitude` and `phone` are the fields that separate them.

### Related Actors

[Google Maps Scraper](https://apify.com/thenetaji/google-maps-scraper) discovers places by searching
an area and returns this same record for each one, twenty per page. It is the cheaper way to obtain
records that are not already identified.

[Google Maps Reviews Scraper](https://apify.com/thenetaji/google-maps-reviews-scraper) takes the same
identifiers this Actor takes and returns each place's reviews, newest first, to the end of the
corpus.

# Actor input Schema

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

One or more Google Maps places. Paste a Maps link, or Google's own place id in `0x…:0x…` form — both work, and you can mix them. Every row the **Google Maps Scraper** saves publishes that id as `cid`, so a harvest feeds straight in here.

## `maxItems` (type: `integer`):

Caps how many businesses the run saves. The default is 0, meaning no cap: every link or id in the list above is looked up. Set a number only to stop a long list early.

## Actor input object example

```json
{
  "places": [
    "0x808587fb77f5f64d:0x25d564f177f99946"
  ],
  "maxItems": 0
}
```

# Actor output Schema

## `dataset` (type: `string`):

All records scraped by 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 = {
    "places": [
        "0x808587fb77f5f64d:0x25d564f177f99946"
    ],
    "maxItems": 0
};

// Run the Actor and wait for it to finish
const run = await client.actor("thenetaji/google-maps-place-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 = {
    "places": ["0x808587fb77f5f64d:0x25d564f177f99946"],
    "maxItems": 0,
}

# Run the Actor and wait for it to finish
run = client.actor("thenetaji/google-maps-place-scraper").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 '{
  "places": [
    "0x808587fb77f5f64d:0x25d564f177f99946"
  ],
  "maxItems": 0
}' |
apify call thenetaji/google-maps-place-scraper --silent --output-dataset

```

## MCP server setup

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

```

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/tdNT8aTLe1zaZbmut/builds/gPiTKcnwQ811eiU6m/openapi.json
