# Google Maps Places Scraper: Google Places API, Phones, Hours (`hyperbach/google-maps-scraper`) Actor

Scrape Google Maps places by search term and location, area, Maps link, place ID or shared list. Every place inside your area: phone, website, rating, review count and stars, opening hours, address, plus code, coordinates. No login, no API key; only delivered places are charged. Misses free.

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

## Pricing

from $1.50 / 1,000 places

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?

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 Places Scraper: Google Places API, Phones, Hours

**Google Maps places as data, logged out: search terms with a location (words, country / state / city / postal code fields, or your own GeoJSON area), Maps search links, place links, place IDs, CIDs or shared lists in — one row per place out.** Each row has the name and categories, the full address and its parts, coordinates and plus code, phone, website, the rating, the review count and **the 1–5-star review distribution**, the opening hours with today's open status, the permanently-closed flag, the main image and photo count, the description and the attribute groups, reservation and order links, the owner, and every Google id (place ID, feature ID, CID, kgmid). **Only places inside the area you asked for** are delivered, each place once, and every search gets a free summary row with its verdict. No login, no API key, proxies handled for you.

**$4.00 per 1,000 `place` rows on the Free plan, $3.00 on Starter.** A run start is $0.0036. The default input returns about 50 rows for about $0.2036. A value that names nothing is a free row that says why.

### Tested head-to-head on 2026-11-01

The job: one search (restaurant, New York, USA), 50 places. Every Actor ran on it the same day from the same harness; a rival's cost is what a buyer account paid, this Actor's is its list price on the Free plan. Fields counts the filled fields of a typical row. the lane is not in cand; the leader is bead 4's (compass), the second the next maps actor by users/30d in the census copy (lukaskrivka, 2,418); compass and clockworks (tiktok-search) take no run under a $0.50 cap, so theirs is $0.50. Re-measured monthly.

| Actor | Rows | First row | Run time | Cost | Fields | Failures |
|---|---|---|---|---|---|---|
| **this Actor** | 50 | 5.3 s | 6.0 s | $0.15 | 49 | none |
| `compass/crawler-google-places` | 50 | 8.1 s | 13 s | $0.15 | 38 | none |
| `lukaskrivka/google-maps-with-contact-details` | 50 | 15 s | 212 s | $0.28 | 41 | none |

### Why this Google Maps scraper

- **Only places inside your area.** Google Maps answers a screen's list with places outside it (73 of 241 places of a New York screen in our tests; a few-match search on a Denver screen answered places 3.5 half-screens away). Every place is tested against the area you asked for — the city's or region's boundary from OpenStreetMap, your polygon or radius, the link's screen, or the postal code on the place's own address — and the places outside it are dropped free and counted on the search row.
- **The whole area, not one screen.** One Maps screen lists a few hundred places at most (103 to 313 in our tests), so a dense area hides places. A screen whose list is full is split into four smaller screens, again and again (the four quarters of a New York screen held 369 places its own list did not), and each search row says COMPLETE, CAPPED or TRUNCATED with the screens it read.
- **The review distribution on every row, in the same call.** The count of 1-, 2-, 3-, 4- and 5-star reviews comes with each place of the list (on 2,641 of 2,644 rated places in our tests, summing to the review count on all of them) — no add-on and no second call per place.
- **Your limit holds.** A search asks Google for no more places than it still has room for, and each place is claimed once for the whole run, so a search delivers at most `maxCrawledPlacesPerSearch` places and a run at most `maxItems` — a place found by two searches is one row and one charge.
- **Fast and plain.** No browser and no scrolling: one call reads a whole screen's list. The common job (50 restaurants in New York) is one call, its first row a few seconds after the start (2.6 to 5.5 seconds on 10 of 11 runs of builds 0.1.5 to 0.1.8; one waited 21 seconds); a place ID is one call of about 5 KB.
- **Every input answers.** A search with no result, a location nobody can place, a place ID Google does not know (checked on a second connection) and a value that names nothing each get a free row that says which. A run never ends green and silent.

### Who it's for

- **Lead generation and sales** — every business of a category in a city or a postal code, with phone, website and address parts, filtered by rating, review count or website, each place once.
- **Local SEO and market research** — ratings, review distributions and categories of every competitor in an area, the rank each one holds in Google's own list, the claimed flag.
- **Analysts and data teams** — typed rows keyed by the place ID with coordinates and plus codes for mapping, a summary row per search with its verdict, and the lane leader's column names in snake_case.
- **Monitoring new openings** — a scheduled run with `onlyNew` that delivers only the places an area did not have before.

### Quick start

**Restaurants in a city (the common job)**

```json
{
  "searchStringsArray": [
    "restaurant"
  ],
  "locationQuery": "New York, USA",
  "maxCrawledPlacesPerSearch": 50
}
```

**Several terms in several cities**

```json
{
  "searchStringsArray": [
    "dentist",
    "orthodontist"
  ],
  "locationQuery": "Austin, Texas",
  "locationQueries": [
    "Dallas, Texas"
  ],
  "maxCrawledPlacesPerSearch": 100
}
```

**Every coffee shop in a postal code**

```json
{
  "searchStringsArray": [
    "coffee"
  ],
  "postalCode": "10001",
  "countryCode": "us"
}
```

**A 2-km radius around a point, 4 stars or more, with a website**

```json
{
  "searchStringsArray": [
    "pizza"
  ],
  "customGeolocation": {
    "type": "Point",
    "coordinates": [
      -73.9857,
      40.7484
    ],
    "radiusKm": 2
  },
  "placeMinimumStars": "four",
  "website": "withWebsite"
}
```

**Place IDs and Maps links**

```json
{
  "placeIds": [
    "ChIJA4JRKzCLwokRHBTppuJxhpg"
  ],
  "startUrls": [
    "https://www.google.com/maps/search/restaurant/@40.801,-73.843,13z"
  ]
}
```

### Switching from `compass/crawler-google-places`?

Your input works as it is. Each name in the left column is read as this Actor's own field and the run log says so (`read <theirs> as <ours>`); a name marked *not read* is ignored with a note. A value in the other Actor's shape, an object list or its own date form, is read too.

| compass/crawler-google-places | this Actor | what to know |
|---|---|---|
| `searchStringsArray` | `searchStringsArray` | The same name (`searchString` and `searchStrings` are read too). |
| `locationQuery` | `locationQuery` | The same name; `locationQueries` adds more locations in one run. |
| `maxCrawledPlacesPerSearch` | `maxCrawledPlacesPerSearch` | The same name; 0 = every place of the area. |
| `language` | `language` | The same name. |
| `startUrls` | `startUrls` | The same name and shape. |
| `placeIds` | `placeIds` | The same name; feature ids and CIDs too. |
| `categoryFilterWords` | `categoryFilterWords` | The same name. |
| `placeMinimumStars` | `placeMinimumStars` | The same name; `placeMaximumStars` and `minimumReviews` are extra here. |
| `website` | `website` | The same name and values. |
| `skipClosedPlaces` | `skipClosedPlaces` | The same name. |
| `countryCode` | `countryCode` | The same name, with `state`, `county`, `city`, `postalCode` and `customGeolocation`. |
| `maxReviews` | *not read* | Not read: reviews are not a row of this Actor. |
| `maxImages` | *not read* | Not read: images are not read. |
| `scrapePlaceDetailPage` | *not read* | Not read: the record is read from Google's place data, no detail page. |
| `scrapeContacts` | *not read* | Not read: no website enrichment here (`maximumLeadsEnrichmentRecords`, `scrapeSocialMediaProfiles` likewise). |
| `searchMatching` | *not read* | Not read: every place of the search is delivered; filter by `categoryFilterWords`. |
| `allPlacesNoSearchAction` | *not read* | Not read: a search link's screen or a GeoJSON area reads an area whole. |

**What differs:**

- The search inputs carry the same names (`searchStringsArray`, `locationQuery`, `maxCrawledPlacesPerSearch`, `language`, the country, state, county, city and postal code fields, `customGeolocation`, `startUrls`, `placeIds`, `categoryFilterWords`, `placeMinimumStars`, `website`, `skipClosedPlaces`), so a saved input runs as it is.
- This Actor delivers the place record (phone, website, rating, review count and stars, hours, address, coordinates); the leader's add-ons (reviews, images, questions, contacts enrichment, leads, competitor analysis, directories, the detail page) are not read, and the price is the place.
- `startUrls` takes strings or `{"url": …}` items; a shared list link and a `?cid=` link work too.

### Output

One row per place (charged), and one free summary row per search term × location, link or place ID:

| field | meaning |
|---|---|
| `record_type` | `place` on a place row (charged); `search` on a search term × location's free summary row; `target` on the free summary row of a link or a place ID. |
| `status` | `ok`, or why an input gave no place: `no_results`, `outside_area_only` (every place Google answered lay outside the area), `not_found` (a place ID or link Google does not know, two connections agreeing), `location_not_found`, `invalid_input`, `failed`, `not_read` (a limit or the time limit stopped the run first). |
| `input` | The value you gave that this row answers (the search term, the link, the place ID). |
| `search_string` | The search term that found the place (search rows and the places they delivered). |
| `search_area` | The place the location resolved to (`New York, United States`), the link's screen, or your custom area. |
| `rank` | The place's position among the places its search delivered, in the order Google answered them, from 1. |
| `title` | The place's name. |
| `category_name` | The place's main Google category. |
| `categories` | Every Google category of the place, the main one first. |
| `address` | The full address as Google shows it. |
| `street` | The street address. |
| `city` | The city. |
| `postal_code` | The postal code. |
| `state` | The state or region. |
| `country_code` | The place's country, two letters (`US`). |
| `neighborhood` | The neighbourhood Google names for the place. |
| `location` | The coordinates: `{lat, lng}`. |
| `located_in` | The place it sits in, as Google's "Located in" names it (a mall, a hotel, a pier): `{name, fid}`. |
| `floor` | The floor Google gives in the address (`Floor 1`). |
| `plus_code` | The global plus code (Open Location Code) of the place's coordinates (`87G8R5JF+J3`). |
| `plus_code_compound` | The local plus code with its town (`R5JF+J3 Bronx, New York`), on rows read by place ID, link or list. |
| `phone` | The phone number as Google shows it. |
| `phone_unformatted` | The phone number in international form (`+17182395362`). |
| `website` | The place's own website; null when Google shows none. |
| `website_domain` | The website's host, `www.` removed. |
| `total_score` | The average rating, 1 to 5; null for a place with no rating. |
| `reviews_count` | How many reviews the place has. |
| `reviews_distribution` | The review count by star: `{one_star, two_star, three_star, four_star, five_star}`, summing to `reviews_count`. |
| `reviews_url` | Google's page of the place's reviews. |
| `opening_hours` | The week's hours, Monday first: `[{day, hours}]` in the run's language (`Open 24 hours`, `Closed`, split hours with a comma). |
| `additional_opening_hours` | Other hour sets Google lists (brunch, delivery, pickup, happy hours): `[{name, hours: [{day, hours}]}]`. |
| `open_now` | Open at the time of the run, by the weekly hours in the place's timezone; null without hours or for a closed place. |
| `timezone` | The place's timezone (`America/New_York`). |
| `permanently_closed` | `true` when Google marks the place permanently closed. |
| `temporarily_closed` | `true` when Google marks the place temporarily closed (a seasonal place out of season, a closure Google was told of); `open_now` is then null. |
| `place_id` | Google's place ID (`ChIJ…`): the key to join on. |
| `fid` | Google Maps' feature ID (`0x…:0x…`): each place is delivered once a run by it. |
| `cid` | The place's CID (the number in `maps.google.com/?cid=`). |
| `kgmid` | The Knowledge Graph id (`/g/…`). |
| `business_profile_id` | The id of the place's Google Business Profile, when it has one. |
| `url` | The place on Google Maps (`…/maps/search/?api=1&query=…&query_place_id=…`). |
| `search_page_url` | The Google Maps search (the screen) that returned the place. |
| `image_url` | The place's main photo. |
| `images_count` | How many photos the place has on Google Maps. |
| `description` | Google's own short description of the place, when it has one. |
| `owner_description` | The description the business wrote itself ("From the business"), when Google serves it: mostly on a place ID or place link input. |
| `additional_info` | The attribute groups Google shows (Accessibility, Service options, Amenities…, and Highlights such as `LGBTQ+ friendly`): `{group: [{attribute: true|false}]}`. |
| `reserve_table_urls` | Table reservation links: `[{url, source}]`. |
| `order_urls` | Online order links: `[{url, source}]` (a delivery service, or Google's own order page). |
| `claimed` | `false` when Google shows 'Claim this business' on the place (the owner has not claimed it). |
| `owner_name` | The owner's name as Google shows it for the business. |
| `owner_id` | Google's id of the owner account. |
| `is_advertisement` | `true` for an ad; the signed-out lists carry none (always `false`). |
| `language` | The language of the run (`language`). |
| `verdict` | On a summary row: COMPLETE (read to the end), CAPPED (a limit stopped it), TRUNCATED (a screen was still full at the smallest size: `note` says), PARTIAL (the run stopped first). |
| `rows_delivered` | On a summary row: the places it delivered. |
| `tiles_read` | On a search row: the map screens the search read. |
| `outside_area` | On a search row: the places Google answered that lay outside the area (dropped, free). |
| `filtered_out` | On a summary row: the places your filters left out (free). |
| `note` | Why a summary row has its status or verdict: the screens and calls read, places outside the area, repeats, filters. |
| `scraped_at` | When this row was read (UTC). |

Example record:

```json
{
  "record_type": "place",
  "status": "ok",
  "input": "restaurant",
  "search_string": "restaurant",
  "search_area": "New York, United States",
  "rank": 4,
  "title": "Superfine",
  "category_name": "Restaurant",
  "categories": [
    "Restaurant",
    "Bar",
    "Brunch restaurant",
    "Live music venue",
    "Organic restaurant"
  ],
  "address": "126 Front St, Brooklyn, NY 11201",
  "street": "126 Front St",
  "city": "Brooklyn",
  "postal_code": "11201",
  "state": "New York",
  "country_code": "US",
  "neighborhood": "Dumbo",
  "location": {
    "lat": 40.7023726,
    "lng": -73.9875072
  },
  "located_in": null,
  "floor": null,
  "plus_code": "87G8P226+WX",
  "plus_code_compound": null,
  "phone": "(718) 243-9005",
  "phone_unformatted": "+17182439005",
  "website": "https://www.superfine.nyc/",
  "website_domain": "superfine.nyc",
  "total_score": 4.4,
  "reviews_count": 1436,
  "reviews_distribution": {
    "one_star": 58,
    "two_star": 34,
    "three_star": 113,
    "four_star": 345,
    "five_star": 886
  },
  "reviews_url": "https://search.google.com/local/reviews?placeid=ChIJYSFd6zNawokRpLteYhPEu_A&q=restaurant&authuser=0&hl=en&gl=US",
  "opening_hours": [
    {
      "day": "Monday",
      "hours": "Closed"
    },
    {
      "day": "Tuesday",
      "hours": "2 PM–1 AM"
    },
    {
      "day": "Wednesday",
      "hours": "12 PM–1 AM"
    },
    {
      "day": "Thursday",
      "hours": "12 PM–2 AM"
    },
    {
      "day": "Friday",
      "hours": "12 PM–2 AM"
    },
    {
      "day": "Saturday",
      "hours": "12 PM–2 AM"
    },
    {
      "day": "Sunday",
      "hours": "11 AM–1 AM"
    }
  ],
  "additional_opening_hours": [
    {
      "name": "Kitchen",
      "hours": [
        {
          "day": "Monday",
          "hours": "Closed"
        },
        {
          "day": "Tuesday",
          "hours": "2–10 PM"
        },
        {
          "day": "Wednesday",
          "hours": "12–10 PM"
        },
        {
          "day": "Thursday",
          "hours": "12–11 PM"
        },
        {
          "day": "Friday",
          "hours": "12–11 PM"
        },
        {
          "day": "Saturday",
          "hours": "12–11 PM"
        },
        {
          "day": "Sunday",
          "hours": "11 AM–10 PM"
        }
      ]
    }
  ],
  "open_now": true,
  "timezone": "America/New_York",
  "permanently_closed": false,
  "temporarily_closed": null,
  "place_id": "ChIJYSFd6zNawokRpLteYhPEu_A",
  "fid": "0x89c25a33eb5d2161:0xf0bbc413625ebba4",
  "cid": "17346673977281395620",
  "kgmid": "/g/1vfp93pt",
  "business_profile_id": null,
  "url": "https://www.google.com/maps/search/?api=1&query=Superfine&query_place_id=ChIJYSFd6zNawokRpLteYhPEu_A",
  "search_page_url": "https://www.google.com/maps/search/restaurant/@40.6971040,-73.9795380,10.8z",
  "image_url": "https://lh3.googleusercontent.com/grass-cs/AABkmLefeNNqWvqBqBN14GrnX6AF-gZH2o6LCiZ0ZAcNgOaVx9SADOOUeeQ3vYlUDTrTbZzAkOb_i3nr8KL_FluAg2nsPeExxjKVQuyR1DOFwjNPTkbJ1mqiBQzO65piLDSk6Eqvbv2O=w122-h92-k-no",
  "images_count": 1120,
  "description": "Local, seasonal Mediterranean fare is offered in a converted warehouse space with an open kitchen.",
  "owner_description": null,
  "additional_info": {
    "Accessibility": [
      {
        "Wheelchair accessible entrance": true
      },
      {
        "Wheelchair accessible restroom": true
      },
      {
        "Wheelchair accessible seating": true
      },
      {
        "Wheelchair accessible parking lot": true
      }
    ]
  },
  "reserve_table_urls": [
    {
      "url": "https://www.opentable.com/restaurant/profile/109453?ref=1068",
      "source": "opentable.com"
    }
  ],
  "order_urls": [
    {
      "url": "https://www.google.com/searchviewer/42?cvd=CLw_EiUKI-ICIDIeEhciFdLX29IPDwoLL2cvMXZmcDkzcHQYADoDCPAN&hl=en-US&gl=us&fo_m=MfohQo559jFvMUOzJVpjPL1YMfZ3bInYwBDuMfaXTPp5KXh-&utm_source=tactile&gei=mXPhatrlIbf8ptQP_PSdcA&ei=mXPhatrlIbf8ptQP_PSdcA&fo_s=OA,SOE&opi=79508299&ebb=1&cs=0&foub=mcpp",
      "source": "Google"
    }
  ],
  "claimed": true,
  "owner_name": "Superfine",
  "owner_id": "100943994162160384374",
  "is_advertisement": false,
  "language": "en",
  "verdict": null,
  "rows_delivered": null,
  "tiles_read": null,
  "outside_area": null,
  "filtered_out": null,
  "note": null,
  "scraped_at": "2026-10-28T04:01:30Z"
}
```

### Pricing

**Pay for the places a run delivers**, plus one run start; no platform usage is billed to you. The place price falls with your Apify plan.

| event | unit | Free | Starter | Scale | Business |
|---|---|---|---|---|---|
| Place (`place`) | per 1,000 | $4.00 | $3.00 | $2.00 | $1.50 |
| Run start (`start`) | per run | $0.0036 | $0.0036 | $0.0036 | $0.0036 |

A place is **$4.00 per 1,000** on the Free plan ($0.004 a row) and $3.00 on Starter, whatever found it (a search, a link, a place ID or a shared list) and whatever it carries: the review distribution, the hours, the photo count, the attributes and every id are inside the row's price, with no add-on. Each run that delivers a place also pays the **run start, $0.0036 once**; a run that delivers none pays nothing at all. So one place costs $0.0076 on the Free plan and $0.0066 on Starter; 50 places $0.2036 and $0.1536; 1,000 places $4.0036 and $3.0036; every search and link of a run shares one start. Free: every search's summary row, a place outside your area, a place a filter, `excludePlaceIds` or `onlyNew` leaves out, a place another search of the run already delivered (one row, one charge), a place ID Google does not know, a location nobody can place, a value that names nothing, and a search left unread at `maxItems` or the time limit. A run that stops early (a timeout, an abort) is charged for the places it delivered. `maxItems` counts the charged places.

Before the first request the log names the price of a place at your plan, the run start and the most the run can cost: each place ID or place link is one place, each search, search link or shared list `maxCrawledPlacesPerSearch`, and `maxItems` over the whole run. A search with no cap has no bound but your charge limit: Google shows no count, and the search ends where Google's list of the area ends. In our uncapped tests one term in one city gave 22 and 23 places (bakeries in Hoboken, two runs, 23–24 s), 358 (hair salons in Boise, 91 s), 688 (dentists in Austin, 229 s) and 753 (coffee shops in Portland, 286 s). A whole state or country takes longer: a state outside the US is read layer by layer, and Queensland's first row came after 112 s, so leave such a run its default timeout (one hour), not a short one. Set `maxItems`, `maxCrawledPlacesPerSearch` or a charge limit for a lower maximum. Enterprise plans have their own rates — the Actor's Pricing tab shows the price for your plan.

### Usage patterns

- **Search terms and locations** — `searchStringsArray` holds what you would type into Google Maps (`restaurant`, `dentist`, a business name). Each term is searched in each location: `locationQuery` and `locationQueries` in words (a city, county, state, country or neighbourhood: the boundary comes from OpenStreetMap), or the fields `countryCode`, `state`, `county`, `city`, `postalCode`, or `customGeolocation` (a GeoJSON Polygon, MultiPolygon, or a Point with `radiusKm`). A term with no location reads Google's own answer to the words. The row's `search_area` names the place each location resolved to.
- **Links and place IDs** — `startUrls` takes Google Maps links: a search link with its `@lat,lng,zoom` (its screen is the area, walked in full), a place link, a `?cid=` link, a shared list (`/maps/placelists/list/…`) or a `maps.app.goo.gl` short link. `placeIds` takes place IDs (`ChIJ…`), feature IDs (`0x…:0x…`) or CIDs. A place link or ID gives one row with the whole place; a list gives a row per place on it.
- **Depth and limits** — A search walks its area until Google's lists end: a screen whose list is full is split into four, down to about 0.3 km wide; `maxCrawledPlacesPerSearch` stops each search earlier and `maxItems` stops the run. The search row's `verdict` says COMPLETE (the area was read to the end), CAPPED (a limit stopped it) or TRUNCATED (a screen was still full at the smallest size, or the search read 1,500 screens), with the screens read in `tiles_read`.
- **Filters** — `categoryFilterWords`, `placeMinimumStars`, `placeMaximumStars`, `minimumReviews`, `website` (with or without), `skipClosedPlaces` and `excludePlaceIds` are applied to each place's own fields. A place a filter leaves out is not delivered and not charged; the search row and the run summary count them by filter. The filters apply to searches and lists; of them only `excludePlaceIds` applies to a place ID you named (the exclude list wins), and a CID in it matches the place's other ID forms.
- **Language** — `language` is Google's language code (`en`, `de`, `fr`, `pt-BR`…): categories, day names and the hours text come in it, as Google Maps shows them in that language.

### Input configuration

| field | type | default | what it does |
|---|---|---|---|
| `searchStringsArray` | `array` | `[]` | What to look for, as you would type it into Google Maps: `restaurant`, `dentist`, `coffee shop`, a business name. Each term is searched in each location below. Give a location for an area walk; a term with no location reads Google's own answer to the words. |
| `locationQuery` | `string` | `""` | Where to search, in words: a city, county, state, country or neighbourhood (`New York, USA`, `Berlin, Germany`, `Queensland, Australia`). The place's boundary comes from OpenStreetMap, and only places inside it are delivered. |
| `locationQueries` | `array` | `[]` | More locations in words, one a line: every search term is searched in each of them (and in `Location`). |
| `maxCrawledPlacesPerSearch` | `integer` | `0` | The most places one search term in one location (or one search link, one shared list) delivers. 0 = every place the area holds. A search asks Google for no more than this. |
| `language` | `string` | `"en"` | Google's language code for the results (`en`, `de`, `fr`, `es`, `pt-BR`, `ja`…): categories, day names and hours come in it. |
| `countryCode` | `string` | `""` | Instead of `Location`: the country as a two-letter code (`us`, `gb`, `de`). Combine it with state, county, city or postal code. |
| `state` | `string` | `""` | A state or region (`California`). |
| `county` | `string` | `""` | A county (`Kings County`). |
| `city` | `string` | `""` | A city (`Austin`). |
| `postalCode` | `string` | `""` | A postal code with its country (`10001` and country code `us`): only places whose own address carries this postal code are delivered. |
| `customGeolocation` | `object` | `{}` | An exact area instead of a location: a GeoJSON `Polygon` or `MultiPolygon` (longitude first, as GeoJSON writes it), or a `Point` with `radiusKm`, e.g. `{"type": "Point", "coordinates": [-73.98, 40.75], "radiusKm": 2}`. Only places inside it are delivered. |
| `startUrls` | `array` | `[]` | Google Maps links: a search link (`google.com/maps/search/pizza/@40.75,-73.99,14z`: its screen is the area), a place link, a `?cid=` link, a shared list or a `maps.app.goo.gl` short link. A search link's screen is walked in full; a place link gives that place; a list gives every place on it. |
| `placeIds` | `array` | `[]` | Google place IDs (`ChIJ…`), feature IDs (`0x89c28b302b518203:0x988671e2a6e9141c`) or CIDs (`10990597158921114652`): one row each with the whole place. |
| `categoryFilterWords` | `array` | `[]` | Keep a place only when one of its Google categories contains one of these words (`pizza`, `italian`). A place left out is not charged. |
| `placeMinimumStars` | ``/ `two` / `twoAndHalf` / `three` / `threeAndHalf` / `four` / `fourAndHalf` | `""` | Keep places rated at least this (an unrated place is left out). |
| `placeMaximumStars` |`` / `two` / `twoAndHalf` / `three` / `threeAndHalf` / `four` / `fourAndHalf` | `""` | Keep places rated at most this (an unrated place is kept). |
| `minimumReviews` | `integer` | `0` | Keep places with at least this many reviews; 0 = any. |
| `website` | `allPlaces` / `withWebsite` / `withoutWebsite` | `"allPlaces"` | Keep every place, only places with a website, or only places without one. |
| `skipClosedPlaces` | `boolean` | `false` | Leave out places Google marks as permanently closed. |
| `excludePlaceIds` | `array` | `[]` | Place IDs, feature IDs, CIDs or place links never delivered or charged (a list you already have). |
| `onlyNew` | `boolean` | `false` | Deliver only the places an earlier run with the same memory did not deliver. For a scheduled task that watches an area for new places. |
| `stateStoreName` | `string` | `""` | The name of the memory `onlyNew` keeps (letters, digits, '.', '\_', '-'); empty = a name derived from the inputs. |
| `maxItems` | `integer` | `0` | The most charged places of the whole run, over every search and link; 0 = no cap beyond your charge limit. |
| `proxyUrl` | `string` |  | Your own proxy (scheme://user:pass@host:port) for every call instead of ours. Leave empty: the Actor brings its own connections at no cost to you. |

### Errors

**A run does not fail because of your input.** When an input cannot be used — a date that is not a date, an id in the wrong form, two settings that contradict each other — the run ends **Succeeded**, charges nothing, and says what to change:

- the status message starts with `INPUT REJECTED`;
- the dataset holds one row, `{"error": true, "code": "…", "message": "…"}`, and no results;
- the key-value store record `ERROR` holds the same object.

From code, check `error` on the first row before reading results.

A search with no place, a location nobody can place, a place ID Google does not know or a value that names nothing is not an error: it gets one free row with its `status` and a `note`, and the run goes on with the others. A run that ends **Failed** is a fault on our side, never your input; it charges nothing, and we are alerted.

### FAQ

**Why are some places of my search missing, or the count lower than another tool's?**

This Actor delivers only places inside the area you asked for; Google's own list answers places outside the screen too, and tools that keep them report more rows. The search row counts the places dropped as `outside_area`. A search with `maxCrawledPlacesPerSearch` stops at it; with 0 the whole area is walked.

**Do you return popular times, the price level or 'people also search'?**

Not yet. The two calls this Actor reads (Google's list and its place call) carry none of them (0 of 2,796 places in our tests had a price level). Google's own place page carries them, one page per place, and some other Actors read it; this Actor does not deliver empty columns for them.

**Do you return reviews or e-mails?**

No: each place's rating, review count and 1–5-star distribution, but not the review texts (reviewers are private persons) and no contact finding on the place's website.

**What does a place cost when two of my searches find it?**

One row and one charge: each place is claimed once for the whole run, by its Google feature ID.

**What is temporarily closed?**

Google marks both on the place's record: `permanently_closed` and `temporarily_closed` (a seasonal place out of season is temporarily closed). `skipClosedPlaces` skips the permanently closed ones only. The run summary counts any other value of the closed mark it meets (`status_unknown`).

**Is today's open status live?**

`open_now` is computed from the place's weekly hours at the moment of the run, in the place's own timezone; Google's record carries no live status text.

**Can I use my own proxy?**

Yes, `proxyUrl`. Leave it empty and the Actor uses its own connections at no extra cost.

**Where does the area's boundary come from?**

For a location in words or fields, from OpenStreetMap's Nominatim (© OpenStreetMap contributors); for a postal code, from Google's own postal-code place and the places that carry the code; when OpenStreetMap does not answer, from the view Google Maps moves to for the words.

### Integration

#### JavaScript

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('hyperbach/google-maps-scraper').call({"searchStringsArray": ["restaurant"], "locationQuery": "New York, USA", "maxCrawledPlacesPerSearch": 50});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

#### Python

```python
from apify_client import ApifyClient
client = ApifyClient('YOUR_TOKEN')
run = client.actor('hyperbach/google-maps-scraper').call(run_input={'searchStringsArray': ['restaurant'], 'locationQuery': 'New York, USA', 'maxCrawledPlacesPerSearch': 50})
items = client.dataset(run['defaultDatasetId']).list_items().items
```

#### CLI

```bash
apify call hyperbach/google-maps-scraper --input '{"searchStringsArray": ["restaurant"], "locationQuery": "New York, USA", "maxCrawledPlacesPerSearch": 50}'
```

#### REST

```bash
curl -X POST "https://api.apify.com/v2/acts/hyperbach~google-maps-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H 'Content-Type: application/json' -d '{"searchStringsArray": ["restaurant"], "locationQuery": "New York, USA", "maxCrawledPlacesPerSearch": 50}'
```

#### AI agents and MCP

Apify's MCP server offers this Actor as a tool to Claude, Cursor, VS Code and any other MCP client. Connect `https://mcp.apify.com?tools=hyperbach/google-maps-scraper` and ask, for example: "Find 50 restaurants in New York on Google Maps with phone numbers and opening hours". The agent fills the input from the table above and reads the rows from the run's dataset. A field under the name another Actor gives it is read as this Actor's own: `searchStrings`, `searchString`, `searchTerms`, `queries` as `searchStringsArray`; `urls`, `url`, `links`, `searchUrls` as `startUrls`; `placeIdList`, `place_ids`, `placeId` as `placeIds`; `location`, `locationName`, `near` as `locationQuery` (and more).

#### n8n, Make and Zapier

The Apify node in n8n, the Apify app in Make and the Apify integration in Zapier run this Actor on a schedule or from a trigger and read its dataset. A webhook on the run's end (Integrations on the Actor page) sends the rows on to your own endpoint. Nothing to install on our side.

### Support

**Disagree with a charge?** Write to apify@hyperbach.com with the run ID. We will ask Apify to compensate you, and the charge comes back to your Apify account as platform credit.

**Did it do the job?** A short [review on the Store](https://apify.com/hyperbach/google-maps-scraper/reviews), at any rating, helps the next buyer choose.

### See also

- **[Airbnb Scraper](https://apify.com/hyperbach/airbnb-scraper)** — Airbnb listings for a place and dates, with the price for the stay to the cent, the calendar and the host.
- **[Zillow Scraper](https://apify.com/hyperbach/zillow-scraper)** — Zillow listings for sale, for rent and sold, by ZIP, city or search URL, with the Zestimate, and a property's price history.
- **[Google Hotels Scraper](https://apify.com/hyperbach/google-hotels-scraper)** — the hotels Google lists for a place and a stay, with the stay price and, on request, the price on each booking site.
- **[Google Flights Scraper](https://apify.com/hyperbach/google-flights-scraper)** — every itinerary Google Flights lists for a one-way, round-trip or multi-city search, with the exact fare.
- **[Eventbrite Scraper](https://apify.com/hyperbach/eventbrite-scraper)** — the events of a place on Eventbrite, with the dates, the venue, the price range and the organizer.
- **[ChatGPT Scraper](https://apify.com/hyperbach/ai-search-answers)** — what ChatGPT, Gemini, Perplexity and Google's AI Mode and AI Overviews answer for your queries, with every source they cite.

*This page is generated from the Actor's schemas and a live sample — it cannot describe a field the Actor does not have.*

# Actor input Schema

## `searchStringsArray` (type: `array`):

What to look for, as you would type it into Google Maps: `restaurant`, `dentist`, `coffee shop`, a business name. Each term is searched in each location below. Give a location for an area walk; a term with no location reads Google's own answer to the words.

## `locationQuery` (type: `string`):

Where to search, in words: a city, county, state, country or neighbourhood (`New York, USA`, `Berlin, Germany`, `Queensland, Australia`). The place's boundary comes from OpenStreetMap, and only places inside it are delivered.

## `locationQueries` (type: `array`):

More locations in words, one a line: every search term is searched in each of them (and in `Location`).

## `maxCrawledPlacesPerSearch` (type: `integer`):

The most places one search term in one location (or one search link, one shared list) delivers. 0 = every place the area holds. A search asks Google for no more than this.

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

Google's language code for the results (`en`, `de`, `fr`, `es`, `pt-BR`, `ja`…): categories, day names and hours come in it.

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

Instead of `Location`: the country as a two-letter code (`us`, `gb`, `de`). Combine it with state, county, city or postal code.

## `state` (type: `string`):

A state or region (`California`).

## `county` (type: `string`):

A county (`Kings County`).

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

A city (`Austin`).

## `postalCode` (type: `string`):

A postal code with its country (`10001` and country code `us`): only places whose own address carries this postal code are delivered.

## `customGeolocation` (type: `object`):

An exact area instead of a location: a GeoJSON `Polygon` or `MultiPolygon` (longitude first, as GeoJSON writes it), or a `Point` with `radiusKm`, e.g. `{"type": "Point", "coordinates": [-73.98, 40.75], "radiusKm": 2}`. Only places inside it are delivered.

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

Google Maps links: a search link (`google.com/maps/search/pizza/@40.75,-73.99,14z`: its screen is the area), a place link, a `?cid=` link, a shared list or a `maps.app.goo.gl` short link. A search link's screen is walked in full; a place link gives that place; a list gives every place on it.

## `placeIds` (type: `array`):

Google place IDs (`ChIJ…`), feature IDs (`0x89c28b302b518203:0x988671e2a6e9141c`) or CIDs (`10990597158921114652`): one row each with the whole place.

## `categoryFilterWords` (type: `array`):

Keep a place only when one of its Google categories contains one of these words (`pizza`, `italian`). A place left out is not charged.

## `placeMinimumStars` (type: `string`):

Keep places rated at least this (an unrated place is left out).

## `placeMaximumStars` (type: `string`):

Keep places rated at most this (an unrated place is kept).

## `minimumReviews` (type: `integer`):

Keep places with at least this many reviews; 0 = any.

## `website` (type: `string`):

Keep every place, only places with a website, or only places without one.

## `skipClosedPlaces` (type: `boolean`):

Leave out places Google marks as permanently closed.

## `excludePlaceIds` (type: `array`):

Place IDs, feature IDs, CIDs or place links never delivered or charged (a list you already have).

## `onlyNew` (type: `boolean`):

Deliver only the places an earlier run with the same memory did not deliver. For a scheduled task that watches an area for new places.

## `stateStoreName` (type: `string`):

The name of the memory `onlyNew` keeps (letters, digits, '.', '\_', '-'); empty = a name derived from the inputs.

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

The most charged places of the whole run, over every search and link; 0 = no cap beyond your charge limit.

## `proxyUrl` (type: `string`):

Your own proxy (scheme://user:pass@host:port) for every call instead of ours. Leave empty: the Actor brings its own connections at no cost to you.

## Actor input object example

```json
{
  "searchStringsArray": [
    "restaurant"
  ],
  "locationQuery": "New York, USA",
  "locationQueries": [],
  "maxCrawledPlacesPerSearch": 50,
  "language": "en",
  "countryCode": "",
  "state": "",
  "county": "",
  "city": "",
  "postalCode": "",
  "customGeolocation": {},
  "startUrls": [],
  "placeIds": [],
  "categoryFilterWords": [],
  "placeMinimumStars": "",
  "placeMaximumStars": "",
  "minimumReviews": 0,
  "website": "allPlaces",
  "skipClosedPlaces": false,
  "excludePlaceIds": [],
  "onlyNew": false,
  "stateStoreName": "",
  "maxItems": 0
}
```

# Actor output Schema

## `results` (type: `string`):

All scraped records in the default dataset. One row per place (charged), and one free summary row per search term × location, link or place ID:

## `runSummary` (type: `string`):

How the run went: places charged, free rows by status, each search's screens, calls, places outside the area and filtered, the geocoder's calls, the source counters (calls by kind, their slowest answer, exits, walls, rotations, slow answers, bytes by connection) and the memory watch.

# 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 = {
    "searchStringsArray": [
        "restaurant"
    ],
    "locationQuery": "New York, USA",
    "maxCrawledPlacesPerSearch": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("hyperbach/google-maps-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 = {
    "searchStringsArray": ["restaurant"],
    "locationQuery": "New York, USA",
    "maxCrawledPlacesPerSearch": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("hyperbach/google-maps-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 '{
  "searchStringsArray": [
    "restaurant"
  ],
  "locationQuery": "New York, USA",
  "maxCrawledPlacesPerSearch": 50
}' |
apify call hyperbach/google-maps-scraper --silent --output-dataset

```

## MCP server setup

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