# Google Maps Business Leads Extractor (`kayhermes/google-maps-business-leads-extractor`) Actor

Extract Google Maps businesses, enrich public contact data, remove duplicates, audit websites, and rank actionable leads for agencies and sales teams.

- **URL**: https://apify.com/kayhermes/google-maps-business-leads-extractor.md
- **Developed by:** [Khoa Nguyen](https://apify.com/kayhermes) (community)
- **Categories:** Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 lead results

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 Leads Extractor

Turn local-business search results into ranked, sales-ready leads. The Actor discovers Google Maps listings, normalizes and deduplicates business records, enriches public website contact details, audits common local-marketing gaps, and explains why each lead is worth contacting.

### What you get

Each output item combines:

- Business identity: name, category, status, place ID, address, coordinates, phone, website, rating, and review count.
- Public contact enrichment: emails displayed on scanned pages, phone links, and supported social profiles.
- Website intelligence: HTTPS, mobile viewport, title/meta/H1, schema markup, forms, calls to action, booking, ordering, analytics, technologies, content depth, and freshness signals.
- Lead intelligence: opportunity, contactability, and data-confidence scores; final 0–100 lead score; A–D tier; recommended service; and evidence-backed reasons.
- Provenance: source URL, query, location, run ID, and ISO timestamp.
- Exports: dataset, `OUTPUT.json`, browser-friendly HTML report, CSV, and structured error report.

The default run uses realistic offline fixtures and executes the same normalization, filtering, deduplication, website-audit, scoring, reporting, and schema-validation pipeline as imported or live data. It needs no credentials and returns a non-empty dataset for Store QA.

### Best use cases

- Local SEO agencies prioritizing businesses with weak on-page SEO or low review volume.
- Web-design agencies finding businesses without a website, mobile support, forms, or strong calls to action.
- Reputation-management teams finding low-review or rating-improvement opportunities.
- Franchise and multi-location teams building clean prospect datasets without collapsing distinct locations that share one domain.
- Sales operations importing existing lists for normalization, deduplication, enrichment, and scoring.
- Scheduled prospecting runs that output only businesses not seen in earlier runs.

### Quick start

#### Apify Console

1. Create or deploy the Actor.
2. Keep **Run mode** set to **Demo**.
3. Click **Start**.
4. Open the **Ranked business leads** dataset view or the HTML report.

#### Local default run

```bash
npm ci
npm start
```

When the Apify SDK is unavailable outside Apify, the project uses a local storage-compatible adapter. Results are written under:

```text
storage/datasets/default/
storage/key_value_stores/default/
```

#### Run tests and validation

```bash
## Requires npm ci first; the command intentionally rejects a global tsc
npm run typecheck
npm run lint
npm test
npm run test:coverage
npm run validate
npm run smoke
npm run benchmark
npm run security
```

### Input modes

#### Demo mode

Credential-free and network-free. It uses realistic fixture businesses and website HTML while exercising the production pipeline. Demo records use reserved `.test` domains and are clearly marked as fixture data.

```json
{
  "runMode": "demo",
  "includeWebsiteEnrichment": true,
  "scoringProfile": "general"
}
```

#### Live mode

Search by business type and location, process direct Google Maps place URLs, or combine both.

```json
{
  "runMode": "live",
  "searchQueries": ["dentists", "roofing contractors"],
  "locations": ["Austin, Texas", "Round Rock, Texas"],
  "maxResultsPerSearch": 50,
  "maxTotalResults": 500,
  "includeWebsiteEnrichment": true,
  "maxWebsitePages": 3,
  "scoringProfile": "localSeo",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

A proxy is strongly recommended for live discovery. The implementation uses bounded concurrency, bounded scrolling, request-per-minute limits, same-domain delays, timeouts, retries, and transparent partial-failure records. It does not include CAPTCHA solving, authentication bypass, `retryOnBlocked`, or obfuscated anti-bot bypass code.

#### Import mode

Use this mode when you already have business data from a licensed source, CRM export, prior dataset, or another Actor.

```json
{
  "runMode": "import",
  "importedBusinesses": [
    {
      "name": "Acme Dental",
      "category": "Dentist",
      "address": "100 Main Street, Austin, TX 78701",
      "phone": "+1 512 555 0123",
      "website": "https://acmedental.example",
      "rating": 4.2,
      "reviewCount": 37,
      "placeId": "your-source-place-id",
      "googleMapsUrl": "https://www.google.com/maps/place/..."
    }
  ],
  "includeWebsiteEnrichment": true,
  "scoringProfile": "webDesign"
}
```

Common aliases such as `title`, `businessName`, `type`, `categories`, `reviews`, `reviewsCount`, `lat`, `lng`, `url`, and `mapsUrl` are normalized by the importer.

### Important inputs

| Input | Purpose | Default |
|---|---|---:|
| `runMode` | `demo`, `live`, or `import` | `demo` |
| `searchQueries` | Business types or complete search phrases | `dentists` |
| `locations` | Cities, ZIP codes, regions, or countries | `Austin, Texas` |
| `startUrls` | Direct Google Maps place URLs | empty |
| `maxResultsPerSearch` | Per-query discovery cap | 25 |
| `maxTotalResults` | Hard unique-lead cap | 500 |
| `includeWebsiteEnrichment` | Crawl public business websites | true |
| `maxWebsitePages` | Homepage plus selected contact/about/booking pages | 3 |
| `scoringProfile` | `general`, `localSeo`, `webDesign`, or `reputation` | `general` |
| `onlyNewResults` | Emit only leads not present in the named history store | false |
| `maxConcurrency` | Browser concurrency cap | 5 |
| `maxRequestsPerMinute` | Request-rate cap per crawl stage | 120 |
| `sameDomainDelaySecs` | Minimum same-domain delay | 0.5 |
| `maxRequestRetries` | Retry cap | 3 |
| `emitErrorItems` | Put structured partial failures in the dataset | true |

The full form includes descriptions, sensible limits, section captions, editor types, and defaults in `.actor/input_schema.json`.

### Lead scoring

The final score is deterministic and evidence-based:

- **Opportunity score:** business gaps such as no website, inaccessible site, missing mobile viewport, missing meta description/H1/schema, weak CTA, no form, no booking or ordering, thin content, low review volume, or rating opportunity.
- **Contactability score:** phone, website, public email, and social-profile availability.
- **Data-confidence score:** identity, address, coordinates, reputation, and contact completeness.

The final score combines these dimensions as 55% opportunity, 30% contactability, and 15% data confidence. Profile multipliers change signal weighting but never hide the underlying evidence.

| Tier | Score | Meaning |
|---|---:|---|
| A | 80–100 | High-priority, reachable opportunity |
| B | 65–79 | Strong opportunity |
| C | 45–64 | Moderate opportunity |
| D | 0–44 | Low-priority or low-confidence record |

Scores are prospecting heuristics, not factual claims about business performance or guaranteed sales outcomes.

### Deduplication strategy

The Actor uses transitive identity grouping with multiple keys:

- Google place ID or CID.
- Canonical business name plus normalized address.
- Canonical business name plus normalized phone.
- Canonical business name plus website domain.
- Canonical business name plus rounded coordinates.

A domain alone is intentionally not enough to merge records. This prevents separate franchise or multi-location businesses sharing a corporate website from collapsing into one lead. The richest record becomes the base, while categories, identifiers, and provenance are merged deterministically.

### Website enrichment behavior

For each unique domain, the Actor:

1. Loads the homepage.
2. Extracts public contact and website-quality signals.
3. Ranks internal links for contact, booking, about, and locations pages.
4. Enqueues up to `maxWebsitePages - 1` supplemental pages through the same Crawlee request queue.
5. Applies the same proxy policy, robots rules, request limits, retries, timeouts, and resource blocking to every queued page.
6. Shares the merged domain audit across matching business records.

Website enrichment honors `robots.txt`. Automatic blocked-site bypass is disabled. Public emails are filtered to remove obvious example, no-reply, image-file, and placeholder addresses.

### Output example

```json
{
  "recordType": "business-lead",
  "schemaVersion": "1.0",
  "leadId": "lead_...",
  "name": "North Loop Roofing & Exteriors",
  "category": "Roofing contractor",
  "address": "4812 North Loop Boulevard, Austin, TX 78751",
  "phone": "512-555-0188",
  "website": "https://northlooproofing.test",
  "email": "estimates@northlooproofing.test",
  "rating": 4.1,
  "reviewCount": 18,
  "leadScore": 88,
  "leadTier": "A",
  "recommendedService": "Reputation management",
  "leadIntelligence": {
    "opportunityScore": 78,
    "contactabilityScore": 100,
    "dataConfidenceScore": 100,
    "opportunityReasons": [
      "No mobile viewport detected",
      "Rating improvement opportunity",
      "Low review volume"
    ]
  },
  "provenance": {
    "sourceType": "demo-fixture",
    "sourceUrl": "https://www.google.com/maps/place/...",
    "sourceQuery": "roofing contractors",
    "sourceLocation": "Austin, Texas",
    "runId": "local_...",
    "scrapedAt": "2026-07-25T12:00:00.000Z"
  }
}
```

See `sample-output/dataset.json` for the complete contract. The dataset schema defines nested business, contact, location, reputation, website-audit, scoring, error, and provenance fields.

### Stored artifacts

| Artifact | Storage | Purpose |
|---|---|---|
| Business leads and error items | Default dataset | Machine-readable result stream |
| `OUTPUT` | Default key-value store | Counts, quality metrics, billing info, and run metadata |
| `REPORT.html` | Default key-value store | Human-readable lead report |
| `LEADS.csv` | Default key-value store | CRM/spreadsheet import |
| `ERRORS` | Default key-value store | Error-only JSON array |
| `SEEN_*` records | Named history store | Optional recurring-run deduplication |

### Error handling

One failed source does not silently invalidate successful results. Failures produce records such as:

```json
{
  "recordType": "error",
  "schemaVersion": "1.0",
  "error": {
    "code": "WEBSITE_ENRICHMENT_FAILED",
    "message": "Navigation timeout exceeded",
    "retriable": true,
    "attempts": 4
  },
  "provenance": {
    "sourceUrl": "https://business.example",
    "sourceQuery": null,
    "sourceLocation": null,
    "runId": "...",
    "occurredAt": "2026-07-25T12:00:00.000Z"
  }
}
```

Input-contract violations fail early with a clear error. Network and parser failures are retried within configured bounds and then reported without secrets.

### Pricing recommendation

The roadmap defines the primary value unit as one business result and proposes pay-per-result pricing:

- Launch: **$2.50 per 1,000 raw business results**.
- Target after validation: **$3.75 per 1,000 raw business results**.

Recommended Store configuration:

- Use the synthetic `apify-default-dataset-item` event for simple one-result/one-charge billing, or configure the roadmap event `google-maps-business-leads-extractor-result` and pass it to `Actor.pushData` after cloud PPE validation.
- Keep pricing to one primary result event plus the optional synthetic Actor start event.
- Benchmark actual browser, proxy, and memory costs in Apify cloud before enabling the final price.
- Error items are visible work products; decide during Store publication whether they are billable or should be stored only in `ERRORS`.

Pricing is intentionally not hardcoded in source code because Store pricing is publication configuration and must be tested against real cloud costs.

### Responsible use and limitations

- Website contact enrichment processes public pages only and respects `robots.txt`.
- The Actor does not log credentials or include API keys, cookies, CAPTCHA solving, login automation, or anti-bot bypass payloads.
- Google Maps pages and selectors change. Live discovery requires maintenance and cloud/browser regression testing.
- Google's current terms include restrictions on scraping and storing Google Maps content. Users must review the applicable Google terms, website terms, privacy laws, marketing laws, and contractual rights before using live mode.
- Import mode is available for data the user is authorized to process.
- No email validity, ownership, deliverability, or consent is guaranteed.
- Technology detection and lead scoring are heuristic.
- Website checks are lightweight lead qualification, not a full accessibility, security, Core Web Vitals, or technical SEO audit.
- No Actor ID is included. Add a real Actor ID only when linking this package to an existing Apify Actor.

### Development structure

```text
.actor/               Actor manifest and schemas
src/core/             validation, normalization, identity, scoring, output
src/maps/             search URL, result-feed, and place-page extraction
src/website/          pure HTML audit and merge logic
src/pipeline/         normalize/filter/deduplicate/finalize stages
src/runtime/          Apify/local adapters, crawling, demo, reporting
scripts/              lint, validation, smoke, benchmark, security
fixtures/             realistic offline business and website fixtures
tests/                unit, integration, schema, and edge-case tests
sample-output/        generated example dataset and reports
```

Architecture and product decisions are documented in `ARCHITECTURE.md` and `PRODUCT_SPEC.md`.

### Verification status

The source package includes reproducible evidence in `BUILD_REPORT.md`, `VALIDATION.md`, and `evidence/`. A check is marked passed only when its command was run successfully. Docker and Apify cloud results are never inferred from local tests.

# Actor input Schema

## `runMode` (type: `string`):

Demo uses offline fixtures and the complete production pipeline. Live searches Google Maps. Import processes supplied records and can enrich their websites.

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

Each query is combined with every location. Examples: dentists, roofing contractors, bubble tea shops.

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

Cities, ZIP codes, counties, states, or countries. Leave empty when each query already includes a location.

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

Optional place URLs. These can be used alone or together with live searches.

## `importedBusinesses` (type: `array`):

Used in Import mode. Accepts common fields such as name, category, address, phone, website, rating, reviewCount, latitude, longitude, placeId, and googleMapsUrl.

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

Maximum place URLs collected for each query/location combination.

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

Hard cap after normalization, filtering, and deduplication.

## `languageCode` (type: `string`):

Language code such as en, es, de, or en-US. English is the best-tested locale.

## `countryCode` (type: `string`):

Two-letter country code used for Maps localization and phone normalization.

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

Include temporarily or permanently closed listings.

## `minimumRating` (type: `number`):

Records with no rating are treated as zero for this filter.

## `minimumReviewCount` (type: `integer`):

Records with no review count are treated as zero.

## `requirePhone` (type: `boolean`):

Remove businesses without a normalized phone number.

## `requireWebsite` (type: `boolean`):

Keep this off when finding web-design prospects with no website.

## `requireEmail` (type: `boolean`):

Requires website enrichment and removes businesses where no public email is found.

## `includeWebsiteEnrichment` (type: `boolean`):

Visits each unique domain to extract public emails, phone links, social profiles, SEO signals, technologies, booking, ordering, and conversion signals.

## `maxWebsitePages` (type: `integer`):

Includes the homepage and highest-priority contact, booking, or about pages.

## `extractEmails` (type: `boolean`):

Extracts emails displayed on the scanned website pages.

## `extractSocialProfiles` (type: `boolean`):

Extracts public Facebook, Instagram, LinkedIn, X, YouTube, TikTok, and Pinterest links.

## `scoringProfile` (type: `string`):

Changes how opportunity signals are weighted without hiding the underlying evidence.

## `minimumLeadScore` (type: `integer`):

Only output leads at or above this 0–100 score.

## `onlyNewResults` (type: `boolean`):

Uses a named key-value store so scheduled runs can deliver fresh leads only.

## `historyStoreName` (type: `string`):

Reuse the same name across scheduled runs.

## `emitErrorItems` (type: `boolean`):

Writes structured non-secret error records for failed sources while valid sources continue.

## `maxConcurrency` (type: `integer`):

Higher values are faster but consume more memory and can increase blocking.

## `maxRequestRetries` (type: `integer`):

Crawlee retries failed browser requests with session rotation and backoff.

## `maxRequestsPerMinute` (type: `integer`):

Global upper bound for browser requests in each crawl stage. Use a lower value for conservative traffic.

## `sameDomainDelaySecs` (type: `number`):

Minimum delay Crawlee applies between requests to one domain.

## `navigationTimeoutSecs` (type: `integer`):

Maximum time for a page navigation.

## `requestHandlerTimeoutSecs` (type: `integer`):

Maximum total processing time for one request.

## `maxSearchScrolls` (type: `integer`):

Stops earlier after five scroll rounds with no new places.

## `searchScrollWaitMs` (type: `integer`):

Allows dynamically loaded place cards to appear.

## `blockHeavyResources` (type: `boolean`):

Reduces browser bandwidth and memory. Disable only when diagnosing a site that depends on those resources.

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

Apify Proxy is strongly recommended for live Google Maps discovery.

## `useProxyForWebsites` (type: `boolean`):

Usually unnecessary and increases cost. Enable for websites that block datacenter traffic.

## Actor input object example

```json
{
  "runMode": "demo",
  "searchQueries": [
    "dentists"
  ],
  "locations": [
    "Austin, Texas"
  ],
  "startUrls": [],
  "importedBusinesses": [],
  "maxResultsPerSearch": 25,
  "maxTotalResults": 500,
  "languageCode": "en",
  "countryCode": "US",
  "includeClosedBusinesses": false,
  "minimumRating": 0,
  "minimumReviewCount": 0,
  "requirePhone": false,
  "requireWebsite": false,
  "requireEmail": false,
  "includeWebsiteEnrichment": true,
  "maxWebsitePages": 3,
  "extractEmails": true,
  "extractSocialProfiles": true,
  "scoringProfile": "general",
  "minimumLeadScore": 0,
  "onlyNewResults": false,
  "historyStoreName": "google-maps-business-leads-history",
  "emitErrorItems": true,
  "maxConcurrency": 5,
  "maxRequestRetries": 3,
  "maxRequestsPerMinute": 120,
  "sameDomainDelaySecs": 0.5,
  "navigationTimeoutSecs": 45,
  "requestHandlerTimeoutSecs": 120,
  "maxSearchScrolls": 40,
  "searchScrollWaitMs": 900,
  "blockHeavyResources": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "useProxyForWebsites": false
}
```

# Actor output Schema

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

Default dataset containing ranked business-lead records and optional transparent error records.

## `summary` (type: `string`):

Machine-readable run counts, quality metrics, timing, and observed billing configuration.

## `report` (type: `string`):

Human-readable HTML table of ranked leads and opportunity reasons.

## `csv` (type: `string`):

Flat CRM- and spreadsheet-friendly export of the lead convenience fields.

## `errors` (type: `string`):

JSON array containing source and enrichment failures without secrets or raw page content.

# 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"
    ],
    "locations": [
        "Austin, Texas"
    ],
    "startUrls": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("kayhermes/google-maps-business-leads-extractor").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"],
    "locations": ["Austin, Texas"],
    "startUrls": [],
}

# Run the Actor and wait for it to finish
run = client.actor("kayhermes/google-maps-business-leads-extractor").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": [
    "dentists"
  ],
  "locations": [
    "Austin, Texas"
  ],
  "startUrls": []
}' |
apify call kayhermes/google-maps-business-leads-extractor --silent --output-dataset

```

## MCP server setup

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

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/LFwUjo4LB0EI8eR9t/builds/x6QPBeaK8GlYcrU7k/openapi.json
