# Google Maps Phone Scraper (`nodeflow/google-maps-phone-scraper`) Actor

Extract public phone numbers from Google Maps businesses based on your search queries. Automatically normalizes numbers to international E.164 format, removes duplicates, and filters by rating or category. Export clean data to JSON, CSV, or Excel.

- **URL**: https://apify.com/nodeflow/google-maps-phone-scraper.md
- **Developed by:** [NodeFlow](https://apify.com/nodeflow) (community)
- **Stats:** 2 total users, 1 monthly users, 75.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $1.00 / 1,000 results

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/actors/running/actors-in-store.md#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

## Google Maps Phone Scraper

Google Maps Phone Scraper is a focused Apify Actor that finds businesses for one or more Google Maps searches and saves **only businesses with a public phone number**. Phone numbers are normalized to international E.164 format, and duplicate places or duplicate phone numbers are removed across the whole run.

### What it does

- Searches Google Maps with your query, city, and country.
- Opens business detail pages concurrently.
- Uses DOM-readiness checks and automatic navigation retries instead of waiting for Google Maps to finish every background request.
- Skips every business without a public, usable phone number.
- Normalizes national numbers with the selected country's calling code (for example, `(212) 555-1234` becomes `+12125551234`).
- Removes duplicates by Google place identity and normalized phone number.
- Optionally filters by minimum rating and category text.
- Writes clean JSON, CSV, Excel, XML, or other formats through the Apify dataset export API.

The Actor reads only information shown publicly on Google Maps. It does not sign in, bypass access controls, or collect private contact data.

### Input

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `searchQueries` | string\[] | Yes | Business searches such as `dentists` or `coffee shops`. |
| `city` | string | No | City/locality appended to each query. |
| `country` | string | No | ISO alpha-2 code or English/Turkish country name. Also controls phone parsing. Default: `US`. |
| `maxResults` | integer | Yes | Run-wide cap for unique phone-bearing businesses, from 1 to 500. |
| `minRating` | number | No | Minimum rating from 0 to 5. Unrated places are excluded if greater than 0. |
| `categoryFilter` | string | No | Case-insensitive substring matched against the displayed category. |
| `language` | string | No | Two-letter Google Maps UI language. Default: `en`. |
| `maxConcurrency` | integer | No | Parallel detail pages, from 1 to 8. Default: 3. |
| `proxyConfiguration` | object | No | Apify Proxy configuration. Recommended on the platform; disable for local tests. |

#### Example input

```json
{
  "searchQueries": [
    "dentists",
    "orthodontists"
  ],
  "city": "New York",
  "country": "US",
  "maxResults": 25,
  "minRating": 4.5,
  "categoryFilter": "dental",
  "language": "en",
  "maxConcurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

`maxResults` is a cap, not a guarantee. A run can return fewer records when Google Maps has fewer matching businesses with public phone numbers, filters remove candidates, or Google limits access.

### Output

Each dataset row has the same shape. Optional source fields that are not displayed by Google Maps are `null` or an empty string.

```json
{
  "businessName": "Example Dental Clinic",
  "phone": "+12125551234",
  "address": "123 Main Street, New York, NY 10001",
  "city": "New York",
  "country": "US",
  "category": "Dental clinic",
  "website": "https://example.com/",
  "rating": 4.8,
  "reviewCount": 125,
  "googleMapsUrl": "https://www.google.com/maps/place/...",
  "searchQuery": "dentists"
}
```

The `phone` field is always a usable international number beginning with `+`. A row is never written if its phone number cannot be normalized.

### Run locally

Requirements: Node.js 22 or newer.

```bash
npm install
npx playwright install chromium
```

Create `storage/key_value_stores/default/INPUT.json`:

```json
{
  "searchQueries": ["dentists"],
  "city": "New York",
  "country": "US",
  "maxResults": 5,
  "proxyConfiguration": { "useApifyProxy": false }
}
```

Then run:

```bash
npm start
```

Results are written to `storage/datasets/default/` in local mode.

### Tests

```bash
npm test
npm run test:live
```

The unit suite checks input validation, country resolution, E.164 normalization, duplicate keys, numeric parsing, and filters. The live suite performs a real Google Maps search and verifies that at least one returned business has a valid E.164 phone. Because the live test depends on an external website, its result can vary with network location, consent screens, rate limits, and Google UI changes.

### Reliability and responsible operation

- Keep concurrency conservative. A value of 2-4 is normally sufficient; higher values can trigger rate limits without improving throughput.
- Use Apify Proxy for cloud runs and choose a proxy location consistent with the target country when appropriate.
- Each run creates a fresh sticky proxy session, avoiding accidental reuse of a previously throttled session while keeping one stable IP inside the browser run.
- Google Maps is an external website and can change its interface. Monitor empty runs and update selectors when necessary.
- Do not use output for spam, harassment, unlawful profiling, or automated calling that violates consent and do-not-call rules.
- Respect privacy, database, consumer-protection, marketing, and telecommunications laws in every affected jurisdiction.

### Google Maps data responsibilities

Google controls access to and permitted use of Google Maps content. Automated extraction, storage, reuse, and creation of business or telephone-listing databases may be restricted by the [Google Maps/Google Earth Additional Terms](https://www.google.com/help/terms_maps/) and the applicable [Google Maps Platform Terms](https://cloud.google.com/maps-platform/terms). These terms and local laws can change.

Before running this Actor, you are responsible for confirming that your intended collection, retention, export, and use are lawful and permitted, obtaining any required authorization, honoring deletion/opt-out requests, and applying appropriate retention and security controls. Public availability of a phone number does not by itself grant permission for every downstream use. This project is a technical tool, not legal advice, and is not affiliated with or endorsed by Google.

### Limitations

- Only phone numbers visible on Google Maps detail pages are returned.
- The category filter matches Google's displayed category text; it is not a taxonomy lookup.
- `city` and `country` describe the requested search scope. Google can occasionally show nearby places outside that scope.
- Google may show localized or experiment-specific layouts, consent pages, or anti-bot challenges.
- A business that shares a phone number with another listing appears only once by design.

# Actor input Schema

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

One or more business searches, for example: dentists or coffee shops.

## `city` (type: `string`):

City or locality to append to every search query.

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

ISO 3166-1 alpha-2 country code or English/Turkish country name. Used for searching and phone normalization.

## `maxResults` (type: `integer`):

Maximum number of unique businesses with phone numbers across all queries.

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

Optional minimum Google rating (0-5). Businesses without a rating are excluded when this is set above 0.

## `categoryFilter` (type: `string`):

Optional case-insensitive text matched against the Google Maps category.

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

Two-letter interface language. English is the most thoroughly tested.

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

Number of Google Maps business detail pages processed in parallel.

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

Apify Proxy is recommended for reliable cloud runs. Disable it for local testing.

## Actor input object example

```json
{
  "searchQueries": [
    "dentists"
  ],
  "city": "New York",
  "country": "US",
  "maxResults": 50,
  "minRating": 0,
  "language": "en",
  "maxConcurrency": 3,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `dataset` (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 = {
    "searchQueries": [
        "dentists"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("nodeflow/google-maps-phone-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 = { "searchQueries": ["dentists"] }

# Run the Actor and wait for it to finish
run = client.actor("nodeflow/google-maps-phone-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 '{
  "searchQueries": [
    "dentists"
  ]
}' |
apify call nodeflow/google-maps-phone-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,nodeflow/google-maps-phone-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/eUCePdjPrHr40HKZp/builds/BkbOKYcFRE3xXCZLf/openapi.json
