# Local Business Opportunity Scanner (Google Maps) (`leafy-dev-jr/local-business-opportunity-scanner`) Actor

Should you open business here, or is the area already too crowded? Scan any local market to see how saturated it is, how strong demand looks, and where competitors are weak. Get the verdict as a shareable PDF, backed by real customer reviews.

- **URL**: https://apify.com/leafy-dev-jr/local-business-opportunity-scanner.md
- **Developed by:** [Leafy](https://apify.com/leafy-dev-jr) (community)
- **Categories:** Lead generation, Automation
- **Stats:** 3 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

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

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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use 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

## Local Business Opportunity Scanner

**Should you open here, or is the area already too crowded?** Enter a business type and a specific area to see how saturated the market is, how strong demand looks, and where competitors are weak. You get the verdict as a shareable PDF, a scored competitor list, and what customers actually praise and complain about.

Built for owners validating a location, franchise buyers comparing territories, and agencies prospecting local clients with weak digital presence. It reads *public* Google Maps signals, with no APIs to wire up and no data to prepare.

---

### What you get

Every run produces **three things**:

#### 📄 1. A ready-to-share PDF report (`REPORT.pdf`)

A single document that answers *"is this area worth it?"* in plain language. Contents:

````

Local Business Opportunity Report
coffee shop · Makati, Metro Manila · 124 competitors

Executive Summary       Opportunity 64/100: "promising but competitive"
Market Scores           Competition 82 · Demand 78 · Digital weakness 61
Market Interpretation   "Review volume suggests demand appears strong,
but the area is already competitive..."
Competitor Landscape    124 analyzed · 88 have websites · 19 look weak
Digital Weakness        missing sites, no booking links, no socials
What Customers Value &  praise: ambiance, friendly staff
Complain About        complaints: slow service, pricing  (+ real quotes)
Top Strong / Weak Competitors
Recommended Strategy    differentiation angles based on the above
Caveat

````

#### 🏪 2. A scored competitor list (dataset)

**One row per business:** rating, review count, competitive strength, threat level, web-presence score, and the specific digital gaps (`no_booking_link`, `no_instagram`, missing website…) you could exploit.

#### 💬 3. The voice of the customer (reviews, per business)

For the most-reviewed competitors, the actor samples recent Google reviews and mines them (no paid API) into **what people love vs. complain about**, then attaches the raw reviews to each business row so you can read the sentiment yourself. Complaints become differentiation ideas, like *"competitors are dinged for slow service, so getting that right could be an opening."*

---

### Input

Two fields is all it needs:

```json
{
  "businessType": "coffee shop",
  "location": "Makati, Metro Manila"
}
````

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `businessType` \* | string | `coffee shop` | The category to analyze (coffee shop, dental clinic, gym, salon, med spa, plumber…). |
| `location` \* | string | `Makati, Metro Manila` | **Must be specific.** Use `Place, City/Region` or a postal/ZIP code. A bare country/state is rejected. |
| `radiusKm` | number | `3` | Catchment radius (0.5 to 50), used only for the density metric. |
| `mapsConcurrency` | integer | `3` | Parallel Maps page extractions (1 to 10). Higher is faster and heavier. |
| `proxyConfiguration` | object | *off* | Leave off unless you hit blocks/captchas. |

\* required. Website audit and review analysis are **always on**, because they're core to the result rather than toggles.

✅ `Makati, Metro Manila` · `Soho, London` · `Austin, TX` · `90210`
❌ `Philippines` · `Texas` · `London` (too broad, rejected with a clear message)

> **No result cap by design.** To measure saturation the actor collects *every* business Maps surfaces (Maps itself caps a search at ~120). If an internal safety ceiling is ever hit, the summary sets `result_limit_reached: true`, so read the count as "at least N".

***

### Output

#### Dataset (one row per business)

| Field | Meaning |
| --- | --- |
| `business_name` | Business name (first column). |
| `rating` · `review_count` | Google rating and total review count. |
| `competitive_strength` | `strong` · `moderate` · `weak`. |
| `threat_level` | How much of a threat this competitor is. |
| `web_presence_score` | Digital strength, 0 to 100. |
| `weakness_tags` | Exploitable gaps, e.g. `["no_booking_link","no_instagram"]`. |
| `opportunity_note` | One-line read on this competitor. |
| `website_status` | `working` · `missing` · `error`. |
| `reviews_sampled` · `reviews_text` | Sampled reviews as readable text (renders in the Apify table). |
| `reviews` | The same reviews as a structured array (`rating`, `sentiment`, `aspects`, `date`, `text`). |

```json
{
  "business_name": "Example Coffee",
  "rating": 4.6,
  "review_count": 240,
  "competitive_strength": "strong",
  "threat_level": "high",
  "web_presence_score": 72,
  "weakness_tags": ["no_booking_link", "no_instagram"],
  "opportunity_note": "Strong overall, but has digital gaps a new entrant could exploit.",
  "reviews_sampled": 5,
  "reviews_text": "[5★ · positive, 2 weeks ago] Friendly baristas and great coffee\n\n[2★ · negative, a month ago] Service was slow...",
  "reviews": [
    { "rating": 5, "sentiment": "positive", "aspects": ["service & staff"], "date": "2 weeks ago", "text": "Friendly baristas and great coffee" }
  ]
}
```

Two dataset tabs: **Overview** (business + key metrics + readable reviews) and **All fields**. If Maps returns nothing usable, a single `no_data_found` row is pushed instead of crashing.

#### Key-value store

| File | What it is |
| --- | --- |
| `REPORT.pdf` | The full shareable report (above). Falls back to `REPORT.md` if rendering fails. |
| `MARKET_SUMMARY.json` | The headline verdict: four scores + levels, market status, interpretation, recommended strategy, and `voice_of_customer`. |
| `REVIEW_THEMES.json` | The value-vs-complain breakdown: `{ sentiment, theme, mentions, sample_quote }[]`. |

***

### The four scores (0 to 100, directional)

| Score | What it measures |
| --- | --- |
| **Competition** | How crowded and strong the market looks. |
| **Demand signal** | Observed demand, proxied by review activity. |
| **Digital weakness** | How exploitable competitors' online presence is. |
| **Overall opportunity** | `demand × 0.55 + (100 − competition) × 0.25 + digitalWeakness × 0.20`. |

High competition isn't automatically bad. A crowded market often means demand is *proven*, so the report reads demand and competition **together** (proven-but-competitive, risky/saturated, digital-first opening, and so on).

***

### How the customer-review read works

For the most-reviewed competitors (about 15 businesses), the actor pulls up to 5 recent Google reviews each and reads them with no paid API:

1. **Star rating is the sentiment signal.** 4 to 5 stars counts as positive, 1 to 2 as negative, 3 as neutral.
2. **Aspect lexicons tag what each review is about:** service & staff, price & value, wait time, quality, food & menu, cleanliness, ambiance, parking, amenities, booking.
3. **Frequent phrases** are pulled from the positive bucket and the negative bucket separately.

The result is a plain-language read like *"Reviews frequently praise: ambiance, friendly staff"* vs *"Common complaints mention: slow service, price,"* backed by example quotes. Recurring complaints also turn into a differentiation hint in the recommended strategy (*"competitors are dinged for X, so getting X right could be an opening"*).

It's directional keyword-and-sentiment scanning, English-centric, and not full NLP.

***

### Feedback, issues & requests

Found a bug, want a new metric, or have a business type that doesn't parse cleanly? I'd genuinely like to hear it. Email **[leafydevjr@gmail.com](mailto:leafydevjr@gmail.com?subject=Local%20Business%20Opportunity%20Scanner%20feedback)** and I'll take a look.

# Actor input Schema

## `businessType` (type: `string`):

The local business category to analyze, for example: coffee shop, dental clinic, salon, gym, med spa, plumber, HVAC, roofing company.

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

Be specific — this only works on a real local area. Use at least 'Place, City/Region' (e.g. 'Makati, Metro Manila', 'Soho, London', 'Austin, TX') or a postal/ZIP code. A whole country or state is too broad and will be rejected.

## `radiusKm` (type: `number`):

Approximate radius of the area you are evaluating. Used only to estimate competitor density (competitors per sq km). Does not change which businesses are found.

## `mapsConcurrency` (type: `integer`):

How many Google Maps place pages to extract in parallel. Higher is faster but heavier.

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

Off by default — Google Maps loads fast and reliably from the actor's own IP, and results stay local because the area is part of the search query. Enable a proxy only if you start hitting blocks/captchas; residential is slower but harder to block.

## Actor input object example

```json
{
  "businessType": "coffee shop",
  "location": "Makati, Metro Manila",
  "radiusKm": 3,
  "mapsConcurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `competitors` (type: `string`):

One row per business: rating, review count, competitive strength, threat level, digital weakness gaps, and sampled reviews.

## `marketReport` (type: `string`):

The full shareable opportunity report: scores, interpretation, competitor landscape, digital weaknesses, voice of customer, and recommended strategy.

## `marketSummary` (type: `string`):

The headline verdict: the four scores + levels, market status, interpretation, recommended strategy, and voice\_of\_customer.

## `reviewThemes` (type: `string`):

The value-vs-complain breakdown: what customers praise and complain about, with mention counts and sample quotes.

# 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 = {
    "businessType": "coffee shop",
    "location": "Makati, Metro Manila"
};

// Run the Actor and wait for it to finish
const run = await client.actor("leafy-dev-jr/local-business-opportunity-scanner").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 = {
    "businessType": "coffee shop",
    "location": "Makati, Metro Manila",
}

# Run the Actor and wait for it to finish
run = client.actor("leafy-dev-jr/local-business-opportunity-scanner").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 '{
  "businessType": "coffee shop",
  "location": "Makati, Metro Manila"
}' |
apify call leafy-dev-jr/local-business-opportunity-scanner --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=leafy-dev-jr/local-business-opportunity-scanner",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Local Business Opportunity Scanner (Google Maps)",
        "description": "Should you open business here, or is the area already too crowded? Scan any local market to see how saturated it is, how strong demand looks, and where competitors are weak. Get the verdict as a shareable PDF, backed by real customer reviews.",
        "version": "1.0",
        "x-build-id": "BNBF97479RTnPcSsb"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/leafy-dev-jr~local-business-opportunity-scanner/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-leafy-dev-jr-local-business-opportunity-scanner",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/leafy-dev-jr~local-business-opportunity-scanner/runs": {
            "post": {
                "operationId": "runs-sync-leafy-dev-jr-local-business-opportunity-scanner",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/leafy-dev-jr~local-business-opportunity-scanner/run-sync": {
            "post": {
                "operationId": "run-sync-leafy-dev-jr-local-business-opportunity-scanner",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "required": [
                    "businessType",
                    "location"
                ],
                "properties": {
                    "businessType": {
                        "title": "Business type",
                        "type": "string",
                        "description": "The local business category to analyze, for example: coffee shop, dental clinic, salon, gym, med spa, plumber, HVAC, roofing company."
                    },
                    "location": {
                        "title": "Specific location",
                        "type": "string",
                        "description": "Be specific — this only works on a real local area. Use at least 'Place, City/Region' (e.g. 'Makati, Metro Manila', 'Soho, London', 'Austin, TX') or a postal/ZIP code. A whole country or state is too broad and will be rejected."
                    },
                    "radiusKm": {
                        "title": "Catchment radius in km (optional)",
                        "minimum": 0.5,
                        "maximum": 50,
                        "type": "number",
                        "description": "Approximate radius of the area you are evaluating. Used only to estimate competitor density (competitors per sq km). Does not change which businesses are found.",
                        "default": 3
                    },
                    "mapsConcurrency": {
                        "title": "Maps extraction concurrency",
                        "minimum": 1,
                        "maximum": 10,
                        "type": "integer",
                        "description": "How many Google Maps place pages to extract in parallel. Higher is faster but heavier.",
                        "default": 3
                    },
                    "proxyConfiguration": {
                        "title": "Proxy configuration",
                        "type": "object",
                        "description": "Off by default — Google Maps loads fast and reliably from the actor's own IP, and results stay local because the area is part of the search query. Enable a proxy only if you start hitting blocks/captchas; residential is slower but harder to block.",
                        "default": {
                            "useApifyProxy": false
                        }
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
