# Google Maps Lead Finder & Local Business Prospector (`egeusta/google-maps-local-business-opportunity-finder`) Actor

Find local business leads from Google Maps and score website, SEO, review and outreach opportunities for agencies and sales teams.

- **URL**: https://apify.com/egeusta/google-maps-local-business-opportunity-finder.md
- **Developed by:** [Ege Usta](https://apify.com/egeusta) (community)
- **Categories:** Lead generation, SEO tools
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## 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 Local Business Opportunity Finder

Generate qualified local business leads for web agencies, SEO consultants and
sales teams. Search Google Maps by niche and location, find businesses with
clear digital gaps, and prioritize outreach using explainable scores.

Discovers local businesses from Google Maps searches or direct place URLs and
scores each one for website, SEO, reputation and outreach opportunity using a
**deterministic** formula.

### Input

| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `searchQueries` | string\[] | `[]` | Categories/keywords. Combined with every `location`. |
| `locations` | string\[] | `[]` | One task is created per query × location. Requires `searchQueries`. |
| `startUrls` | array | `[]` | Google Maps **search** URLs or direct **place** URLs (`{ "url": "..." }` or a bare string). `requestsFromUrl` is rejected. |
| `maxResultsPerSearch` | integer | `20` | Cap per search task. |
| `maxTotalResults` | integer | `100` | Global cap on unique businesses emitted. |
| `includeClosedBusinesses` | boolean | `false` | When `false`, permanently/temporarily closed places are dropped; their status is still reported when `true`. |
| `language` | string | `"en"` | `hl` hint passed to Google Maps. |
| `requestTimeoutSecs` | integer | `30` | Per-request timeout for Maps and website fetches. |
| `enrichFromWebsite` | boolean | `true` | Enables the SSRF-guarded website crawl. |
| `maxWebsitePagesPerBusiness` | integer | `2` | Homepage + N−1 same-origin secondary pages. |
| `websiteConcurrency` | integer | `5` | Parallel website fetches. |
| `respectRobotsTxt` | boolean | `true` | Skip pages disallowed by the site's robots.txt. |
| `maxWebsiteBytes` | integer | `1500000` | Hard byte ceiling per website page. |

Every input field changes behaviour; there are no inert options.

### Output

One dataset item per unique business — see `.actor/dataset_schema.json` for the
full contract. Highlights:

- `placeId` — a real `ChIJ…` Place ID or `0x…:0x…` CID **only** when present in
  the source. URL slugs never become a Place ID; the value is `null` otherwise.
- `identityKey` — deterministic `name + address + rounded geo` (or `Place ID`, or
  `name + phone`). The source URL is deliberately excluded so the same business
  found via different URLs merges while the same name at another address stays
  separate.
- `matchedSearches` — **every** search input that produced the business; unioned
  on merge.
- `businessStatus` — `OPERATIONAL | CLOSED_TEMPORARILY | CLOSED_PERMANENTLY | UNKNOWN`.
- `websiteEnrichment` — `requested` / `attempted` / `status`
  (`ok | skipped | failed | not-requested`) / `reason`, plus HTTPS, mobile
  viewport, title, meta description, and **only literally present** emails,
  phones and social profiles. No email guessing.
- `opportunityScore`, `leadPriorityScore` (0–100), `opportunityGrade` (A–F),
  `opportunityReasons`, `recommendedPitch`, `scoreBreakdown`.

The run writes a `SUMMARY` record to the default key-value store with search /
dedupe / website counters and `failuresByReason`.

### Scoring

`opportunityScore` is a weighted average of 0–1 signals: no website (w3),
insecure website (w2), no mobile viewport (w2), missing meta description (w1),
weak reputation vs. a 3.0–4.8 benchmark (w2), low review count (w2). Missing data
**lowers** a signal — it never fabricates an opportunity. `leadPriorityScore`
blends the opportunity with reachability (phone/website/email/social) and review
volume. Grades use the shared 80/65/50/35 bands.

### Safety

- Google Maps host allow-list; non-Maps hosts and non-http(s) URLs are rejected
  with an actionable `ConfigError`.
- Website fetches go through the shared `safeFetch`: http/https only, DNS checked
  against private/loopback/link-local/CGNAT/metadata ranges on every hop, manual
  redirects with a cap + loop detection, content-type allow-list, real byte
  ceiling, `AbortController` timeout always cleared in `finally`.
- robots.txt is honoured for website pages when `respectRobotsTxt` is on.
- Only same-origin secondary pages are crawled.
- No CAPTCHA solving, no login, no private endpoints.

### Known limitations

- Google frequently serves a consent / verification interstitial to datacenter
  IPs. The Maps parser (`parseMapsResponse`) is defensive: structural drift or an
  interstitial yields **zero results and a warning**, never a crash. For reliable
  volume, run behind a residential proxy / unblocker or feed `startUrls` from a
  dedicated Google Maps scraper. The scoring, dedupe, safety and contract layers
  are fully implemented and fixture-tested regardless.
- `categoryName` is only populated when the payload exposes it.

### Commands

```bash
npm ci                 # from the monorepo root
npm run check  -w google-maps-local-business-opportunity-finder
npm run smoke  -w google-maps-local-business-opportunity-finder   # offline
npx apify validate-schema
```

### Deployment

This Actor is **not** deployed. `apify push` is intentionally not run here — the
lead engineer performs deployment.

# Actor input Schema

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

Business categories or keywords to search on Google Maps, e.g. "dentist", "coffee shop".

## `locations` (type: `array`):

Locations to combine with each search query, e.g. "Austin, TX". Each query is run once per location.

## `startUrls` (type: `array`):

Google Maps search URLs or direct place URLs. Place URLs are scraped directly without a feed selector.

## `maxResultsPerSearch` (type: `integer`):

Upper bound on places collected from a single search query/location or search URL.

## `maxTotalResults` (type: `integer`):

Global cap on the number of unique businesses emitted to the dataset.

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

When disabled, permanently and temporarily closed businesses are filtered out (their status is still reported when included).

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

Google Maps UI language hint (BCP-47 code such as "en", "de").

## `requestTimeoutSecs` (type: `integer`):

Per-request timeout for Google Maps and website fetches.

## `enrichFromWebsite` (type: `boolean`):

Fetch each business website (SSRF-guarded) to detect HTTPS, mobile readiness, and public contact details.

## `maxWebsitePagesPerBusiness` (type: `integer`):

Homepage plus this many same-origin secondary pages (e.g. contact, about).

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

Maximum number of business websites fetched in parallel.

## `respectRobotsTxt` (type: `boolean`):

Skip website pages disallowed by the site's robots.txt for this crawler.

## `maxWebsiteBytes` (type: `integer`):

Hard ceiling on bytes read from a single website page.

## Actor input object example

```json
{
  "searchQueries": [],
  "locations": [],
  "startUrls": [],
  "maxResultsPerSearch": 20,
  "maxTotalResults": 100,
  "includeClosedBusinesses": false,
  "language": "en",
  "requestTimeoutSecs": 30,
  "enrichFromWebsite": true,
  "maxWebsitePagesPerBusiness": 2,
  "websiteConcurrency": 5,
  "respectRobotsTxt": true,
  "maxWebsiteBytes": 1500000
}
```

# Actor output Schema

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

Structured local business leads with digital opportunity scores.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("egeusta/google-maps-local-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("egeusta/google-maps-local-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 '{}' |
apify call egeusta/google-maps-local-business-opportunity-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,egeusta/google-maps-local-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/MwcCPVktOAaJ64QnV/builds/tIXJCaIF0P9IIKbY2/openapi.json
