# Google Maps Competitor Finder (`coregent/google-maps-competitor-finder`) Actor

Find and export local competitors from Google Maps by keyword and location. Returns clean, flat, CSV-friendly competitor data with ratings, review counts, contact details, address fields, competitor strength score, ranking, and market position hint.

- **URL**: https://apify.com/coregent/google-maps-competitor-finder.md
- **Developed by:** [Delowar Munna](https://apify.com/coregent) (community)
- **Categories:** Lead generation, Automation, Developer tools
- **Stats:** 2 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.40 / 1,000 competitor-results

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 Competitor Finder

![Google Maps Competitor Finder](https://raw.githubusercontent.com/coregentdevspace/google-maps-competitor-finder-assets/main/thumbnail-google-maps-competitor-finder.jpg)

Find and rank local Google Maps competitors by **keyword + location**. Returns a clean, flat, CSV-friendly competitor row per result with **competitor strength scoring**, **comparison tags**, and a **market position hint** — built for **local SEO, agency research, market analysis, and small-business strategy**.

V1 deliberately avoids website crawling, email extraction, full review scraping, photos, menus, and AI enrichment so the run stays **fast and cheap**. You only pay for unique competitor rows that pass your filters and are successfully written to the dataset.

### Why this actor

- **Competitor-focused** — 30 flat fields tuned for local competitor research (rank, rating, review count, contact details, address parts, strength score, tags, market position hint).
- **Transparent strength score** — rule-based 0–100, no AI. Explained below.
- **Market position hint** — short rule-based label like `Strong established competitor`, `Emerging well-rated competitor`, `Offline-heavy competitor`.
- **Pay-Per-Event** — one charge per saved unique competitor row. Duplicates, filtered rows, and failed pushes are not charged.
- **No login, no cookies, no sessions** — just keyword + location.
- **CSV-friendly output** — flat structure, no nested objects, drops cleanly into Sheets/Excel/CRMs.

***

### Quick start — sample inputs

#### Example 1 — single query

```json
{
  "searchQueries": [
    { "key": "plumbers", "value": "Canberra ACT" }
  ],
  "maxResults": 100,
  "country": "AU",
  "language": "en",
  "websiteFilter": "any",
  "phoneRequired": false,
  "includeOpeningHours": true,
  "includeCoordinates": true,
  "includeCompetitorScore": true,
  "deduplicateResults": true,
  "proxyConfiguration": { "useApifyProxy": true }
}
```

#### Example 2 — multi-query competitor research

```json
{
  "searchQueries": [
    { "key": "electricians", "value": "Sydney NSW" },
    { "key": "dentists",     "value": "Melbourne VIC" },
    { "key": "cafes",        "value": "Brisbane QLD" }
  ],
  "maxResults": 200,
  "country": "AU",
  "language": "en",
  "minRating": 4.0,
  "minReviewCount": 10,
  "websiteFilter": "any",
  "phoneRequired": false,
  "categoryFilter": "",
  "includeOpeningHours": true,
  "includeCoordinates": true,
  "includeCompetitorScore": true,
  "deduplicateResults": true,
  "proxyConfiguration": { "useApifyProxy": true }
}
```

> The `searchQueries` field uses Apify's **Key/Value form editor** — the **Key** column is the business keyword (e.g. `plumbers`), the **Value** column is the location (e.g. `Canberra ACT`). Add one row per search.

***

### Input fields

| Field | Type | Required | Default | Purpose |
|---|---|---:|---|---|
| `searchQueries` | array | Yes | `[]` | Keyword + location pairs (one search per row). |
| `maxResults` | integer | No | `100` | Per-query cap (1–500). |
| `country` | string | No | `""` | ISO country code (e.g. `AU`, `US`). |
| `language` | string | No | `en` | Google Maps UI language. |
| `minRating` | integer | No | `0` | Drop competitors below this star rating. |
| `minReviewCount` | integer | No | `0` | Drop competitors below this review count. |
| `websiteFilter` | enum | No | `any` | `any` / `hasWebsite` / `missingWebsite`. |
| `phoneRequired` | boolean | No | `false` | Keep only competitors with a visible phone. |
| `categoryFilter` | string | No | `""` | Case-insensitive contains match against category. |
| `includeOpeningHours` | boolean | No | `true` | Include opening hours when visible. |
| `includeCoordinates` | boolean | No | `true` | Include latitude/longitude when available. |
| `includeCompetitorScore` | boolean | No | `true` | Compute strength score, label, tags, and market position hint. |
| `deduplicateResults` | boolean | No | `true` | Remove duplicate competitors across queries. |
| `proxyConfiguration` | object | No | Apify Proxy | Apify Datacenter proxy / no proxy / custom proxy URLs. **Apify Residential is rejected** (see Proxy policy below). |

***

### Output

The dataset has one view: **Competitors** — a 30-column flat table.

![Competitors table view](https://raw.githubusercontent.com/coregentdevspace/google-maps-competitor-finder-assets/main/google-maps-competitor-finder-output-competitors-table-view.png)

#### Sample record — Competitors

```json
{
  "search_keyword": "electricians",
  "search_location": "New York, USA",
  "competitor_rank": 1,
  "business_name": "H&A Electrician",
  "category": "Electrician",
  "rating": 4.8,
  "review_count": null,
  "phone": "646) 351-0882",
  "website": "https://www.bestnycelectricianmanhattan.com/",
  "website_domain": "bestnycelectricianmanhattan.com",
  "address": "40 Fulton St",
  "street_address": "40 Fulton St",
  "suburb": "",
  "city": "",
  "state": "",
  "postcode": "",
  "country": "",
  "google_maps_url": "https://www.google.com/maps/place/H%26A+Electrician/data=!4m7!3m6!1s0x89c25a3d39458305:0xf6758912f6af992a!8m2!3d40.707876!4d-74.004745!16s%2Fg%2F1hc3pdw6w!19sChIJBYNFOT1awokRKpmv9hKJdfY",
  "place_id": "0x89c25a3d39458305:0xf6758912f6af992a",
  "latitude": 40.707876,
  "longitude": -74.004745,
  "opening_hours": "",
  "is_open_now": true,
  "has_website": true,
  "has_phone": true,
  "competitor_strength_score": 60,
  "competitor_strength_label": "Established Competitor",
  "competitor_tags": [
    "has_website",
    "has_phone",
    "high_rating",
    "category_found",
    "established_competitor"
  ],
  "market_position_hint": "Established competitor",
  "scraped_at": "2026-05-07T04:37:25.314Z"
}
```

#### Output fields (30)

`search_keyword`, `search_location`, `competitor_rank`, `business_name`, `category`, `rating`, `review_count`, `phone`, `website`, `website_domain`, `address`, `street_address`, `suburb`, `city`, `state`, `postcode`, `country`, `google_maps_url`, `place_id`, `latitude`, `longitude`, `opening_hours`, `is_open_now`, `has_website`, `has_phone`, `competitor_strength_score`, `competitor_strength_label`, `competitor_tags`, `market_position_hint`, `scraped_at`.

***

### Competitor strength score

Transparent rule-based score (0–100) computed from visible fields — no AI, no external enrichment.

| Signal | Points |
|---|---:|
| Has website | +20 |
| Has phone | +15 |
| Rating ≥ 4.5 | +20 |
| Rating 4.0–4.49 | +10 |
| Review count ≥ 100 | +25 |
| Review count 30–99 | +15 |
| Review count 10–29 | +8 |
| Has full address | +10 |
| Category found | +5 |
| Opening hours found | +5 |

Score is capped at 100.

**Labels**

| Score | Label |
|---:|---|
| 80–100 | `Strong Competitor` |
| 60–79 | `Established Competitor` |
| 40–59 | `Moderate Competitor` |
| 0–39 | `Weak Competitor` |

**Competitor tags** explain the score and surface comparison signals:

```
has_website / missing_website
has_phone / missing_phone
high_rating / low_rating
many_reviews / few_reviews
complete_address / missing_address
category_found / opening_hours_found
strong_competitor / established_competitor / moderate_competitor / weak_competitor
reputation_strength / reputation_weakness
seo_visible_competitor / weak_digital_presence
```

**Market position hint** is a short rule-based interpretation of the score and tags. Examples: `Strong established competitor`, `Emerging well-rated competitor`, `High-visibility but reputation weakness`, `Offline-heavy competitor`, `Weak digital competitor`.

***

### Pricing

**Pay-Per-Event**. The actor honors the user-configured per-run spending cap (Apify `eventChargeLimitReached`) and stops cleanly when reached.

| Event | Fires |
|---|---|
| `competitor-result` | Once per unique competitor row that passed all filters and was successfully pushed to the dataset. |

#### 🚦 Proxy policy

Use **Apify Datacenter** proxy or **no proxy** for normal runs — both work reliably for Google Maps search at this actor's conservative concurrency.

**Apify Residential proxy is not supported.** The actor will fail at startup if `apifyProxyGroups` includes `RESIDENTIAL`. Reason: in pay-per-event actors, residential bandwidth (~/GB) is billed to the developer, not the run user, so a single bandwidth-heavy run could exceed the per-result event revenue.

If you genuinely need residential routing, supply your own residential provider via the proxy editor's **Custom proxy URLs** field — that traffic goes through your provider, not Apify, and is unaffected:

```
http://user:pass@proxy.iproyal.com:12321
http://user:pass@proxy.brightdata.com:22225
http://user:pass@proxy.oxylabs.io:7777
```

Not charged for `competitor-result`:

- Duplicates (de-duplicated by `place_id`, listing URL, or name+address).
- Rows filtered out by `minRating`, `minReviewCount`, `websiteFilter`, `phoneRequired`, or `categoryFilter`.
- Rows missing a `business_name`.
- Failed dataset pushes.
- Anything after the per-run spending cap is reached.

***

### Run summary

After each run, a `RUN_SUMMARY` entry is written to the key-value store:

```json
{
  "search_queries_total": 3,
  "successful_queries": 3,
  "failed_queries": 0,
  "raw_results_found": 240,
  "results_saved": 187,
  "duplicates_removed": 21,
  "filtered_out": 32,
  "charged_events": 187,
  "blocked_requests": 0,
  "retried_requests": 4,
  "runtime_seconds": 142,
  "scraped_at": "2026-05-06T12:00:00.000Z"
}
```

`charged_events` always equals `results_saved`.

***

### Filters

| Filter | Effect |
|---|---|
| `minRating` | Drop competitors below this star rating (0 disables). |
| `minReviewCount` | Drop competitors with fewer reviews (0 disables). |
| `websiteFilter` | `any` / `hasWebsite` / `missingWebsite`. |
| `phoneRequired` | If `true`, keep only competitors with a visible phone. |
| `categoryFilter` | Case-insensitive contains match against the category. |
| `deduplicateResults` | Drop duplicates across queries (recommended ON). |

Filters are applied **after** extraction and **before** dataset push or event charge.

***

### Limitations (V1)

- **Cards-only extraction**: V1 reads each competitor row directly from the search results panel and does **not** click into individual place detail panels. Phone, website, full opening hours, and `place_id` only appear when Google surfaces them on the card itself; otherwise these fields are empty/`null`.
- No website crawling, email extraction, or social link extraction.
- No full review text or sentiment analysis.
- No photos, menus, prices, or popular times.
- No login/cookie/session-based scraping.
- No true distance/radius calculation against a seed business in V1.
- Address parsing into street/suburb/city/state/postcode is best-effort; the full `address` field is the source of truth.
- Per-query hard cap is **500 results**; per-run hard cap is **5,000 results**.

***

### Technical notes

- **Stack**: Node.js 22 · Apify SDK 3 · Crawlee · Puppeteer.
- **Concurrency**: `min=1`, `max=3` (conservative to reduce blocking).
- **Memory**: 1 GB min · 2 GB default · 4 GB max.
- **Proxy**: Apify Datacenter proxy by default; custom proxy URLs accepted. Apify Residential is rejected at startup (see Proxy policy above).
- **Diagnostics**: On the first failed render (no feed, or feed but zero cards), the actor saves the page HTML and URL to the key-value store as `debug-no-feed-html` / `debug-zero-cards-html`.

# Actor input Schema

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

List of keyword + location pairs to search on Google Maps. Add one row per search — "Key" is the business keyword/category (e.g. plumbers), "Value" is the location (e.g. Canberra ACT). Each row becomes one search like "plumbers in Canberra ACT".

## `maxResults` (type: `integer`):

Maximum number of saved competitor rows per search query. Hard cap is 500 per query and 5000 per run.

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

Optional ISO country code used as a Google Maps locale hint, for example AU, US, GB. Leave empty to use the default Google locale.

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

Language code used for Google Maps UI (IETF BCP-47), for example en, es, de.

## `minRating` (type: `integer`):

Filter out competitors with a star rating below this value (0-5). Set to 0 to disable.

## `minReviewCount` (type: `integer`):

Filter out competitors with fewer than this many Google reviews. Set to 0 to disable.

## `websiteFilter` (type: `string`):

Filter by website availability. "any" keeps everything; "hasWebsite" keeps only competitors with a website; "missingWebsite" keeps only competitors without a website.

## `phoneRequired` (type: `boolean`):

If enabled, only keep competitors that have a visible phone number.

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

Optional case-insensitive contains match against the competitor's category. Leave empty to disable.

## `includeOpeningHours` (type: `boolean`):

Include opening hours summary when visible without expensive extra steps.

## `includeCoordinates` (type: `boolean`):

Include latitude/longitude when available from the listing URL.

## `includeCompetitorScore` (type: `boolean`):

Add competitor\_strength\_score, competitor\_strength\_label, competitor\_tags, and market\_position\_hint to each row. When disabled, score is null, tags is \[], and hint is empty.

## `deduplicateResults` (type: `boolean`):

Remove duplicate competitors across queries using place\_id, listing URL, and name+address keys.

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

Apify Proxy configuration. Defaults to Apify Datacenter proxy. Apify Residential proxy is rejected at startup — supply your own residential provider via Custom proxy URLs if needed.

## Actor input object example

```json
{
  "searchQueries": [
    {
      "key": "plumbers",
      "value": "Canberra ACT"
    },
    {
      "key": "electricians",
      "value": "Sydney NSW"
    }
  ],
  "maxResults": 100,
  "country": "",
  "language": "en",
  "minRating": 0,
  "minReviewCount": 0,
  "websiteFilter": "any",
  "phoneRequired": false,
  "categoryFilter": "",
  "includeOpeningHours": true,
  "includeCoordinates": true,
  "includeCompetitorScore": true,
  "deduplicateResults": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

Flat 30-field table view of every competitor row pushed to the dataset.

# 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 = {
    "searchQueries": [
        {
            "key": "plumbers",
            "value": "Canberra ACT"
        },
        {
            "key": "electricians",
            "value": "Sydney NSW"
        }
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("coregent/google-maps-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 = {
    "searchQueries": [
        {
            "key": "plumbers",
            "value": "Canberra ACT",
        },
        {
            "key": "electricians",
            "value": "Sydney NSW",
        },
    ],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("coregent/google-maps-competitor-finder").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "searchQueries": [
    {
      "key": "plumbers",
      "value": "Canberra ACT"
    },
    {
      "key": "electricians",
      "value": "Sydney NSW"
    }
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call coregent/google-maps-competitor-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=coregent/google-maps-competitor-finder",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/bgpwDJ0zwQDUJPj4v/builds/9Fq2f6HAIc5d1KrTb/openapi.json
