# Google Maps Business Directory Scraper (`automation-lab/google-maps-keyword-location-directory`) Actor

Build bounded Google Maps business directories from keyword and location batches, with place identities, addresses, public contacts, coordinates and ratings.

- **URL**: https://apify.com/automation-lab/google-maps-keyword-location-directory.md
- **Developed by:** [Automation Lab](https://apify.com/automation-lab) (community)
- **Categories:** Lead generation, Travel
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.44 / 1,000 business 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

## Google Maps Business Directory Scraper

Build a **Google Maps business directory** from supplied business keywords and locations. Export bounded public place records with names, categories, addresses, ratings, business websites, phones, coordinates and Maps identity. Use repeated runs to refresh a local contact directory; compare snapshots downstream rather than assuming this Actor detects changes.

This is a limited-view directory tool, not an exhaustive area scanner. It does not collect reviews, reviewer identities, photos, email addresses, employee contacts or website enrichment.

### Who is it for?

- Local-market researchers comparing visible businesses across cities.
- Agencies building small, recurring prospecting directories from public business contacts.
- Operations teams connecting place coordinates to route planning or spreadsheets.

### Why use it?

The input is a small keyword × location batch. Each business row carries the originating query, stable feature identity and extraction time. Run-global deduplication keeps one row per place without mixing prior runs into the current export. Missing public contact fields remain null; nothing is guessed or enriched.

Unlike broader Maps products, this Actor intentionally focuses on a bounded first search view and public business profiles. It offers no reviews, galleries or exhaustive discovery.

### Getting started

1. Enter one or more business keywords, such as `coffee` or `bakery`.
2. Enter cities or regions, such as `Austin Texas`.
3. Choose a per-query and global row limit.
4. Run the Actor and inspect the Business directory dataset view.
5. Export JSON, CSV or Excel from Apify, or read the dataset through its API.

### Input example

```json
{
  "keywords": ["coffee"],
  "locations": ["Austin Texas"],
  "maxPlacesPerQuery": 10,
  "maxItems": 100
}
```

### Input parameters

| Parameter | Default | Meaning |
|---|---|---|
| `keywords` | Required | 1–10 business search strings, each up to 150 characters. |
| `locations` | Required | 1–10 city/region strings, each up to 150 characters. |
| `maxPlacesPerQuery` | 10 | 1–20 new business rows per keyword/location combination after deduplication. |
| `maxItems` | 100 | 1–200 saved business rows across the entire run. |

At most 10 keyword/location combinations are supported. Every keyword is combined with every location in keyword-major input order. Strings are trimmed and whitespace-normalized; exact normalized duplicates are removed case-sensitively.

Locations are appended as search text, **not strict geographic boundaries**. Google decides relevance and may include nearby or related businesses. English interface and US search context are fixed; place records need not be in the US. No exact phrase, whole-word, rating or category filter is applied by this Actor.

Zero and unlimited limits are unsupported. The global limit, per-query limit and customer charge limit may stop collection earlier than the source view. Only up to 20 source results per query are fetched; there is no pagination.

### Extracted data

| Fields | Meaning |
|---|---|
| `name`, `category`, `categories` | Public business name and source categories. |
| `address` | Source formatted street address when shown. |
| `rating`, `reviewsCount` | Aggregate rating and review count, not individual reviews. |
| `website`, `phone` | Public business contacts shown by Maps; null when absent. |
| `location.lat`, `location.lng` | Business coordinates, not viewport center. |
| `placeId`, `fid`, `cid` | Google identities; CID is a string to preserve precision. |
| `googleMapsUrl` | Maps link using CID. |
| `keyword`, `searchLocation`, `effectiveQuery` | First query that produced the saved place. |
| `sourceUrl`, `scrapedAt`, `coverage` | Search provenance, UTC extraction time and `limited_view` scope. |

Business records must have a name, feature identity, category and valid coordinates. Geographic-only results without business categories are excluded. Optional fields can be null.

### Output example

A representative Austin coffee result has this shape (public business fields; identities omitted here for readability):

```json
{
  "name": "Epoch Coffee",
  "category": "Coffee shop",
  "categories": ["Coffee shop"],
  "address": "221 W N Loop Blvd, Austin, TX 78751",
  "rating": 4.5,
  "website": "http://www.epochcoffee.com/",
  "location": {"lat": 30.3186037, "lng": -97.7245402},
  "keyword": "coffee",
  "searchLocation": "Austin Texas",
  "effectiveQuery": "coffee Austin Texas",
  "coverage": "limited_view"
}
```

Actual rows include all documented fields. Source values and result order change over time. The key-value store `OUTPUT` record contains saved count, coverage and deduplication semantics, not additional places.

### How much does it cost to extract Google Maps business directories?

Pricing is a $0.005 run start plus each unique saved business place. BRONZE item price is $0.0024; FREE $0.00276, SILVER $0.001872, and GOLD/PLATINUM/DIAMOND $0.00144. Spend tiers follow qualifying aggregate monthly Apify Store spend, not this Actor's individual run volume.

At BRONZE, estimated costs are $0.029 for 10 places, $0.065 for 25 places and $0.245 for 100 places. The start fee applies even if no business rows are returned. Duplicates, excluded geographic records and failed requests have no item charge. Failed runs may contain already charged partial output. Consult the Pricing tab for your applicable plan tier and set a maximum total charge when needed.

### Limits and reliability

- Limited first-view results, not all businesses in a city or category.
- Run-global FID deduplication; the first producing query retains provenance.
- Sequential queries and at most two attempts per query with renewed request session identity.
- No automatic residential proxy or browser fallback, and no customer proxy controls.
- Unexpected shells, challenges and unrecognized response shapes fail instead of claiming a valid empty directory.
- A recognized geographic-only result can yield zero business rows legitimately.
- Upstream interruptions can leave partial rows; a failed run is not a complete batch.

### Tips for useful searches

Use specific business keywords and city/region names. Start with one query before expanding a batch. Nearby results are source relevance, not a hard-boundary error. Use coordinates for downstream territory checks; this Actor does not apply a geofence. Compare stable FIDs across snapshots instead of business names alone.

### Integrations

Export a directory to Google Sheets or a CRM through an Apify integration. Schedule the same input to refresh contact listings, then compare snapshots in your own workflow. Use webhooks to trigger downstream processing only after a successful run; treat failed-run exports as partial.

For routing, pass the returned coordinates to [Google Maps Directions and Routes API](https://apify.com/automation-lab/google-maps-directions-routes-api). It is a separate route-record product, not business directory enrichment.

### API usage

Start a run once and retain its run/storage IDs:

```bash
curl -X POST 'https://api.apify.com/v2/acts/automation-lab~google-maps-keyword-location-directory/runs' \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"keywords":["coffee"],"locations":["Austin Texas"],"maxPlacesPerQuery":10}'
```

JavaScript with `apify-client`:

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/google-maps-keyword-location-directory')
  .call({ keywords: ['coffee'], locations: ['Austin Texas'], maxPlacesPerQuery: 10 });
const { items } = await client.dataset(run.defaultDatasetId).listItems({ limit: 20 });
```

Python with `apify-client`:

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/google-maps-keyword-location-directory').call(
    run_input={'keywords': ['coffee'], 'locations': ['Austin Texas'], 'maxPlacesPerQuery': 10})
items = client.dataset(run['defaultDatasetId']).list_items(limit=20).items
```

Initialize clients with your own token kept outside source. Poll the same run if using the asynchronous API; do not start a replacement merely to check status.

### MCP usage

Use the Actor-specific hosted endpoint:

```bash
claude mcp add --transport http apify \
  'https://mcp.apify.com?tools=automation-lab/google-maps-keyword-location-directory'
```

Claude Desktop, Cursor and VS Code: use an HTTP-capable MCP client with this configuration (authenticate with your own Apify token):

```json
{"mcpServers":{"apify":{"url":"https://mcp.apify.com?tools=automation-lab/google-maps-keyword-location-directory"}}}
```

Discover actual names and argument schemas with scoped `tools/list`. Actor selection also adds run/storage/abort tools; discovery does not authorize execution. Example prompt: “Run the business directory for coffee in Austin Texas, maximum 10 places, and show names, categories and websites.”

For an authorized invocation, start once, retain the run ID, and poll that same run with bounded backoff (2, 4, 8, then 10 seconds) up to a 120-second consumer deadline. `call-actor` can wait at most 45 seconds, so its response may contain only run metadata. Configure client timeout above the chosen wait plus transport margin. On timeout recover the known run rather than restart; if its ID is unknown, report uncertainty.

After success, read `get-dataset-items` with explicit limit, offset and needed fields. An example consumer budget is pages of 20 source rows, at most 100 rows and 64 KiB serialized UTF-8 admitted to model context. Enforce bytes host-side; omit or summarize oversized rows with disclosure. Report continuation offset and truncated status. If the client cannot enforce bytes, do not promise a hard byte guarantee. Full exports remain outside model context. These are consumer limits, not Actor collection settings.

### Legality and privacy

Use public business data responsibly, comply with source terms and applicable privacy/contact laws, and do not assume that public phone listings authorize unsolicited outreach. This Actor is not affiliated with Google and offers no legal advice.

Failed operations send sanitized diagnostic input, exceptions and actor/build/run IDs to our private GlitchTip service for repair. Secret fields and URL queries are removed; reports are retained for 30 days. No diagnostic credentials are exposed in the input schema. Runtime uses no AI provider. Query text is sent to Google for public search; business websites are not visited. Results and run logs remain in your Apify storage until your account retention settings or deletion remove them; this Actor sets no independent dataset expiry. Request sessions are ephemeral. Contact us through the Actor's Apify Issues tab with a safe input and run ID for support.

### Troubleshooting and FAQ

**Why fewer rows than requested?** Limits are ceilings. The first view may contain fewer business results, or duplicate FIDs may already have been saved by earlier queries.

**Why are website or phone null?** The public Maps result did not show the field. No website visit or enrichment is performed.

**Can I collect reviews, photos or emails?** No. Only aggregate review counts and ratings are included.

**Does this cover every business in an area?** No. Source relevance and first-view limits make exhaustive claims inappropriate.

**What if the run fails?** Check the log for upstream status or unexpected response shape. Keep partial rows separate from complete batches. A challenge or shell is not reported as a successful empty directory.

### Related Actor

[Google Maps Directions and Routes API](https://apify.com/automation-lab/google-maps-directions-routes-api) turns known coordinates into route records. This directory Actor does not reinstate retired enrichment products or collect route records itself.

# Changelog

This Actor's version history is a separate document: https://apify.com/automation-lab/google-maps-keyword-location-directory/changelog.md

# Actor input Schema

## `keywords` (type: `array`):

1–10 nonempty business keyword strings, each at most 150 characters. Trimmed and whitespace-normalized; identical normalized values deduplicated case-sensitively. Every keyword is combined with every location (at most 10 combinations). Google relevance matching, not exact phrase or whole-word filtering.

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

1–10 city/region strings, each at most 150 characters. Cartesian-combined with keywords (maximum 10 combinations). Location is appended as search text, not a strict geographic boundary; Google may return nearby or related places. English interface and US search context are fixed, not a restriction to US place records.

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

Maximum new business rows per keyword/location combination, after run-global FID deduplication. Only the first limited search view (up to 20 source results) is fetched; no pagination or exhaustive coverage. Fewer results are valid. Zero and unlimited are unsupported.

## `maxItems` (type: `integer`):

Global maximum saved business rows across all combinations, after FID deduplication. Queries run in keyword-major input order and stop scheduling once this cap is reached; first query provenance wins. The per-query cap and customer charge limit may stop earlier. Zero and unlimited are unsupported.

## Actor input object example

```json
{
  "keywords": [
    "coffee"
  ],
  "locations": [
    "Austin Texas"
  ],
  "maxPlacesPerQuery": 10,
  "maxItems": 20
}
```

# Actor output Schema

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

Deduplicated place records for this run.

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

Saved count and coverage limitation.

# 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 = {
    "keywords": [
        "coffee"
    ],
    "locations": [
        "Austin Texas"
    ],
    "maxPlacesPerQuery": 10,
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/google-maps-keyword-location-directory").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 = {
    "keywords": ["coffee"],
    "locations": ["Austin Texas"],
    "maxPlacesPerQuery": 10,
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/google-maps-keyword-location-directory").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 '{
  "keywords": [
    "coffee"
  ],
  "locations": [
    "Austin Texas"
  ],
  "maxPlacesPerQuery": 10,
  "maxItems": 20
}' |
apify call automation-lab/google-maps-keyword-location-directory --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/google-maps-keyword-location-directory"
        }
    }
}
```

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/ldzENTXUO4i61LcuP/builds/fDYfUcOKtmwVY0Lv8/openapi.json
