# Google Maps Places Extractor (`enckay/google-maps-places-extractor`) Actor

Extract Places and businesses from Google Maps.  Scrap phone numbers, emails, website, business hours,  address and gps, Google Place ID. Use filters to include or exclude irrelevant leads.

- **URL**: https://apify.com/enckay/google-maps-places-extractor.md
- **Developed by:** [Enckay Data](https://apify.com/enckay) (community)
- **Categories:** Lead generation, Automation, Developer tools
- **Stats:** 1,496 total users, 157 monthly users, 100.0% runs succeeded, 11 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 1,000 place scrapeds

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?

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 Lead Extractor

Advanced lead extraction from Google Maps with comprehensive business data, contact information, reviews, and 80+ additional data fields.

> **Found a bug or have feedback?** Feel free to open an issue on the Actor's Issues tab in Apify Console — bug reports, feature requests, and complaints are all welcome directly.

### Features

#### Core Extraction

- **Multi-cell area coverage** - Systematic grid-based or polygon-based scraping for complete area coverage
- **Contact information** - Phone numbers (70-85% success) and emails (20-40% from Google, 60-80% from websites)
- **Business reviews** - Extract customer reviews with ratings, text, dates, and reviewer info using browser automation
- **Multiple photos** - Extract up to 50 photo URLs per business

#### Advanced Data Fields (82+ Fields)

- **Amenities** (82 fields) - Seating, accessibility, parking, payments, food/beverage options
- **Hourly busyness** (168 datapoints) - 7-day hourly traffic patterns with smart contact timing recommendations
- **Price distribution** - Customer spending patterns by price range with percentages
- **Atmosphere** - Vibe descriptors (casual, cozy, hip, upscale, etc.)
- **Social media** - Extract from Google profile and scrape business websites for additional links
- **Lead quality scoring** - Automatic scoring (0-100) and priority classification (PREMIUM/HIGH/MEDIUM/LOW)
- **Geographic hierarchy** - Multi-level location data with confidence scores (neighborhood, city, county, state, country)
- **Country code** - ISO country code extraction
- **Travel time & accessibility** - Distance intervals and accessibility scoring

#### Filtering & Export

- Filter by rating, reviews, price level, and price range
- Export to CSV or JSON
- Deduplication across search cells

### Quick Start

#### Basic Search

```json
{
  "keywords": ["coffee shops"],
  "location": "Brooklyn, NY",
  "maxResults": 50,
  "exportToCsv": true
}
```

#### Full Feature Extraction

```json
{
  "keywords": ["restaurants", "italian restaurants", "sushi"],
  "location": "Austin, TX",
  "maxResults": 100,
  "minRating": 4.0,
  "minReviews": 10,
  "extractContactDetails": true,
  "extractAmenities": true,
  "extractBusyness": true,
  "extractPhotos": true,
  "maxPhotos": 10,
  "extractSocialMedia": true,
  "extractWebsiteEmails": true,
  "extractReviews": true,
  "maxReviewsPerBusiness": 50,
  "calculateLeadScore": true,
  "exportToCsv": true
}
```

### Input Parameters

#### Basic Search

| Parameter | Required | Default | Description |
|-----------|----------|---------|-------------|
| `keywords` | **Yes** | - | One or more search terms/categories to search (e.g., `["dentists"]`, `["restaurant", "pizza restaurant", "sushi restaurant", "italian restaurant"]`). In dense areas like Manhattan, Google Maps only surfaces popular places, capping results at around 150 per search. Expanding into similar niches within the same business domain helps surface less popular, long-tail places that wouldn't otherwise appear. Each keyword is searched in every cell to exhaustion before moving to the next. Note: avoid overusing this, as each additional keyword multiplies the total time to scrape an area. |
| `location` | **Yes** | - | City, address, or ZIP code to search in (e.g., "Seattle, WA", "90210"). When a custom polygon is provided, this field is used only as the search query location name — the polygon coordinates take precedence for geographic scoping. The field is still required. |
| `maxResults` | No | 0 | Maximum number of unique businesses to extract. Set to 0 for unlimited results until results is exhausted. |

> **Getting phone numbers and emails?** Enable `extractContactDetails` under **Extraction Features** to retrieve phone numbers and emails from Google Maps profiles (70–85% phone success rate, 20–40% for emails). For higher email coverage, also enable `extractWebsiteEmails` under **Website Extraction** — this visits each business's website (if present) directly and achieves 60–80% success.

#### Filters

| Parameter | Default | Description |
|-----------|---------|-------------|
| `minRating` | 0 | Minimum rating (0-5.0) |
| `minReviews` | 0 | Minimum number of reviews |
| `filterByPriceLevel` | - | Filter by price symbols (\["$", "$$", "$$$", "$$$$"]) |
| `minPrice` | 0 | Minimum price in dollars |
| `maxPrice` | 0 | Maximum price in dollars |

#### Extraction Features (All Optional)

| Parameter | Default | Description |
|-----------|---------|-------------|
| `extractContactDetails` | false | Extract phone and email from Google profile |
| `extractAmenities` | false | Extract 82 amenity fields |
| `extractBusyness` | false | Extract 168 hourly busyness datapoints |
| `extractPriceDistribution` | false | Extract customer spending patterns |
| `extractAtmosphere` | false | Extract atmosphere descriptors |
| `extractPhotos` | false | Extract multiple photo URLs |
| `maxPhotos` | 10 | Maximum photos per business (1-50) |
| `extractSocialMedia` | false | Extract social media from Google profile |
| `calculateLeadScore` | false | Calculate lead quality score (0-100) and priority |
| `extractReviews` | false | Extract customer reviews using browser automation |
| `maxReviewsPerBusiness` | 100 | Max reviews per business (10-500) |
| `extractGeographic` | **true** | Extract geographic hierarchy with confidence scores |
| `extractCountry` | **true** | Extract ISO country code |
| `extractTravel` | **true** | Extract travel time and accessibility data |

#### Website Extraction (All Optional)

| Parameter | Default | Description |
|-----------|---------|-------------|
| `extractWebsiteEmails` | false | Visit business websites to extract email addresses (60-80% success rate) |
| `maxEmails` | 5 | Max emails to extract per business website (1-20) |
| `extractSocialMediaFromWebsite` | false | Scrape business websites for social media links not in Google profile |
| `socialMediaPlatforms` | All | Platforms to extract from Google profile and website (facebook, instagram, twitter, linkedin, tiktok, youtube) |
| `websiteTimeout` | 5000 | Timeout per website page request in ms (1000-30000) |
| `websiteMaxPages` | 3 | Max pages to visit per website — home, contact, about (1-10) |

#### Performance Settings

| Parameter | Default | Description |
|-----------|---------|-------------|
| `concurrency` | 2 | Search cells to process in parallel (1-10) |
| `detailsConcurrency` | 10 | Concurrent business detail extractions (1-50) |

#### Advanced Options

| Parameter | Description |
|-----------|-------------|
| `customPolygon` | GeoJSON Polygon/MultiPolygon for exact search boundaries |
| `exportToCsv` | Export results as CSV (saved in key-value store as 'OUTPUT') |

### Output Format

#### Complete Dataset Structure

```json
{
  "businessName": "Acme Coffee Roasters",
  "placeId": "ChIJN1t_tDeuEmsRUsoyG83frY4",

  "address": "123 Main St, Brooklyn, NY 11201, United States",
  "street": "123 Main St",
  "city": "Brooklyn",
  "state": "NY",
  "zip": "11201",
  "country": "United States",
  "latitude": 40.6892,
  "longitude": -73.9915,
  "countryCode": "US",

  "geographicHierarchy": [
    { "name": "Brooklyn", "confidence": 1.0, "confidencePercent": "100.0%" },
    { "name": "Kings County", "confidence": 0.95, "confidencePercent": "95.0%" },
    { "name": "New York", "confidence": 0.90, "confidencePercent": "90.0%" },
    { "name": "United States", "confidence": 0.85, "confidencePercent": "85.0%" }
  ],
  "primaryLocation": { "name": "Brooklyn", "confidence": 1.0, "confidencePercent": "100.0%" },
  "locationCategories": {
    "neighborhood": { "name": "Brooklyn", "confidence": 1.0 },
    "city": { "name": "New York City", "confidence": 0.95 },
    "county": { "name": "Kings County", "confidence": 0.95 },
    "state": { "name": "New York", "confidence": 0.90 },
    "country": { "name": "United States", "confidence": 0.85 }
  },

  "travelTimes": [
    { "label": "5 min", "minutes": 5, "coordinates": { "lat": 40.689, "lng": -73.991 } },
    { "label": "10 min", "minutes": 10, "coordinates": { "lat": 40.690, "lng": -73.992 } }
  ],
  "accessibility": {
    "accessibilityScore": 85,
    "averageTravelMinutes": 7.5
  },

  "phone": "+17185551234",
  "website": "https://acmecoffee.com/",
  "email": "info@acmecoffee.com",
  "emailSource": "google",

  "rating": 4.8,
  "reviewCount": 542,
  "category": "Coffee shop",
  "priceLevel": "$$",
  "priceRange": "$10–20",

  "googleMapsUrl": "https://www.google.com/maps/place/?q=place_id:ChIJN1t_tDeuEmsRUsoyG83frY4",
  "mainPhoto": "https://lh3.googleusercontent.com/...",

  "businessHours": {
    "Monday": "7:00 AM – 7:00 PM",
    "Tuesday": "7:00 AM – 7:00 PM",
    "Wednesday": "7:00 AM – 7:00 PM",
    "Thursday": "7:00 AM – 7:00 PM",
    "Friday": "7:00 AM – 8:00 PM",
    "Saturday": "8:00 AM – 8:00 PM",
    "Sunday": "8:00 AM – 6:00 PM"
  },

  "temporarilyClosed": false,
  "permanentlyClosed": false,

  "photos": [
    "https://lh3.googleusercontent.com/photo1...",
    "https://lh3.googleusercontent.com/photo2...",
    "https://lh3.googleusercontent.com/photo3..."
  ],

  "amenities": {
    "dineIn": true,
    "takeout": true,
    "delivery": false,
    "outdoorSeating": true,
    "wheelchairAccessible": true,
    "parking": true,
    "wifi": true,
    "creditCards": true,
    "reservations": false,
    "goodForGroups": true,
    "goodForKids": true,
    "restroom": true,
    "petFriendly": true,
    "liveMusic": false,
    "breakfast": true,
    "lunch": true,
    "dinner": false,
    "coffee": true,
    "dessert": true,
    "alcohol": false
  },
  "amenitiesSummary": {
    "total": 82,
    "available": 15,
    "categories": {
      "dining": 8,
      "accessibility": 3,
      "amenities": 4
    }
  },

  "busyness": {
    "Monday": [0, 0, 0, 0, 0, 0, 15, 35, 60, 75, 80, 70, 65, 55, 50, 60, 70, 50, 30, 20, 10, 0, 0, 0],
    "Tuesday": [0, 0, 0, 0, 0, 0, 20, 40, 65, 80, 85, 75, 70, 60, 55, 65, 75, 55, 35, 25, 15, 0, 0, 0],
    "Wednesday": [0, 0, 0, 0, 0, 0, 20, 40, 65, 80, 85, 75, 70, 60, 55, 65, 75, 55, 35, 25, 15, 0, 0, 0],
    "Thursday": [0, 0, 0, 0, 0, 0, 25, 45, 70, 85, 90, 80, 75, 65, 60, 70, 80, 60, 40, 30, 20, 0, 0, 0],
    "Friday": [0, 0, 0, 0, 0, 0, 25, 45, 70, 85, 90, 85, 80, 70, 65, 75, 90, 80, 60, 45, 30, 0, 0, 0],
    "Saturday": [0, 0, 0, 0, 0, 0, 0, 30, 60, 85, 95, 90, 85, 75, 70, 80, 90, 75, 50, 35, 20, 0, 0, 0],
    "Sunday": [0, 0, 0, 0, 0, 0, 0, 25, 55, 80, 90, 85, 80, 70, 65, 75, 70, 50, 30, 20, 10, 0, 0, 0]
  },
  "bestContactTimes": {
    "leastBusy": ["Monday 6 AM", "Tuesday 6 AM", "Sunday 6 PM"],
    "moderatelyBusy": ["Monday 10 AM", "Tuesday 3 PM"],
    "recommendation": "Best times to contact: Monday 6 AM, Tuesday 6 AM, or Sunday 6 PM (least busy)"
  },

  "priceDistribution": {
    "under10": 15,
    "10to20": 65,
    "20to30": 15,
    "over30": 5
  },
  "averagePrice": "$12–18",

  "atmosphere": ["casual", "cozy", "trendy", "hip"],

  "socialMedia": {
    "facebook": "https://facebook.com/acmecoffee",
    "instagram": "https://instagram.com/acmecoffee",
    "twitter": "https://twitter.com/acmecoffee"
  },
  "socialMediaSources": {
    "facebook": "google",
    "instagram": "website",
    "twitter": "website"
  },

  "websiteEmails": ["contact@acmecoffee.com", "info@acmecoffee.com"],
  "allEmails": ["info@acmecoffee.com", "contact@acmecoffee.com"],
  "emailsSource": "website",

  "reviews": [
    {
      "reviewId": "ChZDSUhNMG9nS0VJQ0FnSUQ3...",
      "rating": 5,
      "text": "Amazing coffee and friendly staff! The atmosphere is perfect for working or meeting friends.",
      "publishedAt": "2025-10-15",
      "publishedAtDate": 1729036800000,
      "reviewerName": "Jane Smith",
      "reviewerPhotoUrl": "https://lh3.googleusercontent.com/...",
      "reviewerTotalReviews": 42,
      "reviewerIsLocalGuide": true,
      "ownerResponse": {
        "text": "Thank you for your kind words!",
        "publishedAt": "2025-10-16"
      }
    }
  ],

  "leadScore": 87,
  "leadPriority": "PREMIUM"
}
```

#### Field Descriptions

##### Basic Information

| Field | Type | Description |
|-------|------|-------------|
| `businessName` | string | Business name |
| `placeId` | string | Google Place ID (unique identifier) |
| `category` | string | Primary business category |

##### Location

| Field | Type | Description |
|-------|------|-------------|
| `address` | string | Full formatted address |
| `street` | string | Street address |
| `city` | string | City name |
| `state` | string | State/province code |
| `zip` | string | ZIP/postal code |
| `country` | string | Country name |
| `countryCode` | string | **NEW** - ISO country code (e.g., "US", "GB", "CA") |
| `latitude` | number | GPS latitude |
| `longitude` | number | GPS longitude |
| `googleMapsUrl` | string | Direct link to Google Maps |
| `geographicHierarchy` | array | **NEW** - Multi-level locations with confidence scores |
| `primaryLocation` | object | **NEW** - Primary geographic location |
| `locationCategories` | object | **NEW** - Categorized locations (neighborhood, city, county, state, country) |
| `travelTimes` | array | **NEW** - Travel time intervals with coordinates |
| `accessibility` | object | **NEW** - Accessibility score and metrics |

##### Contact Information

| Field | Type | Description |
|-------|------|-------------|
| `phone` | string | Phone number (from Google profile) |
| `website` | string | Official website URL |
| `email` | string | Primary email (from Google profile) |
| `emailSource` | string | Source: `google`, `website`, or `not_found` |
| `websiteEmails` | array | Emails extracted from website scraping |
| `allEmails` | array | All unique emails (Google + website) |

##### Ratings & Reviews

| Field | Type | Description |
|-------|------|-------------|
| `rating` | number | Average rating (0-5.0) |
| `reviewCount` | number | Total number of reviews |
| `reviews` | array | Customer reviews (if `extractReviews: true`) |

##### Pricing

| Field | Type | Description |
|-------|------|-------------|
| `priceLevel` | string | Price level symbols ($, $$, $$$, $$$$) |
| `priceRange` | string | Price range (e.g., "$10-20") |
| `priceDistribution` | object | Spending patterns by price range |
| `averagePrice` | string | Calculated average price |

##### Photos

| Field | Type | Description |
|-------|------|-------------|
| `mainPhoto` | string | Primary business photo (always included) |
| `photos` | array | Additional photo URLs (if `extractPhotos: true`) |

##### Hours & Status

| Field | Type | Description |
|-------|------|-------------|
| `businessHours` | object | Operating hours by day of week (e.g., `{"Monday": "9:00 AM – 5:00 PM"}`) - May include multiple time windows per day (e.g., `"10 AM-2 PM, 6 PM-10 PM"`) |
| `temporarilyClosed` | boolean | Whether the business is temporarily closed |
| `permanentlyClosed` | boolean | Whether the business is permanently closed |

##### Amenities (82 fields)

| Field | Type | Description |
|-------|------|-------------|
| `amenities` | object | 82 boolean fields for business amenities |
| `amenitiesSummary` | object | Summary statistics for amenities |

**Amenity categories include:**

- **Dining**: dineIn, takeout, delivery, curbsidePickup, breakfast, lunch, dinner, dessert, coffee
- **Accessibility**: wheelchairAccessible, wheelchairAccessibleEntrance, wheelchairAccessibleParking, wheelchairAccessibleRestroom, wheelchairAccessibleSeating
- **Seating**: outdoorSeating, indoorSeating, bar, highChairs
- **Parking**: parking, paidParking, freeParking, streetParking, garagePark, valetParking
- **Payments**: creditCards, debitCards, nfcMobilePayments, cashOnly
- **Services**: wifi, restroom, petFriendly, goodForKids, goodForGroups, reservations, waitlist, liveMusic, karaoke
- **Food/Beverage**: alcohol, beer, wine, cocktails, happyHour, lateNightFood, healthyOptions, vegetarianOptions, veganOptions
- And 40+ more...

##### Busyness Patterns

| Field | Type | Description |
|-------|------|-------------|
| `busyness` | object | 7-day hourly traffic (168 datapoints, 0-100 scale) |
| `bestContactTimes` | object | AI-recommended contact times based on traffic |

##### Atmosphere

| Field | Type | Description |
|-------|------|-------------|
| `atmosphere` | array | Vibe descriptors (casual, cozy, romantic, trendy, upscale, etc.) |

##### Social Media

| Field | Type | Description |
|-------|------|-------------|
| `socialMedia` | object | Social media profile URLs by platform |
| `socialMediaSources` | object | Source for each platform (`google` or `website`) |

**Supported platforms**: Facebook, Instagram, Twitter, LinkedIn, TikTok, YouTube

##### Lead Scoring

| Field | Type | Description |
|-------|------|-------------|
| `leadScore` | number | Quality score 0-100 (higher = better quality lead) |
| `leadPriority` | string | Priority classification: `PREMIUM`, `HIGH`, `MEDIUM`, `LOW` |

**Scoring factors:**

- Rating (weight: 25%)
- Review count (weight: 20%)
- Contact completeness - phone, email, website (weight: 30%)
- Social media presence (weight: 15%)
- Data richness - amenities, photos, hours (weight: 10%)

##### Reviews (if enabled)

| Field | Type | Description |
|-------|------|-------------|
| `reviewId` | string | Unique review identifier |
| `rating` | number | Review rating (1-5) |
| `text` | string | Review text content |
| `publishedAt` | string | Publication date (YYYY-MM-DD) |
| `publishedAtDate` | number | Unix timestamp |
| `reviewerName` | string | Reviewer display name |
| `reviewerPhotoUrl` | string | Reviewer profile photo URL |
| `reviewerTotalReviews` | number | Total reviews by this reviewer |
| `reviewerIsLocalGuide` | boolean | Whether reviewer is a Google Local Guide |
| `ownerResponse` | object | Owner's response (if any) with text and date |

#### CSV Export

When `exportToCsv: true`, results are saved to the Key-Value Store as `OUTPUT.csv` with all extracted fields flattened.

**Note**: Complex fields (amenities, busyness, reviews, photos, etc.) are JSON-stringified in CSV format.

### Performance

#### Factors Affecting Speed

- Number of enabled features
- Website scraping (adds time per business)
- Review extraction (adds time per business depending on `maxReviewsPerBusiness`)
- Concurrency settings
- Rate limiting
- Total number of businesses to extract

#### Success Rates

- **Phone numbers**: 70-85% (from Google profile)
- **Emails from Google**: 20-40%
- **Emails from websites**: 60-80% (when website is accessible)
- **Amenities**: 90-95% (varies by business category)
- **Busyness data**: 60-70% (not all businesses provide this)
- **Social media**: 40-60% (Google), 70-80% (Google + website scraping)
- **Reviews**: 95-100% (limited by `maxReviewsPerBusiness`)

### Use Cases

#### Lead Generation

- Extract contact information for cold outreach
- Calculate lead quality scores to prioritize prospects
- Find businesses with specific amenities (e.g., "pet-friendly coffee shops")

#### Market Research

- Analyze competitor pricing and amenities
- Study customer reviews and sentiment
- Map business density in specific areas

#### Business Intelligence

- Identify optimal contact times using busyness patterns
- Track social media presence across competitors
- Monitor review trends and ratings

#### Data Enrichment

- Enhance existing CRM data with additional fields
- Verify and update contact information
- Add social media profiles and photos

### Pricing Model

This actor uses **pay-per-event pricing** for transparent, usage-based billing. You only pay for the specific features and data you extract.

#### Event Types

- **Core**: Actor start, place scraped
- **Filters**: Per filter per place (rating, reviews, price)
- **Features**: Contact info, amenities, busyness, photos, social media (per place)
- **Website Scraping**: Only charged on successful extraction
- **Reviews**: Per review extracted (structure ready, pricing TBD)
- **FREE Features**: Geographic hierarchy, country code, travel time & accessibility (no charge)

See Apify Console for current pricing details.

### Advanced Features

#### Custom Polygon Search

Define exact search boundaries using GeoJSON:

```json
{
  "keywords": ["restaurants"],
  "customPolygon": {
    "type": "Polygon",
    "coordinates": [
      [
        [-122.4, 47.6],
        [-122.3, 47.6],
        [-122.3, 47.7],
        [-122.4, 47.7],
        [-122.4, 47.6]
      ]
    ]
  },
  "maxResults": 500
}
```

#### Multi-Cell Grid Coverage

The actor automatically divides large areas into smaller cells for complete coverage:

- Systematic grid-based scanning
- Deduplication across cells
- Progress tracking per cell
- Optimal for extracting 1,000+ businesses from large areas

### Troubleshooting

#### No Results Found

- Verify location spelling and format
- Try broader keywords (e.g., "restaurants" instead of "Italian restaurants")
- Use full city names (e.g., "Brooklyn, New York" instead of "Brooklyn")
- Check if location is supported by Google Maps geocoding

#### Low Email Success Rate

- Enable `extractWebsiteEmails: true` to scrape websites (60-80% vs 20-40%)
- Increase `websiteMaxPages` to 5-10 for deeper scraping
- Some businesses don't publish emails publicly

#### CSV Not Generated

- Ensure `exportToCsv: true` is set
- Check Key-Value Store (not Dataset) for `OUTPUT.csv`
- Large datasets may take time to generate

#### Slow Extraction

- Reduce `maxReviewsPerBusiness` (reviews extraction add significant time)
- Disable website scraping features if not needed
- Reduce `detailsConcurrency` if hitting rate limits or overwhelming Google APIs
- Reduce `concurrency` to process fewer cells in parallel

#### Missing Amenities or Busyness

- Not all businesses provide this data
- Try different business categories (restaurants have higher success rates)
- This is normal - check `amenitiesSummary` for availability stats

### Results & Output Location

Your extracted data is saved to:

- **Dataset**: JSON array of all business records (default output)
- **Key-Value Store** (when CSV export is enabled):
  - `OUTPUT.csv` - Flattened CSV with all extracted fields
  - `SCRAPE_STATS` - Detailed extraction statistics

The actor provides detailed progress updates in the console log including:

- Cell processing progress
- Business extraction counts
- Feature extraction statistics
- Deduplication metrics
- Pay-per-event charging summary

# Actor input Schema

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

Keywords to search for (e.g., 'restaurants', 'dentists', 'coffee shops'). Each line is considered as 1 keyword search term. For multiple search terms, add multiple lines.

In dense areas like Manhattan, Google Maps only surfaces popular places, capping results at around 150 per search. Expanding into similar niches within the same business domain helps surface less popular, long-tail places that wouldn't otherwise appear. E.g., for restaurant coverage, use multiple niche names or cuisines to increase coverage (`["restaurant", "pizza restaurant","sushi restaurant", "italian restaurant"]`).

Avoid overusing this, as each additional keyword multiplies the total scrape time.

Takes precedence over the legacy "keyword" field if both are provided.

## `keyword` (type: `string`):

Deprecated: use "Search Keywords" instead. A single search term. Ignored if "keywords" is also provided.

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

City, address, or ZIP code to search in (e.g., 'Brooklyn, NY', 'Austin, TX', '90210'). Used for worldwide coverage via OpenStreetMap geocoding.

Note: When a custom polygon is provided, this field is used only as the search query location name — the polygon coordinates take precedence for geographic scoping. The field is still required.

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

Maximum number of unique businesses to scrape. Set to 0 for unlimited results until results is exhausted.

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

Only include businesses with this rating or higher (0 = no filter)

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

Only include businesses with at least this many reviews (0 = no filter)

## `filterByPriceLevel` (type: `array`):

Filter by price level symbols (leave empty for no filter). Select multiple to include all matching levels. Note: This is not strict.

## `minPrice` (type: `integer`):

Minimum price in dollars (e.g., 50 will filter out businesses cheaper than $50). 0 = no filter

## `maxPrice` (type: `integer`):

Maximum price in dollars (e.g., 100 will filter out businesses more expensive than $100). 0 = no filter

## `filterTemporarilyClosed` (type: `boolean`):

Filter out businesses that are temporarily closed

## `filterPermanentlyClosed` (type: `boolean`):

Filter out businesses that are permanently closed

## `extractContactDetails` (type: `boolean`):

Extract phone number and email from Google profile. Extraction rate: 70-85% for phones, 20-40% for emails.

## `extractAmenities` (type: `boolean`):

Extract all 82 business amenities: seating, accessibility, parking, payments, food/beverage options from Google Maps data.

## `extractBusyness` (type: `boolean`):

Extract 7-day hourly busyness patterns (168 datapoints) with smart contact timing recommendations.

## `extractPriceDistribution` (type: `boolean`):

Extract customer spending patterns by price range with percentages.

## `extractAtmosphere` (type: `boolean`):

Extract atmosphere/vibe descriptors (casual, cozy, hip, upscale, etc.).

## `extractPhotos` (type: `boolean`):

Extract multiple photo URLs from Google Maps data.

## `maxPhotos` (type: `integer`):

Maximum number of photo URLs to extract per business

## `extractSocialMedia` (type: `boolean`):

Extract social media profile URLs from Google Maps business profile (Facebook, Instagram, Twitter, LinkedIn, TikTok, YouTube).

## `calculateLeadScore` (type: `boolean`):

Calculate lead quality score (0-100) and priority classification (PREMIUM/HIGH/MEDIUM/LOW) based on extracted data.

## `extractReviews` (type: `boolean`):

Extract customer reviews — review text, ratings, dates, and reviewer info. Runs concurrently with cell processing for optimal performance.

## `maxReviewsPerBusiness` (type: `integer`):

Maximum number of reviews to extract per business. Higher numbers take longer but provide more data. Recommended: 50-100.

## `extractGeographic` (type: `boolean`):

Extract multi-level geographic location data with confidence scores (neighborhood, city, county, state, region, country). Enabled by default. Adds: geographicHierarchy, primaryLocation, locationCategories fields.

## `extractCountry` (type: `boolean`):

Extract ISO country code (e.g., 'US', 'GB', 'CA'). Enabled by default. Adds: countryCode field.

## `extractTravel` (type: `boolean`):

Extract travel time intervals and calculate accessibility score based on proximity metrics. Enabled by default. Adds: travelTimes, accessibility fields.

## `extractWebsiteEmails` (type: `boolean`):

Visit business websites to extract email addresses from contact pages. Success rate: 60-80% (vs 20-40% from Google profile). Requires a valid website URL on the business listing.

## `maxEmails` (type: `integer`):

Maximum number of email addresses to extract per business website.

## `extractSocialMediaFromWebsite` (type: `boolean`):

Scrape business websites to find social media links not present in the Google Maps profile. Requires a valid website URL on the business listing.

## `socialMediaPlatforms` (type: `array`):

Which social media platforms to extract (applies to both Google profile and website scraping).

## `websiteTimeout` (type: `integer`):

Maximum time in milliseconds to wait when fetching a business website page. Increase for slow sites; decrease to skip unresponsive ones faster.

## `websiteMaxPages` (type: `integer`):

Maximum number of pages to visit per website (e.g., home, contact, about). Higher values improve email/social media discovery but increase scrape time.

## `concurrency` (type: `integer`):

Number of search cells to process in parallel (higher = faster but more resource intensive). Recommended: 2-5.

## `detailsConcurrency` (type: `integer`):

Number of concurrent business detail extractions. Higher = faster extraction but more resource intensive. Recommended: 5-15.

## `customPolygon` (type: `object`):

Advanced: Provide custom GeoJSON Polygon or MultiPolygon to define exact search boundaries. Overrides automatic location geocoding. Must be valid GeoJSON geometry with closed coordinates.

## `webhookUrl` (type: `string`):

Stream scraped businesses in real-time to this URL. Each POST contains a batch of businesses plus run metadata (runId, batchNumber, keyword, location, cursor). Use runId to identify which run the data belongs to when sharing a single endpoint across multiple jobs.

## `webhookBatchSize` (type: `integer`):

Maximum number of businesses per webhook POST. 2 retries on failure; worker auto-stops after 3 consecutive unreachable batches.

## `exportToCsv` (type: `boolean`):

Export results as CSV file in addition to JSON (CSV file will be saved in key-value store as 'OUTPUT')

## Actor input object example

```json
{
  "keywords": [
    "restaurants"
  ],
  "keyword": "restaurants",
  "location": "Ithaca, NY",
  "maxResults": 10,
  "minRating": 0,
  "minReviews": 0,
  "minPrice": 0,
  "maxPrice": 0,
  "filterTemporarilyClosed": false,
  "filterPermanentlyClosed": false,
  "extractContactDetails": false,
  "extractAmenities": false,
  "extractBusyness": false,
  "extractPriceDistribution": false,
  "extractAtmosphere": false,
  "extractPhotos": false,
  "maxPhotos": 10,
  "extractSocialMedia": false,
  "calculateLeadScore": false,
  "extractReviews": false,
  "maxReviewsPerBusiness": 100,
  "extractGeographic": false,
  "extractCountry": true,
  "extractTravel": false,
  "extractWebsiteEmails": false,
  "maxEmails": 5,
  "extractSocialMediaFromWebsite": false,
  "socialMediaPlatforms": [
    "facebook",
    "instagram",
    "twitter",
    "linkedin",
    "tiktok",
    "youtube"
  ],
  "websiteTimeout": 5000,
  "websiteMaxPages": 3,
  "concurrency": 2,
  "detailsConcurrency": 10,
  "webhookBatchSize": 25,
  "exportToCsv": true
}
```

# Actor output Schema

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

Complete dataset of extracted businesses in JSON format with all fields

## `csvExport` (type: `string`):

Flattened CSV export of all businesses (available when exportToCsv is enabled)

## `statistics` (type: `string`):

Detailed statistics about the scraping session including success rates, deduplication metrics, and charging summary

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("enckay/google-maps-places-extractor").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("enckay/google-maps-places-extractor").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 '{}' |
apify call enckay/google-maps-places-extractor --silent --output-dataset

```

## MCP server setup

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

```

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/LmLOOMYKuCUrYsda2/builds/hj5AxuJFPkeM6PNOI/openapi.json
