# Google Maps Scraper: Leads Without Websites, Emails, Site Audit (`scrape-lads/google-maps-scraper`) Actor

Scrape Google Maps places by keyword and location: name, phone, website, rating, reviews, address, hours, plus emails from each business website and a website check that finds businesses with no site, a broken or suspended site, or no mobile version. No browser, no API key.

- **URL**: https://apify.com/scrape-lads/google-maps-scraper.md
- **Developed by:** [Scrape Lads](https://apify.com/scrape-lads) (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

from $0.80 / 1,000 places

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

### What does Google Maps Scraper do?

**Google Maps Scraper** turns a Google Maps search like *"plumber in Dallas, TX"* into a clean lead list: **business name, phone, website, rating, review count, full address, opening hours, coordinates and categories**, plus optional **emails and social links** read from each business's own website, and an optional **website check** that tells you which businesses need a new site: no website, a site that is down or suspended, no mobile version, no HTTPS, or a copyright notice that hasn't changed in years. Type search terms and locations, click Start, and download the places as JSON, CSV or Excel.

No browser, no Google API key and no Google account. It runs on the Apify platform, so you also get scheduling, API access, integrations (Google Sheets, Zapier, Make, n8n, webhooks) and run monitoring.

### What data can you extract from Google Maps?

| 🏢 Name and categories | 📞 Phone (local and international) | 🌐 Website and domain |
|---|---|---|
| 📧 Emails from the website | 🔗 Facebook, Instagram, LinkedIn, X, YouTube, TikTok | ⭐ Rating and review count |
| 📍 Address, street, city, ZIP, state, country | 🗺️ Latitude and longitude | 🕒 Opening hours per day |
| 📅 Booking link | 👤 Listing owner name | 🆔 Place ID, CID, Maps link |
| 🩺 Website status: ok, down, suspended | 📱 Mobile-friendly, HTTPS, copyright year | ✅ Needs website work (yes/no) |

### Why use this Google Maps lead scraper?

- **Find businesses that need a new website.** Set **Website filter → Only places that need website work** and you get local businesses with no website, a broken or suspended site, no mobile version, no HTTPS, or a footer that hasn't changed in 5+ years, each with the reason. Ready-made leads for web designers, SEO and marketing agencies.
- **Qualify by reputation.** Keep only places with a minimum rating, or with few reviews (small businesses), or many reviews (busy ones).
- **Contacts in one run.** Switch on **Find emails and social links** and the scraper opens each business website, plus its contact page if needed.
- **Fast and cheap.** Plain HTTP, no headless browser: hundreds of places per minute.

### How to scrape Google Maps

1. Open the **Input** tab.
2. Add **Search terms** (for example `plumber`, `dentist`, `roofing contractor`).
3. Add **Locations** (for example `Dallas, TX`, `75206`, `Berlin Kreuzberg`). Every term is searched in every location.
4. Optional: switch on **Find emails and social links** and **Check each website**, set a **Website filter** or a minimum rating.
5. Click **Start** and download the results from the **Output** tab.

**Getting more than ~120 places for one city:** Google Maps shows at most about 120 to 200 places per search. Split a big city into ZIP codes or neighborhoods (one per line in Locations). Duplicates across searches are removed automatically.

### Input example

```json
{
    "searchTerms": ["plumber", "electrician"],
    "locations": ["Dallas, TX", "Fort Worth, TX"],
    "maxPlacesPerSearch": 100,
    "scrapeContacts": true,
    "checkWebsite": true,
    "websiteFilter": "needsWork",
    "minReviews": 10
}
```

### Output example

```json
{
    "name": "Public Service Plumbers",
    "category": "Plumber",
    "categories": ["Plumber", "Air conditioning contractor", "Drainage service"],
    "phone": "(214) 831-1959",
    "phoneInternational": "+12148311959",
    "website": "https://publicserviceplumbers.com/",
    "domain": "publicserviceplumbers.com",
    "hasWebsite": true,
    "emails": ["office@example.com"],
    "socialLinks": { "facebook": "https://www.facebook.com/example" },
    "contactsStatus": "ok",
    "rating": 4.9,
    "reviewCount": 2011,
    "address": "5610 Dyer St, Dallas, TX 75206",
    "street": "5610 Dyer St",
    "city": "Dallas",
    "postalCode": "75206",
    "state": "Texas",
    "countryCode": "US",
    "latitude": 32.8442506,
    "longitude": -96.7735602,
    "openingHours": [{ "day": "Monday", "hours": "Open 24 hours" }],
    "timezone": "America/Chicago",
    "permanentlyClosed": false,
    "bookingUrl": "https://book.servicetitan.com/...",
    "ownerName": "Public Service Plumbers",
    "mapsUrl": "https://www.google.com/maps?cid=9557479062172064673",
    "placeId": "ChIJ-xSG3QyfToYRodv_wJH8ooQ",
    "cid": "9557479062172064673",
    "searchQuery": "plumber in Dallas, TX",
    "rank": 1,
    "scrapedAt": "2026-11-01T14:07:30.734Z"
}
```

The `emails` and `socialLinks` values above are placeholders. `contactsStatus` is `ok` when the website was read, `failed` when the site blocked us or was down, and `no-website` when the place has none. It's `null` when contact scraping is off.

### Website check: find businesses that need a new website

Switch on **Check each website** and every place gets four more fields:

```json
{
    "websiteStatus": "down",
    "websiteIssues": ["website is down: shows \"Account Suspended\""],
    "needsWebsiteWork": true,
    "websiteCheck": { "status": "down", "detail": "shows \"Account Suspended\"", "https": null, "mobileViewport": null, "copyrightYear": null }
}
```

A real example from a test run in Austin, TX: a dentist with **4.8 stars from 325 reviews** whose website link shows "Account Suspended", and a body shop with **458 reviews** whose site has no mobile version and still says © 2011. Those are the businesses that answer a web designer's email.

| `websiteStatus` | Meaning |
|---|---|
| `ok` | The homepage loaded. `websiteIssues` lists what we found: no HTTPS, not mobile-friendly, old copyright year, no page title or meta description, no contact form, no tap-to-call link, built with Wix/GoDaddy/Weebly, no LocalBusiness schema. |
| `down` | The domain doesn't resolve, the server refuses connections, the homepage returns 404/410/5xx twice, or the page shows a suspended-hosting or domain-for-sale notice. |
| `no-website` | No website on Google Maps, or only a Facebook/Instagram page. |
| `unknown` | The site blocks automated checks or only renders with JavaScript. We say nothing about it, and it isn't charged. |

`needsWebsiteWork` is `true` for the strong reasons only: no website, down, no mobile version, no HTTPS, or a copyright notice 5+ years old. Weak findings (no meta description, no schema) are listed in `websiteIssues` but don't set it.

**We never guess.** A site counts as down only after two failed tries, and only when its homepage fails too: a broken deep link on a working site is `unknown`, not `down`. Copyright years are read from the visible page, not from code comments. Emails found on a site are kept only when they belong to the business: the web designer's address, font credits and template placeholders are removed.

### How much does it cost to scrape Google Maps?

Pay per result, no subscription:

| You get | Price |
|---|---|
| A place in your dataset | $1.00 per 1,000 |
| A place with an email found on its website | + $1.00 per 1,000 |
| A website checked (status `ok` or `down`) | + $1.00 per 1,000 |

Places removed by your filters, duplicates, and websites we couldn't check (`unknown`) are free. Higher Apify plans get a discount, shown on the **Pricing** tab.

Examples: 1,000 plumbers with phone, website and rating cost **$1**. The same 1,000 with emails switched on cost about **$1.40** when 40% have an email on their site. 1,000 businesses that need website work, with the reason, cost **about $2**.

### Tips

- **Language and country:** set `language` (for example `de`) and `countryCode` (for example `de`) to search Maps as a local would.
- **Your own Maps search:** paste a `https://www.google.com/maps/search/...` URL into **Google Maps search URLs**.
- **Schedule it:** run the same input daily or weekly and compare the datasets to spot new businesses.

### FAQ

**Is it legal to scrape Google Maps?** The scraper collects publicly visible business listing information. Business contact data can still fall under privacy laws such as GDPR in some countries, and sending marketing messages is regulated (for example CAN-SPAM in the US). Check the rules where you operate.

**Does it scrape reviews?** Not yet. It returns the rating, review count and a link to the reviews.

**Why do I get fewer places with "Only places that need website work"?** Usually 5-15% of businesses in a search qualify, and Google returns at most about 120-200 places per search. For a bigger list, add more locations (ZIP codes or neighborhoods) and related search terms.

**Why is a website `unknown`?** Some sites block automated visitors or only show their content with JavaScript. We don't claim anything about those, and you aren't charged for the check.

**Something missing or broken?** Open an issue on the **Issues** tab and we'll take a look.

# Actor input Schema

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

What to search on Google Maps, one per line, for example "plumber" or "dentist". Each term is searched in every location below.

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

Cities, neighborhoods or ZIP codes, one per line, for example "Dallas, TX" or "75206". Each search returns at most about 120-200 places, so split a big city into ZIP codes or neighborhoods to get more. Leave empty to search the terms as written.

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

Optional. Google Maps search URLs (https://www.google.com/maps/search/...), one per line, if you prefer to set up the search in Maps yourself.

## `maxPlacesPerSearch` (type: `integer`):

Stop each search after this many places (after filters). Google returns at most about 120-200 per search.

## `maxPlaces` (type: `integer`):

Stop the whole run after this many places. 0 means no limit.

## `scrapeContacts` (type: `boolean`):

Open each business website (and its contact page if needed) and collect emails plus Facebook, Instagram, LinkedIn, X, YouTube and TikTok links. Charged only for places where an email was found.

## `checkWebsite` (type: `boolean`):

Open each business website and report what a web designer or SEO agency can pitch on: site down or showing "Account Suspended", 404, no mobile version, no HTTPS, an old copyright year, no contact form, no tap-to-call link. Sites that block automated checks are marked unknown and are not charged.

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

Keep all places, only places with or without a website, or only places that need website work: no website, a site that is down or suspended, no mobile version, no HTTPS, or a copyright notice 5+ years old (switches on the website check). Filtered places are never charged.

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

Skip places rated below this (1 to 5). Leave empty for no limit.

## `minReviews` (type: `integer`):

Skip places with fewer reviews than this.

## `maxReviews` (type: `integer`):

Skip places with more reviews than this, for example to find small businesses.

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

Leave out places Google marks as permanently closed.

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

Language code for results, for example en, de, fr, es.

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

Two-letter country code Google should search from, for example us, gb, de, ca.

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

How many searches run at the same time.

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

Proxy for Google requests. The default works for most runs.

## Actor input object example

```json
{
  "searchTerms": [
    "plumber"
  ],
  "locations": [
    "Dallas, TX"
  ],
  "maxPlacesPerSearch": 100,
  "maxPlaces": 0,
  "scrapeContacts": false,
  "checkWebsite": false,
  "websiteFilter": "all",
  "skipClosed": true,
  "language": "en",
  "countryCode": "us",
  "maxConcurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

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

No description

## `websiteCheck` (type: `string`):

No description

## `allFields` (type: `string`):

No description

## `runSummary` (type: `string`):

No description

# 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": [
        "plumber"
    ],
    "locations": [
        "Dallas, TX"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrape-lads/google-maps-scraper").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": ["plumber"],
    "locations": ["Dallas, TX"],
}

# Run the Actor and wait for it to finish
run = client.actor("scrape-lads/google-maps-scraper").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": [
    "plumber"
  ],
  "locations": [
    "Dallas, TX"
  ]
}' |
apify call scrape-lads/google-maps-scraper --silent --output-dataset

```

## MCP server setup

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

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/e0qrgPmyluFLLufBT/builds/7EuxnaSHJzaRXh75T/openapi.json
