# Google Maps Category Scraper ⚡ Advanced Data, Best Value (`claygenius/google-maps-category-scraper`) Actor

Scrape every Google Maps business in one category across any list of cities or zip codes. Keeps only listings whose Google category matches yours: name, phone, website, rating, reviews, address, coordinates, place ID.

- **URL**: https://apify.com/claygenius/google-maps-category-scraper.md
- **Developed by:** [Muhammad Shamshad Aslam](https://apify.com/claygenius) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% 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

## Google Maps Category Scraper

Pick a Google Maps category, like **Fence contractor**, **Dentist** or **Roofing contractor**, list the cities or zip codes you care about, and get **every business in that category and nothing else**. Each business comes with its phone number, website, domain, star rating, review count, address, coordinates, Google place ID and a Google Maps link.

Google search results are messy. Search "fence contractor in Austin" and you also get remodelers, general contractors, lumber stores and handymen. This scraper reads the category Google itself shows for every business and keeps only the ones that match yours, so your lead list is clean from the start.

### Features

- ⚡ **Fast, no browser**: reads Google Maps' own results data directly, about 1-2 seconds per 20 businesses, so whole states run in minutes at a very low cost per result
- 🎯 **Category-exact results**: only businesses whose Google category matches yours. Choose exact match, or "contains" to also keep sub-categories (Dentist also keeps Cosmetic dentist and Pediatric dentist)
- 💯 **Minimum reviews filter**: keep only businesses with, say, 100+ Google reviews, so you get established companies and skip one-person startups
- ➕ **Extra categories**: accept related categories too, e.g. Fence contractor plus Fence supply store
- 🗺️ **Any number of locations**: cities, neighborhoods, zip codes or regions in any country, all in one run and one dataset
- ✅ **No duplicates**: a business found in two neighboring cities is saved once
- 📞 **Contact data**: phone number, website and clean domain, ready for enrichment or outreach
- ⭐ **Reputation data**: star rating and number of reviews, to sort or filter by size and quality. Google often hides review counts from automated visitors on Maps. Those businesses are kept and marked `reviewsCountKnown: false` so you never lose them, or switch on the optional (slow) lookup to fill the counts from Google Search
- 📍 **Location data**: street address, latitude and longitude, and the search location it was found in
- 🆔 **Google IDs**: place ID (works with the Google Places API) and CID, plus a permanent Google Maps link
- 🔍 **See what was filtered out**: switch on "Also save businesses from other categories" to get everything Google returned with a `matchesCategory` true/false flag
- 🧾 **Failure report**: any location that could not be loaded is listed in the key-value store record `FAILED_LOCATIONS`, never silently dropped

### Use cases

- Build a lead list of every plumber, roofer or dentist in a state
- Size a local market: how many fence contractors are there in each city, and how many reviews do they have
- Find acquisition targets in a trade, filtered by review count
- Feed clean business domains into email finders and enrichment tools such as Clay

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `category` | string | required | Google Maps category as Google shows it, e.g. `Fence contractor` |
| `locations` | array | required | Cities, neighborhoods, zip codes or regions, one per line |
| `searchTerms` | array | empty | What to type into Google Maps per location, e.g. `windows and doors`. Empty = search the category. Every term runs in every location |
| `matchMode` | string | `exact` | `exact`: category must equal yours. `contains`: category must contain your text. Not case-sensitive |
| `extraCategories` | array | empty | Extra Google categories to keep |
| `mainCategoryOnly` | boolean | `false` | Off: save every business that lists your category, as its main category or as an extra. On: only businesses whose main category is yours |
| `includeOtherCategories` | boolean | `false` | Also save non-matching businesses, flagged with `matchesCategory: false` |
| `minReviews` | integer | `0` | Only save businesses with at least this many Google reviews. Businesses whose count Google hid are kept and marked `reviewsCountKnown: false`. `0` = no filter |
| `fetchReviewCounts` | boolean | `false` | **Slow.** Find hidden review counts on Google Search (20 businesses per request where possible, otherwise one per business). Each request takes 20 to 90 seconds and is billed as a Google search proxy request |
| `maxItemsPerLocation` | integer | `0` | Stop after this many listings per location, before the category filter. `0` = all (pages until Google has no more results) |
| `language` | string | `en` | Google language code. Write your category in this language |
| `engine` | string | `fast` | `fast` reads Google's results data directly (recommended). `browser` opens Google Maps in Chrome, much slower. Locations the fast engine can not read fall back to the browser automatically |
| `fastConcurrency` | integer | `150` | Fast engine: searches in parallel. 150 with the default 16 GB was fastest in tests; higher gets slower as Google throttles the proxy IPs |
| `skipKnownPages` | boolean | `false` | Fast engine: stop paging a search once a page has only businesses already found in this run. Faster on dense zip lists |
| `maxConcurrency` | integer | `6` | Browser engine only: locations open in Chrome at once. Each needs about 2/3 of a CPU core (1 core per 4 GB). The fast engine runs 25 at once on little memory |
| `proxyConfig` | proxy | Apify proxy | Datacenter proxy works. Switch to Residential if locations fail |

#### Example input

```json
{
  "category": "Fence contractor",
  "locations": ["Austin, TX", "Round Rock, TX", "78701", "Georgetown, TX"],
  "matchMode": "exact",
  "extraCategories": ["Fence supply store"],
  "includeOtherCategories": false,
  "minReviews": 100,
  "fetchReviewCounts": false,
  "maxItemsPerLocation": 0
}
```

#### Getting full coverage of a big city

One Google Maps search returns a few hundred businesses at most. A city like Houston has far more plumbers than that. List the city's zip codes or neighborhoods as separate locations instead of the city name. Businesses that show up in several of them are saved once.

### Output

One record per business:

```json
{
  "name": "Discount Fence USA",
  "category": "Fence contractor",
  "categories": ["Fence contractor", "Deck builder"],
  "matchesCategory": true,
  "isMainCategory": true,
  "rating": 4.8,
  "reviewsCount": 742,
  "reviewsCountKnown": true,
  "phone": "+1 737-258-9744",
  "website": "https://discountfenceusa.com/",
  "domain": "discountfenceusa.com",
  "address": "3000 S I-35 Frontage Rd Ste 225, Austin, TX 78704, United States",
  "openingStatus": "Closed · Opens 7 AM",
  "latitude": 30.2273054,
  "longitude": -97.7461563,
  "placeId": "ChIJIbBlbY4yW4YRk2BLXHzF6vY",
  "cid": "17792250415795626131",
  "googleMapsUrl": "https://maps.google.com/?cid=17792250415795626131",
  "searchCategory": "Fence contractor",
  "searchLocation": "Austin, TX",
  "scrapedAt": "2026-10-09T10:18:19.989Z"
}
```

#### Field notes

- `address` is the full address with city, state and zip (fast engine). The browser engine only sees the street part. Businesses that only list a service area, common for contractors, have no public address and return `null`. Use `latitude`, `longitude` and `searchLocation` to place them.
- `categories` lists every Google category of the business, main one first, and `category` is the main one. A business can list up to 10 categories: one main category (shown under its name on Google Maps) and extras. In a category search Google moves the searched category to the front of every business's list, so the actor re-checks every business with several categories by searching it by name, which returns Google's real order.
- `isMainCategory` is `true` when your category is the business's main category (a real fence company), `false` when it is only an extra (e.g. a roofer that also builds fences), and `null` when it was not checked. The check costs one extra request per business with several categories, so it only runs with `checkMainCategory` or `mainCategoryOnly` on. Without it, multi-category businesses come back with `isMainCategory: null`, and their `category` field is just the first one listed, which Google sets to your searched category. With `mainCategoryOnly` on, only `true` rows are saved.
- `rating` is `null` and `reviewsCount` is `0` for businesses with no reviews.
- `reviewsCountKnown` is `false` when Google hid the review count (`reviewsCount` is then `null`). Google hides counts from automated visitors on some runs and not others. These businesses are always kept, even with `minReviews` set, so you can fill the count in later by `placeId` or `cid`, or switch on `fetchReviewCounts`. Every saved business with a known count has `reviewsCount` at or above `minReviews`.
- `openingStatus` is open or closed at the time of the run, as Google shows it.
- `placeId` works with the Google Places API. `cid` is Google's numeric business ID, and `googleMapsUrl` built from it is a permanent link.
- `matchesCategory` is always `true` unless you switch on "Also save businesses from other categories".

### FAQ

**How do I find the exact category name?** Open any business of that type on Google Maps. The category is the grey text right under the business name.

**Does it work outside the US?** Yes. Use local place names or postal codes, and set `language` if you want categories in another language.

**Can I get full opening hours or review texts?** Not in this version. It reads what Google shows in the search results list, which keeps runs fast and low cost per result.

# Actor input Schema

## `category` (type: `string`):

The business category exactly as Google Maps shows it under a business name, e.g. Fence contractor, Dentist, Plumber, Roofing contractor, Italian restaurant. Open any matching business on Google Maps to see the exact wording.

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

Cities, neighborhoods, zip codes or regions, one per line, e.g. Austin, TX or 78701 or Manchester, UK. One search returns a few hundred businesses at most, so for full coverage of a big city list its zip codes or neighborhoods. Businesses found in more than one location are kept once.

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

What to type into Google Maps for each location, e.g. windows and doors, or window installation. Leave empty to search the category itself. Every term is searched in every location, so 3 terms x 100 zip codes = 300 searches. The category filter below decides what is kept.

## `matchMode` (type: `string`):

Exact: the business's Google category must equal yours (Dentist keeps only Dentist). Contains: the category must contain your text (Dentist also keeps Cosmetic dentist and Pediatric dentist). Not case-sensitive either way.

## `extraCategories` (type: `array`):

Extra Google categories to keep alongside the main one, e.g. Fence supply store, or Deck builder.

## `checkMainCategory` (type: `boolean`):

Off: businesses that list your category are kept, with isMainCategory true when it is their only category and null when they have several. On: one extra request per multi-category match finds their real main category (Google moves the searched category to the front, so the list order can't be trusted). Always on when 'Main category only' is on.

## `mainCategoryOnly` (type: `boolean`):

Off (default): every business that lists your category is saved, whether it is the business's main category or one of its extras. isMainCategory tells you which (true = main, false = extra, e.g. a roofer that also does fences). On: only businesses whose main category is yours are saved.

## `includeOtherCategories` (type: `boolean`):

Off: only matching businesses are saved. On: everything Google returned is saved, and matchesCategory (true/false) tells you which ones matched. Handy for finding category names to add above.

## `minReviews` (type: `integer`):

Only save businesses with at least this many Google reviews, e.g. 100 to keep only established businesses. Businesses with no reviews count as 0. Businesses whose count Google hid are kept and marked reviewsCountKnown: false, so you can check them yourself (or switch on Find hidden review counts). 0 = no review filter.

## `fetchReviewCounts` (type: `boolean`):

The fast engine already retries on fresh IPs until review counts are visible, so this is rarely needed. When a count is still hidden, this looks it up on Google Search: 20 to 90 seconds per request, billed by Apify as a Google search proxy request. Off: those businesses are kept with reviewsCount null and reviewsCountKnown false.

## `maxItemsPerLocation` (type: `integer`):

Stop after this many listings per location, before the category filter. 0 = all (pages until Google has no more results). 20 = only the first page, fastest. The fast engine reads 20 per request.

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

Language code Google Maps uses for categories and opening hours, e.g. en, de, fr, es. Write your category in this language.

## `engine` (type: `string`):

Fast (recommended): reads Google Maps' own results data directly, no browser. About 1-2 seconds per page of 20 businesses, full address with city and zip, all categories, and it retries on a fresh IP until review counts are visible. Browser: opens Google Maps in Chrome like a person, much slower and more expensive. Locations the fast engine can not read are retried with the browser automatically.

## `fastConcurrency` (type: `integer`):

How many searches the fast engine runs in parallel. 150 with the default 16 GB of memory was fastest in tests with Apify's datacenter proxy (about 1.7x faster than 50). Going higher made runs slower, because Google starts slowing down the proxy IPs. More proxy IPs allow more.

## `skipKnownPages` (type: `boolean`):

Neighbouring zip codes return mostly the same businesses. On: a search stops reading further pages as soon as a page contains only businesses already found by another search in this run. Much faster on dense zip lists; may miss a few businesses ranked low in one zip.

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

Browser engine only: how many locations are opened in Chrome at the same time. Each needs about 2/3 of a CPU core and Apify gives 1 core per 4 GB of memory. The fast engine always runs 25 locations at once and needs little memory.

## `proxyConfig` (type: `object`):

Apify datacenter proxy works for Google Maps. Switch to Residential if you see blocked or failed locations.

## Actor input object example

```json
{
  "category": "Fence contractor",
  "locations": [
    "Austin, TX",
    "Round Rock, TX"
  ],
  "searchTerms": [],
  "matchMode": "exact",
  "extraCategories": [],
  "checkMainCategory": false,
  "mainCategoryOnly": false,
  "includeOtherCategories": false,
  "minReviews": 0,
  "fetchReviewCounts": false,
  "maxItemsPerLocation": 20,
  "language": "en",
  "engine": "fast",
  "fastConcurrency": 150,
  "skipKnownPages": false,
  "maxConcurrency": 6,
  "proxyConfig": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Businesses in your category with phone, website, rating, reviews, address, coordinates and place ID

# 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 = {
    "category": "Fence contractor",
    "locations": [
        "Austin, TX",
        "Round Rock, TX"
    ],
    "searchTerms": [],
    "matchMode": "exact",
    "extraCategories": [],
    "mainCategoryOnly": false,
    "includeOtherCategories": false,
    "minReviews": 0,
    "fetchReviewCounts": false,
    "maxItemsPerLocation": 20,
    "language": "en",
    "engine": "fast",
    "fastConcurrency": 150,
    "skipKnownPages": false,
    "maxConcurrency": 6,
    "proxyConfig": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("claygenius/google-maps-category-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 = {
    "category": "Fence contractor",
    "locations": [
        "Austin, TX",
        "Round Rock, TX",
    ],
    "searchTerms": [],
    "matchMode": "exact",
    "extraCategories": [],
    "mainCategoryOnly": False,
    "includeOtherCategories": False,
    "minReviews": 0,
    "fetchReviewCounts": False,
    "maxItemsPerLocation": 20,
    "language": "en",
    "engine": "fast",
    "fastConcurrency": 150,
    "skipKnownPages": False,
    "maxConcurrency": 6,
    "proxyConfig": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("claygenius/google-maps-category-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 '{
  "category": "Fence contractor",
  "locations": [
    "Austin, TX",
    "Round Rock, TX"
  ],
  "searchTerms": [],
  "matchMode": "exact",
  "extraCategories": [],
  "mainCategoryOnly": false,
  "includeOtherCategories": false,
  "minReviews": 0,
  "fetchReviewCounts": false,
  "maxItemsPerLocation": 20,
  "language": "en",
  "engine": "fast",
  "fastConcurrency": 150,
  "skipKnownPages": false,
  "maxConcurrency": 6,
  "proxyConfig": {
    "useApifyProxy": true
  }
}' |
apify call claygenius/google-maps-category-scraper --silent --output-dataset

```

## MCP server setup

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