Google Maps Review Scraper $0.25 per 1k results
Pricing
from $0.25 / 1,000 results
Google Maps Review Scraper $0.25 per 1k results
Scrape Google Maps reviews at scale for any business or place using URLs, place names, or Place IDs. This actor extracts full review data—including rating, text, date, reviewer name and profile, photos, likes, and owner responses. Perfect for sentiment analysis, reputation monitoring, local SEO.
Pricing
from $0.25 / 1,000 results
Rating
0.0
(0)
Developer
Dipendra KC
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
Google Maps Reviews Scraper
A production-ready, high-volume Google Maps review scraper built in TypeScript (Node.js 20+) using Crawlee and Playwright (headless Chromium). Given Google Maps place URLs or Place IDs, it extracts all reviews for each place with complete review text, ratings, reviewer profiles, owner responses, photos, translation variants, and metadata, exportable to JSON, NDJSON, CSV, and Excel (
.xlsx).
Important Compliance Notice (Personal Data & TOS)
[!CAUTION] Reviewer Personal Data & GDPR Compliance: Reviewer data (display name, profile URL, contributor ID, and profile photo) constitutes personal data under privacy regulations such as the General Data Protection Regulation (GDPR), CCPA, and equivalent frameworks worldwide.
- Opt-In Privacy Gating (
includeReviewerDetails): By default, reviewer metadata is captured. WhenincludeReviewerDetailsis set tofalse, the scraper sanitizes reviewer-identifiable fields (name: "Anonymous",reviewerId: "",reviewerUrl: "",reviewerPhotoUrl: null), while preserving review metrics (stars, detailed ratings, review text, timestamps, owner responses).- Legal Responsibility: Users of this software are solely responsible for ensuring that their data collection, storage, and processing practices comply with applicable data protection laws and Google Maps' Terms of Service. This is especially critical before retaining or analyzing reviewer-identifiable records beyond aggregate sentiment or analytical research.
Key Features
- Single-Place-In, All-Reviews-Out: Narrower, deeper extraction designed to retrieve complete review histories for specified venues.
- Crawlee + Playwright Engine: Navigates dynamic JavaScript-rendered infinite scroll review panels with humanized scroll steps and random jitter.
- Residential Proxy Rotation & Resilience: Pluggable proxy configuration with session management, bot/CAPTCHA detection, and backoff recovery.
- Fail-Fast Zod Schema Validation: Strict input validation preventing wasted runs; full Zod schema validation for output records.
- Sort & Date Filtering: Supports sorting by
newest,mostRelevant,highestRating, andlowestRating. Combines date-range bounding (dateFrom,dateTo) withnewestsort. - Reviews Origin Handling (
allvsgoogle): Handles Google's blended view vs. native Google reviews, critical for hotel and travel venues. - Full Text & Translation Extraction: Expands truncated "Read more" links; captures both original and translated text variants along with language tags.
- Owner Response Capture: Detects and extracts owner responses and response timestamps.
- Isolated Per-Field Extractors: Every DOM extractor is isolated with individual
try/catchboundaries—a missing or non-standard field never drops a review. - Multi-Format Streaming Storage: Exports incrementally to JSON, NDJSON, CSV, and Excel (
.xlsx) simultaneously. - Production Observability: Structured JSON/console logging, progress metrics, and end-of-run yield shortfall summaries.
System Architecture
/src/config # Zod input/output schemas and runtime defaults/resolve # URL & Place ID canonicalization (shortlink follow, ChIJ converter)/crawlersplaceCrawler.ts # Place-level metadata & cookie interstitial handlingreviewsCrawler.ts # Infinite scroll loop, sort, origin, and card expansion/extractors # Pure functions: DOM -> typed fields with per-field error isolationplaceExtractor.tsreviewTextExtractor.tsreviewerExtractor.tsreviewRatingExtractor.tsreviewDateExtractor.tsreviewResponseExtractor.tsreviewMediaExtractor.tsreviewContextExtractor.ts/storage # Export adapters (JSON, NDJSON, CSV, Excel XLSX)/proxy # Residential proxy configuration & session manager/utils # Logger, dateParser, humanize jitter delaysorchestrator.ts # Batch orchestrator managing Crawlee PlaywrightCrawlerindex.ts # CLI entrypoint (reviews-scraper)/testsfixtures/ # DOM snapshots (standard, owner response, image-rich, translated, ad)schema.test.tsextractors.test.tsdateParser.test.tsurlResolver.test.tsstorage.test.tsscripts/ # Manual integration sanity script
Input Configuration Schema
| Field | Type | Default | Description |
|---|---|---|---|
startUrls | string[] | undefined | Array of Google Maps URLs (direct, search, or maps.app.goo.gl shortlinks). |
placeIds | string[] | undefined | Array of raw Google Place IDs (ChIJ...). Canonical URLs are constructed automatically. |
maxReviews | number | 1000 | Maximum reviews to retrieve per place. Helps bound runtime and proxy bandwidth. |
reviewsSort | enum | 'newest' | Sort order: 'newest', 'mostRelevant', 'highestRating', 'lowestRating'. |
reviewsOrigin | enum | 'all' | 'all' (blended view) or 'google' (Google-native only). |
dateFrom | string (ISO) | undefined | ISO 8601 start date. Only valid when reviewsSort === 'newest'. |
dateTo | string (ISO) | undefined | ISO 8601 end date. Only valid when reviewsSort === 'newest'. |
language | string | 'en' | Host language parameter (hl=...) for the Google Maps UI. |
includeImages | boolean | true | Extract high-resolution photo URLs attached by reviewers. |
includeReviewerDetails | boolean | true | When false, anonymizes personal reviewer identifiers. |
concurrency | number | 2 | Number of places crawled concurrently. Low per-place, parallel across places. |
proxyConfig.urls | string[] | undefined | Array of residential proxy URLs (http://user:pass@host:port). |
Validation Rules Enforced at Startup
- Target Requirement: At least one
startUrlsorplaceIdsmust be provided. - Date Range Constraint:
dateFromanddateToare strictly rejected unlessreviewsSortis'newest'. Sorting by rating or relevance renders reviews non-chronologically; applying a date boundary on those sorts would result in silent under-scraping. - Date Logic:
dateFromcannot be later thandateTo. - Volume Warnings: If
maxReviewsis unset or exceeds5000, a warning is logged detailing the memory and proxy consumption tradeoffs.
Output Row Schema (ReviewRow)
Each row represents one review, denormalized with place metadata to enable direct CSV/Excel export without nested flattening:
interface ReviewRow {// Review Identification & ContentreviewId: string;reviewUrl: string;reviewerId: string;reviewerUrl: string;name: string;reviewerNumberOfReviews: number;isLocalGuide: boolean;reviewerPhotoUrl: string | null;text: string | null;textTranslated: string | null;originalLanguage: string | null;translatedLanguage: string | null;publishedAtDate: string; // ISO 8601publishAt: string; // Relative string verbatim (e.g. "2 weeks ago")likesCount: number;stars: number | null;reviewOrigin: string; // "Google" or third-party name// Responses, Media & MetadataresponseFromOwnerDate: string | null;responseFromOwnerText: string | null;reviewImageUrls: string[];reviewDetailedRating: Record<string, number>; // e.g. { Food: 5, Service: 5 }reviewContext: Record<string, string>; // e.g. { "Visited in": "October 2024" }visitedIn: string | null;isAdvertisement: boolean;// Denormalized Place InformationplaceId: string;cid: string;fid: string;title: string;categoryName: string;categories: string[];totalScore: number | null;reviewsCount: number;location: { lat: number; lng: number };address: string | null;url: string;scrapedAt: string; // ISO 8601}
Core Scraping Strategies & Edge Cases
1. Handling Hotel & Travel Categories (reviewsOrigin)
Google Maps blends third-party partner reviews (e.g., TripAdvisor, Agoda, Booking.com) into hotel reviews under the default "All" view. This causes two issues:
- Some partner reviews cannot be sorted by "Newest".
- Native Google review yields are reduced.
Solution: Set
reviewsOrigin: "google"to switch the reviews panel to Google-native reviews only.
2. Google UI Review Count vs. Retrievable Yield Shortfall
A venue's displayed review count (e.g., (1,482)) often exceeds the number of reviews Google's infinite scroll panel actually returns (due to filtered spam, shadow-banned reviews, or API-level deduplication).
Solution: The scraper tracks this discrepancy and logs an explicit Yield Shortfall summary at the end of every place run without failing the job:
======================================================================PLACE SCRAPING SUMMARY: The Roasted Bean (ChIJN1t_tDeuEmsRUsoyG83frY4)- Reviews Requested: 1000- Reviews Delivered: 890- Google Total Count: 950- Yield Shortfall: 60 (Google UI count vs retrievable reviews gap)- Stop Reason: end_of_list- Duration: 42.4s======================================================================
3. Infinite Scroll Deduplication
Infinite scroll events or retry attempts can cause duplicate cards to re-render in the DOM. Reviews are deduplicated in real-time by their unique reviewId before being written to disk.
4. Shortened URL Resolution (maps.app.goo.gl)
Shortened URLs do not contain coordinate or Place ID information. The scraper unwinds redirects via fast HTTP headers prior to launching the browser context.
Installation & Setup
Prerequisites
- Node.js 20+
- npm 10+
# Clone the repository and enter the directorygit clone <repository-url>cd "Google maps review scraper"# Install dependenciesnpm install# Install Playwright Chromium browser binarynpx playwright install chromium# Build TypeScriptnpm run build
CLI Usage
1. Run with a JSON configuration file
$node dist/index.js run --config input.example.json --out ./results
2. Run with command line arguments
node dist/index.js run \--place-ids ChIJN1t_tDeuEmsRUsoyG83frY4 \--max-reviews 100 \--sort newest \--origin google \--date-from 2024-01-01T00:00:00.000Z \--format json csv xlsx \--out ./results
3. Anonymize reviewer details for GDPR compliance
node dist/index.js run \--config input.example.json \--no-reviewer-details \--out ./results
4. CLI Options Reference
Usage: reviews-scraper run [options]Options:-c, --config <path> Path to JSON configuration file-o, --out <path> Output directory for export files (default: "./results")-f, --format <formats...> Export format(s): json, ndjson, csv, xlsx, all (default: ["json","csv"])-u, --urls <urls...> Google Maps place URLs-p, --place-ids <placeIds...> Google Place IDs (ChIJ...)-m, --max-reviews <number> Maximum reviews to scrape per place-s, --sort <sort> Sort order: newest, mostRelevant, highestRating, lowestRating--origin <origin> Reviews origin: all, google--date-from <isoDate> ISO 8601 start date (only valid with newest sort)--date-to <isoDate> ISO 8601 end date (only valid with newest sort)-l, --language <lang> Host language (e.g. en, fr, de, es)--no-images Disable review image extraction--no-reviewer-details Gate personal reviewer data (anonymizes name, photo, IDs)--concurrency <number> Number of places to scrape concurrently (default: 2)--proxies <proxies...> List of residential proxy URLs-h, --help display help for command
Proxy Configuration
Residential proxies are recommended for scraping large volumes of reviews. Configure them either via the .env file or in your input JSON:
$cp .env.example .env
Edit .env:
# Comma-separated residential proxy URLsPROXY_URLS=http://user:pass@geo.iproyal.com:12321,http://user:pass@proxy2.com:12321# Browser settingsHEADLESS=trueLOG_LEVEL=info
Testing
The project includes an extensive test suite that tests schemas, relative date parsing, canonical URL resolution, and DOM extractors against realistic HTML fixtures without hitting live Google Maps servers:
# Run all unit testsnpm test# Run tests with coveragenpm run test:coverage
Fixtures Tested
tests/fixtures/review_card_standard.html: Complete standard review with scores and context.tests/fixtures/review_card_owner_response.html: Card containing owner reply text and date.tests/fixtures/review_card_with_images.html: Card with high-res photo gallery.tests/fixtures/review_card_translated.html: Card with Google machine translation and original text.tests/fixtures/review_card_advertisement.html: Card flagged with sponsored/ad badges.tests/fixtures/place_page_header.html: Place headline with score, count, category, and address.
Manual Integration Test
To run a live sanity test with Chromium against a real place (maxReviews: 20):
$RUN_INTEGRATION_TEST=true npm run test:manual
Docker Deployment
Build and run with Docker:
# Build the Docker imagedocker build -t google-maps-reviews-scraper .# Run with local volume mount for resultsdocker run --rm \-v $(pwd)/results:/usr/src/app/results \google-maps-reviews-scraper \--config input.example.json \--format json csv xlsx \--out ./results
GitHub Actions Scheduled Workflow
An automated workflow is provided at . It can be triggered on a weekly cron schedule (every Monday at 02:00 UTC) or manually via workflow_dispatch with artifact uploads of the generated reviews.