From b5fc0a188ba6a21670ac07e8857589f84cde0d31 Mon Sep 17 00:00:00 2001 From: jcmpagel Date: Sun, 3 May 2026 13:04:46 +0200 Subject: [PATCH 1/2] Add review removal fields --- gmaps/entry.go | 110 ++++++++++++++++++++++++++++++++++++++ gmaps/entry_test.go | 127 ++++++++++++++++++++++++++++++++++++++++++++ gmaps/multiple.go | 1 + 3 files changed, 238 insertions(+) diff --git a/gmaps/entry.go b/gmaps/entry.go index 007f2270..cc8ba814 100644 --- a/gmaps/entry.go +++ b/gmaps/entry.go @@ -6,12 +6,19 @@ import ( "iter" "math" "net/url" + "regexp" "runtime/debug" "slices" "strconv" "strings" ) +var ( + removedReviewNumericRangeRe = regexp.MustCompile(`([0-9][0-9,.]*)[[:space:]]*(to|-|bis)[[:space:]]*([0-9][0-9,.]*)`) + removedReviewOpenEndedRe = regexp.MustCompile(`(über|ueber|mehr als|over|more than|above)[[:space:]]+([0-9][0-9,.]*)`) + removedReviewSingleRe = regexp.MustCompile(`(^|\b)(one|1|eine)[[:space:]]+(review|bewertung|rezension)`) +) + type Image struct { Title string `json:"title"` Image string `json:"image"` @@ -75,6 +82,8 @@ type Entry struct { ReviewCount int `json:"review_count"` ReviewRating float64 `json:"review_rating"` ReviewsPerRating map[int]int `json:"reviews_per_rating"` + RemovedReviewsMin int `json:"removed_reviews_min"` + RemovedReviewsMax int `json:"removed_reviews_max"` Latitude float64 `json:"latitude"` Longtitude float64 `json:"longtitude"` Status string `json:"status"` @@ -171,6 +180,13 @@ func (e *Entry) CsvHeaders() []string { "review_count", "review_rating", "reviews_per_rating", + "reviews_1_star", + "reviews_2_star", + "reviews_3_star", + "reviews_4_star", + "reviews_5_star", + "removed_reviews_min", + "removed_reviews_max", "latitude", "longitude", "cid", @@ -210,6 +226,13 @@ func (e *Entry) CsvRow() []string { stringify(e.ReviewCount), stringify(e.ReviewRating), stringify(e.ReviewsPerRating), + stringify(e.ReviewsPerRating[1]), + stringify(e.ReviewsPerRating[2]), + stringify(e.ReviewsPerRating[3]), + stringify(e.ReviewsPerRating[4]), + stringify(e.ReviewsPerRating[5]), + stringify(e.RemovedReviewsMin), + stringify(e.RemovedReviewsMax), stringify(e.Latitude), stringify(e.Longtitude), e.Cid, @@ -434,6 +457,7 @@ func EntryFromJSON(raw []byte, reviewCountOnly ...bool) (entry Entry, err error) 4: int(getNthElementAndCast[float64](darray, 175, 3, 3)), 5: int(getNthElementAndCast[float64](darray, 175, 3, 4)), } + entry.RemovedReviewsMin, entry.RemovedReviewsMax = getRemovedReviewsRange(darray) // Parse inline reviews from the page data reviewsI := getNthElementAndCast[[]any](darray, 175, 9, 0, 0) @@ -452,6 +476,92 @@ func EntryFromJSON(raw []byte, reviewCountOnly ...bool) (entry Entry, err error) return entry, nil } +func getRemovedReviewsRange(darray []any) (int, int) { + noticesI := getNthElementAndCast[[]any](darray, 241, 0) + if len(noticesI) == 0 { + return 0, 0 + } + + for i := range noticesI { + noticeI := getNthElementAndCast[[]any](noticesI, i) + if int(getNthElementAndCast[float64](noticeI, 0)) != 3 { + continue + } + + noticeBody := getNthElementAndCast[[]any](noticeI, 1) + for _, textIndex := range []int{0, 3} { + minReviews, maxReviews := parseRemovedReviewsRange(getNthElementAndCast[string](noticeBody, textIndex)) + if minReviews != 0 || maxReviews != 0 { + return minReviews, maxReviews + } + } + } + + return 0, 0 +} + +func parseRemovedReviewsRange(text string) (int, int) { + lower := strings.ToLower(text) + if matches := removedReviewNumericRangeRe.FindStringSubmatch(lower); len(matches) == 4 { + minReviews, minOK := parseReviewRangeNumber(matches[1]) + maxReviews, maxOK := parseReviewRangeNumber(matches[3]) + if minOK && maxOK { + return minReviews, maxReviews + } + } + + if matches := removedReviewOpenEndedRe.FindStringSubmatch(lower); len(matches) == 3 { + minReviews, ok := parseReviewRangeNumber(matches[2]) + if ok { + return minReviews + 1, 0 + } + } + + if removedReviewSingleRe.MatchString(lower) { + return 1, 1 + } + + fields := strings.Fields(lower) + for i := 0; i+2 < len(fields); i++ { + if fields[i+1] != "to" { + continue + } + + minReviews, minOK := parseReviewRangeNumber(fields[i]) + maxReviews, maxOK := parseReviewRangeNumber(fields[i+2]) + if minOK && maxOK { + return minReviews, maxReviews + } + } + + return 0, 0 +} + +func parseReviewRangeNumber(s string) (int, bool) { + s = strings.Trim(s, " ,.;:") + s = strings.NewReplacer(",", "", ".", "").Replace(s) + + if n, err := strconv.Atoi(s); err == nil { + return n, true + } + + words := map[string]int{ + "one": 1, + "two": 2, + "three": 3, + "four": 4, + "five": 5, + "six": 6, + "seven": 7, + "eight": 8, + "nine": 9, + "ten": 10, + } + n, ok := words[s] + + return n, ok +} + func parseReviews(reviewsI []any) []Review { ans := make([]Review, 0, len(reviewsI)) diff --git a/gmaps/entry_test.go b/gmaps/entry_test.go index f709b747..a2dced02 100644 --- a/gmaps/entry_test.go +++ b/gmaps/entry_test.go @@ -1,6 +1,7 @@ package gmaps_test import ( + "encoding/json" "fmt" "os" "testing" @@ -191,6 +192,132 @@ func Test_EntryFromJSON2(t *testing.T) { } } +func Test_EntryFromJSONReviewRemovalNotices(t *testing.T) { + jd := make([]any, 7) + darray := make([]any, 242) + darray[11] = "TÜRKITCH Köfte & Kebap" + darray[13] = []any{"Turkish restaurant"} + darray[241] = []any{ + []any{ + []any{ + float64(3), + []any{ + "201 to 250 reviews were removed in the past year due to defamation complaints under German law.", + "Reviews have been removed from this place", + []any{ + []any{"https://support.google.com/contributionpolicy/answer/16997273"}, + "More about defamation removal notices in Germany", + }, + "201 to 250 reviews removed due to defamation complaints.", + float64(1), + nil, + float64(2), + "en-GB", + }, + }, + []any{ + float64(3), + []any{ + "Six to ten reviews were removed in the past year due to defamation complaints under German law.", + "Reviews have been removed from this place", + []any{ + []any{"https://support.google.com/contributionpolicy/answer/16997273"}, + "More about defamation removal notices in Germany", + }, + "Six to ten reviews removed due to defamation complaints.", + float64(1), + nil, + float64(2), + "en-GB", + }, + }, + }, + } + jd[6] = darray + + raw, err := json.Marshal(jd) + require.NoError(t, err) + + entry, err := gmaps.EntryFromJSON(raw) + require.NoError(t, err) + require.Equal(t, 201, entry.RemovedReviewsMin) + require.Equal(t, 250, entry.RemovedReviewsMax) + + darray[241] = []any{ + []any{ + []any{ + float64(3), + []any{ + "Six to ten reviews were removed in the past year due to defamation complaints under German law.", + }, + }, + }, + } + + raw, err = json.Marshal(jd) + require.NoError(t, err) + + entry, err = gmaps.EntryFromJSON(raw) + require.NoError(t, err) + require.Equal(t, 6, entry.RemovedReviewsMin) + require.Equal(t, 10, entry.RemovedReviewsMax) + + darray[241] = []any{ + []any{ + []any{ + float64(3), + []any{ + "Unseren Richtlinien sind Beiträge zu dieser Art von Ort nicht zulässig.", + "Das Posten ist derzeit deaktiviert", + }, + }, + []any{ + float64(3), + []any{ + "Im vergangenen Jahr wurden über 250 Bewertungen aufgrund einer Beschwerde wegen Diffamierung nach deutschem Recht entfernt.", + "Es wurden Rezensionen zu diesem Ort entfernt", + []any{ + []any{"https://support.google.com/contributionpolicy/answer/16997273"}, + "Weitere Informationen zu Hinweisen zur Entfernung von Bewertungen wegen Diffamierungen in Deutschland", + }, + "Über 250 Bewertungen aufgrund von Beschwerden wegen Diffamierung entfernt.", + float64(1), + nil, + float64(2), + "de", + }, + }, + }, + } + + raw, err = json.Marshal(jd) + require.NoError(t, err) + + entry, err = gmaps.EntryFromJSON(raw) + require.NoError(t, err) + require.Equal(t, 251, entry.RemovedReviewsMin) + require.Equal(t, 0, entry.RemovedReviewsMax) + + darray[241] = []any{ + []any{ + []any{ + float64(3), + []any{ + "One review was removed in the past year due to a defamation complaint under German law.", + }, + }, + }, + } + + raw, err = json.Marshal(jd) + require.NoError(t, err) + + entry, err = gmaps.EntryFromJSON(raw) + require.NoError(t, err) + require.Equal(t, 1, entry.RemovedReviewsMin) + require.Equal(t, 1, entry.RemovedReviewsMax) +} + func Test_EntryFromJSONRaw2(t *testing.T) { raw, err := os.ReadFile("../testdata/raw2.json") diff --git a/gmaps/multiple.go b/gmaps/multiple.go index 46b307b7..5b490451 100644 --- a/gmaps/multiple.go +++ b/gmaps/multiple.go @@ -47,6 +47,7 @@ func ParseSearchResults(raw []byte) ([]*Entry, error) { entry.ReviewRating = getNthElementAndCast[float64](business, 4, 7) entry.ReviewCount = int(getNthElementAndCast[float64](business, 4, 8)) + entry.RemovedReviewsMin, entry.RemovedReviewsMax = getRemovedReviewsRange(business) fullAddress := getNthElementAndCast[[]any](business, 2) From 9440de3a53d5695b30b95732f18198b93722945b Mon Sep 17 00:00:00 2001 From: jcmpagel Date: Sun, 3 May 2026 13:11:02 +0200 Subject: [PATCH 2/2] Update README for review removal fields --- README.md | 285 ++++++++---------------------------------------------- 1 file changed, 38 insertions(+), 247 deletions(-) diff --git a/README.md b/README.md index 05e1e86e..5732652f 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,5 @@ # Google Maps Scraper -

- GitHub Stars - GitHub Forks - Tweet -

- [![Build Status](https://github.com/gosom/google-maps-scraper/actions/workflows/build.yml/badge.svg)](https://github.com/gosom/google-maps-scraper/actions/workflows/build.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/gosom/google-maps-scraper)](https://goreportcard.com/report/github.com/gosom/google-maps-scraper) [![GoDoc](https://godoc.org/github.com/gosom/google-maps-scraper?status.svg)](https://godoc.org/github.com/gosom/google-maps-scraper) @@ -27,100 +21,6 @@ Use it for lead generation, local business research, sales prospecting, data enr ![Example GIF](img/example.gif) -If this project is useful to you, a GitHub star helps others discover it. Sponsorships help fund maintenance and new work. - ---- - -## Sponsored By - -

This project is made possible by our amazing sponsors

- -### [Scrap.io](https://scrap.io?utm_medium=ads&utm_source=github_gosom_gmap_scraper) - Extract ALL Google Maps listings at country-scale - -[![Scrap.io - Extract ALL Google Maps Listings](./img/premium_scrap_io.png)](https://scrap.io?utm_medium=ads&utm_source=github_gosom_gmap_scraper) - -No keywords needed. No limits. Export millions of businesses in 2 clicks. [**Try it free →**](https://scrap.io?utm_medium=ads&utm_source=github_gosom_gmap_scraper) - ---- - -### [G Maps Extractor](https://gmapsextractor.com?utm_source=github&utm_medium=banner&utm_campaign=gosom) - No-code Google Maps scraper - -[![G Maps Extractor](./img/gmaps-extractor-banner.png)](https://gmapsextractor.com?utm_source=github&utm_medium=banner&utm_campaign=gosom) - -Chrome extension that extracts emails, social profiles, phone numbers, reviews & more. [**Get 1,000 free leads →**](https://gmapsextractor.com?utm_source=github&utm_medium=banner&utm_campaign=gosom) - ---- - -### [SerpApi](https://serpapi.com/?utm_source=google-maps-scraper) - Google Maps API and 30+ search engine APIs - -[![SerpApi](./img/SerpApi-banner.png)](https://serpapi.com/?utm_source=google-maps-scraper) - -Fast, reliable, and scalable. Used by Fortune 500 companies. [**View all APIs →**](https://serpapi.com/search-api) - ---- - -### [SearchApi](https://www.searchapi.io/google-maps?via=gosom) - Google Maps API for SERP scraping - -[![SearchApi](https://www.searchapi.io/press/v1/svg/searchapi_logo_black_h.svg)](https://www.searchapi.io/google-maps?via=gosom) - -Real-time Google Maps data with a simple integration. [**Explore the API →**](https://www.searchapi.io/google-maps?via=gosom) - ---- - -### [Evomi](https://evomi.com?utm_source=github&utm_medium=banner&utm_campaign=gosom-maps) - Swiss quality proxies for scraping - -[![Evomi](https://my.evomi.com/images/brand/cta.png)](https://evomi.com?utm_source=github&utm_medium=banner&utm_campaign=gosom-maps) - -Swiss quality proxies from $0.49/GB across 150+ countries, with 24/7 support and 99.9% uptime. [**Visit Evomi →**](https://evomi.com?utm_source=github&utm_medium=banner&utm_campaign=gosom-maps) - ---- - -### [HasData](https://hasdata.com/scrapers/google-maps?utm_source=github&utm_medium=sponsorship&utm_campaign=gosom) - No-code Google Maps Scraper & Email Extraction - -[![HasData Google Maps Scraper](./img/hd-gm-banner.png)](https://hasdata.com/scrapers/google-maps?utm_source=github&utm_medium=sponsorship&utm_campaign=gosom) - -Extract business leads, emails, addresses, phones, reviews and more. [**Get 1,000 free credits →**](https://hasdata.com/scrapers/google-maps?utm_source=github&utm_medium=sponsorship&utm_campaign=gosom) - ---- - -### [RapidProxy](https://www.rapidproxy.io/?ref=gosom) - High-Performance Proxy Solution - -[![RapidProxy](./img/rapidproxy-banner.png)](https://www.rapidproxy.io/?ref=gosom) - -Unlock global access with consistent, high-speed connections from $0.65/GB, 90M+ real residential IPs worldwide, and traffic that never expires. [**Try it free →**](https://www.rapidproxy.io/?ref=gosom) - ---- - -### [LeadsDB](https://getleadsdb.com/) - Your Central Database for Business Leads - -[![LeadsDB](./img/leadsdb-banner.png)](https://getleadsdb.com/) - -Push leads via API or AI agent, remove duplicates automatically, and export when ready. [**Start free →**](https://getleadsdb.com/) - ---- - -### [Webshare](https://www.webshare.io/?referral_code=0q3l81eet8mp) - Premium proxies for scraping at scale - -[![Webshare](./img/webshare-banner.png)](https://www.webshare.io/?referral_code=0q3l81eet8mp) - -The most affordable premium proxies across 195 countries & 80+ million IPs, plus a FREE plan for new users. [Learn more](webshare.md) - ---- - -### [Legion Proxy](https://legionproxy.io/?utm_source=github&utm_campaign=gmaps) - Residential proxies for Google Maps Scraper - -74M+ real residential IPs, HTTP/3 & UDP support, SOCKS5-ready, 195+ countries. Works out of the box with Google Maps Scraper. [**Get 1GB free →**](https://legionproxy.io/?utm_source=github&utm_campaign=gmaps) - -[![Legion Proxy](./img/legion-proxy.png)](https://legionproxy.io/?utm_source=github&utm_campaign=gmaps) - ---- - -

- View all sponsors | Become a sponsor -

- ---- - ## Why Use This Scraper? | | | @@ -128,28 +28,13 @@ The most affordable premium proxies across 195 countries & 80+ million IPs, plus | **Completely Free & Open Source** | MIT licensed, no hidden costs or usage limits | | **Multiple Interfaces** | CLI, Web UI, REST API - use what fits your workflow | | **High Performance** | ~120 places/minute with optimized concurrency | -| **33+ Data Points** | Business details, reviews, emails, coordinates, and more | +| **40+ Output Fields** | Business details, reviews, review-removal notices, emails, coordinates, and more | | **Production Ready** | Scale from a single machine to Kubernetes clusters | -| **Flexible Output** | CSV, JSON, PostgreSQL, S3, LeadsDB, or custom plugins | +| **Flexible Output** | CSV, JSON, PostgreSQL, S3, or custom plugins | | **Proxy Support** | Built-in SOCKS5/HTTP/HTTPS proxy rotation | --- -## What's Next After Scraping? - -Once you've collected your data, you'll need to manage, deduplicate, and work with your leads. **[LeadsDB](https://getleadsdb.com/)** is a companion tool designed exactly for this: - -- **Automatic Deduplication** - Import from multiple scrapes without worrying about duplicates -- **AI Agent Ready** - Query and manage leads with natural language via MCP -- **Advanced Filtering** - Combine filters with AND/OR logic on any field -- **Export Anywhere** - CSV, JSON, or use the REST API - -The scraper has [built-in LeadsDB integration](#export-to-leadsdb) - just add your API key and leads flow directly into your database. - -**[Start free with 500 leads](https://getleadsdb.com/)** - ---- - ## Table of Contents - [Quick Start](#quick-start) @@ -159,7 +44,6 @@ The scraper has [built-in LeadsDB integration](#export-to-leadsdb) - just add yo - [SaaS Edition](#saas-edition) - [AI Agent Skill](#ai-agent-skill) - [Recipes](docs/recipes.md) -- [Proxy Sponsors](docs/proxies.md) - [Installation](#installation) - [Features](#features) - [Extracted Data Points](#extracted-data-points) @@ -168,13 +52,11 @@ The scraper has [built-in LeadsDB integration](#export-to-leadsdb) - just add yo - [Using Proxies](#using-proxies) - [Email Extraction](#email-extraction) - [Fast Mode](#fast-mode) -- [Export to LeadsDB](#export-to-leadsdb) - [Advanced Usage](#advanced-usage) - [PostgreSQL Database Provider](#postgresql-database-provider) - [Kubernetes Deployment](#kubernetes-deployment) - [Custom Writer Plugins](#custom-writer-plugins) - [Performance](#performance) -- [Support the Project](#support-the-project) - [Community](#community) - [Contributing](#contributing) - [License](#license) @@ -211,19 +93,6 @@ Useful options: `-c` controls how many scrape jobs run in parallel. Higher concurrency can finish large input files faster, but it also uses more CPU/RAM and can increase blocking or failures, especially without proxies. Start with the default for a first run. For larger jobs on a capable machine, try `-c 4`, `-c 8`, or `-c 16` and measure the result. -**Want to skip CSV files?** Send leads directly to [LeadsDB](https://getleadsdb.com/): - -```bash -docker run \ - -v gmaps-playwright-cache:/opt \ - -v "$PWD/example-queries.txt:/queries.txt:ro" \ - gosom/google-maps-scraper \ - -input /queries.txt \ - -depth 1 \ - -leadsdb-api-key "your-api-key" \ - -exit-on-inactivity 3m -``` - ### Web UI Start the web interface with a single command: @@ -271,7 +140,7 @@ curl -fsSL https://raw.githubusercontent.com/gosom/google-maps-scraper/main/PROV See [SaaS documentation](docs/saas.md) for deployment and operations details. There is also a [5-minute deployment walkthrough](https://gosom.dev/deploy-your-own-maps-scraping-api-in-5-minutes/) and a [YouTube video walkthrough](https://www.youtube.com/watch?v=STG9mZw_nac). -More examples are available in [Recipes](docs/recipes.md). If you need proxies for larger jobs, see [Proxy Sponsors](docs/proxies.md). +More examples are available in [Recipes](docs/recipes.md). --- @@ -326,9 +195,9 @@ go build | Feature | Description | |---------|-------------| -| **33+ Data Points** | Business name, address, phone, website, reviews, coordinates, and more | +| **40+ Output Fields** | Business name, address, phone, website, reviews, review-removal notices, coordinates, and more | | **Email Extraction** | Optional crawling of business websites for email addresses | -| **Multiple Output Formats** | CSV, JSON, PostgreSQL, S3, LeadsDB, or custom plugins | +| **Multiple Output Formats** | CSV, JSON, PostgreSQL, S3, or custom plugins | | **Proxy Support** | SOCKS5, HTTP, HTTPS with authentication | | **Scalable Architecture** | Single machine to Kubernetes cluster | | **REST API** | Programmatic control for automation | @@ -341,7 +210,7 @@ go build ## Extracted Data Points
-Click to expand all 33 data points +Click to expand available output fields | # | Field | Description | |---|-------|-------------| @@ -358,30 +227,41 @@ go build | 11 | `review_count` | Total number of reviews | | 12 | `review_rating` | Average star rating | | 13 | `reviews_per_rating` | Breakdown by star rating | -| 14 | `latitude` | GPS latitude | -| 15 | `longitude` | GPS longitude | -| 16 | `cid` | Google's unique Customer ID | -| 17 | `status` | Business status (open/closed/temporary) | -| 18 | `descriptions` | Business description | -| 19 | `reviews_link` | Direct link to reviews | -| 20 | `thumbnail` | Thumbnail image URL | -| 21 | `timezone` | Business timezone | -| 22 | `price_range` | Price level ($, $$, $$$) | -| 23 | `data_id` | Internal Google Maps identifier | -| 24 | `images` | Associated image URLs | -| 25 | `reservations` | Reservation booking link | -| 26 | `order_online` | Online ordering link | -| 27 | `menu` | Menu link | -| 28 | `owner` | Owner-claimed status | -| 29 | `complete_address` | Full formatted address | -| 30 | `about` | Additional business info | -| 31 | `user_reviews` | Customer reviews (text, rating, timestamp) | -| 32 | `emails` | Extracted email addresses (requires `-email` flag) | -| 33 | `user_reviews_extended` | Extended reviews up to ~300 (requires `-extra-reviews`) | -| 34 | `place_id` | Google's unique place id | +| 14 | `reviews_1_star` | One-star review count, flattened for CSV output | +| 15 | `reviews_2_star` | Two-star review count, flattened for CSV output | +| 16 | `reviews_3_star` | Three-star review count, flattened for CSV output | +| 17 | `reviews_4_star` | Four-star review count, flattened for CSV output | +| 18 | `reviews_5_star` | Five-star review count, flattened for CSV output | +| 19 | `removed_reviews_min` | Lower bound of reviews Google reports as removed under a review-removal notice | +| 20 | `removed_reviews_max` | Upper bound of reviews Google reports as removed; `0` means no upper bound was present | +| 21 | `latitude` | GPS latitude | +| 22 | `longitude` | GPS longitude | +| 23 | `cid` | Google's unique Customer ID | +| 24 | `status` | Business status (open/closed/temporary) | +| 25 | `descriptions` | Business description | +| 26 | `reviews_link` | Direct link to reviews | +| 27 | `thumbnail` | Thumbnail image URL | +| 28 | `timezone` | Business timezone | +| 29 | `price_range` | Price level ($, $$, $$$) | +| 30 | `data_id` | Internal Google Maps identifier | +| 31 | `images` | Associated image URLs | +| 32 | `reservations` | Reservation booking link | +| 33 | `order_online` | Online ordering link | +| 34 | `menu` | Menu link | +| 35 | `owner` | Owner-claimed status | +| 36 | `complete_address` | Full formatted address | +| 37 | `about` | Additional business info | +| 38 | `user_reviews` | Customer reviews (text, rating, timestamp) | +| 39 | `emails` | Extracted email addresses (requires `-email` flag) | +| 40 | `user_reviews_extended` | Extended reviews up to ~300 (requires `-extra-reviews`) | +| 41 | `place_id` | Google's unique place id |
+The `removed_reviews_min` and `removed_reviews_max` fields are parsed from Google Maps review-removal notices when Google includes that notice in the Maps payload. For example, a notice such as `201 to 250 reviews were removed` is exported as `removed_reviews_min=201` and `removed_reviews_max=250`; an open-ended notice such as `over 250` is exported as `removed_reviews_min=251` and `removed_reviews_max=0`. When no notice is present, both fields remain `0`. + +JSON output includes the full `reviews_per_rating` map plus `removed_reviews_min` and `removed_reviews_max`. CSV output also includes the flattened `reviews_1_star` through `reviews_5_star` columns for easier spreadsheet analysis. + **Custom Input IDs:** Define your own IDs in the input file: ``` Matsuhisa Athens #!#MyCustomID @@ -428,9 +308,6 @@ Proxy: -proxies string Comma-separated proxy list Format: protocol://user:pass@host:port -Export: - -leadsdb-api-key Export directly to LeadsDB (get key at getleadsdb.com) - Advanced: -exit-on-inactivity duration Exit after inactivity (e.g., '5m') -fast-mode Quick mode with reduced data @@ -458,8 +335,6 @@ For larger scraping jobs, proxies help avoid rate limiting. Here's how to config **Supported protocols:** `socks5`, `socks5h`, `http`, `https` -Current proxy sponsors are listed in [Proxy Sponsors](docs/proxies.md). Using those links helps fund project maintenance. - ### Email Extraction Email extraction is **disabled by default**. When enabled, the scraper visits each business website to find email addresses. @@ -515,62 +390,6 @@ Notes: --- -## Export to LeadsDB - -Skip the CSV files and send leads directly to a managed database. [LeadsDB](https://getleadsdb.com/) handles deduplication, filtering, and provides an API for your applications. - -**Using Docker:** -```bash -docker run \ - -v gmaps-playwright-cache:/opt \ - -v "$PWD/example-queries.txt:/queries.txt:ro" \ - gosom/google-maps-scraper \ - -input /queries.txt \ - -depth 1 \ - -leadsdb-api-key "your-api-key" \ - -exit-on-inactivity 3m -``` - -**Using binary:** -```bash -./google-maps-scraper \ - -input queries.txt \ - -leadsdb-api-key "your-api-key" \ - -exit-on-inactivity 3m -``` - -Or via environment variable: -```bash -export LEADSDB_API_KEY="your-api-key" -./google-maps-scraper -input queries.txt -exit-on-inactivity 3m -``` - -
-Field Mapping - -| Google Maps | LeadsDB | -|-------------|---------| -| Title | Name | -| Category | Category | -| Categories | Tags | -| Phone | Phone | -| Website | Website | -| Address | Address, City, State, Country, PostalCode | -| Latitude/Longitude | Coordinates | -| Review Rating | Rating | -| Review Count | ReviewCount | -| Emails | Email | -| Thumbnail | LogoURL | -| CID | SourceID | - -Additional fields (Google Maps link, plus code, price range, etc.) are stored as custom attributes. - -
- -Get your API key at [getleadsdb.com/settings](https://getleadsdb.com/settings) after signing up. - ---- - ## Advanced Usage ### PostgreSQL Database Provider @@ -665,20 +484,6 @@ Anonymous usage statistics are collected for improvement purposes. Opt out: export DISABLE_TELEMETRY=1 ``` ---- - -## Support the Project - -This project is free and open source. Stars, sponsorships, and sponsor referrals help fund maintenance. - -- Star the repository: [github.com/gosom/google-maps-scraper](https://github.com/gosom/google-maps-scraper) -- Sponsor development: [GitHub Sponsors](https://github.com/sponsors/gosom) -- Need proxies? See [Proxy Sponsors](docs/proxies.md) -- Deploying the self-hosted platform? See [SaaS deployment options](docs/saas.md) -- Managing scraped leads? See [LeadsDB](https://getleadsdb.com/) - ---- - ## Community [![Discord](https://img.shields.io/badge/Discord-Join%20Our%20Server-7289DA?logo=discord&logoColor=white&style=for-the-badge)](https://discord.gg/fpaAVhNCCu) @@ -718,20 +523,6 @@ See [AGENTS.md](AGENTS.md) for development guidelines. This project is licensed under the [MIT License](LICENSE). ---- - -## Star History - - - - - - Star History Chart - - - ---- - ## Legal Notice Please use this scraper responsibly and in accordance with applicable laws and regulations. Unauthorized scraping may violate terms of service.