# Google Maps Business Opportunity Finder (`intrabit/google-maps-business-opportunity-finder`) Actor

Find strong local businesses with specific, evidence-backed digital growth opportunities. Analyzes Google Maps profiles, websites, reviews, technologies, and competitors to detect gaps in booking, reputation, websites, tracking, and more. 100% deterministic scoring — no AI.

- **URL**: https://apify.com/intrabit/google-maps-business-opportunity-finder.md
- **Developed by:** [Intrabit](https://apify.com/intrabit) (community)
- **Categories:** Social media, E-commerce, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$10.00 / 1,000 business analyzeds

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/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 Business Opportunity Finder

Find **strong local businesses** with **specific, evidence-backed digital growth opportunities**.

This Actor analyzes Google Maps profiles, websites, reviews, technologies, conversion features, and **local competitors** to detect concrete gaps in online booking, websites, reputation management, conversion, tracking, and contact automation — then ranks every business by a deterministic, explainable opportunity score.

**No AI-generated scoring.** Every score is computed from observable signals and configurable deterministic rules. Identical input → identical output.

***

### What does it do?

1. **Discovers businesses** on Google Maps (by query + location, or from your own list of Maps URLs).
2. **Scores business strength** (0–100): observable activity/credibility — rating, review volume, recent activity, completeness, digital presence, status. *Not* revenue.
3. **Audits each website**: HTTP/HTTPS, redirects, response time, title/meta/viewport/canonical, schema markup, forms, CTAs, click-to-call, WhatsApp, analytics, ads pixels, chat, ecommerce, and 40+ technology fingerprints.
4. **Analyzes reviews** (when enabled): recent velocity, owner response rate, unanswered negative reviews.
5. **Benchmarks competitors**: for each business, a local cohort of top competitors and their adoption of booking, websites, HTTPS, contact forms, chat, analytics, etc.
6. **Detects opportunities** with explainable rules (see below).
7. **Ranks** businesses by `overallOpportunityScore` and returns a CSV-friendly dataset.

### What makes it different?

| Generic Google Maps scraper | This Actor |
| --- | --- |
| Returns businesses | Finds businesses, analyzes their digital presence |
| Lists leads | Benchmarks local competitors |
| Raw data | Detects specific gaps (no booking, no CTA, unanswered negatives…) |
| No signal | Ranks commercial opportunities |

Instead of *"give me dentists in Miami"*, this Actor enables:

> *"give me strong dentists in Miami that appear to need online booking"*

> *"find local businesses with strong customer demand but weak reputation management"*

> *"find businesses advertising online but lacking detectable conversion infrastructure"*

### Business strength vs opportunity gap

These are separate dimensions. A business with `2 reviews / 2.1 stars / no website` is a poor prospect even if it has "problems". A business with `4.8 stars / 642 reviews / active / no online booking` is a strong opportunity.

```
                  OPPORTUNITY GAP

                 LOW        HIGH

BUSINESS HIGH     LOW        BEST TARGET
STRENGTH
         LOW      IGNORE     QUESTIONABLE
```

`overallOpportunityScore` is deliberately **not** an average of all detectors — one strong, specific signal should dominate:

```
overall = bestOpportunity × 0.75
        + secondBest × 0.15
        + businessStrength × 0.10
```

### Available opportunity types

The V1 detectors (all deterministic):

| Detector | What it finds |
| --- | --- |
| `WEBSITE` | Missing, dead, broken, HTTP-only, or weak website; social-only presence |
| `ONLINE_BOOKING` | No booking system/CTA while strong and (often) competitors offer booking |
| `REPUTATION_MANAGEMENT` | Unanswered negative reviews, low owner response rate, rating below local median |
| `CONVERSION` | No primary CTA, no form / click-to-call / booking / ordering path |
| `TRACKING` | No analytics; ads pixels detected without any conversion action |
| `CONTACT_AUTOMATION` | No contact form, no click-to-call, no WhatsApp, no chat |
| `ONLINE_ORDERING` | Restaurants/cafes without online ordering |
| `LOCAL_SEO` | Missing title/meta/schema/canonical, no website |
| `LIVE_CHAT` | No chat widget in chat-relevant categories |
| `EMAIL_MARKETING` | Ecommerce/retail without email marketing platform |
| `SOCIAL_PRESENCE` | No social links on website (category-relevant) |
| `ECOMMERCE` | Retail without online store |

### How opportunity detection works

Every detector is a small rule engine:

1. **Evidence** — e.g. `NO_BOOKING` (weight 35), `NO_BOOKING_CTA` (15), `NO_BOOKING_COMPETITOR_GAP` (30).
2. **Category context** — booking matters more for dentists than museums (`CATEGORY_OPPORTUNITY_WEIGHTS`).
3. **Competitor benchmark** — *"No booking detected; 8/10 top local competitors offer booking"* is far stronger than *"no booking"* alone. Cohort claims require a minimum sample size (default 5); otherwise competitor evidence is simply not used.
4. **Score = Σ weights × category relevance**, clamped 0–100, with `scoreComponents` exposed for every point.

Example for a strong dentist with no booking and 9/10 competitors offering it:

```
Base                0
Business strength   +18   (strength 90 × 20%)
No booking          +35
No booking CTA      +15
Competitor gap      +27   (90% × 30)
Category (DENTAL)   ×1.0
TOTAL               95
```

**Tri-state signals.** Missing data never silently counts as "bad". Signals are `true | false | null`, where `null` = "could not be reliably determined" (e.g. website fetch failed → booking is *unknown*, not *absent*).

### Input

All fields are optional with sensible defaults. Minimal example:

```json
{
  "searchQueries": ["dentists"],
  "location": "Miami, Florida",
  "maxBusinesses": 100
}
```

Full control:

```json
{
  "searchQueries": ["dentists", "orthodontists"],
  "location": "Austin, Texas",
  "maxBusinesses": 500,
  "analysisMode": "STANDARD",
  "findOpportunitiesFor": ["ONLINE_BOOKING", "REPUTATION_MANAGEMENT"],
  "minimumBusinessStrength": 35,
  "minimumOpportunityScore": 55,
  "reviewAnalysisEnabled": true,
  "maxReviewsPerBusiness": 30,
  "websiteConcurrency": 15,
  "includeClosedBusinesses": false
}
```

#### Enrich your own lead list (Mode B)

```json
{
  "googleMapsUrls": [
    "https://www.google.com/maps/place/...",
    "https://www.google.com/maps/place/..."
  ]
}
```

#### Analysis modes

- **FAST** — Maps data + website analysis, no reviews.
- **STANDARD** *(default)* — website, technologies, conversion, competitors, limited reviews.
- **DEEP** — everything, with up to 2× review sampling.

### Output

One dataset item per business, CSV-friendly. First columns:

```
overallOpportunityScore | bestOpportunity | bestOpportunityScore | businessStrengthScore | businessName | primaryCategory | rating | reviewCount | websiteStatus | website | phone | googleMapsUrl
```

Every item also includes:

- `opportunities[]` — per-detector `{ type, score, confidence, evidence[], scoreComponents, competitorComparison }`
- `detectedTechnologies[]` — `{ technology, detected, confidence, evidence[] }`
- `signals` — flattened tri-state website signals
- `reviewMetrics` — response rate, unanswered negatives, velocity
- `competitorBenchmarks` — cohort adoption rates and medians
- `whyThisLeadText` — one-line, spreadsheet-ready explanation

```text
4.8 rating / 642 reviews | No online booking system detected | No appointment CTA | 8/10 benchmark competitors offer booking
```

A run summary (businesses discovered/analyzed/returned, opportunity counts, estimated cost) is logged and stored as `SUMMARY` in the key-value store.

### Google Maps search examples

```json
{ "searchQueries": ["dentists"], "location": "Dallas, Texas", "findOpportunitiesFor": ["WEBSITE", "CONVERSION"] }
```

→ highly reviewed local businesses with measurable website/conversion gaps.

```json
{ "searchQueries": ["dentists", "beauty salons", "veterinarians"], "location": "Austin, Texas", "findOpportunitiesFor": ["ONLINE_BOOKING"] }
```

→ strong appointment-based businesses that don't accept online bookings.

```json
{ "searchQueries": ["restaurants"], "location": "Orlando, Florida", "findOpportunitiesFor": ["REPUTATION_MANAGEMENT"], "reviewAnalysisEnabled": true }
```

→ popular restaurants with unanswered negative reviews.

### Agency examples

- **Web design agency** → `WEBSITE`, `CONVERSION` → businesses with dead/weak sites and no CTA.
- **Booking software provider** → `ONLINE_BOOKING` → strong dentists/salons/vets that can't book online.
- **Reputation management company** → `REPUTATION_MANAGEMENT` → high-volume businesses that don't respond to reviews.
- **Analytics / conversion consultants** → `TRACKING`, `CONVERSION` → businesses running ads without measurable conversion paths.
- **Marketing agency** → `CONTACT_AUTOMATION`, `LOCAL_SEO` → businesses missing forms, chat, and local schema.

### Competitor comparison

For each business the Actor builds a cohort of the top local competitors (same normalized category, then same city/state), ranked by review count, excluding the target. It then computes adoption rates used as benchmarks:

```
bookingAdoptionRate  httpsAdoptionRate  contactFormRate  chatAdoptionRate
analyticsRate        websiteAdoptionRate socialPresenceRate responseRate
```

No competitor claims are made from tiny cohorts: with fewer than `minCompetitorsForGap` (default 5) competitors, competitor evidence is simply not used.

### Deterministic scoring

All scoring is rule-based and versioned (`scoringVersion: "1.0"`). Thresholds live in `src/config/scoring.ts` and `src/config/opportunity-weights.ts` — there are no scattered magic numbers. `scoreComponents` makes every score reconstructable, and `evidence[]` shows exactly which machine-detectable observations produced it.

> No AI-generated scoring. Opportunity scores are computed from observable signals and configurable deterministic rules.

### Determinism & non-AI checklist

- \[x] No OpenAI / Claude / Gemini / Llama / Mistral
- \[x] No LLM API, no embeddings, no vector DB, no ML classification
- \[x] No generated summaries, pitches, or sales copy
- \[x] All scoring rules inspectable (`src/scoring/`, `src/opportunities/`, `src/config/`)
- \[x] All evidence derives from observable data (Maps profile, HTTP/HTML, fingerprints, reviews, cohort stats)
- \[x] Identical inputs produce identical outputs

### Cost notes

- Reviews are fetched only for businesses with enough reviews and only when `REPUTATION_MANAGEMENT` is requested or reviews are enabled — never for every business.
- Websites are fetched once per domain (franchises with one domain are cached for the run).
- Websites are analyzed for all discovered (non-closed) businesses so that competitor cohorts have benchmark data. Use `excludeWebsites: true` or `FAST` mode to skip website fetching entirely.
- Set `maxBusinesses` low for cheap test runs.
- A rough cost estimate for the run (Maps upstream + reviews upstream) is logged and stored in `SUMMARY`.

### Privacy & compliance

- Public business data only. No owner emails, personal phones, or home addresses.
- No outreach automation — this Actor outputs intelligence, it never contacts anyone.
- Normal access restrictions are respected (timeouts, redirect limits, size limits, no CAPTCHA bypass).

### Known limitations

- Technology detection is fingerprint-based and imperfect — it reports confidence, never false certainty.
- Booking/ordering detection works on the analyzed homepage; deep multi-page navigation is out of scope for V1.
- Competitor benchmarks require a reasonably sized discovered universe (Mode B with a short list yields no competitor claims).
- Review analysis samples a configurable number of recent reviews (`maxReviewsPerBusiness`), it is not a census.

### Local development

```bash
npm install
npm test          # run the test suite
npm run typecheck
npm run build     # compile to dist/
```

### Deployment (Apify)

```bash
npm install
npm test
apify login       # authenticate with your Apify account token
apify push        # build and push the Actor
```

Requirements:

- An **Apify account token** (`APIFY_TOKEN`) — the Actor calls an established Google Maps upstream Actor (`compass/crawler-google-places`) and, when reviews are enabled, a reviews upstream Actor (`compass/google-maps-reviews-scraper`). These run on your account and are billed as standard Apify platform usage.
- Node.js ≥ 20.

### Pricing & monetization

This Actor uses **Pay-per-event** pricing on the Apify Store. Charged unit: **one analyzed business**.

| Event | Price | Notes |
| --- | --- | --- |
| `business-analyzed` | $0.01 / business | ~$10 per 1,000 businesses analyzed. Primary event. |

- Charging is **budget-aware**: analysis stops immediately once the user's configured max-charge budget is reached — no wasted spend.
- Outside pay-per-event runs (local dev, free/pay-per-usage executions), charging is a strict no-op.
- Platform usage from upstream Maps/reviews Actors is billed separately by Apify; see `docs/COST_MODEL.md` for the full cost model and pricing rationale.

### Example result (illustrative, not real data)

```text
#1 Example Dental     Business Strength: 94   Booking Opportunity: 97
WHY:
✓ 4.9 rating
✓ 721 reviews
✗ no booking system detected
✗ no appointment CTA detected
✓ 9/10 benchmark competitors provide booking
```

***

**License:** Apache-2.0

# Actor input Schema

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

What kinds of businesses to look for, e.g. "dentists", "beauty salons", "restaurants". Combine with a location.

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

Where to search, e.g. "Miami, Florida", "Austin, Texas". Ignored when Google Maps URLs are provided.

## `googleMapsUrls` (type: `array`):

Enrich an existing lead list. If provided, these businesses are analyzed directly instead of running a market search. Ignored searchQueries/location.

## `maxBusinesses` (type: `integer`):

Maximum number of businesses to discover/analyze from the market search.

## `analysisMode` (type: `string`):

Controls how much enrichment is performed per business.

## `findOpportunitiesFor` (type: `array`):

Which opportunity detectors to run. Only selected detectors are evaluated (saves cost). Accepts underscore or human-friendly values, e.g. "ONLINE\_BOOKING" / "online booking", "REPUTATION\_MANAGEMENT" / "reputation", "CONTACT\_AUTOMATION". Options: WEBSITE, ONLINE\_BOOKING, REPUTATION\_MANAGEMENT, CONVERSION, TRACKING, CONTACT\_AUTOMATION, ONLINE\_ORDERING, LOCAL\_SEO, LIVE\_CHAT, EMAIL\_MARKETING, SOCIAL\_PRESENCE, ECOMMERCE.

## `minimumBusinessStrength` (type: `integer`):

Only return businesses with a business-strength score at or above this value (0-100). Strength estimates observable business activity/credibility.

## `minimumOpportunityScore` (type: `integer`):

Only return businesses whose best opportunity scores at or above this value (0-100).

## `reviewAnalysisEnabled` (type: `boolean`):

Fetch and analyze public reviews (recent activity, owner response rate, unanswered negatives). Enables REPUTATION\_MANAGEMENT evidence.

## `maxReviewsPerBusiness` (type: `integer`):

Maximum number of recent reviews sampled per business. Keeps costs low.

## `websiteConcurrency` (type: `integer`):

How many websites to fetch in parallel.

## `includeClosedBusinesses` (type: `boolean`):

By default permanently closed businesses are excluded.

## `minCompetitorsForGap` (type: `integer`):

Minimum cohort size before competitor-adoption claims are made. Below this, competitor evidence is not used.

## `maxCompetitorsPerCohort` (type: `integer`):

Maximum number of competitors benchmarked against each business.

## `excludeWebsites` (type: `boolean`):

Skip all website fetching. Useful to reduce cost when only Maps-level analysis is wanted.

## Actor input object example

```json
{
  "searchQueries": [
    "dentists"
  ],
  "location": "Miami, Florida",
  "googleMapsUrls": [],
  "maxBusinesses": 100,
  "analysisMode": "STANDARD",
  "findOpportunitiesFor": [
    "WEBSITE",
    "ONLINE_BOOKING",
    "REPUTATION_MANAGEMENT",
    "CONVERSION",
    "TRACKING",
    "CONTACT_AUTOMATION"
  ],
  "minimumBusinessStrength": 30,
  "minimumOpportunityScore": 50,
  "reviewAnalysisEnabled": true,
  "maxReviewsPerBusiness": 30,
  "websiteConcurrency": 15,
  "includeClosedBusinesses": false,
  "minCompetitorsForGap": 5,
  "maxCompetitorsPerCohort": 10,
  "excludeWebsites": false
}
```

# Actor output Schema

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

Business opportunity analysis results as dataset items, one per analyzed business.

# 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": [
        "dentists"
    ],
    "location": "Miami, Florida",
    "googleMapsUrls": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("intrabit/google-maps-business-opportunity-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": ["dentists"],
    "location": "Miami, Florida",
    "googleMapsUrls": [],
}

# Run the Actor and wait for it to finish
run = client.actor("intrabit/google-maps-business-opportunity-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 '{
  "searchQueries": [
    "dentists"
  ],
  "location": "Miami, Florida",
  "googleMapsUrls": []
}' |
apify call intrabit/google-maps-business-opportunity-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,intrabit/google-maps-business-opportunity-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/G9KXJ3G64w2p2fHa9/builds/vU6BiuWglbDql8QTT/openapi.json
