Let's be real: "Lead Generation" is usually boring. But when you need to extract thousands of verified business contacts for a client (or your own SaaS) from a specific area, it becomes a fun engineering challenge.
I recently needed to build a pipeline to find local businesses in specific zip codes. I didn't just need names; I needed the "High-Value" metadata: phone numbers, website URLs, review counts, and exact coordinates.
Most Python scripts I tried hit the Google Maps API rate limits and died halfway through. So, I built a robust workflow in n8n that handles the heavy lifting, error handling, and data cleaning automatically.
Here represents the architecture of a production-ready scraper.
The Output: What are we actually getting?
Before we look at the nodes, let's look at the data. This workflow doesn't just dump raw JSON. It structures the data into a clean Google Sheet that your marketing team (or CRM) can actually use.
We are extracting specific fields via the Google Places API (New v1) using a FieldMask to save costs and bandwidth:
-
nationalPhoneNumber: Direct line for cold calling. -
websiteUri: Essential for scraping their site later or finding emails. -
rating&userRatingCount: This is the gold. (e.g., Filter for businesses with < 3 stars to sell them reputation management services). -
formattedAddress&location: For precise mapping.
The Logic: "Carpet Bombing" via Nested Loops
A simple search for "Plumbers in New York" misses 80% of the results. To get everyone, you need to be granular.
This workflow uses a Nested Loop strategy:
- Outer Loop (Zip Codes): Reads a list of target zip codes from Google Sheets.
- Inner Loop (Subcategories): Iterates through keywords (e.g., "Plumber," "HVAC," "Electrician") for each zip code.
This ensures comprehensive coverage. If you are targeting a whole state, you simply upload the zip code list, and the bot goes to work.
The "Secret Sauce": Handling Rate Limits (Exponential Backoff)
This is where most low-code automations fail. If you hit Google's API with 500 requests in a minute, you get a 429 Too Many Requests.
Instead of a simple "Retry," I implemented a custom Exponential Backoff algorithm using a Code node and a Wait node.
Here is the logic inside the workflow:
// From the "Exponential Backoff" node
const retryCount = $json["retryCount"] || 0;
const maxRetries = 5;
const initialDelay = 1; // Start with 1 second
if (retryCount < maxRetries) {
// Wait 1s, then 2s, then 4s, etc.
const currentDelayInSeconds = initialDelay * Math.pow(2, retryCount);
return {
json: {
retryCount: retryCount + 1,
waitTimeInSeconds: currentDelayInSeconds,
status: 'retrying',
}
};
}
If the API chokes, the workflow pauses intelligently, cools down, and tries again. It makes the difference between a bot that crashes after 10 minutes and one that runs all night.
Data Hygiene: No Duplicates Allowed
Google Maps often returns the same business for adjacent zip codes. To prevent polluting the database, I added a Remove Duplicates node.
It checks the unique place.id returned by Google. If we've seen this ID before in this run, it gets tossed. The result is a clean, unique list of leads ready for outreach.
How to use it
I've packaged this entire logic—including the OAuth setup, the loop logic, and the error handling code—into a single template.
You can download the JSON here:
👉 Automate Google Maps Lead Generation Workflow
Prerequisites:
- n8n instance (Self-hosted or Cloud).
- Google Cloud Console: Enable "Places API (New)" and create OAuth credentials.
- Google Sheets: To host your zip codes and receive the data.
Final Thoughts
This workflow turns the chaotic Google Maps data into structured SQL-ready rows. I've used this to generate datasets for real estate analysis and local B2B sales, and the cost per lead is a fraction of buying a list.
If you download it and find a way to optimize the FieldMask further, let me know in the comments!
Happy scraping! 🤖

Top comments (0)