# Google Maps Related Place & Competitor Finder (`dataspryva/google-maps-related-place-and-competitor-finder`) Actor

Find the related and 'people also search for' places Google Maps shows on a business, hotel, or landmark page. Get each related place's name, rating, review count, category, coordinates, and image, linked back to the place you searched.

- **URL**: https://apify.com/dataspryva/google-maps-related-place-and-competitor-finder.md
- **Developed by:** [Dataspryva](https://apify.com/dataspryva) (community)
- **Categories:** Automation, Lead generation, Travel
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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 Related Place & Competitor Finder

Find the places Google Maps treats as related to a business, hotel, or landmark — and keep going when Google shows no carousel by discovering same-category places around the same pin. Enter many URLs, Place IDs, or place names in one run, filter and sort the results, and get an explicit `relationMode` on every row so you always know whether Google recommended the place or it was found nearby.

### What it does

- **Two entry modes:** `byPlace` accepts Google Maps URLs, canonical `ChIJ` Place IDs, and legacy `0x…:0x…` feature IDs; `byQuery` accepts business names plus an optional city or area. Batch both.
- **Carousel first, nearby fallback second:** reads Google's related-place carousel when it exists, then fills short results with places from the source's own category around the same coordinates.
- **Explicit provenance:** every row says `googleCarousel` or `sameCategoryNearby`; fallback rows are never presented as Google recommendations.
- **Filters and order:** minimum rating, minimum reviews, maximum straight-line distance, category text, and sort by recommended order, rating, reviews, or distance.
- **Identity, contacts, and distance:** canonical Place ID, legacy feature ID, CID, KG MID, address, website, phone, plus code, photo count, coordinates, image, and computed `distanceMeters`.
- **Empty fields are omitted** rather than sent as `null` or guessed.

### Services

| Service | What it returns | Used by |
|---|---|---|
| Google Maps place record | Source name, canonical and legacy IDs, category, address, rating, reviews, website, phone, coordinates | both modes |
| Google Maps related carousel | Up to the cards Google publishes for that place, with names, ratings, reviews, coordinates, images, and visible card text | `byPlace`, `byQuery` |
| Google Maps same-category search | Ranked places in the source category around the source coordinates, 20 per page | fallback in both modes |
| Google Maps place details | Canonical Place ID, address, contacts, categories, plus code, photo count, image for one returned place | optional enrichment |

### How it works

1. **Resolve the source.** A place URL provides its feature ID and coordinates directly. A canonical `ChIJ` ID is resolved through Google's public place page in one request. A name query is resolved through Google's map search, preferring the focused result and requiring a name match otherwise.
2. **Read the source record and carousel.** The same place response carries the source business data and, when Google exposes one, the related-place carousel.
3. **Decide whether to fall back.** In `auto` mode the fallback runs when the carousel has fewer usable rows than `minSimilar` and the carousel cannot already fill `maxItems`. `carousel` never falls back; `sameCategory` skips the carousel entirely.
4. **Search the same category nearby.** The fallback uses the source's first category (or `categoryOverride`) anchored at the source coordinates, page by page, until enough places pass the filters, the result window is exhausted, or `maxItems` is reached. Sorting by rating, reviews, or distance considers the full window before the cap is applied.
5. **Clean the result set.** The source place is removed, filters are applied, identities are deduplicated, and rows are sorted.
6. **Optionally enrich.** `includeDetails=true` looks up each emitted place's own page for its canonical Place ID, contacts, category, plus code, photo count, and image.
7. **Push immediately.** Records are emitted as they are finalised, with the global `maxItems` cap. Unresolved inputs are skipped and reported without failing the whole run.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `mode` | string | `byPlace` | `byPlace` accepts URLs or IDs; `byQuery` accepts place names |
| `placeUrls` | array | – | Google Maps URLs, `ChIJ…` IDs, or `0x…:0x…` feature IDs (mode=`byPlace`) |
| `searchQueries` | array | – | Business, hotel, or landmark names (mode=`byQuery`) |
| `location` | string | – | City or area appended to every name (mode=`byQuery`) |
| `maxItems` | integer | `100` | Global cap on returned rows (1–500) |
| `similaritySource` | string | `auto` | `auto`, `carousel`, or `sameCategory` |
| `minSimilar` | integer | `5` | In `auto`, fall back when the carousel has fewer usable rows than this and cannot fill `maxItems` (0–20) |
| `categoryOverride` | string | – | Category used by the fallback instead of the source's own |
| `minRating` | number | – | Drop places below this rating (0–5) |
| `minReviews` | integer | – | Drop places below this review count |
| `maxDistanceKm` | number | – | Keep places within this straight-line distance (0.1–50 km) |
| `categoryFilter` | string | – | Keep places whose category list contains this text |
| `sortBy` | string | `recommended` | `recommended`, `rating`, `reviews`, or `distance` |
| `deduplicate` | boolean | `true` | Keep one row per place identity |
| `includeDetails` | boolean | `true` | Look up each returned place's own page for extra fields |
| `includeMedia` | boolean | `true` | Include image URLs when Google publishes them |
| `language` | string | `en` | Maps language: `en`, `es`, `fr`, `de`, `it`, `pt`, `ja`, `ko`, `zh`, `ar`, `ru` |

#### Example: related hotels for one place URL

```json
{
  "mode": "byPlace",
  "placeUrls": [
    "https://www.google.com/maps/place/The+Ritz+London/@51.5070782,-0.1416273,17z/data=!3m1!4b1!4m6!3m5!1s0x48760529d30fc857:0x596135811e044014!8m2!3d51.5070782!4d-0.1416273!16zL20vMDQ3enJ2?hl=en"
  ],
  "maxItems": 20,
  "similaritySource": "auto",
  "minSimilar": 5
}
```

#### Example: competitors for a restaurant with no carousel

```json
{
  "mode": "byPlace",
  "placeUrls": ["ChIJ8Q2WSpJZwokRQz-bYYgEskM"],
  "similaritySource": "sameCategory",
  "maxItems": 50,
  "includeDetails": true
}
```

#### Example: several names in one city, filtered and sorted

```json
{
  "mode": "byQuery",
  "searchQueries": ["Joe's Pizza", "Peter Luger Steak House"],
  "location": "New York",
  "minRating": 4.2,
  "minReviews": 500,
  "maxDistanceKm": 5,
  "sortBy": "rating",
  "maxItems": 30
}
```

#### Example: carousel only, no fallback

```json
{
  "mode": "byPlace",
  "placeUrls": ["ChIJ4fRwZb25yhQRpHwVijb3LeU"],
  "similaritySource": "carousel",
  "includeMedia": false,
  "maxItems": 10
}
```

### Output

Every row is one related place:

- `relationMode` — `googleCarousel` or `sameCategoryNearby`
- `relationQuery` — category used by the fallback, when applicable
- `relatedSectionType` — Google's own section label when the response exposes one
- `rank` — 1-based position in the returned order
- `name` — related place name
- `placeId` — canonical `ChIJ…` Place ID, only when Google publishes one
- `featureId` — legacy `0x…:0x…` feature ID, never used in place of `placeId`
- `cid`, `kgmid` — other observed identities
- `googleMapsUrl` — link to the related place
- `categoryName`, `categories` — primary category and full observed list
- `totalScore`, `reviewsCount` — rating and review count
- `address`, `website`, `phone` — contacts when Google publishes them
- `plusCode`, `photoCount` — detail fields when enrichment is enabled
- `price` — visible carousel price string, kept exactly as shown
- `description` — visible carousel comparison text
- `distance` — visible carousel distance badge
- `distanceMeters` — straight-line distance from the source coordinates, in metres
- `latitude`, `longitude` — related place coordinates
- `image` — image URL at a larger size, when available
- `sourceBusinessName`, `sourcePlaceId`, `sourceFeatureId`, `sourcePlaceUrl` — the place you searched
- `sourceCategory`, `sourceAddress`, `sourceLatitude`, `sourceLongitude` — source context
- `sourceUrl` — alias of `sourcePlaceUrl` for older integrations
- `recordType` — always `similarPlace`
- `scrapedAt` — ISO timestamp

### Use cases

- **Local SEO and competitive analysis** — compare the places Google associates with a client's listing, or the competitors around its pin.
- **Market and category research** — expand a handful of seed businesses into a wider set of nearby places in the same category.
- **Lead and partner discovery** — collect contacts, websites, and ratings for businesses around a target area.
- **Data enrichment** — attach canonical Place IDs, categories, and coordinates to an existing list of businesses.
- **Recommendation feeds** — build a "places like this" list from Google's own related signal plus a transparent fallback.

### Usage cost

- No Google API key, no billing account, and no paid API calls are required.
- The actor runs in 1024 MB and needs no browser.
- A small run with `includeDetails=false` returns in seconds. `includeDetails=true` adds one place lookup per returned row, so large `maxItems` values take longer.
- `maxItems` is a hard cap: the actor stops when it reaches it, when Google's result window is exhausted, or when every input is resolved. Filters are counted before the cap, so they do not shorten the result set while eligible places remain.
- `sortBy=rating`, `reviews`, or `distance` reads the full nearby window before selecting rows; `recommended` stops as soon as the cap is met.

### FAQ

**Why did `carousel` mode return no rows?** Google does not show a related-place carousel for many signed-out place pages. `carousel` is truthful about that; use `auto` or `sameCategory` when you need rows.

**Are fallback rows Google recommendations?** No. They are places in the source's category near the source coordinates. `relationMode` says `sameCategoryNearby`, and `relationQuery` shows the category used.

**How many carousel results can I get?** Google typically publishes 3–5 cards, and sometimes none. That is the whole carousel; no setting can make Google show more.

**How many same-category places are available?** Google returns 20 per page and the observed window is around 104 unique places for a dense query. Sparse categories or small towns exhaust sooner.

**Why is `address` or `website` missing from a carousel row?** The carousel does not carry contacts. Turn on `includeDetails` to look up each place's own page; if Google still does not publish the field, it is omitted.

**Why does `distanceMeters` differ from the distance badge on the card?** The badge is Google's own display string. `distanceMeters` is a straight-line calculation from the source coordinates to the related place coordinates.

**Why are prices in an unexpected currency?** The carousel price is Google's localized display string for the request location. It is kept raw, not converted or normalised.

**Can I use a Place ID instead of a URL?** Yes. Canonical `ChIJ…` IDs work in `placeUrls`, and legacy `0x…:0x…` feature IDs are accepted too.

**Do I need a proxy or cookies?** No. The actor reads public place data and selects a working connection automatically.

**What happens if one input is invalid?** That input is skipped and reported in the status message; the other inputs still run. If no input resolves, the run fails with a clear message instead of returning an empty success.

### Notes

- `price`, `description`, `distance`, and `relatedSectionType` are emitted only when Google's response actually carries them.
- A canonical `ChIJ…` value always goes to `placeId`; a legacy `0x…:0x…` value always goes to `featureId`. They are never relabelled.
- `sourceUrl` remains an alias of `sourcePlaceUrl` so older integrations keep working.
- Duplicate identities are removed by default: canonical Place ID first, then feature ID, then name plus rounded coordinates.
- In `auto` mode, carousel rows and fallback rows can both appear; `relationMode` separates them.

### Limitations

- **The carousel is conditional.** Most signed-out place pages show no related carousel at all. The actor cannot create one; it uses the honest same-category fallback instead.
- **Fallback relevance depends on category.** If the source category is broad or wrong, nearby results can be broad too. Use `categoryOverride` or `categoryFilter` to narrow them.
- **The nearby window is finite.** Google stops returning new places after roughly 100 unique results for a dense query, and sooner for sparse categories.
- **No similarity score.** Google does not publish one, and the actor does not invent one.
- **Detail fields are optional and conditional.** `includeDetails` improves coverage, but a field Google does not publish is omitted rather than fabricated.

# Actor input Schema

## `mode` (type: `string`):

Choose how to enter the source places.

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

Google Maps place URLs, canonical ChIJ Place IDs, or legacy 0x…:0x… feature IDs. Mixed values are allowed (mode=byPlace).

## `searchQueries` (type: `array`):

Business, hotel, or landmark names to resolve and analyze (mode=byQuery).

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

City or area appended to every place name (mode=byQuery).

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

Maximum number of similar places to return across all sources.

## `similaritySource` (type: `string`):

Auto uses Google's related carousel and falls back to same-category nearby places when the carousel has fewer than Min similar. Carousel never uses the fallback. Same category skips the carousel.

## `minSimilar` (type: `integer`):

In auto mode, use the same-category fallback when the carousel has fewer usable rows than this number and cannot already fill Max results.

## `categoryOverride` (type: `string`):

Use this category for the same-category fallback instead of the source place's own category.

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

Drop related places rated below this value. Places without a published rating are dropped when this is above zero.

## `minReviews` (type: `integer`):

Drop related places with fewer than this many reviews.

## `maxDistanceKm` (type: `number`):

Keep only related places within this straight-line distance from the source. Requires published coordinates.

## `categoryFilter` (type: `string`):

Keep only places whose category list contains this text (case-insensitive).

## `sortBy` (type: `string`):

Order of the returned rows. Recommended keeps Google's carousel and search order.

## `deduplicate` (type: `boolean`):

Keep one row per place, using the canonical Place ID, then the feature ID, then name and coordinates.

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

Look up each returned place's own page for its canonical Place ID, address, website, phone, categories, plus code, photo count, and image. Adds one request per row and slows dense runs.

## `includeMedia` (type: `boolean`):

Include the image URL when Google publishes one.

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

Google Maps interface language. Affects names, categories, and the fallback category query.

## Actor input object example

```json
{
  "mode": "byPlace",
  "placeUrls": [
    "ChIJ8Q2WSpJZwokRQz-bYYgEskM",
    "https://maps.app.goo.gl/example"
  ],
  "searchQueries": [
    "Joe's Pizza",
    "The Ritz London"
  ],
  "location": "New York",
  "maxItems": 3,
  "similaritySource": "auto",
  "minSimilar": 1,
  "categoryOverride": "Pizza restaurant",
  "minRating": 4.5,
  "minReviews": 1000,
  "maxDistanceKm": 2,
  "categoryFilter": "Hotel",
  "sortBy": "recommended",
  "deduplicate": true,
  "includeDetails": true,
  "includeMedia": true,
  "language": "en"
}
```

# Actor output Schema

## `similarPlaces` (type: `string`):

Each item is one related place. relationMode says whether Google recommended it (googleCarousel) or it was found nearby in the same category (sameCategoryNearby). Fields Google does not publish are omitted.

# 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 = {
    "mode": "byPlace",
    "placeUrls": [
        "https://www.google.com/maps/place/The+Ritz+London/@51.5070782,-0.1416273,17z/data=!3m1!4b1!4m6!3m5!1s0x48760529d30fc857:0x596135811e044014!8m2!3d51.5070782!4d-0.1416273!16zL20vMDQ3enJ2?hl=en"
    ],
    "maxItems": 3,
    "similaritySource": "auto",
    "minSimilar": 1,
    "sortBy": "recommended",
    "deduplicate": true,
    "includeDetails": false,
    "includeMedia": true,
    "language": "en"
};

// Run the Actor and wait for it to finish
const run = await client.actor("dataspryva/google-maps-related-place-and-competitor-finder").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 = {
    "mode": "byPlace",
    "placeUrls": ["https://www.google.com/maps/place/The+Ritz+London/@51.5070782,-0.1416273,17z/data=!3m1!4b1!4m6!3m5!1s0x48760529d30fc857:0x596135811e044014!8m2!3d51.5070782!4d-0.1416273!16zL20vMDQ3enJ2?hl=en"],
    "maxItems": 3,
    "similaritySource": "auto",
    "minSimilar": 1,
    "sortBy": "recommended",
    "deduplicate": True,
    "includeDetails": False,
    "includeMedia": True,
    "language": "en",
}

# Run the Actor and wait for it to finish
run = client.actor("dataspryva/google-maps-related-place-and-competitor-finder").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 '{
  "mode": "byPlace",
  "placeUrls": [
    "https://www.google.com/maps/place/The+Ritz+London/@51.5070782,-0.1416273,17z/data=!3m1!4b1!4m6!3m5!1s0x48760529d30fc857:0x596135811e044014!8m2!3d51.5070782!4d-0.1416273!16zL20vMDQ3enJ2?hl=en"
  ],
  "maxItems": 3,
  "similaritySource": "auto",
  "minSimilar": 1,
  "sortBy": "recommended",
  "deduplicate": true,
  "includeDetails": false,
  "includeMedia": true,
  "language": "en"
}' |
apify call dataspryva/google-maps-related-place-and-competitor-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dataspryva/google-maps-related-place-and-competitor-finder"
        }
    }
}
```

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/d1d5hOcDg4pByYO0X/builds/97AjPnapucPrZpz9l/openapi.json
