Arcadium Data Scrapers
Home / Actors / Google Maps Places Scraper

Google Maps Places Scraper

Give it a search term and an area (a city, district or postcode, a bounding box, a circle or a GeoJSON polygon) and it returns every business Google Maps shows there as one JSON row each: name, categories, full address and its parts, coordinates, star rating and review count, website, phone, opening hours for the next 7 days, open-now text and booking links. Or give it place links, place IDs or CIDs. $2.00 per 1,000 places; you pay only for rows you get.

$2.00 per 1,000 places on the Free plan, down to $1.00 on higher plans

Why this actor

What you get

Place row (rowType: "place", charged as place):

FieldWhat it holds
placeId, cid, featureId, placeUrlGoogle's ids of the place and its Maps link. placeId is stable: deduplicate on it between runs.
name, personNamedBusiness name as Google shows it; personNamed: true when the listing is named after a person (e.g. a sole practitioner's practice, "Bo Stanley, DDS").
category, categories, descriptionMain and all Google categories; Google's one-line summary.
address, street, neighborhood, city, postalCode, state, countryCodeFull address and its parts.
latitude, longitude, timeZoneLocation.
rating, reviewCountGoogle stars and number of reviews.
website, bookingLinksWebsite the business lists; booking and ordering links (tracking removed).
openingHours, openNowText, closedHours of the next 7 days ([{ day, date, hours }], holiday-aware), open-now text, closed flag.
phone, phoneE164Business phone as published on the listing (on by default; switch includePhones off to leave them empty).
detailsWith includeDetails (charged as place-details): plus code, long description, amenity and accessibility attributes, review topics with mention counts. null (and not charged) when the detail request failed.
searchTerm, searchLocation, rank, scrapedAtWhich search found it, its position, when it was read.

Coverage row (rowType: "coverage", free): delivered, duplicates (places an earlier search of the run already delivered), cellsSearched, cellsSplit, cellsSaturatedAtMin (cells still full at the smallest size: places there may be missing; lower minCellKm to go deeper), filteredOut, outsideArea, personNamed, detailsFailed, detailsSkipped (places delivered without a detail request while details were paused after 5 failures in a row; one retry after 25 places turns them back on), detailsCapped, pages and maxPages, areaSource (location, coordinates, link), stopReason (limit: max places reached; end: the area is exhausted; budget: the page budget or the run's max charge was reached; error), capSource (what cut the search: user for a limit you set or the run's max charge, default for a default page budget, site when Google's own result cap left places out).

Page budget. Filters run on the places Google returns, so a strict filter over a big area could read a whole city and keep almost nothing. Each search therefore stops after maxPagesPerSearch search pages (default 3 per 20 places asked for, at least 30; "all places" = 3,000 pages) and the run after maxPagesPerRun (default 5,000), with stopReason: "budget".

Error row (rowType: "error", free): input, code (invalid_input, not_found, no_match, blocked, unavailable), message. Retry blocked and unavailable later.

Listings are published by the businesses for customer contact. A sole trader's name and phone may still be personal data under GDPR; you are responsible for using them lawfully.

Reviews of the places are not part of this actor: pass the placeId values to our Google Maps Reviews Scraper.

Sample row (from the README example; long text shortened, lists 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"
}

Price

Live Apify price on 2026-11-03, per 1,000 rows, by Apify plan. You pay only for rows delivered; error and summary rows marked free in the README cost nothing. Plus $0.001 per GB of memory when a run starts.

Per 1,000FreeBronzeSilverGoldPlatinumDiamond
Place (main)$2.00$1.60$1.40$1.00$1.00$1.00
Place details$0.50$0.50$0.50$0.50$0.50$0.50

Run it from code or an AI agent

Actor ID arcadium/google-maps-places. Set APIFY_TOKEN to your Apify API token. The input below is the actor’s prefilled example; see the guide for every field.

curl
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}'
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);
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)
MCP
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com/?tools=arcadium/google-maps-places",
      "headers": {
        "Authorization": "Bearer <APIFY_TOKEN>"
      }
    }
  }
}
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])
LlamaIndex
# pip install llama-index-readers-apify
import os
from llama_index.core import Document, VectorStoreIndex
from llama_index.readers.apify import ApifyActor

reader = ApifyActor(os.environ["APIFY_TOKEN"])
documents = reader.load_data(
    actor_id="arcadium/google-maps-places",
    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,
    },
    dataset_mapping_function=lambda item: Document(text=str(item.get("description") or ""), metadata={"url": item.get("url")}),
)
index = VectorStoreIndex.from_documents(documents)
CrewAI
# pip install crewai crewai-tools langchain-apify   (reads APIFY_API_TOKEN from the environment)
from crewai import Agent
from crewai_tools import ApifyActorsTool

tool = ApifyActorsTool(actor_name="arcadium/google-maps-places")
researcher = Agent(
    role="Research analyst",
    goal="Answer with data collected by the tool",
    backstory="Uses public web data.",
    tools=[tool],
)
n8n
{
  "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": {}
}
Google Sheets
// Extensions > Apps Script. Store your token once: Project Settings > Script properties > APIFY_TOKEN.
function importLastRun() {
  const token = PropertiesService.getScriptProperties().getProperty('APIFY_TOKEN');
  const url = 'https://api.apify.com/v2/acts/arcadium~google-maps-places/runs/last/dataset/items?status=SUCCEEDED&clean=true&format=json';
  const res = UrlFetchApp.fetch(url, { headers: { Authorization: 'Bearer ' + token } });
  const rows = JSON.parse(res.getContentText());
  if (!rows.length) return;
  const keys = Object.keys(rows[0]);
  const values = rows.map((r) => keys.map((k) => (typeof r[k] === 'object' ? JSON.stringify(r[k]) : r[k])));
  const sheet = SpreadsheetApp.getActiveSheet();
  sheet.clearContents();
  sheet.getRange(1, 1, 1, keys.length).setValues([keys]);
  sheet.getRange(2, 1, values.length, keys.length).setValues(values);
}

Tutorials

Limitations