# Google Maps Scraper - Places, Reviews, Emails & Socials (`lanternlab318/google-maps`) Actor

- **URL**: https://apify.com/lanternlab318/google-maps.md
- **Developed by:** [Lantern Lab](https://apify.com/lanternlab318) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.49 / 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.

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, Reviews, E-mails & Social Profiles

A Lantern Lab Actor. Search Google Maps by keyword and location, or paste place links, and get each business back: name, categories, address, coordinates, rating, review count, the business's published phone and website, opening hours, Google place ID and a Maps URL. Optionally add the public reviews Google shows for each place, and the e-mail addresses and social profiles published on the business's own website. Works in any country — tested in the US, Europe, Latin America and Asia. No Google login, no cookies, no account of your own.

**Price:** pay per result, one price for everyone:

| Event | Price | When it is charged |
|---|---|---|
| `place` | **USD 2.49 per 1,000 places** | One per place row. |
| `contact` | **USD 1.49 per 1,000 places with contacts** | Only when the website actually yielded at least one e-mail or social profile. |
| `review` | **USD 0.49 per 1,000 reviews** | One per review row. |
| Run start | **USD 0.005 per run** | Once per run. |

Empty results, blocked or failed inputs, duplicates, per-input status rows and the run summary are free.

### What you get

```json
{
  "recordType": "place",
  "placeId": "ChIJP8p2kJC1RIYR2qrGoXZtZCk",
  "featureId": "0x8644b5909076ca3f:0x29646d76a1c6aada",
  "cid": "2982629209513831130",
  "name": "Terrible Love Coffee",
  "categories": ["Coffee shop", "Coffee stand"],
  "address": "3908 Avenue B, Austin, TX 78751",
  "city": "Austin",
  "postalCode": "78751",
  "state": "Texas",
  "countryCode": "US",
  "latitude": 30.3045132,
  "longitude": -97.735632,
  "rating": 4.9,
  "reviewCount": 433,
  "phone": null,
  "website": "http://terriblelovecoffee.com/",
  "openingHours": [{ "day": "Monday", "hours": "7:30 AM–2 PM" }],
  "openStatusText": "Open · Closes 2 PM",
  "url": "https://www.google.com/maps/search/?api=1&query=Terrible+Love+Coffee&query_place_id=ChIJP8p2kJC1RIYR2qrGoXZtZCk",
  "emails": ["info@terriblelovecoffee.com"],
  "socialProfiles": {},
  "contactsStatus": "found",
  "reviewsStatus": "ok",
  "reviewsCollected": 5,
  "reviewsLimited": true,
  "searchTerm": "coffee",
  "searchQuery": "coffee in Austin, TX",
  "rank": 1,
  "partial": false,
  "collectedAt": "2026-10-30T12:44:50.402Z"
}
```

```json
{
  "recordType": "review",
  "reviewId": "Ci9DQUlRQUNvZENodHljRjlvT2s5eVpGUjNhbGcxTUdzMVlWOXZPSFZLUkRGeGFHYxAB",
  "placeId": "ChIJP8p2kJC1RIYR2qrGoXZtZCk",
  "placeName": "Terrible Love Coffee",
  "rating": 5,
  "text": "In my search for unique coffee shops, I visited Terrible Love…",
  "textLanguage": "en",
  "publishedAt": "2026-09-02T03:35:54.561Z",
  "publishedAtText": "a month ago",
  "authorName": "Turan A.",
  "collectedAt": "2026-10-30T12:44:50.409Z"
}
```

A field Google does not publish for a place is `null` — never guessed.

### Input

Give it search terms with a location, place links, or both.

| Field | Default | Meaning |
|---|---|---|
| `searchTerms` | — | What to search for, one per line (`"dentist"`, `"coffee shop"`). |
| `location` | — | Where: `"Austin, TX"`, `"Berlin, Germany"`, `"Mexico City"`. Appended as `"<term> in <location>"`. |
| `placeUrls` | — | Google Maps place URLs, `maps.app.goo.gl` short links, `?cid=` links or place IDs (`ChIJ…`). |
| `maxPlaces` | 20 | Hard limit per search term. Never exceeded and never over-charged. |
| `includeReviews` | false | Add the public reviews Google shows for each place. |
| `maxReviewsPerPlace` | 5 | Hard limit on review rows per place. |
| `includeContacts` | false | Read each place's website for e-mails and social profiles. |
| `language` / `countryCode` | `en` / `us` | Google interface language and country. |
| `maxUpstreamRequests` | derived | Hard ceiling on requests to Google. |

```json
{
  "searchTerms": ["dentist"],
  "location": "Mexico City",
  "maxPlaces": 20,
  "includeContacts": true
}
```

### Output

Three row types share the dataset; the **Places**, **Reviews** and **Input status** views split them.

- `place` — one per place, deduplicated across the whole run by Google place ID.
- `review` — one per review, with its parent `placeId`, `featureId` and `placeName`, and Google's own `publishedAt`.
- `input_status` — one per search term and per place link, never charged: how many places it returned, how many pages were read, whether Google ran out of results (`endOfResults`), and when it is incomplete, `partial: true` with a `partialReason`.
- `RUN_SUMMARY` — the last row, also saved in the key-value store.

#### What is charged

- `place`: every place row. A place that matches several of your search terms is written and charged once.
- `contact`: only places whose website gave at least one e-mail or social profile. `contactsStatus` says what happened for every other place: `no_website`, `none_found` or `website_unreachable` — all free.
- `review`: every review row. A review is written once even if its place shows up twice.
- Nothing for an input that returned no place, for a blocked or failed input, or for the status and summary rows.
- With a maximum charge set on the run, collection stops at what it pays for and the inputs not reached are marked `charge_limit_reached` / `not_collected`.

#### Empty is not the same as incomplete

An input that Google answered with no place is `partial: false, placesReturned: 0` — a real empty result. An input that was refused, failed, hit the request ceiling or the maximum charge is `partial: true` with the reason (`blocked`, `upstream_error`, `upstream_request_cap`, `charge_limit_reached`, `not_collected`). A run where every input was refused fails instead of finishing with zero rows.

### Limits

- **Reviews:** Google shows signed-out visitors a "limited view" of Maps with only a handful of reviews per place (usually 5); the full review list needs a Google sign-in, which this Actor does not use. Every place row says `reviewsLimited: true` when Google lists more reviews than it showed.
- **Contacts** (e-mails; Facebook, Instagram, X, LinkedIn company pages, YouTube, TikTok and Pinterest links) come from the business's own home page and, when that has none, from its contact page. Addresses are not verified, and websites that block automated visitors give `website_unreachable`.
- Not collected: popular times, photos, owner replies, anything behind a Google sign-in.
- Only public business information is collected. For reviews, only the reviewer's public display name is kept — no profile link, no photo.

### Development

```bash
npm install
npm test
```

Tests run offline against recorded Google responses in `test/fixtures/`.

# Actor input Schema

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

What to search Google Maps for, one per line: "coffee shop", "dentist", "hotel". Each term is searched separately in the location below and its results are labelled with it. A place that matches several of your terms is returned once and charged once.

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

City, region or country to search in: "Austin, TX", "Berlin, Germany", "São Paulo", "Tokyo". It is appended to every search term as "<term> in <location>" unless the term already names it. Leave it empty only when your terms name the place themselves.

## `placeUrls` (type: `array`):

Collect specific places instead of, or in addition to, searching. Paste a Google Maps place URL (https://www.google.com/maps/place/...), a short link (https://maps.app.goo.gl/...), a ?cid= link, or a place ID (ChIJ...). Search terms are not required when this is filled in.

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

Upper bound on the places one search term may produce. Never exceeded: with 20 you get at most 20 rows per term and pay for at most 20 places. Google serves 20 places per page.

## `includeReviews` (type: `boolean`):

Also write the public reviews Google shows for each place as separate rows: star rating, text, publish time and the reviewer's public display name. Google shows signed-out visitors only a handful of reviews per place (usually 5); each place row says when its full review list is longer. One extra request per place.

## `maxReviewsPerPlace` (type: `integer`):

Upper bound on the review rows one place may produce. Only used when Add public reviews is on.

## `includeContacts` (type: `boolean`):

Read each place's own website (home page, and its contact page when the home page has nothing) for published e-mail addresses and links to Facebook, Instagram, X, LinkedIn (company pages), YouTube, TikTok and Pinterest. The contact event is charged only for places where at least one was actually found; places without a website, or whose website shows none, cost nothing extra. Addresses are not verified.

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

Language Google answers in (names of categories, opening hours, review text where translated): "en", "de", "es", "pt-BR", "ja".

## `countryCode` (type: `string`):

Two-letter country code of the Google interface ("us", "de", "mx"). It does not limit results to that country - the location does that.

## `maxUpstreamRequests` (type: `integer`):

Hard ceiling on the HTTP requests this run sends to Google: search pages, place cards and retries together (website requests for contacts are not counted). At the ceiling collection stops and the inputs not reached are marked partial with reason "upstream_request_cap". Leave empty and the Actor works a ceiling out from your inputs, so a run is never unbounded.

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

The proxy requests go through. The default Apify Proxy works; a refused request is retried twice from a new IP, and a run that is refused anyway reports blocked, never zero results.

## Actor input object example

```json
{
  "searchTerms": [
    "coffee shop"
  ],
  "location": "Austin, TX",
  "maxPlaces": 20,
  "includeReviews": false,
  "maxReviewsPerPlace": 5,
  "includeContacts": false,
  "language": "en",
  "countryCode": "us",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

No description

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

No description

## `reviews` (type: `string`):

No description

## `runSummary` (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 = {
    "searchTerms": [
        "coffee shop"
    ],
    "location": "Austin, TX"
};

// Run the Actor and wait for it to finish
const run = await client.actor("lanternlab318/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 = {
    "searchTerms": ["coffee shop"],
    "location": "Austin, TX",
}

# Run the Actor and wait for it to finish
run = client.actor("lanternlab318/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 '{
  "searchTerms": [
    "coffee shop"
  ],
  "location": "Austin, TX"
}' |
apify call lanternlab318/google-maps --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,lanternlab318/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/7OlwdGpDTzQA5O8EK/builds/xROgMq8ME8ggJMYa1/openapi.json
