# Google Maps Shared List Scraper: Export Saved Places to CSV (`steadyscrape/google-maps-shared-list-scraper`) Actor

Export every place of a public Google Maps list or saved places share link (maps.app.goo.gl, placelists link or list id): name, address, coordinates, owner note, date added, CID, place id, plus rating, reviews, category, phone, website and hours. No login. Export to JSON, CSV or Excel, or via API.

- **URL**: https://apify.com/steadyscrape/google-maps-shared-list-scraper.md
- **Developed by:** [Steady Scrape](https://apify.com/steadyscrape) (community)
- **Categories:** Travel, Lead generation, Marketing
- **Security:** Limited permissions
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 place with details

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 Shared List Scraper (export Google Maps lists and saved places)

Export **every place of a shared Google Maps list** to a spreadsheet: give the list's **share link**
(`maps.app.goo.gl/…`), its link (`google.com/maps/placelists/list/…`) or its id, and get one row per place in the
list's own order with the **place name, address, coordinates, the note the list's owner wrote, the date it was added,
its CID, place id and Google Maps link**, plus the list's name, owner and description. Turn on details (on by default)
and each place also gets its **rating, reviews count, categories, phone, website, opening hours** and whether Google
marks it closed, read from its own Google Maps page.

No login, no browser, no Google API key. Export the Google Maps list to CSV, Excel or JSON, or read it through the API.

### What you can do with an exported Google Maps list

- **Move saved places out of Google Maps**: a travel list, a "want to go" list a friend shared, a list of shops for a
  trip, into a spreadsheet, a CRM or another map app, with coordinates and notes kept.
- **Turn curated lists into leads**: a list of restaurants, coffee shops or suppliers someone put together becomes a
  table with phone, website, rating and opening hours for each place.
- **Track a list over time**: run it on a schedule and compare `addedAt` and the place count to see what was added.
- **Read Google's own lists**: Google Maps publishes curated lists such as "Fan-Favorite Dining: New York" (the form's
  example); every place comes with Google's note on why it was picked.

### Input

| field | what it does |
|---|---|
| `listUrls` | One per line: a share link (`https://maps.app.goo.gl/…`), a list link (`https://www.google.com/maps/placelists/list/…`), or a list id. Up to 100 lists. |
| `maxPlacesPerList` | The first N places of each list, in the list's order; 0 for every place (up to 5,000 a list). |
| `includeDetails` | On (default): each place's rating, reviews count, categories, phone, website, opening hours and closed mark, from its Google Maps page. Off: only what the list shows, much faster and cheaper. |
| `language`, `country` | Language of categories and opening hours, and the country Google answers for (default `en`, `us`). |

#### Input example

```json
{
    "listUrls": [
        "https://maps.app.goo.gl/cBTwYoeA1SYb141h8",
        "https://www.google.com/maps/placelists/list/JJsYTIW5umJ8dVdHm675Eg"
    ],
    "maxPlacesPerList": 0,
    "includeDetails": true
}
```

### Output: one row per place of the Google Maps list

```json
{
    "rowType": "place_with_details",
    "input": "https://www.google.com/maps/placelists/list/niLmM5FKR_pa_3dHR1UTfw",
    "inputIndex": 1,
    "listId": "niLmM5FKR_pa_3dHR1UTfw",
    "listName": "Fan-Favorite Dining: New York",
    "listDescription": "New York picks on the 2026 fan-favorite dining list. …",
    "listOwner": "Google Maps",
    "listPlaceCount": 43,
    "listUrl": "https://www.google.com/maps/placelists/list/niLmM5FKR_pa_3dHR1UTfw",
    "positionInList": 1,
    "name": "Katz's Delicatessen",
    "address": "205 E Houston St, New York, NY 10002",
    "lat": 40.7222727,
    "lng": -73.9873739,
    "note": "Featured as an essential stop for a meal.",
    "addedAt": "2026-09-21T22:20:44.000Z",
    "cid": "3545819340560108778",
    "placeId": "ChIJCar0f49ZwokR6ozLV-dHNTE",
    "featureId": "0x89c2598f7ff4aa09:0x313547e757cb8cea",
    "kgmid": null,
    "mapsUrl": "https://www.google.com/maps?cid=3545819340560108778",
    "rating": 4.5,
    "reviewCount": 56495,
    "categories": ["Deli", "American restaurant", "Jewish restaurant", "Restaurant", "Sandwich shop"],
    "primaryCategory": "Deli",
    "phone": "(212) 254-2246",
    "phoneInternational": "+12122542246",
    "website": "https://katzsdelicatessen.com/",
    "openingHours": ["Monday: 8 AM–11 PM", "Tuesday: 8 AM–11 PM", "…", "Saturday: Open 24 hours", "Sunday: 12 AM–11 PM"],
    "closed": false,
    "scrapedAt": "2026-11-05T12:00:00.000Z"
}
```

#### Place fields

| field | meaning |
|---|---|
| `rowType` | `place_with_details`, `place` (details off, or not available for that place), or `list` for a free row (see below) |
| `listId`, `listName`, `listDescription`, `listOwner`, `listPlaceCount`, `listUrl` | The list: its name, description, the owner's name as the list page shows it, and how many places it holds |
| `positionInList` | The place's position in the list (1 = first) |
| `name` | The place's name |
| `address` | As the list shows it; Google's own lists show none, so with details it comes from the place's page |
| `lat`, `lng` | Coordinates |
| `note` | The note the list's owner wrote for the place |
| `addedAt` | When it was added to the list (ISO, UTC) |
| `cid`, `placeId`, `featureId`, `kgmid` | Google's ids for the place: the customer id used in `?cid=` links, the place id (`ChIJ…`) of Google's Places API, the feature id `0x…:0x…`, and the knowledge graph id when the list carries it |
| `mapsUrl` | The place's page on Google Maps |
| `rating`, `reviewCount` | Star rating and number of reviews (`null` and 0 for a place nobody rated, such as a town) |
| `categories`, `primaryCategory` | Google's categories, primary first |
| `phone`, `phoneInternational` | As shown, and in international format |
| `website` | The website on the place's profile |
| `openingHours` | One line per day, Monday first; empty when the place shows no hours |
| `closed` | Google Maps marks the place closed |
| `detailsStatus` | On a `place` row: `not_asked` (details off), `place_not_found` (Google Maps no longer has the place's page) or `blocked` (Google refused it) |
| `input`, `inputIndex` | The list as you gave it, and its position among the lists given |
| `scrapedAt` | When the run read it (ISO, UTC) |

#### Free rows

A list that delivers no place gets one free `list` row with a `status` and a `reason`:

| `status` | when |
|---|---|
| `list_not_found` | Google Maps has no public list with that id (deleted, made private, never existed), or the share link does not exist |
| `empty` | The list holds no places, or only entries that are not a Google Maps place (dropped pins) |
| `invalid` | Not a list: another site's link, or a share link that opens a single place |
| `blocked` | Google refused every IP the run could ask from before the list was read |
| `spending_limit` | The run's spending limit was reached before this list |

`SUMMARY` in the run's key-value store holds the run's counts.

### Pricing

- **US$0.001 per place with details** (US$1 per 1,000 places): the list row plus rating, reviews, categories, phone,
  website and opening hours.
- **US$0.0005 per place** without details (US$0.50 per 1,000 places), and for a place whose Google Maps page could not be
  read in a run that asked for details.
- Free rows (not found, empty, invalid, blocked, spending limit) are never charged. The run stops at your spending
  limit before reading a place it cannot pay for.

### How it reads Google Maps, and when Google asks for verification

It asks Google Maps the same questions its own web page asks when you open a shared list: up to 500 places per request,
so most lists take one, and with details one more request per place, at a person's pace, over HTTP. **If Google asks an
IP for verification** — its "unusual traffic" page, a CAPTCHA, a JavaScript check, or any answer that is not Google
Maps' normal data — **that IP is done for the run**: nothing more is sent from it, and the run goes on from another IP of
Apify's datacenter proxy. It never solves, bypasses or replays a challenge. Plain overloads (HTTP 429 or 5xx without a
challenge) take the same way. When every IP the run can use has been refused, the places already read are delivered,
the lists left get a free `blocked` row, and the run ends successfully with a message to try again later.

### Limits

- Only lists shared by their owner (public, or "anyone with the link") can be read; a private list answers as not found.
  Your own "Starred" or "Saved" places are read only once you share them as a list.
- Up to 100 lists per run and 5,000 places per list.
- Google Maps does not show a place's price level ("$$") to a signed-out visitor in the answers this Actor reads, so
  it is not in the output.
- A place in a list that is not a business (a town, a park) has no rating, phone or hours.
- An entry that is not a Google Maps place (a dropped pin) has no place id to read and is left out; the run log counts them.

### Is it legal?

It reads lists their owners shared for anyone with the link to see, and the public Google Maps page of each place,
without logging in, at a pace a person browsing the map could produce. A list's name, notes and owner name are what
its owner chose to publish; the owner's photo and Google account id are left out. Use the data in line with Google's
terms and the laws that apply to you, including privacy laws when a list was made by a person.

This Actor is not affiliated with, endorsed by or sponsored by Google. Google and Google Maps are trademarks of Google
LLC.

# Actor input Schema

## `listUrls` (type: `array`):

One per line: the list's <b>share link</b> (<code>https://maps.app.goo.gl/…</code>, from <i>Share</i> in Google Maps), its <b>link</b> (<code>https://www.google.com/maps/placelists/list/…</code>), or its <b>id</b>. The list must be shared (public or "anyone with the link"); a private list is answered as not found. Up to 100.

## `maxPlacesPerList` (type: `integer`):

The first N places of each list, in the list's own order. 0 delivers every place (up to 5,000 a list).

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

Read each place's own Google Maps page: rating, reviews count, categories, phone, website, opening hours, whether Google marks it closed, and its address when the list shows none. Charged as a place with details. Off: only what the list itself shows (name, address, coordinates, note, date added, ids), charged as a place, the whole list in one request.

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

Language of categories and opening hours, as a code: <code>en</code>, <code>es</code>, <code>de</code>, <code>pt-BR</code>. The list's own name, notes and place names are never translated.

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

Two-letter country code Google answers for (<code>us</code>, <code>gb</code>, <code>de</code>).

## Actor input object example

```json
{
  "listUrls": [
    "https://www.google.com/maps/placelists/list/niLmM5FKR_pa_3dHR1UTfw"
  ],
  "maxPlacesPerList": 10,
  "includeDetails": true,
  "language": "en",
  "country": "us"
}
```

# Actor output Schema

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

No description

## `summary` (type: `string`):

No description

# 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 = {
    "listUrls": [
        "https://www.google.com/maps/placelists/list/niLmM5FKR_pa_3dHR1UTfw"
    ],
    "maxPlacesPerList": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadyscrape/google-maps-shared-list-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 = {
    "listUrls": ["https://www.google.com/maps/placelists/list/niLmM5FKR_pa_3dHR1UTfw"],
    "maxPlacesPerList": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("steadyscrape/google-maps-shared-list-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 '{
  "listUrls": [
    "https://www.google.com/maps/placelists/list/niLmM5FKR_pa_3dHR1UTfw"
  ],
  "maxPlacesPerList": 10
}' |
apify call steadyscrape/google-maps-shared-list-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,steadyscrape/google-maps-shared-list-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/IdX0wZDcFL3ABWQot/builds/8jSkdcMbO1gUFcb5O/openapi.json
