# Google Maps Scraper - Places, Contacts & Emails (`solidscrape/google-maps-places-scraper`) Actor

Scrape Google Maps business listings without an API key: name, address, phone, website, rating, reviews, coordinates, categories, hours. Free email + social enrichment and geo-grid tiling to beat the 120-result cap. Export JSON/CSV/Excel.

- **URL**: https://apify.com/solidscrape/google-maps-places-scraper.md
- **Developed by:** [Shahryar](https://apify.com/solidscrape) (community)
- **Categories:** Lead generation
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 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?

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 Scraper – Places, Contacts & Emails (No API Key)

This **Google Maps scraper** extracts structured **business listings from Google Maps** with **no API key** required: **name, full address, phone, website, rating, reviews, coordinates, categories, price level, and opening hours** — straight from a natural-language search query. It then enriches every place with **contact emails and social profiles for free**. Use it as a keyless **Google Maps API** alternative and **export JSON, CSV, or Excel** in one click.

Built for **lead generation, local SEO, sales prospecting, and market research**.

> 💡 **Pairs perfectly with the [Contact Details Scraper](https://apify.com/store)** — feed in the `website` URLs from this scraper to dig out even more emails, phones, and social links for end-to-end **local lead generation**.

### Why this scraper

- 💸 **Free email + social enrichment** – most Google Maps actors charge extra (or sell it as a separate add-on) to turn a listing into a real lead. Here it is **included**: when a place has a website, the Actor visits it and pulls a contact **email** plus **Facebook / Instagram / LinkedIn / X / YouTube / TikTok** links.
- 🧱 **Beats the ~120-result cap** – a single Google query returns only ~120 places. This Actor **subdivides the search area into a geo-grid** of overlapping tiles and re-queries each one, so you can collect far more than 120 when you ask for it.
- ⚡ **Browserless & fast** – it calls Google Maps' internal `tbm=map` search endpoint and parses the protobuf-over-JSON response directly. No headless browser, which makes it much cheaper to run than Playwright-based scrapers.
- 🔎 **Transparent, flat behavior** – one input, one output type, no hidden paid tiers for the data you already scraped.

### What it does

- 🗺️ **Search Google Maps by query** – e.g. `"dentists in Austin TX"`, paginated automatically.
- 🏢 **Full place details** – name, category, address (with city/state/postal split), phone, website, rating, review count (best-effort), latitude/longitude, price level, and opening hours.
- 📧 **Email & social enrichment (free)** – a secondary fetch of each business website extracts a contact email plus Facebook, Instagram, LinkedIn, X/Twitter, YouTube, and TikTok links.
- 🧩 **Geo-grid tiling** – automatically expands beyond Google's per-query cap when `maxItems` is large.
- 🌍 **Locale control** – set `language` (`hl`) and an optional `countryCode` (`gl`).
- 📤 **Export anywhere** – download results as **JSON, CSV, or Excel**, or pull them via the Apify API.

### Example input

```json
{
  "searchQueries": ["dentists in Austin TX"],
  "maxItems": 100,
  "language": "en",
  "countryCode": "us",
  "scrapeEmails": true,
  "geoGridTiling": true,
  "maxTiles": 16,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}
```

### Example output

```json
{
  "type": "place",
  "title": "Smile Dental of Austin",
  "categoryName": "Dentist",
  "categories": ["Dentist", "Cosmetic dentist"],
  "address": "123 Congress Ave, Austin, TX 78701, United States",
  "neighborhood": null,
  "city": "Austin",
  "state": "TX",
  "countryCode": "US",
  "postalCode": "78701",
  "phone": "(512) 555-0142",
  "website": "https://smiledentalaustin.com/",
  "email": "info@smiledentalaustin.com",
  "socials": {
    "facebook": "https://facebook.com/smiledentalaustin",
    "instagram": "https://instagram.com/smiledentalaustin"
  },
  "rating": 4.8,
  "reviewsCount": 412,
  "latitude": 30.2685,
  "longitude": -97.7426,
  "priceLevel": 2,
  "hours": [{ "day": "Monday", "hours": "9 AM–5 PM" }],
  "googleUrl": "https://www.google.com/maps/place/?q=place_id:...&ftid=0x...:0x...",
  "placeId": "ChIJ...",
  "fid": "0x8644b59a...:0x...",
  "language": "en",
  "scrapedAt": "2026-06-26T12:00:00.000Z",
  "facebook": "https://facebook.com/smiledentalaustin",
  "instagram": "https://instagram.com/smiledentalaustin",
  "linkedin": null,
  "twitter": null,
  "youtube": null,
  "tiktok": null
}
```

### Output fields

Every item is a single **place** record (`type: "place"`). The table below lists **every field the scraper emits**, cross-checked against the source code. The most-used social links are also flattened to top-level columns (`facebook`, `instagram`, `linkedin`, `twitter`, `youtube`, `tiktok`) so they map cleanly to CSV/Excel cells.

| Field | Type | Description |
|---|---|---|
| `type` | string | Always `"place"`. Lets you filter records if you merge datasets. |
| `title` | string | Business name. |
| `categoryName` | string | null | Primary Google category (first of `categories`). |
| `categories` | string\[] | All Google categories for the place (may be empty). |
| `address` | string | null | Full formatted address as shown on Google Maps. |
| `neighborhood` | string | null | Neighborhood. Currently rarely populated by Google's list response. |
| `city` | string | null | City, parsed from the address. |
| `state` | string | null | State / region code, parsed from the address. |
| `countryCode` | string | null | Two-letter country code. Falls back to your `countryCode` input when Google omits it. |
| `postalCode` | string | null | Postal / ZIP code, parsed from the address. |
| `phone` | string | null | Phone number (national format, with international `+E.164` fallback). |
| `website` | string | null | Business website URL, when Google lists one. |
| `email` | string | null | Contact email scraped from the website (only when `scrapeEmails` is on and the site exposes one). |
| `socials` | object | Map of social network → profile URL (only networks found). Empty `{}` when none. |
| `rating` | number | null | Average star rating (0–5), when Google shows it. |
| `reviewsCount` | number | null | Total number of reviews. **Best-effort** — Google does not return a count for every place, so this can be `null` even for listings that have reviews (see Notes). |
| `latitude` | number | null | Latitude of the place. |
| `longitude` | number | null | Longitude of the place. |
| `priceLevel` | number | null | Price level `1`–`4` (`$`–`$$$$`), when Google shows it. |
| `hours` | array | null | Opening hours as `[{ "day": "Monday", "hours": "9 AM–5 PM" }, …]`, when shown. |
| `googleUrl` | string | null | Deep link back to the Google Maps place. |
| `placeId` | string | null | Google `ChIJ...` place ID, when present in the list response. |
| `fid` | string | null | Google feature ID (`0x...:0x...`), used internally for deduplication. |
| `language` | string | null | The `language` (`hl`) code used for the request. |
| `scrapedAt` | string | ISO 8601 timestamp of when the record was scraped. |
| `facebook` | string | null | Facebook profile URL (flattened from `socials`). |
| `instagram` | string | null | Instagram profile URL (flattened from `socials`). |
| `linkedin` | string | null | LinkedIn profile URL (flattened from `socials`). |
| `twitter` | string | null | X / Twitter profile URL (flattened from `socials`). |
| `youtube` | string | null | YouTube channel URL (flattened from `socials`). |
| `tiktok` | string | null | TikTok profile URL (flattened from `socials`). |

> Fields Google does not expose for a given place are returned as `null` (or `[]` / `{}` for arrays/objects) rather than guessed — accuracy over completeness.

### Common use cases

- **Lead generation** – build B2B contact lists (name, phone, website, **email**, socials) for any niche and city.
- **Sales prospecting** – target local businesses by category and area, pre-enriched with contact details.
- **Local SEO & market research** – measure competitor density, ratings, and review volume across a city.
- **CRM enrichment** – fill website/email/social fields for businesses you already track.
- **End-to-end contact pipelines** – chain the resulting `website` URLs into the **Contact Details Scraper** to harvest even more emails, phones, and socials.

### Input reference

| Field | Type | Description |
|---|---|---|
| `searchQueries` | array | Google Maps searches, e.g. `"dentists in Austin TX"` (required). |
| `maxItems` | integer | Max places in total (`0` = no limit). Default `100`. |
| `language` | string | UI language code (`hl`), e.g. `en`. Default `en`. |
| `countryCode` | string | Optional country bias (`gl`), e.g. `us`, `gb`, `de`. |
| `scrapeEmails` | boolean | Visit each website to extract email + socials (free). Default `true`. |
| `maxReviews` | integer | Optional filter: keep only places with at most this many reviews. |
| `geoGridTiling` | boolean | Subdivide the area into tiles to beat the ~120 cap. Default `true`. |
| `maxTiles` | integer | Safety cap on grid tiles per query. Default `16`. |
| `proxyConfiguration` | object | Proxy settings. **Residential required** (default). |

### Notes & limits (please read)

- **Residential proxies are mandatory.** Google blocks datacenter IPs almost instantly. Datacenter or no-proxy runs typically see only **~15–30% success**. The default proxy is Apify **Residential** — keep it.
- **Google caps a single search at ~120 results.** Geo-grid tiling pushes well past that, but it is not magic: very dense areas with thousands of businesses may still return **partial coverage**, and very large jobs may see some blocked requests. The Actor **skips a parse miss or a blocked page and continues** rather than crashing.
- **`reviewsCount` is best-effort.** Google's map-search list response does not include a review count for every place, and it occasionally shifts where the count sits. The scraper reads the canonical position first and falls back to a tightly-scoped scan, but for some listings `reviewsCount` will still be `null` even when the place clearly has reviews on the Maps UI. Treat it as a signal, not a guarantee, and do not key billing or hard filters on it being present.
- **`email`/`socials` depend on the business having a reachable website.** Places without a website, or whose site hides contact details, will have `email: null`.
- **Coordinates are needed to build the search viewport.** The Actor geocodes the location part of your query via the keyless OpenStreetMap (Nominatim) service; if that is unavailable it falls back to a coarse country center, which may reduce precision.
- This Actor scrapes only data Google Maps exposes publicly without login. It does not log in, solve CAPTCHAs, or bypass paywalls.

### FAQ

**Do I need a Google API key?** No. This works as a keyless **Google Maps API** alternative — no Places API key, billing, or login required. You just enter a search query.

**How does it get more than 120 results?** Google limits one query to ~120 places. With `geoGridTiling` on and a higher `maxItems`, the Actor splits the area into a grid of overlapping map tiles and queries each tile separately, then deduplicates by Google feature ID.

**Is the email enrichment really free?** Yes. When `scrapeEmails` is on, the Actor fetches each business website and extracts a contact email plus social links at no extra step or tier.

**Why is `reviewsCount` sometimes `null`?** Google's internal map-search response does not include a review count for every place, and occasionally moves it. We read the standard position and fall back to a scoped scan, but a `null` simply means Google did not return a usable count in that response — it is not an error. The `rating` may still be present even when the count is missing.

**Why might other fields be empty?** Google does not show every field for every place (e.g. price level, hours, or neighborhood), and `email`/`socials` require a reachable website. Missing fields are returned as `null` (or `[]` / `{}`) rather than guessed.

**What export formats are supported?** Every run's dataset can be downloaded as **JSON, CSV, Excel (XLSX)**, HTML, RSS, or XML, or pulled programmatically via the **Apify API**.

**Can I scrape a whole city or a single neighborhood?** Both. Put the area in your query (`"bakeries in Brooklyn NY"` for a borough, `"plumbers in Texas"` for a wider region). Larger areas trigger geo-grid tiling automatically when `maxItems` is high.

**Do I have to use residential proxies?** Yes, for reliable results. Google blocks datacenter IPs almost immediately, so the default — and recommended — proxy is Apify **Residential**.

**How do I turn the websites into more contacts?** Take the `website` URLs from this scraper and run them through the **Contact Details Scraper** for deeper email/phone/social extraction — a clean two-step pipeline for **local lead generation**.

**Why is it cheaper than other Google Maps scrapers?** It calls Google's internal JSON search endpoint instead of driving a headless browser, so it uses far less compute per result.

# Actor input Schema

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

What to search on Google Maps. Use natural-language queries that include the place, e.g. "dentists in Austin TX" or "coffee shops Brooklyn NY". One query per line.

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

Maximum number of places to return across all queries. A single Google query is capped at ~120 results; when you ask for more, geo-grid tiling kicks in automatically. Set 0 for no limit (not recommended for large areas). Raise this for real jobs — the default when omitted is 100.

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

Two-letter UI language code passed to Google (the 'hl' parameter). Affects category names and some labels.

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

Optional two-letter country code passed to Google (the 'gl' parameter), e.g. "us", "gb", "de". Biases results toward that country. Leave blank to let Google decide from the query.

## `scrapeEmails` (type: `boolean`):

If enabled, the Actor visits each business website and extracts contact emails and social profile links (Facebook, Instagram, LinkedIn, X/Twitter, YouTube, TikTok). Included for free. Adds one extra request per place that has a website.

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

Optional. Only return places whose total review count is at or below this number. Leave blank to keep all places regardless of review volume. (This is a filter, not a per-place review scraper.)

## `geoGridTiling` (type: `boolean`):

Google caps a single search around 120 results. When enabled (and 'Max results' is higher than one query can return), the Actor subdivides the search viewport into a grid of overlapping tiles and re-queries each tile to collect far more places. Disable to keep one query per search.

## `maxTiles` (type: `integer`):

Safety cap on how many grid tiles a single query may expand into. Higher values find more places but cost more. Ignored when geo-grid tiling is off.

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

RESIDENTIAL proxies are MANDATORY. Google blocks datacenter IPs almost immediately; datacenter proxies typically see only ~15-30% success. The default below uses Apify Residential.

## Actor input object example

```json
{
  "searchQueries": [
    "dentists in Austin TX",
    "coffee shops Brooklyn NY"
  ],
  "maxItems": 100,
  "language": "en",
  "countryCode": "us",
  "scrapeEmails": true,
  "maxReviews": 500,
  "geoGridTiling": true,
  "maxTiles": 16,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}
```

# Actor output Schema

## `places` (type: `string`):

No description

## `contacts` (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 in Austin TX"
    ],
    "maxItems": 10,
    "maxTiles": 16,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("solidscrape/google-maps-places-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 in Austin TX"],
    "maxItems": 10,
    "maxTiles": 16,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("solidscrape/google-maps-places-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 in Austin TX"
  ],
  "maxItems": 10,
  "maxTiles": 16,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  }
}' |
apify call solidscrape/google-maps-places-scraper --silent --output-dataset

```

## MCP server setup

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