Google Maps Extractor with Place ID, CID, Category Codes avatar

Google Maps Extractor with Place ID, CID, Category Codes

Pricing

from $3.99 / 1,000 results

Go to Apify Store
Google Maps Extractor with Place ID, CID, Category Codes

Google Maps Extractor with Place ID, CID, Category Codes

Get accurate local business info from Google Maps with this extractor. Ideal for marketers doing competitor research, citation building, or location-based campaign planning. Fetch data in bulk with no manual copy-paste.

Pricing

from $3.99 / 1,000 results

Rating

0.0

(0)

Developer

Scrapio

Scrapio

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

0

Monthly active users

6 days ago

Last modified

Share

Google Maps Scraper — Extract Places, IDs & Category Codes

Google Maps Extractor With Place ID, CID & Category Codes is a Google Maps scraper that turns a search term and an area into a deduplicated dataset of business places — then adds the identifiers a CRM or warehouse actually joins on: Place ID, CID, Feature ID (FID), Knowledge-Graph MID, owner profile ID, canonical result type, IANA timezone, E7 coordinates and an Open Location Code plus code — plus the language-independent gcid: category code and five ready-to-use Google links. Unlike scraping frameworks that hand back raw HTML, it returns typed JSON, ready for a database, a pipeline, or an LLM context window without any parsing. This guide covers every input and output field and how teams actually run it — one-off lookups, scheduled monitoring, and bulk dataset builds.


🧭 What Does This Google Maps Extractor Do?

It reads Google Maps' own internal map-search JSON endpoint directly — no browser, no Google Maps API key, and no Google account or login required. Give it a business type (keywords) and an area (locations), or paste a Google Maps link (urls), and it geocodes the area, sweeps it with a grid of map tiles, and returns one row per unique place. Every row carries the place's core details (name, address, phone, website, category, rating, hours) plus the full set of Google join keys and, optionally, five deep links built from those keys.

  • 📍 Sweeps any city, district or region you can type into Google Maps, or a pasted Maps link
  • 🆔 Emits place_id, cid, feature_id (FID), kg_mid, owner_profile_id on every row
  • 🧬 Resolves the language-independent gcid: category code alongside the localised category label
  • 🌐 Computes E7 integer coordinates and a global Open Location Code (plus code) locally
  • 🔗 Builds five ready-to-paste Google links (map pin, place page, directions, review, Knowledge Graph)
  • 📐 Checks every place's coordinates against the geocoded bounding box of the area you asked for before saving it
  • 🛰️ Steps up through Apify Proxy tiers automatically if a connection is blocked or serves the wrong region

⚡ Features & Capabilities

The core engine is shared with Scrapio's base Google Maps line; this variant's job is the identifier and category layer on top.

Core features

  • Full Google join-key set on every row: place_id, cid, feature_id, kg_mid, owner_profile_id, result_type, timezone_iana, lat_e7, lng_e7, plus_code.
  • Dual category representation: primary_category / all_categories (Google's localised label) and gcid_category (e.g. gcid:coffee_shop) — identical across languages and countries, so rows collected in different locales still group correctly.
  • Five deep links per row: maps_cid_url, maps_place_url, directions_url, write_review_url, knowledge_graph_url — templated from the row's own identifiers, no extra request.
  • Two output shapes: flat columns (default, best for CSV/Sheets) or a single nested identifiers + deep_links object (best for API consumers), switched by identifierFormat.
  • Area-consistency filtering: a geo probe runs once per area before any row is saved, and a bounding-box gate (restrictToSearchArea) drops out-of-area results.
  • Link input: /maps/search/<terms>/@lat,lng,zoom and /maps/place/<Name>/@lat,lng,zoom URLs are accepted directly, alongside plain keyword/location pairs.

When another tool might suit you better

If you need the full week of opening hours, review text or review counts, price level, or popular-times data, this Actor is not the right pick — Google's map-search JSON surface it reads does not carry those fields at all (opening hours here is Google's own "current day only" payload, not a 7-day schedule), so no scraper reading this same endpoint can add them without a different data source. If your only requirement is a bare name + address + place_id list and you don't need the extra Google join keys, a simpler Google Maps scraper with fewer output columns may be easier to map into an existing pipeline.

This Actor within Scrapio's Google Maps lineup

This is Scrapio's only Google Maps Actor, and it covers business places end to end — search, area filtering, and the full identifier/category layer. For reviews and contact-detail enrichment on the same kind of local businesses (hotels), pair it with Scrapio's Tripadvisor Review Scraper, which extracts reviews plus a property contact block from Tripadvisor listings.


Why do developers and data teams scrape Google Maps?

🏢 Local lead generation and sales teams

Feed a keyword and a territory list in, get back name, phone, website, full_address, place_id and cid for every matching business, deduplicated by place ID across overlapping searches. Because place_id, cid and feature_id are all on the row, the export joins cleanly into a CRM that keys contacts by any of Google's own identifiers, instead of forcing a second lookup to translate between them.

📊 AI training data and RAG indexing

full_address, primary_category, gcid_category and tags are the highest-information text fields for a local-business knowledge base: full_address is Google's own one-line formatted string (not an assembled join of parts), and gcid_category gives a stable, language-independent category label to index and filter on. For RAG, embed name + full_address + tags/primary_category per place; for training data, result_type, timezone_iana and the E7 coordinates are structurally consistent typed fields across every row, since they come straight off Google's own listing payload.

📱 Competitive and market intelligence

Run the same keywords + locations pair on a schedule and track avg_rating, phone, website and hours across runs for a set of competitor locations. A changed website or a dropped avg_rating on a tracked place_id is a concrete, checkable signal, not a guess.

🔬 Research and academic use

Public business-listing data — categories, ratings, coordinates, opening-hours snapshots — supports urban-economics, retail-geography and local-market research datasets. Scope is limited to what a place has published publicly on Google Maps; no reviewer identities or private account data are collected.

🎥 Product and SaaS development

The identifier set is what makes this data usable as a backend for a directory product, a local-business enrichment API, or a monitoring tool: place_id/cid/feature_id let a downstream product re-resolve or link back to the same Google listing later, and the five deep links are ready to render directly in a UI.


🍚 Input Parameters

All nine parameters accepted by this Actor, in schema order, with exact names, types and defaults.

ParameterRequiredTypeDescriptionExample Value
keywordsYesarray of stringsBusiness type or search term. Each term × each area is a separate sweep. Default: ["coffee shops"].["dentists", "hotels"]
locationsNoarray of stringsCities, districts or regions. Each is geocoded to a bounding box and swept with a grid of map tiles. Default: ["New York"].["Kyoto, Japan", "Austin, TX"]
urlsNoarray of stringsOptional Google Maps links (/maps/search/… or /maps/place/…). Default: [].["https://www.google.com/maps/search/bakeries/@37.77,-122.41,13z"]
identifierFormatNostring (select)columns — one identifier per spreadsheet column. bundled — identifiers nested under identifiers, links under deep_links. Enum: columns, bundled. Default: "columns"."bundled"
resolveCategoryCodesNobooleanAdds gcid_category, the language-independent category code. Costs one extra request per saved place. Default: true.false
includeDeepLinksNobooleanAdds maps_cid_url, maps_place_url, directions_url, write_review_url, knowledge_graph_url. No extra requests. Default: true.true
maxResultsNointegerTotal unique places across every term, area and link, duplicates merged. Minimum 1, maximum 10000. Default: 20.500
restrictToSearchAreaNostring (select)strict — keep only places inside the geocoded area (recommended). padded — area + 25% margin. off — no filter. Enum: strict, padded, off. Default: "strict"."padded"
proxyConfigurationNoobject (proxy)Apify Proxy configuration. Default: {"useApifyProxy": true}.{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}
{
"keywords": ["coffee shops"],
"locations": ["New York"],
"urls": [],
"identifierFormat": "columns",
"resolveCategoryCodes": true,
"includeDeepLinks": true,
"maxResults": 20,
"restrictToSearchArea": "strict",
"proxyConfiguration": { "useApifyProxy": true }
}

Supported URL types and input formats

  • Search linkshttps://www.google.com/maps/search/coffee+shops/@40.7128,-74.0060,13z. The @lat,lng,zoom segment defines the search centre and radius directly; the search text becomes the query.
  • Place linkshttps://www.google.com/maps/place/Empire+State+Building/@40.7484,-73.9878,17z. The place name is searched in a tight radius around the link's own coordinates.
  • Query-string links?q=<terms> links without an @lat,lng segment have their search text geocoded like a plain locations entry.
  • Not supported — a link whose only identifier is place_id:, ftid: or cid: (e.g. ?q=place_id:ChIJ...) is reported as an error in the run log and skipped, because Google's map-search surface has no place-ID lookup — a request with that query returns zero places.

📦 Output Format

One dataset row per unique place, deduplicated by place_id across every keyword, area and link in the run. There is a single structural row type — rowType: "place" — carrying the base listing fields plus the identifier, category and deep-link layer this Actor adds.

Output for a place (identifierFormat: "columns", both switches on — the default)

{
"rowType": "place",
"name": "Qahwah Time Specialty Coffee",
"street_address": "66 Court St",
"city": "Brooklyn",
"state": "New York",
"zip": "11201",
"country_code": "US",
"full_address": "66 Court St, Brooklyn, NY 11201",
"website": "http://qahwahtimeeny.com/",
"phone": "(646) 328-0115",
"avg_rating": 4.7,
"tags": ["Coffee shop"],
"hours": [
{ "day": "Sunday", "hours": "7 AM-10 PM", "opens_at": "07:00", "closes_at": "22:00" }
],
"lat": 40.6918975,
"long": -73.9916113,
"place_id": "ChIJSedGC-9bwokRCZQ_F_l6mL0",
"primary_category": "Coffee shop",
"all_categories": ["Coffee shop"],
"gcid_category": "gcid:coffee_shop",
"feature_id": "0x89c25bef0b46e749:0xbd987af9173f9409",
"cid": "13661804679883887625",
"kg_mid": "/g/11lzrpr62p",
"owner_profile_id": "117016208756126274180",
"result_type": "SearchResult.TYPE_COFFEE",
"timezone_iana": "America/New_York",
"lat_e7": 406918975,
"lng_e7": -739916113,
"plus_code": "87G8M2R5+Q9",
"maps_cid_url": "https://www.google.com/maps?cid=13661804679883887625",
"maps_place_url": "https://www.google.com/maps/search/?api=1&query=Qahwah+Time...&query_place_id=ChIJSedGC-9bwokRCZQ_F_l6mL0",
"directions_url": "https://www.google.com/maps/dir/?api=1&destination=40.6918975,-73.9916113&destination_place_id=ChIJSedGC-9bwokRCZQ_F_l6mL0",
"write_review_url": "https://search.google.com/local/writereview?placeid=ChIJSedGC-9bwokRCZQ_F_l6mL0",
"knowledge_graph_url": "https://www.google.com/search?kgmid=%2Fg%2F11lzrpr62p"
}

With identifierFormat: "bundled", the same nine identifier fields move under a single identifiers object (which also repeats place_id), and the five link fields move under deep_links; every other field stays flat on the row:

{
"rowType": "place",
"name": "Qahwah Time Specialty Coffee",
"place_id": "ChIJSedGC-9bwokRCZQ_F_l6mL0",
"identifiers": {
"feature_id": "0x89c25bef0b46e749:0xbd987af9173f9409",
"cid": "13661804679883887625",
"kg_mid": "/g/11lzrpr62p",
"owner_profile_id": "117016208756126274180",
"result_type": "SearchResult.TYPE_COFFEE",
"timezone_iana": "America/New_York",
"lat_e7": 406918975,
"lng_e7": -739916113,
"plus_code": "87G8M2R5+Q9",
"place_id": "ChIJSedGC-9bwokRCZQ_F_l6mL0"
},
"deep_links": {
"maps_cid_url": "https://www.google.com/maps?cid=13661804679883887625",
"maps_place_url": "https://www.google.com/maps/search/?api=1&query=...",
"directions_url": "https://www.google.com/maps/dir/?api=1&destination=...",
"write_review_url": "https://search.google.com/local/writereview?placeid=...",
"knowledge_graph_url": "https://www.google.com/search?kgmid=..."
}
}

Two field-presence details worth knowing before you build a schema around this:

  • gcid_category is omitted, not nulled, when resolveCategoryCodes is off. The key does not exist on the row at all — filter with a "field exists" check, not a null check, if you toggle this input.
  • maps_cid_url, write_review_url and knowledge_graph_url are always present keys but can individually be null when their underlying identifier (cid, place_id, kg_mid) is missing on that place — the deep-link keys don't disappear, only their values do.
  • cid is a string, not a number. CID values exceed the range JavaScript (and some JSON parsers) can represent exactly as an integer.

Schema stability and export options

Field names come from this Actor's own row-building code, not from Google's raw payload keys, so they stay stable across Google Maps front-end changes; only Google changing the underlying tbm=map data contract itself would affect them. Results are stored in the Apify dataset for the run and can be downloaded or exported in JSON, CSV, Excel, XML, RSS or HTML directly from the Apify Console or via the API — this Actor does not add a separate delivery mechanism on top of that.


💡 Google Maps Extractor With Place ID, CID & Category Codes Strategy Guide

🎯 Strategy 1: Real-time enrichment pipeline

Trigger a run whenever an inbound lead or CRM record is missing contact details: call the Actor with keywords set to the business name and locations set to its city (or pass a Google Maps urls link if you already have one). Read back phone, website, full_address, place_id and cid from the single returned row, and write them onto the source record. Because place_id and cid are both returned, the enriched record can be re-joined against any other Google-keyed dataset later without a second lookup.

🎯 Strategy 2: Scheduled monitoring and alerting

Set up an Apify Schedule that reruns the same keywords + locations (or a fixed urls list) daily or weekly. Diff each new run's dataset against the previous one on avg_rating, phone, website and hours for the same place_id, and alert only on the fields that actually changed. A rating drop or a new phone number on a tracked competitor location is a concrete trigger; an unchanged row needs no action.

🎯 Strategy 3: Bulk dataset build

Pass a large locations list and keywords list in one run, or fan out one run per location/keyword group, up to the documented maxResults ceiling of 10,000 unique places per run. For very large builds, running one Actor call per location keeps each run's grid sweep focused on a smaller area rather than one run spreading a large maxResults thin across the whole list. Aggregate the resulting datasets into one CSV or table for the research or lead-list output.

Strategy comparison at a glance

StrategyBest forRun patternOutput format
Real-time enrichmentFilling in missing contact/address fields on inbound recordsOne Actor run per incoming record, called via the APIJSON row, read back immediately
Scheduled monitoringTracking rating/phone/hours changes on named locationsApify Schedule, same input reused each runDataset diffed run-over-run
Bulk dataset buildBuilding a research or lead-list dataset for many areasOne run per location/keyword group, up to 10,000 places eachCSV/JSON export, aggregated

ScraperWhat it extracts
Tripadvisor Review Scraper (Scrapio)Hotel reviews plus a property contact block (phone, website, address, manager name) — an adjacent enrichment source for the same kind of local businesses this Actor returns as places

This Actor is Scrapio's only Google Maps place scraper, so there is no second Scrapio listing to route to for a different Google Maps entity type.


How to integrate Google Maps Extractor With Place ID, CID & Category Codes with your stack

Google Maps Extractor With Place ID, CID & Category Codes works with any language or tool that can make an HTTP request — it runs on the Apify platform and is started through the Apify Console, the Apify API, or an Apify client SDK.

Python

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run_input = {
"keywords": ["coffee shops"],
"locations": ["New York", "Kyoto, Japan"],
"maxResults": 100,
"identifierFormat": "columns",
"resolveCategoryCodes": True,
"includeDeepLinks": True,
}
run = client.actor("Scrapio/google-maps-extractor-with-place-id-cid-category-codes").call(
run_input=run_input
)
rows = []
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
rows.append({
"name": item.get("name"),
"place_id": item.get("place_id"),
"cid": item.get("cid"),
"gcid_category": item.get("gcid_category"),
"phone": item.get("phone"),
"full_address": item.get("full_address"),
})
import csv
with open("places.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=rows[0].keys())
writer.writeheader()
writer.writerows(rows)

Node.js

import { ApifyClient } from "apify-client";
const client = new ApifyClient({ token: "<YOUR_APIFY_TOKEN>" });
const input = {
keywords: ["coffee shops"],
locations: ["New York"],
maxResults: 100,
identifierFormat: "columns",
resolveCategoryCodes: true,
};
const run = await client
.actor("Scrapio/google-maps-extractor-with-place-id-cid-category-codes")
.call(input);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const rows = items.map((r) => ({
name: r.name,
placeId: r.place_id,
cid: r.cid,
category: r.gcid_category,
phone: r.phone,
}));
console.log(rows);

Async and scheduled pipelines

For fire-and-forget large jobs, start the run asynchronously (client.actor(...).start(...) instead of .call(...)) and poll the run status or the dataset item count until it finishes, rather than blocking on the call. For recurring jobs, use an Apify Schedule to rerun the same input on a cron interval, and an Apify webhook on the "run finished" event to trigger your own pipeline instead of polling.


🎯 Who Needs Google Maps Extractor With Place ID, CID & Category Codes? (Use Cases & Industries)

🏢 Local lead generation and sales teams

Build territory-scoped prospect lists with name, phone, website, full_address and place_id/cid already attached, so the list drops straight into a CRM keyed by Google's own identifiers instead of needing a second enrichment pass.

📊 AI engineers building RAG and enrichment pipelines

Index full_address, primary_category, gcid_category and tags as the retrievable text for a local-business assistant, and use place_id/cid as the stable join key back to the source listing whenever the assistant needs to re-verify a fact.

📱 Market and competitive intelligence teams

Track avg_rating, phone, website and hours for a fixed set of competitor place_ids across scheduled runs, and flag only the fields that actually moved.

🔬 Researchers

Use public category, rating and coordinate data for retail-geography, urban-economics or local-market studies. Scope is limited to what a business has published on its public Google Maps listing — no reviewer identities or private account data are ever collected.

🎥 Directory and enrichment product builders

Use the identifier set (place_id, cid, feature_id, kg_mid) as the backend join keys for a business directory, a local-SEO tool, or a data-enrichment API, and render the five deep links directly in a product UI.


Scraping publicly visible Google Maps listings — business names, addresses, categories, ratings and hours — is generally legal. In hiQ Labs, Inc. v. LinkedIn Corp. (9th Cir. 2019), the court held that scraping data a company has made publicly accessible does not violate the U.S. Computer Fraud and Abuse Act, a precedent widely relied on for scraping public web data generally, not specific to any one platform.

Two separate questions matter beyond that:

  • Violating a platform's Terms of Service is a contract and civil-law matter between you and the platform, not a criminal one — it can affect your access to the platform (e.g. account or IP restrictions) but is a different question from whether the underlying data collection is unlawful.
  • Data protection law: this Actor returns business and place records — names, categories, published phone numbers and addresses, coordinates — not personal profiles, reviewer identities, or private account data. GDPR/CCPA's personal-data framing is built around identifying an individual person, which is not what this dataset does, so it does not attach here the way it would to a scraper of individual social-media profiles.

Google Maps Extractor With Place ID, CID & Category Codes returns only publicly accessible data. What you do with that data is your responsibility — consult legal counsel for commercial applications, especially where local law treats a sole proprietor's published phone number as personal data.


❓ Frequently asked questions

Does this Google Maps Extractor work without a Google account?

Yes. It reads Google Maps' own public map-search endpoint over plain HTTP requests — no Google account, login, or Google Maps API key is used or required anywhere in the code.

How does it handle Google's anti-scraping measures?

It routes requests through your configured Apify Proxy tier, and automatically escalates — direct/datacenter → residential — after four consecutive request failures or a hard block signal (HTTP 403/429/503, or an unreadable response). Each request retries up to three times before that escalation kicks in. Separately, a one-request geo-consistency probe runs per area before any row is saved; if the probe's returned coordinates don't match the requested area within a tolerance derived from that area's own size, the connection escalates and retries rather than saving mismatched rows.

Can I run it at scale without getting blocked?

maxResults accepts up to 10,000 unique places per run, and the Actor spreads requests across tiles in waves rather than firing them all at once. There is no published uptime or success-rate figure for scraping at any given volume — outcomes depend on your proxy configuration and Google's response at the time of the run, and the Actor will escalate proxy tiers or fail the run explicitly rather than silently reporting an empty success.

How fresh is the data this Actor returns?

Every run performs a live fetch against Google Maps at the time it runs — there is no caching layer in the code. Ratings, hours, phone and website values reflect whatever Google was serving at that moment.

Which fields work best for AI training and RAG indexing?

For RAG, embed name, full_address and tags/primary_category — the highest-information text fields on the row. For training data, result_type, timezone_iana, lat_e7/lng_e7 and place_id are the most structurally consistent fields across every record, since they come directly off Google's listing payload rather than being derived by heuristics. All fields return as typed primitives (strings, numbers, arrays), so no HTML parsing or text cleanup is needed before use.

Is scraping Google Maps against Google's Terms of Service?

Google's Terms of Service restrict some forms of automated access to its own properties; this is a contractual matter between the user and Google, and a breach is a civil risk to the account or connection used, not a criminal one. This Actor accesses Google Maps' publicly reachable map-search endpoint the same way a browser session does, and does not use or require a Google account.

Does it work with Claude, ChatGPT, and other AI agent tools?

There is no MCP server for this Actor. It is callable as an HTTP endpoint by any agent framework that can call the Apify API — every response is typed JSON, so it can be passed straight into an LLM context window without any parsing step.

How does this compare to other Google Maps scrapers?

Most Google Maps scrapers stop at a name, address and place_id. This Actor also resolves the CID, Feature ID, Knowledge-Graph MID, owner profile ID, canonical result type, IANA timezone, E7 coordinates, a global plus code, and the language-independent gcid: category code — the identifiers a CRM, warehouse or Knowledge-Graph lookup actually needs to join on, all from the same run. It does not add review text, review counts, price level or popular-times data, because that information is not present on the Google Maps surface it reads.

Why does maxResults sometimes return fewer places than requested?

Three things can cause this: the requested area genuinely has fewer matching businesses than maxResults (a sparse rural search will not be padded with results from elsewhere), some raw results are dropped for lacking a place_id or coordinates, and duplicate place_ids found across overlapping tiles, keywords or areas are merged rather than counted twice.

What's the difference between identifierFormat: "columns" and "bundled"?

columns (the default) puts every identifier field directly on the row — best for CSV, Excel or Google Sheets exports. bundled nests the same nine identifier fields under a single identifiers object and the five deep links under deep_links — best for API consumers that prefer a structured sub-object over a wide flat row. place_id and every other base field stay at the top level either way.


ℹ️ Disclaimer

Google Maps Extractor With Place ID, CID & Category Codes extracts only publicly available data from Google Maps. This tool is intended for lawful use cases only. Users are responsible for complying with Google's Terms of Service and applicable data protection laws in their jurisdiction.