Arcadium Data Scrapers
Home / Guides / Google Maps Places Scraper

How to use Google Maps Places Scraper

Step by step: in the Apify Store, over the API, from JavaScript or Python, in n8n, and from AI agents through MCP or LangChain.

$2.00 per 1,000 places on the Free plan, down to $1.00 on higher plans (live Apify price on 2026-11-03).

1. In the Apify Store (no code)

  1. Open arcadium/google-maps-places and sign in to Apify (a free account works).
  2. Fill in the input form. The fields below are the ones the example uses.
  3. Set a maximum cost per run in the run options if you want a hard cap.
  4. Start the run. Rows appear in the Output tab as they arrive.
  5. Export the dataset as JSON, CSV or Excel, or connect an integration.
Input fieldTypeDescription
searchTermsarrayWhat to look for on Google Maps, one term per line, such as "dentist", "coffee shop" or "plumber". Each term is searched in the area below and returns up to the max places per search. Also accepts searchStringsArray.
locationstringWhere to search: a city, district, region or postcode as you would type it into Google Maps, such as "Austin, TX" or "Schwabing, Munich". The area is the map Google shows for it, searched cell by cell so results are not capped at one result list. Leave empty when you give coordinates or a polygon below, or to read Google's own result list for the term (up to about 200 places). Also accepts locationQuery.
maxPlacesPerSearchintegerPlaces to return for each search term (or search link), 0 = all places found in the area (up to 20,000). Default 50. Each place is one charged result. Also accepts maxCrawledPlacesPerSearch; a compass input without it means all places, as in compass.
languagestringLanguage of names, categories and opening hours as a code such as en, de or pt-BR.
countrystringGoogle market as a 2-letter code such as us, de or gb (affects ranking, not the area). Also accepts countryCode.
websitestringFilter by whether the place lists a website.
skipClosedbooleanLeave out places Google marks as closed. Also accepts skipClosedPlaces.
nameMatchstringKeep only places whose name contains (or equals) the search term, ignoring case and accents. Also accepts searchMatching (all, only_includes, only_exact).
includeDetailsbooleanRead each place's detail page too: plus code, long description, amenity and accessibility attributes, review topics. One extra request and one extra place-details charge per place. Also accepts scrapePlaceDetailPage.
includePhonesbooleanAdd the phone number the business publishes on its Google listing (on by default). Switch off to leave phone and phoneE164 empty.
minCellKmnumberAdvanced. The area is split into smaller map cells while a cell still returns 100+ places; cells are not split below this size. Default 0.4.

Example input:

{
  "searchTerms": [
    "dentist"
  ],
  "location": "Austin, TX",
  "maxPlacesPerSearch": 50,
  "language": "en",
  "country": "us",
  "website": "any",
  "skipClosed": false,
  "nameMatch": "all",
  "includeDetails": false,
  "includePhones": true,
  "minCellKm": 0.4
}

2. Over the Apify API

The synchronous endpoint runs the actor and returns its rows in one call (for runs up to about five minutes; use the asynchronous run endpoint for longer ones).

curl -X POST "https://api.apify.com/v2/acts/arcadium~google-maps-places/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"searchTerms":["dentist"],"location":"Austin, TX","maxPlacesPerSearch":50,"language":"en","country":"us","website":"any","skipClosed":false,"nameMatch":"all","includeDetails":false,"includePhones":true,"minCellKm":0.4}'

3. From JavaScript

// npm install apify-client
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('arcadium/google-maps-places').call({
  "searchTerms": [
    "dentist"
  ],
  "location": "Austin, TX",
  "maxPlacesPerSearch": 50,
  "language": "en",
  "country": "us",
  "website": "any",
  "skipClosed": false,
  "nameMatch": "all",
  "includeDetails": false,
  "includePhones": true,
  "minCellKm": 0.4
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);

4. From Python

# pip install apify-client
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("arcadium/google-maps-places").call(run_input={
    "searchTerms": ["dentist"],
    "location": "Austin, TX",
    "maxPlacesPerSearch": 50,
    "language": "en",
    "country": "us",
    "website": "any",
    "skipClosed": False,
    "nameMatch": "all",
    "includeDetails": False,
    "includePhones": True,
    "minCellKm": 0.4,
})
items = client.dataset(run.default_dataset_id).list_items().items  # apify-client 3.x returns a Run object
print(items)

5. In n8n

Paste this node into a workflow (it is a plain HTTP Request node). Create a Header Auth credential with name Authorization and value Bearer <APIFY_TOKEN>; each returned row becomes one item for the next node.

{
  "nodes": [
    {
      "name": "Run arcadium/google-maps-places",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        0,
        0
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.apify.com/v2/acts/arcadium~google-maps-places/run-sync-get-dataset-items",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "{\"searchTerms\":[\"dentist\"],\"location\":\"Austin, TX\",\"maxPlacesPerSearch\":50,\"language\":\"en\",\"country\":\"us\",\"website\":\"any\",\"skipClosed\":false,\"nameMatch\":\"all\",\"includeDetails\":false,\"includePhones\":true,\"minCellKm\":0.4}",
        "options": {
          "timeout": 300000
        }
      }
    }
  ],
  "connections": {}
}

Ready-made workflow to import:

6. From an AI assistant (MCP)

Add Apify’s MCP server to Claude Desktop, Cursor, VS Code or any MCP client, limited to this actor:

{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com/?tools=arcadium/google-maps-places",
      "headers": {
        "Authorization": "Bearer <APIFY_TOKEN>"
      }
    }
  }
}

More on the MCP page.

7. From LangChain

# pip install langchain-apify   (reads APIFY_API_TOKEN from the environment)
from langchain_apify import ApifyActorsTool

tool = ApifyActorsTool("arcadium/google-maps-places")
rows = tool.invoke({"run_input": {
    "searchTerms": ["dentist"],
    "location": "Austin, TX",
    "maxPlacesPerSearch": 50,
    "language": "en",
    "country": "us",
    "website": "any",
    "skipClosed": False,
    "nameMatch": "all",
    "includeDetails": False,
    "includePhones": True,
    "minCellKm": 0.4,
}})
# or give `tool` to an agent: create_react_agent(llm, [tool])

Sample output

One row, from the example in the actor’s README. Long text is shortened and lists are cut to two items.

{
  "rowType": "place",
  "placeId": "ChIJCar0f49ZwokR6ozLV-dHNTE",
  "cid": "3545819340560108778",
  "name": "Katz's Delicatessen",
  "personNamed": false,
  "category": "Deli",
  "address": "205 E Houston St, New York, NY 10002",
  "city": "New York",
  "postalCode": "10002",
  "latitude": 40.722233,
  "longitude": -73.987429,
  "rating": 4.5,
  "reviewCount": 56440,
  "website": "https://katzsdelicatessen.com/",
  "phone": null,
  "phoneE164": null,
  "openingHours": [
    {
      "day": "Monday",
      "date": "2026-11-02",
      "hours": [
        "8 AM–11 PM"
      ]
    }
  ],
  "closed": false,
  "searchTerm": null,
  "scrapedAt": "2026-11-02T18:00:00.000Z"
}
FieldWhat it holds
rowTypeKind of row. Filter on this first. 'place': one business (charged). 'coverage': one free row per search with places delivered, map cells searched and why it stopped. 'error': an input could not be read (free).
placeIdGoogle place id (ChIJ...). Stable; use it to deduplicate between runs and with the Google Places API.
cidGoogle CID of the place (decimal), the number in maps.google.com/?cid=...
featureIdGoogle Maps feature id '0x...:0x...' (as in place links).
placeUrlGoogle Maps link of the place.
nameBusiness name as Google Maps shows it.
personNamedInformation only: true when the listing is named after a person (e.g. a sole practitioner's practice, 'Bo Stanley, DDS'). Such a name and phone may be personal data under GDPR; nothing is withheld.
categoryMain Google category ('Dentist').
categoriesAll Google categories of the place.
descriptionGoogle's one-line summary of the place, when it has one.
addressFull address as Google prints it.
neighborhoodNeighbourhood or district.
streetStreet and number.
cityCity or town of the address.
postalCodePostal code.
stateState or region.
countryCodeCountry, ISO 3166 alpha-2.
latitudeLatitude (WGS84).
longitudeLongitude (WGS84).
timeZoneIANA time zone ('America/Chicago').
websiteWebsite the business lists on Google.
phoneBusiness phone as printed in its country, as published on the Google listing; null when includePhones is off or the listing has none.
phoneE164Business phone in E.164 (+15125550100); same rule as phone.
ratingGoogle star rating, 1-5.
reviewCountNumber of Google reviews.
openingHoursOpening hours of the next 7 days: [{day, date, hours: ['8 AM–5 PM']}] (holiday-aware, dated).
openNowTextOpen-now text at the time of the run ('Open · Closes 5 PM').
closedTrue when Google marks the place closed (permanently or temporarily).
bookingLinksBooking, reservation and ordering links (partner sites), tracking parameters removed.
detailsWith includeDetails: {plusCode, longDescription, attributes: [{group, name, value}], reviewTopics: [{name, mentions}]}; null otherwise.
searchTermSearch term that found the place (null for places given directly).
searchLocationLocation or area of that search.
rankPosition in the delivery order of its search (1-based).
languageLanguage of the run.
scrapedAtWhen the row was read (ISO 8601).
codeError rows: error code (no_match, not_found, blocked, unavailable, invalid_input).
messageError rows: what went wrong.
deliveredCoverage rows: places delivered for the search.
cellsSearchedCoverage rows: map cells searched.
cellsSaturatedAtMinCoverage rows: cells that still had 100+ places at the smallest cell size (some places there may be missing).
stopReasonCoverage rows: why the search ended (limit = max places reached, end = area exhausted, budget = run charge limit, error).
statedCoverage rows: always null (Google states no total for a map search).
shortfallCoverage rows: true when the search ended on an error or some map cells were still full at the smallest cell size.
shortfallReasonCoverage rows: 'error' or 'below_stated' (cells still full at the smallest size); null without a shortfall.

Tutorials

Schedule it

In Apify Console, create a Task from your input and add a Schedule (for example daily). Each run writes a new dataset; integrations can push the rows to Google Sheets, Slack, a webhook or your database.