Google Sheets + ScrapingBee: enriched leads, ready
Your lead sheet looks “full,” but half the rows are dead ends. Wrong websites, missing contact pages, generic directories, and emails that bounce the moment you hit send.
This is the kind of mess that slows down marketing ops first. But agency owners building prospect lists and sales teams doing weekly outreach feel it too. With Sheets lead enrichment automation, you turn a basic “business type + city + state” row into a usable company site and real emails, without spending your afternoon in Google.
This workflow pulls leads from Google Sheets, searches with Serper.dev, scrapes likely pages via ScrapingBee, extracts email addresses, and writes everything back to your sheet with clear status updates.
How This Automation Works
See how this solves the problem:
n8n Workflow Template: Google Sheets + ScrapingBee: enriched leads, ready
flowchart LR
subgraph sg0["Google Sheets Flow"]
direction LR
n0@{ icon: "mdi:swap-vertical", form: "rounded", label: "Loop Over Items", pos: "b", h: 48 }
n1@{ icon: "mdi:swap-horizontal", form: "rounded", label: "If", pos: "b", h: 48 }
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/>Scraping Bee"]
n3["<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/>Website Options"]
n4@{ icon: "mdi:play-circle", form: "rounded", label: "Google Sheets Trigger", pos: "b", h: 48 }
n5@{ icon: "mdi:swap-horizontal", form: "rounded", label: "If1", pos: "b", h: 48 }
n6@{ icon: "mdi:swap-horizontal", form: "rounded", label: "If2", pos: "b", h: 48 }
n7@{ icon: "mdi:cog", form: "rounded", label: "Wait", pos: "b", h: 48 }
n8@{ icon: "mdi:swap-horizontal", form: "rounded", label: "If3", pos: "b", h: 48 }
n9@{ icon: "mdi:swap-vertical", form: "rounded", label: "Set Information", 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/httprequest.dark.svg' width='40' height='40' /></div><br/>Search Companies (Serper.dev)"]
n11["<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/>Extract Company & Website"]
n12@{ icon: "mdi:database", form: "rounded", label: "Update Running Status", pos: "b", h: 48 }
n13@{ icon: "mdi:database", form: "rounded", label: "Update Missing Information S..", pos: "b", h: 48 }
n14@{ icon: "mdi:database", form: "rounded", label: "Add research Results", pos: "b", h: 48 }
n15["<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/>Test pages"]
n16@{ icon: "mdi:database", form: "rounded", label: "Update Finished Status", pos: "b", h: 48 }
n17["<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/>Email Extractor"]
n18@{ icon: "mdi:database", form: "rounded", label: "Get Emails", pos: "b", h: 48 }
n19@{ icon: "mdi:database", form: "rounded", label: "Add Emails", pos: "b", h: 48 }
n1 --> n2
n1 --> n0
n5 --> n17
n5 --> n0
n6 --> n18
n6 --> n0
n8 --> n12
n8 --> n13
n7 --> n0
n19 --> n7
n18 --> n19
n15 --> n1
n2 --> n5
n17 --> n6
n0 --> n16
n0 --> n15
n9 --> n10
n3 --> n0
n14 --> n3
n4 --> n8
n12 --> n9
n11 --> n14
n10 --> n11
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 n4 trigger
class n1,n5,n6,n8 decision
class n12,n13,n14,n16,n18,n19 database
class n2,n10,n15 api
class n3,n11,n17 code
classDef customIcon fill:none,stroke:none
class n2,n3,n10,n11,n15,n17 customIcon
The Challenge: Turning “leads” into contacts you can actually email
Building a lead list is easy. Building one that’s outreach-ready is where the time disappears. You start with a few columns in Google Sheets, then you open a new tab for every row: search the company, guess which site is real, click around for a contact page, and copy-paste anything that looks like an email. Now multiply that by 200 rows. Mistakes creep in fast, and frankly it’s mentally exhausting because every business formats their site differently and directories keep showing up in search results.
It adds up fast. Here’s where it breaks down in real life.
- You waste about 5–10 minutes per lead just figuring out the “real” website versus listings and aggregator pages.
- People copy the wrong URL into the sheet, and that one bad field poisons your entire outreach sequence.
- Email hunting becomes inconsistent, so one person finds great contacts while another finds nothing and nobody knows why.
- Status tracking is usually manual, which means duplicates, skipped rows, and “Did we already do this?” meetings.
The Fix: Google Sheets lead enrichment with Serper.dev + ScrapingBee
This workflow starts inside your Google Sheet. When you “activate” a row, it first checks that the basics are present (business type, city, state). If something is missing, it flags the row so you don’t waste cycles on junk inputs. If the row looks good, it marks the status as Running, prepares search inputs like country and language, then queries Serper.dev to find likely company websites. Next, it generates a set of “site variants” and candidate contact pages, validates those URLs, and sends the best options to ScrapingBee for scraping. Emails are extracted from the scraped pages, checked against what you already have in the sheet, and then written back in a clean comma-separated format. Finally, the row gets marked Finished so your list stays organized.
The workflow kicks off from a Sheets Row Trigger. From there, Serper.dev is used to locate the best company pages, and ScrapingBee handles the messy part of pulling content reliably. The output is simple: updated columns in Google Sheets (company name, URL, emails, and status) so your outreach list stays ready to use.
What Changes: Before vs. After
| What This Eliminates | Impact You’ll See |
|---|---|
|
|
Real-World Impact
Say you enrich 100 leads every week. Manually, you might spend about 8 minutes per lead between searching, clicking, and hunting for a usable email, which is roughly 13 hours of busywork. With this workflow, you activate rows in Google Sheets and let it run: a minute to set up the queue, then the automation searches, validates, scrapes, and updates the sheet while you do other work. Even if you still review the results for a few minutes at the end, you’re usually saving most of that day.
Requirements
- n8n instance (try n8n Cloud free)
- Self-hosting option if you prefer (Hostinger works well)
- Google Sheets to store leads and results.
- Serper.dev to search for real company websites.
- ScrapingBee to scrape pages and extract emails.
- Google Sheets API credentials (get them from Google Cloud Console).
- Serper.dev API key (get it from your Serper.dev dashboard).
- ScrapingBee API key (get it from your ScrapingBee dashboard).
Skill level: Beginner. You’ll connect accounts, paste API keys, and match a few Google Sheets columns.
Need help implementing this? Talk to an automation expert (free 15-minute consultation).
The Workflow Flow
A Google Sheets row gets “activated.” The trigger watches your sheet for rows you want processed, so you control what runs and when (handy when you’re cleaning up inputs first).
Basic input validation happens immediately. If business type, city, or state is missing, the workflow writes a “Missing information” status and moves on. No wasted API calls.
Serper.dev finds likely company pages. n8n sends a search request, parses the results, and appends research rows so the workflow can test multiple candidates instead of trusting the first link it sees.
URLs are validated, then ScrapingBee scrapes the best options. The workflow checks that pages respond properly, scrapes content, and extracts email addresses. If emails exist, it looks up what you already have and updates the record.
Google Sheets is updated and the row is finalized. You get company name, URL, comma-separated emails, and a Finished status so your sheet stays clean.
You can easily modify country, language, or result count to fit different regions and niches. See the full implementation guide below for customization options.
Step-by-Step Implementation Guide
Step 1: Configure the Sheets Row Trigger
Set up the trigger to watch for row updates in your input sheet.
- Add the Sheets Row Trigger node and set Event to
rowUpdate. - Set Columns To Watch to
Activate. - Set Poll Times to
everyMinute. - Select the target Document and Sheet Name for your input sheet.
- Credential Required: Connect your
googleSheetsTriggerOAuth2Apicredentials.
Step 2: Connect Google Sheets for Status and Data Operations
Configure the Google Sheets nodes that update status and write research data.
- In Mark Status Running, set Operation to
update, map Client to{{ $json.Client }}, and set Status toRunning. - In Flag Missing Data, set Operation to
update, map Client to{{ $json.Client }}, and set Status toMissing data. - In Append Research Rows, set Operation to
appendand map: City to{{ $('Assign Search Inputs').item.json.city }}, State to{{ $('Assign Search Inputs').item.json.state }}, Client to{{ $json.client }}, Company to{{ $json.company }}, and website to{{ $json.Website }}. - In Lookup Existing Emails, set the filter to lookup Company with
{{ $json.company }}. - In Update Email Records, set Operation to
updateand map emails to{{ $json.emails ? $json.emails + ", " + $('Extract Email Addresses').item.json.email : $('Extract Email Addresses').item.json.email }}, and Company to{{ $('Extract Email Addresses').item.json.company }}. - In Mark Status Finished, set Operation to
update, map Client to{{ $('Generate Site Variants').item.json.client }}, and set Status toFinished.
Credential Required: Connect your googleSheetsOAuth2Api credentials to Mark Status Running and Flag Missing Data.
⚠️ Common Pitfall: The other Google Sheets nodes (Append Research Rows, Lookup Existing Emails, Update Email Records, Mark Status Finished) also require Google Sheets credentials, but none are configured. Add the same googleSheetsOAuth2Api credentials to each of them.
Step 3: Set Up Validation and Search Input Preparation
Validate incoming rows and build search inputs for the Serper query.
- In Input Validation, ensure the three conditions check for non-empty values:
{{ $json.Client }},{{ $json.City }}, and{{ $json.State }}. - Confirm Input Validation routes valid rows to Mark Status Running and invalid rows to Flag Missing Data.
- In Assign Search Inputs, enable Keep Only Set and set fields: state to
{{ $('Sheets Row Trigger').item.json.State }}, city to{{ $('Sheets Row Trigger').item.json.City }}, client to{{ $('Sheets Row Trigger').item.json.Client }}, business_type to{{ $node["Sheets Row Trigger"].json["Business Type"] }}, country toArgentina, country_code toAR, language toes-419, and result_count to10.
Step 4: Configure Search and Link Parsing
Query Serper and filter the organic results into company candidates.
- In Serper Search Request, set URL to
https://google.serper.dev/searchand Request Method toPOST. - Enable JSON Parameters and set Body Parameters JSON to
{ "q": "{{ $json.business_type }} in {{ $json.city }}, {{ $json.state }}, {{ $json.country }}", "num": {{ $json.result_count }}, "gl": "{{ $json.country_code }}", "hl": "{{ $json.language }}" }. - Credential Required: Connect your
httpHeaderAuthcredentials for the Serper API. - Keep Parse Company Links as-is to filter out blacklisted results and map
company,Website,client,state, andcityvalues.
Step 5: Generate URL Variants and Batch Processing
Create multiple contact/support URL variants and iterate through them for scraping.
- Keep Append Research Rows connected after Parse Company Links to log candidate companies before scraping.
- In Generate Site Variants, keep the JavaScript that builds multiple URL paths for each website.
- In Batch Iterator, leave the default batching options unless you need to control throughput.
- Confirm the flow: Append Research Rows → Generate Site Variants → Batch Iterator → Validate Page URLs.
⚠️ Common Pitfall: If your input sheet uses different column names (e.g., “website” vs “Website”), adjust the mapping in Append Research Rows and Generate Site Variants accordingly.
Step 6: Configure Scraping, Parsing, and Email Updates
Validate pages, scrape HTML, extract emails, and update existing records.
- In Validate Page URLs, set URL to
{{ $('Generate Site Variants').item.json.Website }}. - In Scrape Result Check, keep the condition that checks
{{ $json.error.message }}is empty before continuing to scrape. - In ScrapingBee Request, set URL to
https://app.scrapingbee.com/api/v1/?api_key=[CONFIGURE_YOUR_API_KEY]={{ $('Generate Site Variants').item.json.Website }}&render_js=trueand replace[CONFIGURE_YOUR_API_KEY]with your ScrapingBee key. - In Scrape Success Check, keep the condition that checks
{{ $json.error.message }}is empty before extracting emails. - In Extract Email Addresses, keep the JavaScript that extracts and deduplicates emails from the HTML
datafield. - In Email Presence Check, use the not-empty condition on
{{ $('Extract Email Addresses').item.json.email }}to decide whether to update. - Confirm the update flow: Email Presence Check → Lookup Existing Emails → Update Email Records → Delay Pause → Batch Iterator.
⚠️ Common Pitfall: The ScrapingBee URL includes a placeholder API key. If you leave [CONFIGURE_YOUR_API_KEY] unchanged, scraping will fail and the workflow will loop back via Batch Iterator.
Step 7: Test and Activate Your Workflow
Verify the full enrichment pipeline and then turn it on for production updates.
- Manually run the workflow with a test row update in the input sheet and ensure Sheets Row Trigger fires.
- Check that Mark Status Running updates the input sheet status to
Runningor Flag Missing Data updates it toMissing datawhen fields are empty. - Verify Append Research Rows appends results to the data sheet and Update Email Records writes email values.
- Confirm Mark Status Finished sets the final status to
Finishedafter batch processing. - Activate the workflow by toggling the Active switch in n8n.
Watch Out For
- Google Sheets credentials can expire or need specific permissions. If things break, check the n8n credential connection and your Google Cloud OAuth consent/settings 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.
Common Questions
About 30 minutes if your API keys and Google Sheets access are ready.
Yes. You won’t write code, but you will need to map your sheet columns and paste a couple of API keys into n8n.
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 Serper.dev and ScrapingBee usage (both have free tiers, then usage-based pricing).
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.
Start by making country, country_code, language, and result_count come from columns in your sheet, so each row can control how it searches. You can also expand the blacklist logic in the “Generate Site Variants” / filtering code to avoid directories you hate. If you want more than emails, extend the “Extract Email Addresses” code to capture phones or social links and write them back as new columns.
Usually it’s expired Google OAuth credentials or the wrong Google account connected to the n8n credential. Reconnect Google Sheets in n8n, then confirm the sheet is shared with that account and the correct spreadsheet is selected. If only updates fail, check that your column names match what the workflow expects (including the activate field).
If you self-host, there’s no execution cap (it’s mainly your server and API limits). On n8n Cloud, capacity depends on your plan’s monthly executions. Practically, this workflow is gated by Serper.dev and ScrapingBee rate limits, so most teams run it in batches of a few dozen to a few hundred leads at a time.
Often, yes, because this flow relies on branching logic (multiple checks), looping through candidate URLs, and code-based parsing, which gets awkward and pricey in many no-code tools. n8n handles split-in-batches loops cleanly, and you can self-host for unlimited executions. Zapier or Make can still be fine if your process is “search once, store one result,” but this workflow is built for real-world messiness: duplicates, bad URLs, and multiple pages per company. One more thing: keeping status fields like Running and Finished inside Google Sheets makes ops handoffs easier, and n8n fits that pattern well. If you’re on the fence, Talk to an automation expert and we’ll sanity-check your setup.
Once this is running, your sheet stops being a wish list and starts being an outreach queue. The workflow handles the repetitive digging so you can focus on the message and the offer.
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.