DEV Community

zo Aoo
zo Aoo

Posted on

Build a Local Lead Gen Machine: Scraping Google Maps with n8n (Reliably)

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:

  1. Outer Loop (Zip Codes): Reads a list of target zip codes from Google Sheets.
  2. 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',
        }
    };
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. n8n instance (Self-hosted or Cloud).
  2. Google Cloud Console: Enable "Places API (New)" and create OAuth credentials.
  3. 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)