Weekly Google Maps local business leads to Google Sheets
Businesses of one type in an area with phone, website, rating and review count; every Monday only the places not in the sheet yet. Uses Google Maps Places Scraper.
Every Monday this workflow runs Google Maps Places Scraper (arcadium/google-maps-places) for a business type in an area, for example dentists in Austin, TX, and appends the places that are not in your Google Sheet yet: name, category, address, phone, website, rating, review count, Google Maps link and the date the place was first seen. Over time the sheet becomes a lead list that grows only by new businesses.
Schedule (Mon 07:00) → Apify: Run actor and get dataset → Google Sheets: Get rows → Code: new places only → Google Sheets: Append row
File: workflow.json (n8n workflow export format, with a sticky note that repeats the setup steps).
What you need
| Credential | Where | Placeholder in the file |
|---|---|---|
| Apify API token | Apify Console → Settings → API & Integrations | REPLACE_WITH_YOUR_APIFY_CREDENTIAL_ID (credential type Apify API) |
| Google Sheets | n8n's Google Sheets OAuth2 credential | REPLACE_WITH_YOUR_GOOGLE_SHEETS_CREDENTIAL_ID (two nodes) |
| A spreadsheet | Your Google Drive | REPLACE_WITH_YOUR_SPREADSHEET_ID in the sheet URL (two nodes) |
The Apify node is Apify's official n8n node, package @apify/n8n-nodes-apify (node version 2, operation Run actor and get dataset). On n8n Cloud search for "Apify" in the nodes panel. On self-hosted n8n: Settings → Community nodes → Install → @apify/n8n-nodes-apify.
Import
- Create a Google Sheet with a tab named Places and this header row (the nodes read and write columns by name):
placeId | cid | name | category | address | phone | website | rating | reviewCount | googleMapsUrl | searchTerm | firstSeen
- n8n → Workflows → Import from File →
workflow.json. - Search Google Maps: pick your Apify credential and edit the JSON input:
searchTerms: what you would type into Google Maps, one business type per entry (dentist,plumber,coffee shop).location: a city, district, region or postcode (Austin, TX,Schwabing, Munich). The area is searched map cell by map cell, so a city search is not limited to one result list.maxPlacesPerSearch: places per search term and run (100 in the template,0= every place found in the area).website:anyin the template;withkeeps only places that list a website,withoutonly places without one (a common lead filter for web agencies).skipClosed:trueleaves out places Google marks as closed.- Optional filters:
minRating(such as4),categoryFilter(such as["dentist"]),languageandcountry. - Read places in sheet and Append new places: pick your credential and paste your sheet URL in both.
- Execute once (the first run fills the sheet), check it, then Activate.
How "only new places" works
New places only compares each place with the rows already in the sheet on placeId, Google's stable id of a place, and on cid as a fallback. Places already in the sheet are dropped; new ones get today's date in firstSeen. The same place found by two search terms is written once.
Why a sheet lookup and not n8n's Remove Duplicates node with "Remove items processed in previous executions": that node keeps its history inside n8n. The history is lost when you import or duplicate the workflow, a place counts as seen even when the Sheets append afterwards fails, and it does not notice rows you delete or add by hand. With the lookup the sheet is the only memory: delete a row and the place comes back next week as new, add a place by hand and it is never added twice. Reading one sheet once a week is fast for tens of thousands of rows. If you prefer the Remove Duplicates node anyway, replace Read places in sheet with it (operation "Remove Items Processed in Previous Executions", value to dedupe on {{ $json.placeId }}) and leave the lookup part of New places only with an empty sheet list.
The append node writes values as they are (cellFormat: RAW), so the 19-digit cid stays exact and phone numbers are not turned into numbers.
Cost per run
Pay per event. Prices are the actor's Store pricing at the time of writing (Free plan; paid Apify plans pay less). The live price is on the Store page.
| Event | Price | In this template |
|---|---|---|
| Place | $0.002 ($2.00 per 1,000) | 2 search terms × 100 places = $0.40 per week |
| Coverage and error rows | free |
Plus Apify's small start event per run. The node caps a run at $0.50 (maxTotalChargeUsd). Every place the search returns is charged each week, also the ones already in your sheet: the dedupe keeps the sheet free of repeats, it does not lower the run cost. To spend less, lower maxPlacesPerSearch, run every two or four weeks, or narrow the search with minRating, website or categoryFilter.
What to expect
- The first run writes every place found, up to
maxPlacesPerSearchper search term. Later runs write only places that are new in the results; a week without new places writes nothing. phoneandwebsiteare what the business publishes on its Google listing; places without them have empty cells. Use the data lawfully: a sole trader's name and phone can be personal data under GDPR.googleMapsUrlopens the place on Google Maps (https://maps.google.com/?cid=...).ratingandreviewCountare written once, when the place is first seen; they are not updated later.- Each search also gives a free
coveragerow (map cells searched, why the search stopped) and an input that cannot be read gives a freeerrorrow; New places only skips both.