# Roofing Lead Scraper – Google Maps + Phone & Email (Unofficial) (`brainy_frostfield/roofing-lead-scraper-google-maps-phone-email`) Actor

UNOFFICIAL Google Maps roofing lead scraper with validated phones, optional public emails, and lead scoring. Not affiliated with Google. No AI language models.

- **URL**: https://apify.com/brainy\_frostfield/roofing-lead-scraper-google-maps-phone-email.md
- **Developed by:** [Viv K](https://apify.com/brainy_frostfield) (community)
- **Categories:** Lead generation, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 place leads

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## Roofing Lead Scraper – Google Maps + Phone & Email (Unofficial)

Find **roofing contractors and related local businesses** from Google Maps, with **validated phone numbers**, optional **public website emails**, free **email-domain (MX) checks**, and **lead scoring** (HOT / WARM / COLD).

No AI language models. No Google Places API key required.

> **UNOFFICIAL** — not affiliated with, endorsed by, or sponsored by Google LLC. Google Maps is a trademark of Google LLC.\
> You are solely responsible for complying with Google’s Terms of Service, privacy laws (GDPR/CCPA), anti-spam rules (CAN-SPAM/CASL), website terms, and [Apify’s Acceptable Use Policy](https://docs.apify.com/legal/acceptable-use-policy).\
> **Do not use for unsolicited mass messaging.** Scraping Google Maps may violate Google’s contractual terms even when data is publicly visible.

***

### Features

- Search Google Maps by **location** and **keywords** (roofers, roof repair, metal roofing…)
- Extract **name, address, phone, website, rating, hours, Maps URL**
- Detect **unclaimed** Google listings (often high-value for agencies)
- Optional crawl of public website pages for **more phones**, **emails**, and **social links**
- Normalize phones with **libphonenumber**
- Free **MX domain check** on found emails
- Simple **niche tag** (e.g. emergency roofing, metal roofing, residential roofing)
- **Lead score** 0–100 and tier **HOT / WARM / COLD**

Phones and emails are only taken from Maps and text that appears on public pages. Nothing is invented by AI.

***

### How to use

1. Add one or more **locations** (city or ZIP).
2. Add **search terms** (e.g. `roofers`).
3. Set **Max results per query** (try **10–20** first; defaults stay small for Store tests).
4. Turn on **Extract website phones** / **Extract emails** / **MX check** if you want site enrichment.
5. Set proxy to **Apify Proxy → RESIDENTIAL** (recommended for Maps).
6. Click **Start**, then download the dataset.

#### Example input

```json
{
  "locations": ["Austin, TX"],
  "searchTerms": ["roofers"],
  "maxPlacesPerQuery": 10,
  "extractWebsitePhones": true,
  "extractEmails": true,
  "validateMxRecords": true,
  "maxWebsitePagesPerPlace": 2,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": ["RESIDENTIAL"]
  }
}
```

#### Input fields

| Field | Description |
|---|---|
| Target locations | Cities, states, or ZIPs |
| Search terms | Maps keywords |
| Max results per query | Cap per location × term |
| Max total places | Optional cap for the whole run |
| Extract website phones | Crawl public pages for additional phones |
| Extract emails | Crawl public website pages for emails |
| Validate MX | Free check that the email domain can receive mail |
| Max pages per website | How many site pages to check |
| Proxy for Google Maps | Prefer **residential** |
| Filters | Phone only / email only / include no website |

***

### Output example

```json
{
  "query": "roofers in Austin, TX",
  "businessName": "Austin Peak Roofing",
  "niche": "Residential roofing",
  "address": "100 Congress Ave, Austin, TX 78701",
  "primaryPhone": "+15125550100",
  "primaryPhoneNational": "(512) 555-0100",
  "phones": [
    {
      "phone": "+15125550100",
      "national": "(512) 555-0100",
      "sourceType": "google_maps",
      "confidence": 96
    }
  ],
  "hasPhone": true,
  "website": "https://austinpeakroofing.example",
  "rating": 4.7,
  "isUnclaimed": false,
  "emails": [
    {
      "email": "office@austinpeakroofing.example",
      "source": "mailto_contact",
      "confidence": 95,
      "mxValid": true
    }
  ],
  "primaryEmail": "office@austinpeakroofing.example",
  "hasEmail": true,
  "hasContactForm": true,
  "contactPageUrl": "https://austinpeakroofing.example/contact",
  "leadScore": 58,
  "leadTier": "COLD",
  "googleMapsUrl": "https://www.google.com/maps/place/...",
  "scrapedAt": "2026-08-08T12:00:00.000Z"
}
```

***

### Pricing

**Pay per event** + **platform usage**. You pay **both**:

1. **Event fees** (below) — paid to the Actor developer
2. **Platform usage** — paid to Apify for compute, data transfer, storage, and **proxies**

#### Platform usage & residential proxy (important)

Google Maps runs need **Apify Proxy → RESIDENTIAL**. Residential traffic is billed by Apify **per GB** (rate depends on your plan; often around **$7–$8 / GB** on lower plans).

- Browser/Maps traffic is heavier than simple HTTP scrapes, so even small runs can use tens–hundreds of MB.
- **Proxy + compute usually cost more than the event fees** on short test runs.
- Start with a **small test** (low `maxPlacesPerQuery`) before large city lists.
- Without residential proxy you often get **0 results**, but you still avoid that proxy charge.

#### Event fees

| Event | When charged | Price |
|---|---|---|
| Place lead | Each business saved | **$0.004** ($4.00 per 1,000) |
| Phone enrichment | Only if at least one validated phone is found | **$0.005** ($5.00 per 1,000) |
| Email enrichment | Only if at least one public email is found | **$0.004** ($4.00 per 1,000) |

**Event fee examples** (platform usage — including residential proxy — is **extra**):

| Result mix | Approx. event cost only |
|---|---|
| 1,000 places, all with phone, no emails | **$9.00** + usage |
| 1,000 places, all with phone + email | **$13.00** + usage |
| 1,000 places, ~80% phone, ~40% email | **~$9.60** + usage |

Total cost scales with locations, search terms, max results, **residential proxy GB**, and compute. Defaults stay small for Store auto-tests and cheap first runs.

***

### Tips

- **Residential proxy** is important for Google Maps. Without it you may get zero results.
- Expect **proxy usage** to be a large share of the run cost; keep max results low while testing.
- More cities × more keywords × higher max results = longer runs and higher **proxy + compute** cost.
- **Unclaimed** listings and businesses **without a website** often score **HOT** for agencies.
- Many contractors only offer a **contact form** (no public email). Check `hasContactForm` and `contactPageUrl`.

***

### Compliance

- Public business information only; no login walls.
- Business emails and phones may still be personal data under GDPR/CCPA in some cases.
- You are responsible for lawful use and outreach (anti-spam and privacy rules).
- Do not use for spam or illegal activity.

***

### Limitations

- Google Maps may block non-residential traffic.
- Phones/emails hidden in images or complex scripts may be missed.
- `mxValid` checks the **domain**, not that a specific mailbox is active.
- Lead score is a heuristic, not a guarantee of conversion.
- Maps does not return unlimited listings per area.
- Maps page layout can change over time.

***

### Troubleshooting

| Problem | What to try |
|---|---|
| 0 results | Use residential proxy; lower max results; check the run log |
| Places but no phones | Rare for Maps; open the place log; try another location |
| Places but no emails | Common for form-only sites; try more website pages |
| Slow or costly run | Fewer locations/terms; lower max results |
| Empty after filters | Turn off “only with phone” / “only with email” for testing |

Support: open an **Issue** on this Actor with your run ID and input (no secrets).

# Actor input Schema

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

Cities, states, or ZIP codes (e.g. Austin, TX).

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

Maps keywords (roofer, roof repair, etc.). Raise max results for larger lists.

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

Maximum businesses per location × term. Default is small for fast Store tests; raise for production lists.

## `maxTotalPlaces` (type: `integer`):

Optional run-wide cap. Leave empty/0 for no global cap.

## `extractWebsitePhones` (type: `boolean`):

Crawl public website pages for additional phone numbers (tel links, JSON-LD, text).

## `extractWebsitePhonesOnlyIfMissing` (type: `boolean`):

If on, skip website crawl for phones when Google Maps already shows a phone (still crawls if emails are enabled).

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

Also collect public emails from business websites.

## `validateMxRecords` (type: `boolean`):

Free DNS check that the email domain can receive mail.

## `maxWebsitePagesPerPlace` (type: `integer`):

Pages to check (home, contact, estimate, quote, about).

## `websiteRequestDelayMs` (type: `integer`):

Polite delay between website page requests.

## `defaultCountryCode` (type: `string`):

ISO country code used when parsing phone numbers.

## `includeNoWebsite` (type: `boolean`):

Keep listings even without a website.

## `onlyWithPhone` (type: `boolean`):

Only keep leads that have at least one validated phone.

## `onlyWithEmail` (type: `boolean`):

Only keep leads with at least one public email.

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

Residential proxy is strongly recommended for Maps.

## `websiteProxyConfiguration` (type: `object`):

Optional proxy for website crawling. Leave empty for default network.

## `maxScrolls` (type: `integer`):

How many times to scroll Maps results.

## `debugLog` (type: `boolean`):

More detail in the run log.

## Actor input object example

```json
{
  "locations": [
    "Austin, TX"
  ],
  "searchTerms": [
    "roofers"
  ],
  "maxPlacesPerQuery": 5,
  "extractWebsitePhones": true,
  "extractWebsitePhonesOnlyIfMissing": true,
  "extractEmails": true,
  "validateMxRecords": true,
  "maxWebsitePagesPerPlace": 2,
  "websiteRequestDelayMs": 800,
  "defaultCountryCode": "US",
  "includeNoWebsite": true,
  "onlyWithPhone": false,
  "onlyWithEmail": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "maxScrolls": 10,
  "debugLog": false
}
```

# Actor output Schema

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

Dataset of roofing leads.

# 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 = {
    "locations": [
        "Austin, TX"
    ],
    "searchTerms": [
        "roofers"
    ],
    "maxPlacesPerQuery": 5,
    "maxWebsitePagesPerPlace": 2
};

// Run the Actor and wait for it to finish
const run = await client.actor("brainy_frostfield/roofing-lead-scraper-google-maps-phone-email").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 = {
    "locations": ["Austin, TX"],
    "searchTerms": ["roofers"],
    "maxPlacesPerQuery": 5,
    "maxWebsitePagesPerPlace": 2,
}

# Run the Actor and wait for it to finish
run = client.actor("brainy_frostfield/roofing-lead-scraper-google-maps-phone-email").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 '{
  "locations": [
    "Austin, TX"
  ],
  "searchTerms": [
    "roofers"
  ],
  "maxPlacesPerQuery": 5,
  "maxWebsitePagesPerPlace": 2
}' |
apify call brainy_frostfield/roofing-lead-scraper-google-maps-phone-email --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,brainy_frostfield/roofing-lead-scraper-google-maps-phone-email"
        }
    }
}

```

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/dsd0vVXGXfuBJYtGE/builds/TImiZTf2203FUVach/openapi.json
