# Local Lead Signal Extractor (`savannahprime-agency/local-lead-signal-extractor`) Actor

- **URL**: https://apify.com/savannahprime-agency/local-lead-signal-extractor.md
- **Developed by:** [Fredrick Saruni](https://apify.com/savannahprime-agency) (community)
- **Categories:** Automation, SEO tools, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $7.50 / 1,000 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/actors/running/actors-in-store.md#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

## Local Lead Signal Extractor

Local Lead Signal Extractor is an Apify Actor for agencies, local-SEO consultants, and sales teams. It discovers businesses from Google Maps search queries or direct Google Maps search URLs, normalizes the listing data, optionally audits each business website, and returns an explainable **0–100 opportunity score**.

The product is intentionally narrower than a generic place scraper: its output is organized around **what to do next**. Each record includes observable gaps such as no website, no contact path, no booking flow, low review volume, or weak rating signals.

> Marketplace positioning: the Apify Store’s current popularity signals show substantial demand for Google Maps, social-media, search, website-content, and e-commerce Actors. This Actor differentiates itself by turning local-business discovery into a transparent prospecting workflow rather than returning raw place records only.

### What it collects

For each unique business, the default dataset may include the business name, category-like label, address, phone number, Google rating, review count, Google Maps URL, external website URL, source query, and source location. When place-detail extraction is enabled, it opens the public Google Maps detail page to recover phone and website links that are absent from a compact search card. When website auditing is enabled, it also returns public emails and phone numbers, the final URL, HTTP status, page title, meta description, contact-path signal, booking-path signal, common social-profile signals, HTTPS signal, audited-page count, and any audit error. The export exposes `publicPhones` and `publicEmails` as top-level fields for CSV, spreadsheet, and CRM workflows.

The Actor emits `opportunityScore`, `opportunityTier`, and `opportunityReasons`. The score is not a prediction of commercial success; it is a transparent prioritization heuristic based only on observable signals.

### Quick start on Apify

1. Create or open an Actor from this project.
2. Build the Actor using the included `Dockerfile`.
3. Run it with the following input:

```json
{
  "searchQueries": ["dentists", "roofers"],
  "location": "Austin, Texas",
  "maxResultsPerQuery": 20,
  "scrapeWebsiteSignals": true,
  "websiteTimeoutSecs": 12,
  "skipClosed": true,
  "maxConcurrency": 2
}
```

4. Open the default dataset and use the **Prospecting overview** or **Website and outreach signals** view.

### Input reference

| Input | Type | Default | Purpose |
| --- | --- | --- | --- |
| `searchQueries` | array of strings | `["dentists"]` | Business types or keywords to search. |
| `location` | string | `Austin, Texas` | Location appended to each query. |
| `startUrls` | array of request objects | `[]` | Optional direct Google Maps search URLs. |
| `maxResultsPerQuery` | integer | `20` | Maximum result cards inspected per query or URL; capped at 100. |
| `scrapePlaceDetails` | boolean | `true` | Open each public place detail page to recover phone and website links. |
| `scrapeWebsiteSignals` | boolean | `true` | Enable or disable the lightweight website audit, including public email and phone extraction. |
| `websiteTimeoutSecs` | integer | `12` | Per-site audit timeout, from 2 to 30 seconds. |
| `minRating` | number | optional | Optional minimum Google rating from 0 to 5. |
| `skipClosed` | boolean | `true` | Exclude listings that visibly indicate closure. |
| `maxConcurrency` | integer | `2` | Parallel browser pages, from 1 to 5. |
| `proxyConfiguration` | object | optional | Apify Proxy configuration for larger or more sensitive runs. |

At least one of `searchQueries` or `startUrls` must contain a value. Direct URLs should be Google Maps search URLs rather than individual place pages in this first release.

### Opportunity score

The score is calculated deterministically and the reasons are emitted with each record. The current heuristic adds points for a missing website, a website that cannot be verified, no obvious contact path, no obvious booking or appointment path, no common social profile link, low review volume, a rating below 4.2, and no phone number. The total is capped at 100.

| Tier | Score range | Interpretation |
| --- | ---: | --- |
| `high` | 70–100 | Multiple observable gaps; review manually before outreach. |
| `medium` | 40–69 | Some gaps are present; suitable for secondary prioritization. |
| `low` | 0–39 | Fewer gaps were detected by the heuristic. |

### Run locally

The recommended local runtime is Node.js 20 or newer. Install dependencies and run the Actor with an input file through Apify’s local environment variables or by using the Apify CLI.

```bash
npm install
npm test
npm start
```

For a local smoke test, set an input object containing at least one search query. In a normal Apify run, `Actor.getInput()` reads the JSON configured in the Console or API. Browser-based extraction may need an Apify Proxy configuration and enough memory for Chromium.

### Project files

| File | Purpose |
| --- | --- |
| `.actor/actor.json` | Actor metadata and references to schemas. |
| `.actor/input_schema.json` | Apify-generated input form and validation. |
| `.actor/output_schema.json` | Default dataset output link. |
| `.actor/dataset_schema.json` | Dataset field metadata and table views. |
| `src/main.js` | Actor implementation and reusable scoring helpers. |
| `test/main.test.js` | Deterministic unit tests. |
| `scripts/export_demo_csv.mjs` | Spreadsheet-friendly CSV export helper. |
| `scripts/render_demo_results.mjs` | Markdown and JSON demo-report helper. |
| `Dockerfile` | Browser-enabled Apify runtime configuration. |

### Responsible use

Use this Actor only for lawful and authorized data collection. Review Google’s terms, the terms of the websites you access, applicable privacy and data-protection law, and any contractual restrictions before running it. Do not use the output to send unsolicited bulk communications, infer sensitive personal attributes, bypass access controls, or collect data that you are not permitted to process. The website audit is intentionally limited to publicly reachable pages and does not attempt authentication or access-control bypasses.

### Roadmap for a commercial Store release

The first production hardening priorities are selector monitoring against representative Google Maps locales, richer category and opening-hours extraction, configurable outreach-angle templates, stronger website URL validation, retry telemetry, and a changelog with versioned selector tests. A later release could add optional enrichment integrations, but the current version avoids mandatory third-party API keys so buyers can start with a simple run.

### License

MIT

# Actor input Schema

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

Business types or keywords to search, such as \["dentists", "roofers", "yoga studios"].

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

City, region, or country to append to every search query, such as "Austin, Texas".

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

Optional Google Maps search URLs. Use this when you need a precise map query or an existing search URL.

## `maxResultsPerQuery` (type: `integer`):

Maximum number of result cards to inspect for each query or direct URL.

## `scrapePlaceDetails` (type: `boolean`):

Open each Google Maps place detail page to recover public phone numbers and external website links that may not appear in search cards.

## `scrapeWebsiteSignals` (type: `boolean`):

Fetch each discovered website and detect contact, booking, email, phone, HTTPS, and social-profile signals. Disable this for faster listing-only extraction.

## `websiteTimeoutSecs` (type: `integer`):

Maximum time to wait for each website audit.

## `minRating` (type: `number`):

Optional minimum Google Maps rating. Leave blank to include all ratings.

## `skipClosed` (type: `boolean`):

Exclude places whose listing text indicates temporary or permanent closure.

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

Number of Google Maps pages processed in parallel. Lower values are gentler on memory and rate limits.

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

Optional Apify Proxy configuration. Residential or automatic proxy sessions can improve reliability on larger runs.

## Actor input object example

```json
{
  "searchQueries": [
    "dentists"
  ],
  "location": "Austin, Texas",
  "startUrls": [],
  "maxResultsPerQuery": 20,
  "scrapePlaceDetails": true,
  "scrapeWebsiteSignals": true,
  "websiteTimeoutSecs": 12,
  "skipClosed": true,
  "maxConcurrency": 2
}
```

# Actor output Schema

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

One dataset item per unique local business discovered by the Actor.

# 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("savannahprime-agency/local-lead-signal-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("savannahprime-agency/local-lead-signal-extractor").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 savannahprime-agency/local-lead-signal-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,savannahprime-agency/local-lead-signal-extractor"
        }
    }
}

```

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/WnPNoceI2tqQWLAZw/builds/14g6rtQQdYhlkNp2F/openapi.json
