Google Maps Scraper Deduper | Merge Duplicates | Cross-Run
Pricing
from $1.00 / 1,000 record processeds
Google Maps Scraper Deduper | Merge Duplicates | Cross-Run
Merge duplicate places from Google Maps scraper runs — by placeId, phone, or fuzzy name+address — including duplicates from previous runs. Only get what's new, cut enrichment costs, and keep a full audit trail. Stateless: you hold the memory, we store nothing.
Pricing
from $1.00 / 1,000 record processeds
Rating
0.0
(0)
Developer
Brandon Mensing
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
Share
Google Maps Scraper Deduper
Merge duplicate places from Google Maps Scraper results — by placeId, phone, or fuzzy name+address — including duplicates from previous runs.
Google Maps scrapers structurally produce duplicates: overlapping search terms, the 5,000-review chunking rule, and re-runs of the same area all put the same place in your dataset more than once. Apify's own support guidance is to "post-process the scraped data to remove duplicates based on phone numbers or other criteria." This actor is that post-processing step.
Part of the Broomwagon family: deterministic tools that watch scraped data change. Same input, same output, every run — with a full audit trail.
Quick start: your first two runs
The whole product is one loop — run, keep the memory, run again, get only what's new. Try it in two minutes with no dataset needed:
Run 1. Paste this into Inline items and start the run:
[{"title":"Kopi Cafe","address":"5317 N Clark St, Chicago","phone":"(773) 989-5674"},{"title":"La Colombe","address":"5158 N Clark St, Chicago","phone":"(773) 293-6048"}]
You get both places back, plus — in the run's Storage → Key-value store — a record
called MEMORY. That's the actor's memory of what it has delivered to you, and
you hold it (we store nothing).
Run 2. Copy the whole MEMORY object into the Previous memory field, add a
third place to Inline items, and run again:
[{"title":"Kopi Cafe","address":"5317 N Clark St, Chicago","phone":"(773) 989-5674"},{"title":"La Colombe","address":"5158 N Clark St, Chicago","phone":"(773) 293-6048"},{"title":"Lost Larson","address":"5318 N Clark St, Chicago","phone":"(773) 944-0587"}]
The output contains only Lost Larson — the two places you already received are
dropped as dropped-prior-run. In real use, replace Inline items with your Google
Maps scraper run's Dataset, and either keep passing the memory forward (n8n, Make,
API) or set Memory key-value store once and let Apify Schedules chain runs
automatically.
Dedupe before you enrich
Enrichment events (email verification, contact lookup) typically cost 25x the scrape price. On a documented community run with 5,000 leads and ~700 unique places, enriching raw costs $500; enriching deduped costs $70. Dedupe pays for itself on the first run — and prevents double-sends to the same prospect afterward.
Cross-run dedupe (the part nothing else does)
In-scraper dedupe only works within a single run. This actor remembers across runs
WITHOUT storing your data: every run returns a compact aggregate (the memory, in
the MEMORY key-value record) containing a Bloom-filter ledger of every place key
you've been delivered. Pass it back next run and previously delivered places are
dropped — "only what's new since last time."
- Inline mode: pass last run's
MEMORYrecord asmemory(works in n8n, Make, API calls, and agent workflows). - Store mode: pass
memoryStoreId(a key-value store in your account) and the actor chains runs automatically — bare Apify Schedules just work.
Your data never lives in Broomwagon storage. The ledger is approximate by design: at
the default settings, up to 1% of genuinely new places may be flagged as already seen
(configurable via ledgerCapacity; the false-positive estimate ships in every report).
How matching works
- Exact:
placeId/cid/fid, then normalized phone ((718) 356-5168==+17183565168), then normalized name+address. - Fuzzy: places in the same city/postal block merge when Jaro-Winkler name
similarity clears
fuzzyThreshold(default 92) and address similarity clears threshold minus 5. "Verve Coffee" @ "2101 Pacific Ave" merges with "Verve Coffee Roasters" @ "2101 Pacific Avenue". - Cross-run: ledger hits from previous runs are dropped and counted separately.
Every decision lands in the AUDIT record: kept, merged-within-run (with match reason
and score), or dropped-prior-run.
Input
datasetId: a Google Maps scraper run's dataset (oritemsinline /fileUrl).memory/memoryStoreId: cross-run memory, as above.fuzzyThreshold(50-100, default 92),ledgerCapacity(default 100,000).
Output
- Dataset: your places, deduplicated, original records untouched.
OUTPUT: run report (kept / merged / dropped-prior-run counts, ledger stats).MEMORY: the aggregate to pass back next run.AUDIT: per-record decisions (first 1,000).
Pricing (pay-per-event)
| Event | What you pay for | Price |
|---|---|---|
record-processed | Per place record processed | $1.00 per 1,000 |
apify-actor-start | Run start, per GB of run memory | $0.005 |
Worked examples:
| Places in | Dedupe cost | Why it pays |
|---|---|---|
| 5,000 | $5.01 | A documented community run had 5,000 leads with only ~700 unique places. Enriching raw at $0.10/lead: $500. Enriching deduped: $70. $425 net saved on one run. |
| 50,000 | $50.01 | At a typical 30% duplicate rate, you avoid enriching 15,000 repeats: $1,500 saved before deliverability damage is counted. |
| 500,000 | $500.01 | Streaming, chunked, no browser, no proxy — cost scales linearly and nothing else does at this volume. |
Set a max charge on any run (Maximum cost per run in Console, or
ACTOR_MAX_TOTAL_CHARGE_USD via API) and the actor stops cleanly at your budget.
What the audit looks like on real data
From a real Chicago coffee-shop scrape (96 places in, 94 kept):
| Decision | Records | Reason |
|---|---|---|
| merged | Swedish American Museum → its Museum Café | exact phone match, names agree (same organization) |
| merged | Jewel-Osco → Jewel-Osco Deli | exact phone match, names agree (same store) |
| kept + flagged | Sweethearts (shares phone with Lonesome Rose) | shared line but dissimilar names — a venue that changed hands is NOT silently merged |
| kept | PrintWithMe Kiosk at Eli Tea Bar | fuzzy name match vetoed by conflicting phone — a kiosk inside a venue is not the venue |
Two independent scrapes of the same neighborhood minutes apart overlapped ~90%; with the memory passed between runs, the second run returned exactly the 10 genuinely new places and dropped all 87 repeats.
Integrations
- API:
POST https://api.apify.com/v2/acts/broomwagon~google-maps-deduper/runswith{"datasetId": "<scraper run's dataset>", "memory": <last MEMORY record>}in the JSON body. ReadMEMORYback from the run's key-value store and store it for the next call. - Apify Schedule (zero glue): create a key-value store once (Storage → Key-value
stores → Create), put its ID in
memoryStoreId, and schedule the actor after your scraper. Runs chain themselves; each delivers only new places. - n8n / Make: run the scraper node, pass its
defaultDatasetIdinto this actor'sdatasetId, and map the previous execution'sMEMORYoutput intomemory— or skip the mapping entirely by usingmemoryStoreIdas above. - MCP / AI agents: callable as a tool via the Apify MCP server; agents round-trip
the
MEMORYobject as tool context between calls.
The Broomwagon family
This actor is one of nine deterministic post-processing tools from Broomwagon: the layer that follows your scrapers and agents, cleaning and watching what they produce. Same input, same output, every time.
- Scraper Output Monitor — catch the day your scrape silently breaks.
- Only New Items — deliver only records you have never delivered before.
- Dataset Deduper — exact and fuzzy dedupe for any dataset, with an audit trail.
- CSV Doctor — repair broken CSV files, with a report of every fix.
- LLM Output Guard — validate LLM and agent JSON against your schema.
- PII Redactor — strip emails, phones, SSNs, cards, and addresses, deterministically.
- Record Linker — fuzzy join two datasets that share no key.
- AI Search Visibility Monitor — track your brand's share of voice in AI answers.