Google Maps to NocoDB, clean leads with Apify
Copying Google Maps leads into a spreadsheet sounds simple until you do it for an hour and realize half the rows are junk, duplicates, or missing the one thing you need: a real way to contact them.
This Maps lead cleaning automation hits sales teams first. But marketing agencies building local lists and operators doing their own outreach feel it too. The goal is straightforward: a deduplicated NocoDB table you can actually send campaigns from.
You’ll see how this workflow scrapes Google Maps with Apify, cleans and filters websites, enriches emails via Anymailfinder, and stores only net-new leads using placeId as the “no duplicates” key.
How This Automation Works
The full n8n workflow, from trigger to final output:
n8n Workflow Template: Google Maps to NocoDB, clean leads with Apify
flowchart LR
subgraph sg0["Form Input Flow"]
direction LR
n0["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/form.svg' width='40' height='40' /></div><br/>Form Input Trigger"]
n1["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/httprequest.dark.svg' width='40' height='40' /></div><br/>Map Search Scraper"]
n2["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/httprequest.dark.svg' width='40' height='40' /></div><br/>Retrieve Email Leads"]
n3@{ icon: "mdi:swap-vertical", form: "rounded", label: "Map Data Fields", pos: "b", h: 48 }
n4@{ icon: "mdi:swap-vertical", form: "rounded", label: "Assemble Email Details", pos: "b", h: 48 }
n5["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Sanitize Site Info"]
n6["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Exclude Existing Records"]
n7["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/merge.svg' width='40' height='40' /></div><br/>Combine Branches"]
n8@{ icon: "mdi:swap-horizontal", form: "rounded", label: "Filter Records by Domain", pos: "b", h: 48 }
n9@{ icon: "mdi:swap-vertical", form: "rounded", label: "Batch Through Results", pos: "b", h: 48 }
n10["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/nocodb.svg' width='40' height='40' /></div><br/>Fetch Stored Place IDs"]
n11@{ icon: "mdi:swap-horizontal", form: "rounded", label: "Check PlaceId Exists", pos: "b", h: 48 }
n12["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/nocodb.svg' width='40' height='40' /></div><br/>Insert Database Row"]
n11 --> n8
n11 --> n9
n7 --> n12
n5 --> n10
n2 --> n4
n12 --> n9
n3 --> n5
n4 --> n7
n9 --> n3
n6 --> n11
n0 --> n1
n1 --> n9
n8 --> n2
n8 --> n7
n10 --> n6
end
%% Styling
classDef trigger fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
classDef ai fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
classDef aiModel fill:#e8eaf6,stroke:#3f51b5,stroke-width:2px
classDef decision fill:#fff8e1,stroke:#f9a825,stroke-width:2px
classDef database fill:#fce4ec,stroke:#c2185b,stroke-width:2px
classDef api fill:#fff3e0,stroke:#e65100,stroke-width:2px
classDef code fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
classDef disabled stroke-dasharray: 5 5,opacity: 0.5
class n0 trigger
class n8,n11 decision
class n1,n2 api
class n5,n6 code
classDef customIcon fill:none,stroke:none
class n0,n1,n2,n5,n6,n7,n10,n12 customIcon
The Problem: Google Maps leads are messy (and repetitive)
Google Maps is great for finding local businesses. It’s not great as a lead database. You export or copy results, then realize “website” links are sometimes Facebook pages, sometimes tracking URLs, and sometimes missing altogether. Then duplicates creep in because the same business shows up across searches, categories, or nearby neighborhoods. And email enrichment? That becomes a separate chore, with a separate tool, and a separate set of errors you only notice after you’ve already sent outreach.
The friction compounds. Here’s where it usually breaks down in real life.
- You spend about 2 hours a week reformatting websites into usable domains for enrichment tools.
- The same place gets imported multiple times, so your “lead count” looks bigger than it is.
- Social links sneak in as “websites,” which means email finding fails or returns irrelevant results.
- Outreach lists go stale fast because there’s no reliable process to rerun searches without creating duplicates.
The Solution: Scrape, clean, dedupe, enrich, then store in NocoDB
This workflow turns Google Maps searching into a repeatable system you can run whenever you need fresh leads. It starts with a simple form in n8n where you enter a business type (like “plumber”), a city, a country code, and how many results you want. n8n sends that request to Apify to scrape matching Google Maps listings, then processes results in batches so you’re not trying to handle everything at once. As each lead comes through, the workflow cleans the website field to extract a real root domain, skips obvious non-business sites, and checks your NocoDB table for an existing placeId before saving anything. If a lead has a valid domain, it calls Anymailfinder to fetch professional emails and saves the enriched record to NocoDB.
The workflow starts with a form submission and an Apify scrape. It then cleans website data, prevents duplicates using placeId, and enriches emails only when there’s a usable domain. Finally, it inserts a structured row in NocoDB so your outreach list stays consistent over time.
What You Get: Automation vs. Results
| What This Workflow Automates | Results You’ll Get |
|---|---|
|
|
Example: What This Looks Like
Say you build a weekly list of 100 local businesses across 4 searches (different neighborhoods or categories). Manually, you might spend about 2 minutes per lead copying details, cleaning URLs, and checking if you already have them, which is roughly 3 hours. With this workflow, you submit the form once, let Apify pull the results, and n8n handles dedupe plus enrichment in the background. Realistically you’ll spend maybe 10 minutes reviewing the final table, not rebuilding it from scratch.
What You’ll Need
- n8n instance (try n8n Cloud free)
- Self-hosting option if you prefer (Hostinger works well)
- Apify for scraping Google Maps search results
- Anymailfinder to find and validate business emails
- NocoDB API token (get it from your NocoDB account settings)
Skill level: Intermediate. You’ll be connecting APIs, matching table fields, and checking a few filters to fit your niche.
Don’t want to set this up yourself? Talk to an automation expert (free 15-minute consultation).
How It Works
Form submission kicks everything off. You enter the business type, city, country code, and the number of results you want. It’s the fastest way to run one-off searches without building extra infrastructure.
Apify scrapes Google Maps. n8n sends your search to an Apify actor and receives a batch of listings back (name, phone, address, website, rating, categories, and the placeId that matters for dedupe).
The workflow cleans, filters, and enriches. Website URLs are sanitized to extract the root domain, and obvious non-domains get filtered out. If there’s a good domain, Anymailfinder is called to retrieve professional emails and validation details, which means you’re not guessing.
NocoDB becomes the source of truth. Before inserting a row, the workflow checks stored placeIds and skips anything you already have. New leads get stored with consistent fields, so you can sort, tag, and export for outreach whenever you’re ready.
You can easily modify the trigger to run on a schedule instead of a manual form based on your needs. See the full implementation guide below for customization options.
Step-by-Step Implementation Guide
Step 1: Configure the Form Trigger
Set up the incoming form so your workflow can collect search parameters for local business discovery.
- Add or open Form Input Trigger.
- Set Form Title to
Local Biz Search. - Under Form Fields, create the labels query (required), city (required), country code, and number.
- Save the form and note the generated form URL for testing.
Tip: Ensure field labels match exactly (query, city, country code, number) so downstream expressions resolve correctly.
Step 2: Connect the Scraping and Email Services
Configure the two HTTP services that scrape Google Places data and enrich records with email leads.
- Open Map Search Scraper and set URL to
https://api.apify.com/v2/acts/compass~crawler-google-places/run-sync-get-dataset-items. - Set Method to
POST, Send Body to true, and Body Content Type tojson. - In JSON Body, use the provided expression:
={ "city": "{{ $json.city }}", "countryCode": "{{ $json['country code'] }}", "locationQuery": "{{ $json.city }}", "maxCrawledPlacesPerSearch":{{ $json.number ? $json.number : 99999 }}, "searchStringsArray": [ "{{ $json.query }}" ], "skipClosedPlaces": true }. - Credential Required: Connect your httpQueryAuth credentials in Map Search Scraper.
- Open Retrieve Email Leads, set URL to
https://api.anymailfinder.com/v5.1/find-email/company, and keep Method asPOST. - In Body Parameters, set domain to
={{ $json.domain}}. - Credential Required: Connect your httpHeaderAuth credentials in Retrieve Email Leads.
⚠️ Common Pitfall: If Map Search Scraper returns no items, verify your Apify credentials and the searchStringsArray value is correctly populated from the form input.
Step 3: Map, Batch, and Sanitize Scraped Data
Prepare the scraped place data, clean website domains, and remove already-stored records before enrichment.
- Open Batch Through Results and keep default options to process items in batches.
- In Map Data Fields, confirm the field mappings use these expressions:
={{ $json.title }},={{ $json.address }},={{ $json.categories.join(",") }},={{ $json.scrapedAt.toDateTime().format('dd/LL/yyyy') }}, and the other listed assignments. - In Sanitize Site Info, keep the provided JavaScript to normalize website, derive domain, and remove social network hosts.
- In Fetch Stored Place IDs, set Operation to
getAll, Project ID to[YOUR_ID], and Table to[YOUR_ID]. - Credential Required: Connect your nocoDbApiToken credentials in Fetch Stored Place IDs.
- In Exclude Existing Records, keep the JavaScript that filters out known
placeIdvalues by reading$('Fetch Stored Place IDs').
Tip: The Sanitize Site Info node removes social domains like facebook.com and instagram.com. This helps avoid sending non-business domains to email enrichment.
Step 4: Filter by Domain and Enrich With Emails
Route only valid domains to email discovery, then merge enriched details back into the dataset.
- In Check PlaceId Exists, keep the condition
={{ $json.placeId }}with the exists operator. - In Filter Records by Domain, ensure the condition is
={{ $json.domain }}with the exists operator. - Open Retrieve Email Leads and confirm Continue On Fail is enabled (onError: continueRegularOutput) to avoid stopping the workflow if no email is found.
- In Assemble Email Details, confirm the enrichment mappings reference the filtered record, for example
={{ $('Filter Records by Domain').item.json.title }}and={{ $json.emails.join(",") }}. - Keep Combine Branches connected so both the enriched path and the “no email” path feed into the same output pipeline.
Step 5: Configure Database Output
Write the final lead records into NocoDB with a consistent schema.
- Open Insert Database Row and set Operation to
create. - Set Project ID to
[YOUR_ID]and Table to[YOUR_ID]. - Map fields using the provided expressions, for example email →
={{ $json.emails }}and categories →={{ $('Filter Records by Domain').item.json.categories }}. - Credential Required: Connect your nocoDbApiToken credentials in Insert Database Row.
- Confirm Insert Database Row connects back to Batch Through Results to continue processing remaining items.
Step 6: Test and Activate Your Workflow
Run a controlled test to confirm the end-to-end flow from form input to database insertion.
- Click Execute Workflow and submit the Form Input Trigger with sample values (e.g.,
query=plumber,city=Austin). - Verify that Map Search Scraper returns place items and that Sanitize Site Info produces a valid
domain. - Confirm Retrieve Email Leads runs for items that pass Filter Records by Domain.
- Check Insert Database Row to ensure new records appear in NocoDB with fields like
title,email, andplaceId. - Once successful, toggle Activate to enable production usage.
Common Gotchas
- Apify credentials can expire or the actor can require specific permissions. If things break, check your Apify token and actor run logs first.
- If you’re using Wait nodes or external rendering, processing times vary. Bump up the wait duration if downstream nodes fail on empty responses.
- Default prompts in AI nodes are generic. Add your brand voice early or you’ll be editing outputs forever.
Frequently Asked Questions
About 30 minutes if your API keys and NocoDB table are ready.
No. You’ll mostly connect accounts and match fields. The only “technical” part is verifying your NocoDB columns match what the workflow expects.
Yes. n8n has a free self-hosted option and a free trial on n8n Cloud. Cloud plans start at $20/month for higher volume. You’ll also need to factor in Apify, Anymailfinder, and NocoDB costs based on your usage.
Two options: n8n Cloud (managed, easiest setup) or self-hosting on a VPS. For self-hosting, Hostinger VPS is affordable and handles n8n well. Self-hosting gives you unlimited executions but requires basic server management.
Yes, and it’s a common upgrade. Replace the “Form Input Trigger” with a Schedule Trigger, then store your usual queries (business type, city, country code, result count) as fixed values or rows in a small control table. You can also duplicate the workflow for separate niches if you want cleaner reporting.
Usually it’s an invalid or expired Apify API token in n8n. Double-check the Apify actor you’re calling, then look at the actor run logs to see if the request payload is being rejected. It can also be rate limits or account-level restrictions if you’re running lots of scrapes back-to-back. Fix the token first, because honestly that’s the cause most of the time.
It depends on your n8n plan and your Apify run limits, but hundreds of leads per run is normal.
Often, yes, because this kind of lead scraping and deduping needs branching logic, batch processing, and a real “check before insert” flow. Zapier and Make can do parts of it, but you’ll usually end up paying more as volume grows, and the logic gets harder to keep readable. n8n is also friendlier for self-hosting, which matters when you want unlimited executions. If you only need a simple 2-step capture (no enrichment, no placeId dedupe), Zapier or Make is fine. Talk to an automation expert if you want help choosing.
Once this is running, you stop rebuilding lead lists from scratch. The workflow keeps your NocoDB table clean, current, and ready for outreach whenever you need it.
Need Help Setting This Up?
Our automation experts can build and customize this workflow for your specific needs. Free 15-minute consultation—no commitment required.