# Google Maps Scraper & Business Intelligence (`akatra/google-maps-business-intelligence`) Actor

Find businesses, enrich public company data and track Google Maps changes.

- **URL**: https://apify.com/akatra/google-maps-business-intelligence.md
- **Developed by:** [Akatra](https://apify.com/akatra) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 business results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 Scraper & Business Intelligence

**Find businesses, enrich public company data and track Google Maps changes.**

Business leads, emails and places from Google Maps, by Akatra. Search Google Maps for any kind of business in any city or country and get one clean row per business location: name, category, address, coordinates, phone, website, rating, review count, opening hours and status. Optionally complete the company email and social profiles from the business's own website, but only the fields Google Maps does not already show. Give the job a monitor key and later runs tell you what changed: rating, review count, phone, website, hours, status, address or name, with only the changed businesses written if you want.

**Pay per result** · **No login, no API key** · **Public business data only** · **Any country and language** · **Structured output for AI agents (MCP)**

### 🛠️ What does Google Maps Scraper & Business Intelligence do?

- 🔎 **Search any business anywhere.** `dentists` in London, `Restaurants` in Berlin, `meyve toptancısı` in Antalya, `software companies` in New York. You choose the country and the language of the results.
- 🧭 **Clean, deduplicated rows.** One row per physical location, identified by Google's own place ID. A business found by several queries appears once, with every query and rank that found it. Branches of a chain stay separate rows.
- 📇 **Company contact completion (optional).** Role-based company email (`info@`, `contact@`, `sales@` ...), LinkedIn company page, Instagram, Facebook, X and YouTube from the official website. Only the fields you ask for, and only when Google Maps does not already have them. Every value says where it was found.
- 🔁 **Change tracking.** Run the same job weekly or daily with a monitor key: the first run stores a baseline, later runs report `RATING_CHANGED`, `REVIEW_COUNT_CHANGED`, `PHONE_CHANGED`, `WEBSITE_CHANGED`, `HOURS_CHANGED`, `STATUS_CHANGED`, `ADDRESS_CHANGED`, `NAME_CHANGED`, `NEW_RESULT`, `BUSINESS_REMOVED` and `BUSINESS_RETURNED`.
- 🧹 **Only what changed.** With `onlyChanged`, unchanged businesses are not written and not charged, and their websites are not read again.
- 🗺️ **Area search past the result limit.** One Google Maps search ends after about 240 businesses. Give coordinates and a radius and the area is searched cell by cell (at most 64 cells), with duplicates removed.
- 🔒 **No personal data.** No review texts, no reviewer names, no owner or employee names, no personal emails, no personal LinkedIn profiles.

#### What people use it for

- **Lead lists.** Every dentist, cafe or software company in a city with phone, website and a company contact email.
- **Local market monitoring.** Watch competitors' ratings, review growth, opening hours and closures week by week.
- **Data hygiene.** Track a list of known businesses by Google Maps link and get told when a phone number, website or address changes, or when a place closes.
- **AI agents.** "Find dentists in London", "which places lost rating since last week", "which of these businesses changed their phone number" are answered by plain fields, no text parsing needed.

### 📊 What data can you extract?

| Group | Fields |
|---|---|
| Identity | `placeId`, `cid`, `fid`, `googleMapsUrl`, `businessName`, `primaryCategory`, `categories` |
| Location | `address`, `street`, `city`, `state`, `postalCode`, `countryCode`, `latitude`, `longitude` |
| Google Maps contact | `phone`, `phoneInternational`, `website`, `websiteDomain` |
| Reputation and status | `rating`, `reviewCount`, `openingHours`, `businessStatus` (`open`, `temporarily_closed`, `permanently_closed`, `unknown`), `imageUrl`, `isPartial` |
| Search context | `searchQuery`, `searchLocation`, `searchRank`, `appearances`, `language`, `searchCountryCode`, `sourceUrl`, `scrapedAt` |
| Company contacts (optional) | `businessEmail`, `linkedinCompany`, `instagram`, `facebook`, `x`, `youtube`, `enrichmentStatus`, `enrichmentError`, `enrichedFields`, `fieldsAlreadyComplete`, `contactProvenance` |
| Changes (with a monitor key) | `monitorStatus`, `changeTypes`, `changes`, `previousRating`, `previousReviewCount`, `previousPhone`, `previousWebsite`, `previousBusinessStatus`, `firstSeenAt`, `lastSeenAt`, `changedAt` |

A field Google does not show for a place is `null`. Nothing is guessed.

### 🚀 How to use it

1. Open the Actor and add one or more **search queries**, for example `dentists`.
2. Type a **location** with its country, for example `London, United Kingdom`, and set the **country** and **language** of the results.
3. Set **max businesses per query**. For more than about 240, use **coordinates** and a **radius**.
4. Optional: turn on **Find company email and social profiles** and pick the fields to complete.
5. Optional: set a **monitor key** (and **Only changed businesses**) and schedule the Actor. Download the results as JSON, CSV or Excel, or read them through the API.

### ⬇️ Input examples

Search one city:

```json
{
  "searchQueries": ["dentists"],
  "location": "London, United Kingdom",
  "countryCode": "GB",
  "language": "en",
  "maxResultsPerQuery": 100
}
```

Leads with company contacts:

```json
{
  "searchQueries": ["software companies"],
  "location": "New York, NY, USA",
  "countryCode": "US",
  "language": "en",
  "maxResultsPerQuery": 50,
  "includeContactEnrichment": true,
  "requiredFields": ["businessEmail", "linkedinCompany"]
}
```

Weekly change tracking, only changed businesses:

```json
{
  "searchQueries": ["restaurants", "cafes"],
  "location": "Leeds, United Kingdom",
  "countryCode": "GB",
  "language": "en",
  "maxResultsPerQuery": 60,
  "monitorKey": "leeds-food-weekly",
  "onlyChanged": true
}
```

Track known businesses by Google Maps link (a place ID goes in as `https://www.google.com/maps/place/?q=place_id:<ID>`):

```json
{
  "startUrls": [
    {"url": "https://www.google.com/maps/place/?q=place_id:ChIJnz_uNxpceUgRsqujbrD1VYc"},
    {"url": "https://maps.google.com/?cid=9751970706259946418"}
  ],
  "countryCode": "GB",
  "language": "en",
  "monitorKey": "my-accounts"
}
```

### ⬆️ Output example

One row from a live run (`restaurants` in Leeds, contact enrichment on; shortened):

```json
{
  "placeId": "ChIJnz_uNxpceUgRsqujbrD1VYc",
  "cid": "9751970706259946418",
  "googleMapsUrl": "https://www.google.com/maps/place/?q=place_id:ChIJnz_uNxpceUgRsqujbrD1VYc",
  "businessName": "Ox Club",
  "primaryCategory": "Modern British restaurant",
  "address": "19a The Headrow, Leeds LS1 6PU",
  "city": "Leeds",
  "postalCode": "LS1 6PU",
  "countryCode": "GB",
  "latitude": 53.798875,
  "longitude": -1.540921,
  "phone": "0113 487 1814",
  "phoneInternational": "+44 113 487 1814",
  "website": "http://oxclub.co.uk/",
  "rating": 4.7,
  "reviewCount": 648,
  "openingHours": [{"day": "Saturday", "hours": "12–11 pm"}, {"day": "Sunday", "hours": "12–5:30 pm"}],
  "businessStatus": "open",
  "searchQuery": "restaurants",
  "searchLocation": "Leeds, United Kingdom",
  "searchRank": 5,
  "businessEmail": "info@oxclub.co.uk",
  "instagram": "https://www.instagram.com/oxclubleeds",
  "facebook": "https://www.facebook.com/oxclubleeds",
  "enrichmentStatus": "enriched",
  "enrichedFields": ["businessEmail", "instagram", "facebook"],
  "fieldsAlreadyComplete": [],
  "contactProvenance": {
    "businessEmail": {"source": "official_website", "sourceUrl": "https://www.oxclub.co.uk/contact/", "method": "page_text", "scrapedAt": "2026-10-03T21:19:28+00:00"}
  },
  "monitorStatus": "off"
}
```

A changed row from a monitored run carries `"monitorStatus": "changed"`, `"changeTypes": ["RATING_CHANGED"]`, `"changes": [{"field": "rating", "previous": 4.3, "current": 4.5}]` and `"previousRating": 4.3`.

### 📇 Company contact enrichment

- Only the fields in **Fields to complete** are researched, and only when Google Maps does not already have them. If the Maps website of a business is its Instagram page, `instagram` is filled from Google Maps (`fieldsAlreadyComplete`) and nothing is crawled.
- Only the business's own website is read: the home page, then contact, about and imprint pages linked from it, at most 5 pages, stopping as soon as the requested fields are found. Social platforms are never crawled.
- Branches that share a website share one crawl; each branch keeps its own Google Maps phone, address and hours.
- `businessEmail` is a role-based company address only. Personal-looking addresses (`john.smith@`) are never written. LinkedIn means company pages; `/in/` personal profiles are never written.
- A website that is a page on another organisation's domain (a booking or menu platform, a parent company or council site, a link shortener) is not read, so no other organisation's email lands on the business (`enrichmentStatus = no_website`, `enrichmentError = shared_website` where it applies).
- `enrichmentStatus` tells you what happened: `enriched`, `complete_from_maps`, `not_found`, `no_website`, `failed`, `not_requested`. `enrichmentError` gives the reason (`rate_limited`, `blocked`, `js_only`, `security_blocked`, `timeout` ...).

### 🔁 Change tracking

- **First run of a key:** every business is stored as the baseline (`monitorStatus = baseline`). Nothing is reported as a change.
- **Later runs:** each business is `unchanged`, `changed`, `new`, `returned` or `removed`. With **Only changed businesses** only `new`, `changed`, `removed` and `returned` rows are written.
- **No false alarms from missing data.** A value Google does not show in one run is never a change; the last known value is kept. Opening hours are compared only between two complete answers.
- **`NEW_RESULT`** means the business was not in the previous monitored result set and is now in it: newly listed, newly ranking for your query, or moved inside the result limit. It does not mean the business is newly opened.
- **Removal is conservative.** `BUSINESS_REMOVED` is reported only for businesses you track by link or place ID, after two runs in a row in which Google answered that it knows no such place. Timeouts, blocks and rate limits never count, and a business missing from search results is never "removed" (rankings move). A permanent closure is a `STATUS_CHANGED`, not a removal.
- **Categories are not tracked** as changes: Google returns slightly different category lists for the same place from one search to the next.
- **Keys are isolated.** History is stored per monitor key, language and country in a key-value store of your own account (`maps-business-monitor`). Businesses not seen for 180 days are dropped from the history.

### 🤖 For AI agents, MCP and the API

Call the Actor through the Apify API, the Apify MCP server or any Apify integration. Every answer is a field: `searchRank` and `searchQuery` say which search found a business, `changeTypes` lists what changed, `enrichedFields` lists what the website added and `fieldsAlreadyComplete` what Google Maps already had. Dataset views `overview`, `contacts` and `changes` give ready-made column sets. The `SUMMARY` record of the run's key-value store lists every query with its status and error code, duplicates removed, enrichment counts, changes, requests and bytes per proxy type.

### 💲 Pricing: pay per result

You pay per business row written to the dataset, and separately per row to which the official website added at least one requested contact field. See the Pricing tab for the current prices.

- Contact fields Google Maps already had are not charged as enrichment, and neither are websites with nothing to find or websites that could not be read.
- Rows that **Only changed businesses** suppresses are not written and not charged.
- Failed queries produce no rows and cost nothing. There is no start fee.
- You can cap a run with the maximum charge setting; the run stops writing at the cap.

### 🚦 Run status and messages

A run ends as failed only when the input is invalid (for example a link that is not a Google Maps link). Everything else ends as succeeded with a message such as:

| Message | Meaning |
|---|---|
| `Saved N business(es) from X/Y quer(ies)` | Done. Failed queries are listed in `SUMMARY` with an error code (`blocked`, `rate_limited`, `timeout`, `parse_error`, `temporary_failure`) |
| `N duplicate(s) merged` | The same place came from several queries; one row, all appearances listed |
| `N enriched from websites, M already complete from Maps` | Contact enrichment result |
| `monitor baseline created` | First run of a monitor key |
| `N changed or new; M unchanged not repeated` | Monitoring run with only changed businesses |
| `N quer(ies) returned no businesses` | Google Maps had nothing for that query and location |
| `Stopped early: the run's maximum charge was reached` | The saved rows are complete; the rest was not written |

### ℹ️ Good to know

- **Coverage.** One Google Maps search returns at most about 240 businesses, and the list depends on the query, location, language and time. Results are not an exhaustive register of every business in an area. Area search (coordinates + radius) goes further, within 64 cells.
- **Rank.** `searchRank` is the position in Google's list for that query, location and language at run time. It is not a global ranking.
- **Sponsored results are not identified.** The source carries no sponsored marker, so the output has no sponsored field and makes no claim that a result is organic or sponsored.
- **Reduced answers.** Google sometimes answers with reduced data (no review count, one day of hours). The Actor asks again and completes such places from the place details; what still stays reduced is marked `isPartial: true`.
- **Status.** Permanent closure is read from Google's status codes in any language. Temporary closure is read from the status text and is recognised in English, German, Turkish, French, Spanish, Italian, Portuguese, Dutch, Polish, Russian, Japanese, Chinese, Korean and Nordic languages.
- **Website enrichment limits.** Websites that build their content with JavaScript only are reported as `js_only`. Some website platforms (Shopify stores in our tests, about 15 percent of sites) refuse requests from cloud servers; they are reported as `rate_limited`. robots.txt is respected.
- **Fields not available.** Google's search answer carries no price level and no country name; `countryCode` is given.
- **Access.** Requests go straight from Apify servers; a proxy is used only when Google refuses, so you do not pay for proxy traffic you do not need. Nothing logs in, no CAPTCHA is solved, no private API is used.
- **Monitoring is not real-time.** Changes are found when the Actor runs; schedule it as often as you need.
- **Limits per run:** 50 queries, 500 start URLs, 1,000 businesses per query.

### 📝 Changelog

- **1.0** (October 2026). First version: global Google Maps search, area search, start URLs and place IDs, deduplication, company contact enrichment from official websites, change tracking with only-changed output.

### ⚖️ Legal and privacy

The Actor reads publicly visible business information from Google Maps and, when you ask for it, from the business's own public website. It collects no customer or reviewer data, no review texts, no employee or owner names, no personal emails and no personal social profiles. You are responsible for using the data in line with the terms of the sites and the laws that apply to you (for example data protection and anti-spam rules when contacting businesses). This Actor is not affiliated with Google.

# Changelog

This Actor's version history is a separate document: https://apify.com/akatra/google-maps-business-intelligence/changelog.md

# Actor input Schema

## `searchQueries` (type: `array`):

What to search on Google Maps, one per line, e.g. "dentists", "software companies", "fruit wholesaler". Each query runs with the location below.

## `location` (type: `string`):

City, region or address the queries run in, e.g. "London, United Kingdom" or "Antalya, Türkiye". Add the country: a bare "Berlin" can also match Berlin in the USA.

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

Two-letter country code Google Maps uses for the search (US, GB, DE, TR ...). It localises results and phone formats.

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

Language of categories, addresses and opening hours (en, de, tr, pt-br ...).

## `maxResultsPerQuery` (type: `integer`):

One Google Maps search ends after about 240 businesses. For more, use coordinates with a radius (area search).

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

Optional. Track known businesses by Google Maps link: google.com/maps/place/..., maps.google.com/?cid=..., maps.app.goo.gl/... or a place ID as https://www.google.com/maps/place/?q=place_id:ChIJ... Links on other domains are rejected.

## `includeContactEnrichment` (type: `boolean`):

Read the business's own website (a few same-domain pages: home, contact, about) for the fields below that Google Maps does not show. Only role-based company emails and company profiles; never personal data.

## `requiredFields` (type: `array`):

Only these fields are researched, and only when Google Maps does not already provide them.

## `monitorKey` (type: `string`):

Name of a tracking job, e.g. "london-dentists-weekly". Runs with the same key are compared: the first run stores a baseline, later runs report rating, review count, phone, website, hours, status, address and name changes.

## `onlyChanged` (type: `boolean`):

With a monitor key: after the first run, write only new, changed, removed or returned businesses. Unchanged ones are not written and not charged.

## `coordinates` (type: `object`):

Center of the search instead of a location name, e.g. {"latitude": 51.5072, "longitude": -0.1276}.

## `radiusKm` (type: `number`):

With coordinates: search the circle cell by cell (at most 64 cells) to go past the ~240 results of a single search. Duplicates are removed.

## Actor input object example

```json
{
  "searchQueries": [
    "dentists"
  ],
  "location": "London, United Kingdom",
  "countryCode": "GB",
  "language": "en",
  "maxResultsPerQuery": 100,
  "includeContactEnrichment": false,
  "requiredFields": [
    "businessEmail"
  ],
  "onlyChanged": false
}
```

# Actor output Schema

## `businesses` (type: `string`):

Name, category, address, phone, website, rating, status and search rank.

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

Google Maps contact data and the company email and social profiles found on the official website, with their source.

## `changes` (type: `string`):

Change-tracking columns first (useful with a monitor key).

## `businessesFull` (type: `string`):

Every field of every row.

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

Per-query results and errors, duplicates, enrichment counts, changes, requests, bytes per proxy type and warnings.

# 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 = {
    "searchQueries": [
        "dentists"
    ],
    "location": "London, United Kingdom",
    "countryCode": "GB"
};

// Run the Actor and wait for it to finish
const run = await client.actor("akatra/google-maps-business-intelligence").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 = {
    "searchQueries": ["dentists"],
    "location": "London, United Kingdom",
    "countryCode": "GB",
}

# Run the Actor and wait for it to finish
run = client.actor("akatra/google-maps-business-intelligence").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 '{
  "searchQueries": [
    "dentists"
  ],
  "location": "London, United Kingdom",
  "countryCode": "GB"
}' |
apify call akatra/google-maps-business-intelligence --silent --output-dataset

```

## MCP server setup

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

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/1jPrfHO5iqMZUrrNT/builds/9uzggyQParUdYFR9e/openapi.json
