# Google Maps Leads - Outscraper Alternative, Websites Checked (`locaihost/google-maps-leads`) Actor

Google Maps scraper for leads, an Outscraper alternative that checks each business website: missing, dead, parked, no HTTPS, not mobile-ready, slow. Scored with reasons; optional emails and socials. $1.50 per 1,000 places (the most used Google Maps scraper charges $4), $2 per 1,000 sites checked.

- **URL**: https://apify.com/locaihost/google-maps-leads.md
- **Developed by:** [locaihost data](https://apify.com/locaihost) (community)
- **Categories:** Lead generation, Automation, SEO tools
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.85 / 1,000 business returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Google Maps Leads Scraper: No Website or Outdated Website

Google Maps leads with a reason to call: find local businesses on Google Maps **and see which of them have
no website or an outdated one** (dead, parked, no HTTPS, not mobile-ready, slow), all in one run.

Quick start, copy and run:

```json
{
  "searchTerms": ["plumber"],
  "locations": ["Leeds, UK"],
  "maxPlacesPerQuery": 100,
  "checkWebsites": true,
  "leadProfile": "web-design",
  "websiteFilter": "problem",
  "countryCode": "gb"
}
```

`websiteFilter: "problem"` returns only businesses whose site is missing, social-only, dead, parked,
broken, or has no HTTPS — the ones worth a call. Expect roughly 10–30% of the businesses searched to
qualify, and you pay only for the ones returned. Want to know the price before you commit to a big list?
See **Price first** below.

Every Google Maps scraper gives you a name, an address, a phone number and a website URL. You then
open a few hundred of those websites by hand to work out who is actually worth contacting. This
Actor does that pass for you: it fetches each business's site, checks it, and returns a lead score
with plain-English reasons.

```
Business                     Site status   Score  Why
Cutts Plumbing               unreachable      90  Website is listed but the domain does not resolve.
Smartpipe Plumbing Services  insecure         30  No HTTPS — browsers show visitors a "Not secure" warning.
Leeds Plumber UK             ok               15  Footer copyright still says 2014.
Norton Plumbing              ok               12  Homepage took 3.1s to respond.
```

### What you get per business

**From the listing:** name, categories, full address, city, country, latitude/longitude, business
phone, website, star rating, review count, opening hours, permanently-closed flag, and a Google Maps
link.

**From the website check:** status (`none`, `social-only`, `unreachable`, `ssl-invalid`, `parked`,
`broken`, `insecure`, `blocked`, `timeout`, `ok`; `unchecked` when website checks are off), HTTPS, mobile-ready, response time, CMS, footer copyright
year, SSL days to expiry, email provider and DMARC policy.

**From the scoring:** a 0–100 score, a band (`hot` / `warm` / `cool` / `skip`) and the reasons behind
it, written so you can paste them straight into your own outreach.

**From the business's own website (optional, `includeContacts`):** business email addresses, social
profiles and the contact page. See **Contact details** below.

### Score leads for what *you* sell

The same business is a different lead depending on who you are, so pick a profile:

| Profile | Treats as a reason to call |
|---|---|
| **Web design** | No website, dead domain, parked page, broken site, invalid certificate, no HTTPS, not mobile-ready, stale copyright, slow |
| **SEO** | Missing `<title>`, no meta description, not mobile-ready, slow homepage, few reviews, low rating |
| **Email set-up / IT** | No DMARC, DMARC set to `p=none`, certificate expiring, domain expiring, no HTTPS |
| **Local marketing** | No opening hours, few reviews, no phone, no website, low rating |

A missing DMARC record is gold to an IT provider and irrelevant to a web designer. One global
"quality" number would be worse than useless, so there isn't one.

### Honest limits — please read before buying

- **We tell you when we could not check.** If a site's firewall blocks automated requests, it comes
  back as `blocked`, never as "broken", and nothing read from the firewall's page is reported or scored.
  Facts that don't come from the page still count: DMARC and email provider (from DNS) and the Google
  listing. So a web designer gets no points for a blocked site, while an IT provider still sees its
  missing DMARC. We would rather hand you a shorter list than send you to pitch a business whose
  website is perfectly fine.
- **A timeout is not a fault.** Slow small-business sites are common, so timeouts are reported and
  deliberately not scored.
- **Google caps one search at roughly 120 results** per location. To cover a large city, split it
  into several smaller locations rather than raising the limit.
- **Ratings and review counts are Google's**, captured at the time of the run.
- **Opening hours** can occasionally be partial, in which case the row says so.

### Contact details

Turn on `includeContacts` (website checks must be on too) and each row also gets the contact details the
business publishes on its own website. They are read from the homepage the website check already fetched,
plus at most two more pages on the same site: the contact page, then the about page, both found from the
homepage's links. Those two pages are only read if the site's robots.txt allows it.

```json
{
  "searchTerms": ["dentist"],
  "locations": ["Austin, TX"],
  "maxPlacesPerQuery": 50,
  "checkWebsites": true,
  "includeContacts": true
}
```

- `emails`: shared business mailboxes only, up to 5: info@, hello@, sales@, bookings@, or an address
  named after the business itself. **Anything that looks like a person's address is dropped**:
  firstname@, firstname.lastname@, and personal Gmail, Outlook or Yahoo addresses. Addresses hidden by
  Cloudflare's email protection are decoded.
- `emailSource`: the page the first email was found on.
- `socials`: the business's Facebook, Instagram, LinkedIn company page, X, TikTok and YouTube (`null` where
  there is none). Personal profiles, share buttons and a website builder's own accounts are left out. A
  listing whose website *is* a Facebook or Instagram page gets that page here too.
- `contactPage`: the contact page's URL, when the homepage links to one.

The **Contacts** view of the dataset puts these first. An example row (names and addresses made up):

```
name         Acme Dental Care
phone        (512) 555-0134
website      https://www.acmedentalcare.example/
emails       ["info@acmedentalcare.example", "appointments@acmedentalcare.example"]
socials      {"facebook": "https://www.facebook.com/acmedentalcare",
              "instagram": "https://www.instagram.com/acmedentalcare",
              "linkedin": null, "x": null, "tiktok": null, "youtube": null}
contactPage  https://www.acmedentalcare.example/contact/
emailSource  https://www.acmedentalcare.example/
```

On a test of 55 real small-business websites (dentists, plumbers and cafés, October 2026), 71% gave at least
one email or social profile and 42% gave an email. Sites that block automated visits, or build their page
with JavaScript only, give nothing; those rows come back with empty contact columns and are not charged
for contact details.

### Only what's new since last time

Turn on **onlyNew** and a saved task remembers what it already returned, so a daily or weekly
schedule becomes a new-business radar: only businesses that have appeared since your last run.

### What it costs

| Event | Price per 1,000 |
|---|---|
| `place` — a business returned | **$1.50** |
| `website-check` — a site actually fetched and checked | **$2.00** |
| `contact-details` — only with `includeContacts`, and only when the business's own website gave at least one email or social profile | **$1.50** |

**Businesses with no website are never charged a check** — there is nothing to fetch. Turn
`checkWebsites` off and you pay only the place price. Contact details are off by default; when on, a
business whose site gave none costs nothing extra.

Free-plan users get 500 businesses per run. (Free-plan runs earn us nothing and cost us nothing, so
the cap is generous on purpose.)

### Price first: count, sample, then the full list

Building a list with an AI agent, or want the bill before you pay it? Run it in three steps. Each run's
status message states what the next step will cost, from the run's own pricing, and gives the exact
input for it (a very long search list is too big for a status message, so it then points to the run's
`SUMMARY` record, where `priceFirst` always holds the full figures and the input). Copy these as they are:

**1. Count.** An ordinary places-only run, charged per business returned. It says how many businesses
matched, how many have a website worth checking, what checking all of those would cost, and the input
for step 2.

```json
{
  "searchTerms": ["plumber"],
  "locations": ["Leeds, UK"],
  "maxPlacesPerQuery": 100,
  "countryCode": "gb",
  "checkWebsites": false
}
```

**2. Sample.** Checks a random 50 of those websites (`websiteCheckSample`, 10–500) and returns only those
50 businesses, each charged one place and one website check, so you know its price before it runs. It
reports how many of the 50 are hits, the estimated number of hits in the whole list, and what the full
run would cost. The sample is random, never the first 50 in Google's order (sorted results bunch by area and skew the hit rate). The estimate comes with a 95% range (a larger sample narrows it). The same search always picks the same 50; set `sampleSeed` to any word or number to draw a different random 50, repeatable with the same seed. Every row carries `sampleHit`.

```json
{
  "searchTerms": ["plumber"],
  "locations": ["Leeds, UK"],
  "maxPlacesPerQuery": 100,
  "countryCode": "gb",
  "checkWebsites": true,
  "websiteCheckSample": 50,
  "leadProfile": "web-design",
  "websiteFilter": "problem"
}
```

**3. Full list.** The same input without the sample.

```json
{
  "searchTerms": ["plumber"],
  "locations": ["Leeds, UK"],
  "maxPlacesPerQuery": 100,
  "countryCode": "gb",
  "checkWebsites": true,
  "leadProfile": "web-design",
  "websiteFilter": "problem"
}
```

Want contact details in the final list? Add `"includeContacts": true` to all three inputs. The count then
states the most contact details could add, the sample measures how many websites actually give them, and
the full run's estimate includes them. Each step's next input keeps the setting.

A **hit** is a business that passes your `websiteFilter`. With `websiteFilter: "any"`, which every
business passes, a hit is one scored `hot` or `warm` for your `leadProfile`. Businesses with no website,
or only a social-media page, never need a check: they are counted exactly rather than sampled, and a
sample run doesn't return them. A sample run never reads or updates the `onlyNew` memory, so leave
`onlyNew` off for these three steps.

### Lawful use

This Actor reads what any logged-out visitor sees on Google Maps and on each business's own public
website. It never signs in, never uses an account or session cookie, and never touches anything
behind a login.

It returns **businesses**, together with the contact details those businesses chose to publish. With
`includeContacts`, the emails and social profiles come only from the business's own website, and only
shared business mailboxes and business pages are kept, never a person's personal address. Some
listings — sole traders especially — show a person's name, a home address or a mobile number as the
business's own contact details. Where you are, that may still be personal data. **You are the data
controller for what you do with the output.** If you use it for marketing, the usual rules apply
(GDPR/UK GDPR and PECR in Europe, CAN-SPAM in the US, and local do-not-call registers for phone
numbers). Check before you call or email, and honour opt-outs.

### Support

Open an issue on the Actor's page. Issues are usually answered within a day.

# Actor input Schema

## `searchTerms` (type: `array`):

Business types to search on Google Maps, e.g. "dentist", "plumber", "law firm". Every term is searched in every location below.

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

Towns, cities or areas, e.g. "Austin, TX", "Leeds, UK". Each is looked up on Google Maps first.

## `maxPlacesPerQuery` (type: `integer`):

Google stops at about 120 results for one search in one place. To cover a big city, split it into several smaller locations.

## `checkWebsites` (type: `boolean`):

Fetch each website and report HTTPS, mobile-readiness, speed, CMS, footer copyright year, SSL expiry, DMARC and email provider. This is what produces the lead score. Businesses with no website are never charged a check.

## `websiteCheckSample` (type: `integer`):

0 (the default) checks every website. Set 10–500 to check only that many websites, picked at random (the same search always picks the same ones). The run then returns and charges ONLY those businesses, one place plus one website check each, so its price is known before it starts. It reports how many of them are hits, the estimated hits in the whole list and what the full run would cost. Businesses with no website or only a social page are counted, not returned. Needs website checks on; onlyNew is ignored.

## `sampleSeed` (type: `string`):

Leave empty and the same search always picks the same random sample. Enter any word or number to draw a different random sample of the same search; the same seed always gives the same sample again.

## `includeContacts` (type: `boolean`):

Off by default. Needs website checks on. Reads the homepage the check already fetched plus at most 2 more pages on the same site (the contact page, then the about page, found from its links; robots.txt is respected) and adds emails (shared business mailboxes such as info@ or bookings@ only; addresses that look like a person's are dropped), Facebook, Instagram, LinkedIn company, X, TikTok and YouTube links, and the contact page URL. Charged as one contact-details event per business, only when its website gives at least one email or social profile.

## `leadProfile` (type: `string`):

Which problems count as a reason to call. The same business scores differently for different sellers: a missing DMARC record matters to an IT provider and not to a web designer.

## `websiteFilter` (type: `string`):

"A problem" covers no website, social-only, unreachable, parked, broken or no HTTPS — the ones worth calling.

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

Closed businesses are never a lead.

## `onlyNew` (type: `boolean`):

Remembers what this saved task already returned, so a daily or weekly schedule becomes a new-business radar. Off by default so the first run returns everything.

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

Two-letter language code used for Google's result text.

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

Two-letter country code Google searches from, e.g. "us", "gb", "sg". Affects which results are shown.

## Actor input object example

```json
{
  "searchTerms": [
    "dentist",
    "orthodontist"
  ],
  "locations": [
    "Austin, TX",
    "Dallas, TX"
  ],
  "maxPlacesPerQuery": 10,
  "checkWebsites": true,
  "websiteCheckSample": 0,
  "includeContacts": false,
  "leadProfile": "web-design",
  "websiteFilter": "any",
  "skipClosed": true,
  "onlyNew": false,
  "language": "en",
  "countryCode": "us"
}
```

# Actor output Schema

## `leads` (type: `string`):

One record per business: listing details, website health and the lead score with reasons.

## `contacts` (type: `string`):

The same rows with contact details first: business emails, social profiles and the contact page (filled when includeContacts is on).

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

Places returned, website checks charged, filter counts, block rate, field-drift check and measured cost per 1,000. Count and sample runs add priceFirst: matches, hit rate, estimated hits, prices and the next step's input.

# 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 = {
    "searchTerms": [
        "dentist"
    ],
    "locations": [
        "Austin, TX"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("locaihost/google-maps-leads").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 = {
    "searchTerms": ["dentist"],
    "locations": ["Austin, TX"],
}

# Run the Actor and wait for it to finish
run = client.actor("locaihost/google-maps-leads").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 '{
  "searchTerms": [
    "dentist"
  ],
  "locations": [
    "Austin, TX"
  ]
}' |
apify call locaihost/google-maps-leads --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,locaihost/google-maps-leads"
        }
    }
}
```

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/XcuCRDq42v9q4dh7v/builds/YIw5mMeQjXsSJbDPY/openapi.json
