# No Website Maps Scraper (`jerryboysnorr/no-website-maps-scraper`) Actor

- **URL**: https://apify.com/jerryboysnorr/no-website-maps-scraper.md
- **Developed by:** [Jeroen Ravensbergen](https://apify.com/jerryboysnorr) (community)
- **Security:** Limited permissions
- **Stats:** 6 total users, 1 monthly users, 66.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

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

**Find every business in a city that still has no website — and get it as a ready-to-use Excel file.**

### What does Google Maps – bedrijven zonder website do?

This Actor searches [Google Maps](https://www.google.com/maps) for businesses in an area you choose (for example **Leiden**), throws away everything that already has a website, and exports **only the businesses without a website** to a formatted `.xlsx` file you can open straight in Excel, Numbers or Google Sheets.

It sweeps a built-in list of ~125 common Dutch business categories — hairdressers, plumbers, restaurants, garages, bookkeepers, dog groomers and so on — so you do not have to think of every search term yourself. You can also supply your own list of search terms.

Because it runs on the Apify platform you get scheduling, an API, integrations (Slack, Google Drive, Make, Zapier), run history and monitoring for free — so you can re-run the same city every month and see which businesses are new.

### Why use this Actor?

Businesses without a website are the clearest possible lead list for anyone selling **web design, hosting, online marketing, Google Business Profile optimisation or booking software**. Google Maps already knows who they are; it just will not give you a filtered list.

- **Pre-qualified leads.** Every row is a business that has a phone number and a location, but no site to send customers to.
- **Warm leads, not scraped email dumps.** You get the business name, what it does, its address and phone number — enough to call or walk in.
- **You only pay for leads.** By default Google Maps itself filters out every business that has a link, so each place you are charged for is a business without a website.
- **Social-only businesses optional.** Turn on `includeSocialMediaOnly` to also export businesses whose only link is a Facebook page or a Thuisbezorgd menu, flagged in the **Status website** column. This costs a lot more per lead, see the pricing section.
- **Excel-ready.** No JSON wrangling: open the file, sort, filter and start calling.

### How to use this Actor

1. Open the Actor and go to the **Input** tab.
2. Fill in **Area** — the city, town or neighbourhood to search, for example `Leiden`.
3. Leave **Search terms** empty to sweep the built-in list of ~125 Dutch business categories, or type your own (`kapper`, `loodgieter`, ...).
4. Set **Max places to check in total** to control your spend. 500 places is roughly $2.50.
5. Click **Start**.
6. When the run finishes, open the **Storage → Key-value store** tab and download `leads-zonder-website.xlsx`. The same rows are also in the **Dataset** tab if you prefer CSV or JSON.

The Excel file is rewritten after every batch, so even if you stop the run halfway you still have a usable file.

#### Running it locally

```bash
npm install
apify login          # the Actor calls a paid Store Actor, so it needs your token
apify run --purge
```

### Input

| Field                         | Type    | Default                     | What it does                                                                                                        |
| ----------------------------- | ------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `area` **(required)**         | string  | `Leiden`                    | City, town, neighbourhood or region to search in.                                                                   |
| `country`                     | string  | `Netherlands`               | Appended behind the area so Google Maps knows which country you mean. Ignored when `area` already contains a comma. |
| `searchTerms`                 | array   | `[]`                        | Business types to search for. Empty means: use the built-in list of ~125 Dutch categories.                          |
| `language`                    | string  | `nl`                        | Google Maps interface language; affects the category wording and address format.                                    |
| `includeSocialMediaOnly`      | boolean | `false`                     | Also export businesses whose only link is a social media or platform page. Much more expensive per lead.            |
| `skipClosedPlaces`            | boolean | `true`                      | Leave out permanently and temporarily closed businesses.                                                            |
| `maxPlacesTotal`              | integer | `500`                       | Hard cap on how many places Google Maps returns and charges for. **This is the cost driver.**                       |
| `maxPlacesPerSearchTerm`      | integer | `100`                       | Max places returned per search term.                                                                                |
| `maxLeads`                    | integer | `0`                         | Stop after this many leads. 0 means no limit.                                                                       |
| `maxCostPerMapsRunUsd`        | integer | `0`                         | Extra safety net: stop a single Google Maps run once it has charged this much.                                      |
| `outputFileName`              | string  | `leads-zonder-website.xlsx` | Name of the Excel file in the key-value store.                                                                      |
| `searchTermsPerRun`           | integer | `10`                        | How many search terms are bundled into one Google Maps run.                                                         |
| `googleMapsActorMemoryMbytes` | integer | `4096`                      | Memory given to each Google Maps run. Lower it if you hit your plan's memory limit.                                 |

Example input:

```json
{
    "area": "Leiden",
    "country": "Netherlands",
    "searchTerms": ["kapper", "loodgieter", "trimsalon"],
    "maxPlacesTotal": 300,
    "includeSocialMediaOnly": false
}
```

### Output

Two outputs, containing the same rows:

- **Excel file** — `leads-zonder-website.xlsx` in the run's key-value store, with a frozen header row, filters and clickable Google Maps links.
- **Dataset** — the same rows as JSON, CSV, Excel or HTML. You can download the dataset in various formats such as JSON, HTML, CSV, or Excel.

```json
{
    "Bedrijf": "Kapsalon Knip & Co",
    "Soort bedrijf": "Kapper",
    "Link naar Google Maps pagina": "https://www.google.com/maps/place/Kapsalon+Knip/@52.1583,4.4900,17z",
    "Adres": "Breestraat 12, 2311 CS Leiden",
    "Telefoonnummer": "071 512 3456",
    "Status website": "Geen website",
    "zoekterm": "kapper",
    "plaats": "Leiden",
    "postcode": "2311 CS",
    "gevondenLink": "",
    "placeId": "ChIJ1",
    "aantalReviews": 34,
    "beoordeling": 4.6
}
```

#### Data table

| Column                           | Description                                                                                                                       |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| **Bedrijf**                      | Business name as shown on Google Maps.                                                                                            |
| **Soort bedrijf**                | The primary Google Maps category, e.g. *Kapper*, *Loodgieter*.                                                                    |
| **Link naar Google Maps pagina** | Direct link to the listing — clickable in Excel.                                                                                  |
| **Adres**                        | Full street address.                                                                                                              |
| **Telefoonnummer**               | Phone number as Google shows it, or the international format when that is all there is.                                           |
| **Status website**               | `Geen website`, `Alleen Facebook`, `Alleen Instagram`, `Alleen platformpagina (Thuisbezorgd)`, `Alleen gratis Google-pagina`, ... |

The dataset carries a few extra fields that are left out of the Excel file: `zoekterm`, `plaats`, `postcode`, `gevondenLink` (the raw link found, if any), `placeId`, `aantalReviews` and `beoordeling`.

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

This Actor does not scrape Google Maps itself — it calls [compass/crawler-google-places](https://apify.com/compass/crawler-google-places) from Apify Store and pays per place that scraper returns:

| What                                                              | Price   |
| ----------------------------------------------------------------- | ------- |
| Each place returned                                               | ~$0.004 |
| Website filter, per place returned (`includeSocialMediaOnly` off) | ~$0.001 |
| Each Google Maps run started                                      | ~$0.007 |

With the default settings Google Maps only returns businesses **without** a website, so you pay about **$0.005 per lead**:

| `maxPlacesTotal` | Rough cost |
| ---------------- | ---------- |
| 200              | ~$1.05     |
| 500              | ~$2.60     |
| 2000             | ~$10.10    |

With `includeSocialMediaOnly` on, the website filter cannot be used, so you pay for **every** place, including all the ones that already have a website. Google Maps lists established businesses (which usually have a website) first, so a run of a few hundred places can easily return only a handful of leads.

On the **Apify Free plan** you get a monthly credit allowance; a run stops once it is used up. The Actor logs a cost estimate before it starts and caps itself at `maxPlacesTotal`.

### Tips

- **Start small.** Run with `maxPlacesTotal: 100` on your own city first to see the data before committing to a full sweep.
- **Target the trades.** `loodgieter`, `klusbedrijf`, `stukadoor`, `hovenier`, `trimsalon` and `nagelstudio` have far more website-less businesses than restaurants or retail chains.
- **Keep `includeSocialMediaOnly` off** unless you really need the Facebook-only businesses. Off, you pay only for leads; on, you pay for every business checked.
- **Schedule it monthly** and compare runs to spot newly opened businesses — they are the most likely to still need a website.

### FAQ

**Does "no website" really mean they have no website?**
It means *no website is linked on their Google Maps listing*. A business can have a site it never added to Google. In practice that is still a useful signal — an unlinked website is its own sales pitch.

**Can I also get businesses that only have a Facebook page?**
Yes, turn on `includeSocialMediaOnly`. A Facebook or Thuisbezorgd page is not a website the owner controls, so those businesses are exported too and marked in the **Status website** column. Because Google Maps cannot filter on "only a Facebook link", you then pay for every business checked, which makes each lead far more expensive.

**Can I search a whole province or country?**
Yes, the Google Maps Scraper splits a large area into smaller map tiles itself. Raise `maxPlacesPerSearchTerm` and `maxPlacesTotal` accordingly, and keep an eye on the cost.

**Why do I sometimes get fewer results than `maxPlacesTotal`?**
Google Maps simply may not have that many businesses matching your terms in that area.

#### Is it legal to scrape Google Maps?

Scraping publicly available business information — names, addresses, phone numbers, categories — is generally legal, and this Actor collects no personal data and no reviews. You are responsible for how you use the data, including compliance with GDPR and the Dutch telemarketing rules for calling businesses. If in doubt, consult a lawyer.

#### Feedback and support

Found a bug or want another column? Open an issue on the Actor's **Issues** tab. Requests for a customised version — different country, different categories, extra enrichment — are welcome there too.

# Actor input Schema

## `area` (type: `string`):

The city, town or region to search in, for example <b>Leiden</b>. You can also give a neighbourhood (<b>Leiden Noord</b>) or a full location (<b>Leiden, Netherlands</b>).

## `country` (type: `string`):

Added behind the area so Google Maps knows which country you mean. Ignored when the area already contains a comma. Leave empty to search the area exactly as typed.

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

Business types to search for in the area, for example <b>kapper</b> or <b>loodgieter</b>. <b>Leave empty</b> to sweep a built-in list of ~125 common Dutch business categories. More terms means better coverage but a higher cost.

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

Google Maps interface language. Affects the wording of the business category and the address format.

## `includeSocialMediaOnly` (type: `boolean`):

Off (recommended): Google Maps only returns businesses with no link at all, so you only pay for actual leads. On: a business whose only link is a Facebook, Instagram or platform page (Thuisbezorgd, Treatwell, ...) is exported too, but then every place is charged, including the many that already have a website. Expect 10-20x higher cost per lead.

## `skipClosedPlaces` (type: `boolean`):

Leave out places that Google Maps marks as permanently or temporarily closed.

## `maxPlacesTotal` (type: `integer`):

Hard cap on how many places the Google Maps Scraper returns (and charges for). This is the main cost driver: about $0.005 per place, so 500 places ≈ $2.50. With social media off, every returned place is a lead.

## `maxPlacesPerSearchTerm` (type: `integer`):

Max places the Google Maps Scraper returns per search term. With social media off, only businesses without a website are returned, so this is the max number of leads per search term.

## `maxLeads` (type: `integer`):

Stop once this many businesses without a website have been found. Set to 0 for no limit.

## `maxCostPerMapsRunUsd` (type: `integer`):

Extra safety net: the platform stops a single Google Maps Scraper run once it has charged this much. Set to 0 to disable.

## `outputFileName` (type: `string`):

Name of the .xlsx file in the key-value store. Unsupported characters are replaced with a dash.

## `searchTermsPerRun` (type: `integer`):

Search terms are bundled into batches; each batch is one Google Maps Scraper run. Smaller batches give more frequent intermediate results, larger batches pay the $0.007 start fee less often.

## `googleMapsActorMemoryMbytes` (type: `integer`):

Memory in MB given to each Google Maps Scraper run. Lower it to 1024 or 2048 if you hit the memory limit of your Apify plan.

## Actor input object example

```json
{
  "area": "Leiden",
  "country": "Netherlands",
  "searchTerms": [],
  "language": "nl",
  "includeSocialMediaOnly": false,
  "skipClosedPlaces": true,
  "maxPlacesTotal": 500,
  "maxPlacesPerSearchTerm": 100,
  "maxLeads": 0,
  "maxCostPerMapsRunUsd": 0,
  "outputFileName": "leads-zonder-website.xlsx",
  "searchTermsPerRun": 10,
  "googleMapsActorMemoryMbytes": 4096
}
```

# Actor output Schema

## `excel` (type: `string`):

Download the Excel file with all businesses without a website.

## `dataset` (type: `string`):

The same rows as JSON, CSV or Excel.

# 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 = {
    "area": "Leiden",
    "searchTerms": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("jerryboysnorr/no-website-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 = {
    "area": "Leiden",
    "searchTerms": [],
}

# Run the Actor and wait for it to finish
run = client.actor("jerryboysnorr/no-website-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 '{
  "area": "Leiden",
  "searchTerms": []
}' |
apify call jerryboysnorr/no-website-maps-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jerryboysnorr/no-website-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/ZOVCKRVqTQAf7uuPr/builds/rz0QwXp4LI4tfkAra/openapi.json
