Skip to content

Instantly share code, notes, and snippets.

@valka465
Created April 24, 2026 20:16
Show Gist options
  • Select an option

  • Save valka465/c64f49e5933400a7f7a42ce2933ddb93 to your computer and use it in GitHub Desktop.

Select an option

Save valka465/c64f49e5933400a7f7a42ce2933ddb93 to your computer and use it in GitHub Desktop.
Scrape Unlimited Leads with Google Maps and N8N
{
"name": "Lead Generation Flow",
"nodes": [
{
"parameters": {},
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
-2080,
256
],
"id": "f72f7271-3c3e-41ae-9fae-0593b87fc408",
"name": "Manual Trigger",
"notes": "Click to start the workflow manually"
},
{
"parameters": {
"operation": "create",
"documentId": {
"__rl": true,
"value": "1nfFNvbdD1dyUNWxRzuWiTzAODSxH8j5B06z_YKvOFg4",
"mode": "list",
"cachedResultName": "Leads",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nfFNvbdD1dyUNWxRzuWiTzAODSxH8j5B06z_YKvOFg4/edit?usp=drivesdk"
},
"title": "={{ $now.toFormat(\"yyyy-MM-dd HH:mm\") }}",
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
-1872,
256
],
"id": "70ab6843-92be-4846-8b6b-0569fe090f4f",
"name": "Create Results Sheet",
"credentials": {
"googleSheetsOAuth2Api": {
"id": "jWatmzLTdYjLY30d",
"name": "Google Sheets account"
}
},
"notes": "Creates a new sheet with timestamp to store results"
},
{
"parameters": {
"documentId": {
"__rl": true,
"value": "={{ $json.spreadsheetId }}",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "config",
"mode": "name"
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
-1664,
256
],
"id": "21f6bec3-a62f-4216-9484-661b5f1ab6e2",
"name": "Read Config Sheet",
"credentials": {
"googleSheetsOAuth2Api": {
"id": "jWatmzLTdYjLY30d",
"name": "Google Sheets account"
}
},
"notes": "Reads search queries and parameters from config sheet"
},
{
"parameters": {
"jsCode": "// 1. Initialize an array to store all tasks\nlet allTasks = [];\n\n// 2. Constants\nconst RESULTS_PER_PAGE = 20;\n\n// 3. Loop through Google Sheets rows\nfor (const item of $input.all()) {\n const config = item.json;\n \n // Validation: basic check for the query\n if (!config.leads_query) continue;\n\n const leads_query = config.leads_query;\n const location = config.location || \"\"; \n const pageCount = parseInt(config.page_count) || 1; \n \n // Get the starting page number from the table (default to 1 if empty)\n const startPage = parseInt(config.offset) || 1;\n\n // Generate a task for each requested page\n for (let p = 0; p < pageCount; p++) {\n const currentPageNumber = startPage + p;\n \n // Convert Page Number to API Position (Offset)\n // Page 1 -> (1-1) * 20 = 0\n // Page 2 -> (2-1) * 20 = 20\n const apiPosition = (currentPageNumber - 1) * RESULTS_PER_PAGE;\n \n allTasks.push({\n json: {\n leads_query: leads_query,\n ll: \"\",\n location: location,\n pageOffset: apiPosition, // This goes to the API\n pageNumber: currentPageNumber, // Optional: for your own tracking\n original_row: config.row_number || null \n }\n });\n }\n}\n\n// 4. Return the tasks\nreturn allTasks;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
-1456,
256
],
"id": "5254f5c9-0c16-4ebf-b199-3717dc823084",
"name": "Build Search Tasks",
"notes": "Converts config into multiple search tasks with pagination (20 results per page)"
},
{
"parameters": {
"resource": "google_maps",
"q": "={{ $json.leads_query }} {{ $json.location }}",
"additionalFields": {
"start": "={{ $json.pageOffset }}",
"ll": "=@40.7455096,-74.0083012,14z"
}
},
"type": "@hasdata/n8n-nodes-hasdata.hasData",
"typeVersion": 1,
"position": [
-1248,
256
],
"id": "59a2cb00-5e6a-4e18-b133-40b5e499820c",
"name": "Search Google Maps",
"retryOnFail": true,
"credentials": {
"hasDataApi": {
"id": "0sIR2iCQF0hXfHsH",
"name": "HasData account"
}
},
"onError": "continueRegularOutput",
"notes": "Fetches business listings from Google Maps based on query and location"
},
{
"parameters": {
"fieldToSplitOut": "localResults",
"include": "=",
"options": {}
},
"type": "n8n-nodes-base.splitOut",
"typeVersion": 1,
"position": [
-1040,
256
],
"id": "502d0020-9115-4e94-94ed-0005af31664f",
"name": "Split Results",
"notes": "Separates each business into individual items for processing"
},
{
"parameters": {
"compare": "selectedFields",
"fieldsToCompare": "localResults.title, localResults.kgmid",
"options": {}
},
"type": "n8n-nodes-base.removeDuplicates",
"typeVersion": 2,
"position": [
-832,
256
],
"id": "93b5fee9-d26f-49c3-a7c0-8edce026ef3e",
"name": "Remove Duplicates",
"notes": "Removes duplicate businesses based on name and address"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "d2c86a6c-2c9d-472e-a328-71d92bdc74f2",
"leftValue": "={{ $json.localResults.website }}",
"rightValue": "",
"operator": {
"type": "string",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-624,
256
],
"id": "6baaac69-71d4-440f-9aa3-2dec01bd4538",
"name": "Has Website?",
"notes": "Checks if business has a website - TRUE path scrapes it, FALSE path searches Google"
},
{
"parameters": {
"resource": "web_scraping",
"url": "={{ $json.localResults.website }}",
"additionalFields": {
"extractEmails": true
}
},
"type": "@hasdata/n8n-nodes-hasdata.hasData",
"typeVersion": 1,
"position": [
-416,
160
],
"id": "394e8937-6a21-4036-a142-25fc3b79a81c",
"name": "Scrape Website",
"retryOnFail": true,
"credentials": {
"hasDataApi": {
"id": "0sIR2iCQF0hXfHsH",
"name": "HasData account"
}
},
"onError": "continueRegularOutput",
"notes": "Scrapes the business website to extract email addresses"
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict",
"version": 3
},
"conditions": [
{
"id": "5590ef2a-7d53-4637-afa0-10acdeb7dc41",
"leftValue": "={{ $json.emails }}",
"rightValue": "",
"operator": {
"type": "array",
"operation": "notEmpty",
"singleValue": true
}
}
],
"combinator": "and"
},
"options": {}
},
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-208,
96
],
"id": "6885fbf9-0e3d-44ed-8fb4-584dfd17c201",
"name": "Emails Found?",
"notes": "Checks if emails were found on website - TRUE adds them, FALSE searches Google"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "a574f281-57df-4ee8-9d56-945f141b5345",
"name": "title",
"value": "={{ $('Has Website?').item.json.localResults.title }}",
"type": "string"
},
{
"id": "f92a90cd-b7ea-4740-8d0e-739d18912fa4",
"name": "phone",
"value": "={{ $('Has Website?').item.json.localResults.phone }}",
"type": "string"
},
{
"id": "c6a92d26-418d-41f0-86d1-4fe67755bf94",
"name": "address",
"value": "={{ $('Has Website?').item.json.localResults.address }}",
"type": "string"
},
{
"id": "5a1e6e7c-559f-4419-9719-73de143ba76a",
"name": "website",
"value": "={{ $('Has Website?').item.json.localResults.website }}",
"type": "string"
},
{
"id": "3ebc4a23-0bb6-43a0-b8f1-804acc29793b",
"name": "emails",
"value": "={{ $json.emails.join(', ') }}",
"type": "string"
},
{
"id": "03dd5a32-cc01-4cab-872b-8d18a40797cc",
"name": "rating",
"value": "={{ $('Has Website?').item.json.localResults.rating }}",
"type": "number"
},
{
"id": "361d3f7a-bfd4-40a7-901c-13e8e895d1fa",
"name": "reviews",
"value": "={{ $('Has Website?').item.json.localResults.reviews }}",
"type": "number"
},
{
"id": "b1a783ca-5559-4e2d-8d28-5b98140cc89d",
"name": "type",
"value": "={{ $('Has Website?').item.json.localResults.types.join(', ') }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
0,
0
],
"id": "5a80f8c2-8169-4c06-9a14-b6cca319fc96",
"name": "Format Lead with Emails",
"notes": "Prepares lead data including scraped emails for output"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "ad7fae25-b7e0-46ab-ad95-568ab9c6813a",
"name": "localResults.title",
"value": "={{ $('Has Website?').item.json.localResults.title }}",
"type": "string"
},
{
"id": "183dca66-d14a-4a63-9c14-30fffdaf4745",
"name": "localResults.phone",
"value": "={{ $('Has Website?').item.json.localResults.phone }}",
"type": "string"
},
{
"id": "d2791f5a-9e15-4d29-b1a7-488137c0deb8",
"name": "localResults.address",
"value": "={{ $('Has Website?').item.json.localResults.address }}",
"type": "string"
},
{
"id": "0d935b92-cb1e-4779-bd7e-3af1d2dd38ba",
"name": "localResults.website",
"value": "={{ $('Has Website?').item.json.localResults.website }}",
"type": "string"
},
{
"id": "faff970a-cb9d-4511-abb3-c97908cfdf59",
"name": "rating",
"value": "={{ $('Has Website?').item.json.localResults.rating }}",
"type": "number"
},
{
"id": "f4252269-c3b3-4ee2-a45c-458d3021123d",
"name": "reviews",
"value": "={{ $('Has Website?').item.json.localResults.reviews }}",
"type": "number"
},
{
"id": "4cc0b0f7-e726-4c69-8682-c87a75306efb",
"name": "type",
"value": "={{ $('Has Website?').item.json.localResults.types.join(', ') }}",
"type": "string"
}
]
},
"options": {}
},
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
0,
160
],
"id": "578e651f-d067-44be-860a-80473fc5510e",
"name": "Format Lead without Emails",
"notes": "Prepares lead data without emails (will search Google next)"
},
{
"parameters": {},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [
-368,
400
],
"id": "1033c1b5-9a71-4049-99ef-302cacc5a748",
"name": "Merge for Google Search",
"notes": "Combines leads without website or without scraped emails for Google search"
},
{
"parameters": {
"q": "={{ $json.localResults.title }}, {{ $json.localResults.address.split(', ').slice(1).join(', ') }} email \"@\"",
"additionalFields": {}
},
"type": "@hasdata/n8n-nodes-hasdata.hasData",
"typeVersion": 1,
"position": [
-192,
400
],
"id": "493d6ab0-d7b0-4553-ac19-b0cb6a9a28b4",
"name": "Search Google for Emails",
"credentials": {
"hasDataApi": {
"id": "0sIR2iCQF0hXfHsH",
"name": "HasData account"
}
},
"notes": "Searches Google to find email addresses for businesses"
},
{
"parameters": {
"jsCode": "/**\n * Optimized lead processor for n8n.\n * Features: Ancestor matching for Merge1 data and advanced email cleanup.\n */\n\nlet finalResults = [];\n\n// Iterate through items coming from the previous node\nfor (let i = 0; i < $input.all().length; i++) {\n const item = $input.all()[i];\n const data = item.json;\n const results = data.organicResults || [];\n\n /**\n * Safe data retrieval from Merge for Google Search.\n * Using itemMatching(i) ensures we sync correctly with previous workflow steps.\n */\n let sourceData = {};\n let fullData = {};\n try {\n const ancestor = $(\"Merge for Google Search\").itemMatching(i).json;\n sourceData = ancestor.localResults || ancestor;\n fullData = ancestor;\n } catch (e) {\n const fallback = $(\"Merge for Google Search\").all()[i]?.json;\n sourceData = fallback?.localResults || fallback || {};\n fullData = fallback;\n }\n\n let foundData = {\n website: null,\n emails: null,\n title: sourceData.title || null,\n phone: sourceData.phone || null,\n address: sourceData.address || null,\n rating: fullData.rating || sourceData.rating || null,\n reviews: fullData.reviews || sourceData.reviews || null,\n type: fullData.type || sourceData.type || null\n };\n\n /**\n * REFINED EMAIL REGEX\n * [a-z]{2,6} - Look for lowercase extensions only.\n * (?![A-Z]) - Negative lookahead: stop if a Capital letter follows (like .Read).\n */\n const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-z]{2,6}(?![A-Z])/;\n\n for (const res of results) {\n const textToScan = (res.snippet || \"\") + \" \" + (res.title || \"\");\n const match = textToScan.match(emailRegex);\n\n if (match) {\n let cleanedEmail = match[0];\n \n /**\n * MANUAL ARTIFACT REMOVAL\n * Cleans up cases where snippet text is glued to the email.\n */\n const artifacts = [\".Read\", \".View\", \".More\", \".Check\", \".Full\", \".Website\"];\n for (const art of artifacts) {\n if (cleanedEmail.endsWith(art)) {\n cleanedEmail = cleanedEmail.slice(0, -art.length);\n }\n }\n\n // Final trim for any trailing dots\n cleanedEmail = cleanedEmail.replace(/\\.+$/, \"\"); \n\n foundData.emails = cleanedEmail;\n foundData.website = res.link;\n \n // Stop at the first valid email found in the results list\n break; \n }\n }\n\n finalResults.push({ json: foundData });\n}\n\nreturn finalResults;"
},
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
16,
400
],
"id": "5ea07ca7-9c06-4f56-bc32-45856a647af9",
"name": "Extract Emails from Results",
"notes": "Extracts email addresses from Google search results using regex"
},
{
"parameters": {},
"type": "n8n-nodes-base.merge",
"typeVersion": 3.2,
"position": [
400,
208
],
"id": "da35c842-1741-4c17-be24-56567d96d3b7",
"name": "Merge All Leads",
"notes": "Combines all leads (with website emails and Google-found emails)"
},
{
"parameters": {
"operation": "append",
"documentId": {
"__rl": true,
"value": "={{ $('Create Results Sheet').first().json.spreadsheetId }}",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "={{ $('Create Results Sheet').first().json.title }}",
"mode": "name"
},
"columns": {
"mappingMode": "autoMapInputData",
"value": {},
"matchingColumns": [],
"schema": [
{
"id": "title",
"displayName": "title",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "phone",
"displayName": "phone",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "address",
"displayName": "address",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "website",
"displayName": "website",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "emails",
"displayName": "emails",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "rating",
"displayName": "rating",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "reviews",
"displayName": "reviews",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
},
{
"id": "type",
"displayName": "type",
"required": false,
"defaultMatch": false,
"display": true,
"type": "string",
"canBeUsedToMatch": true,
"removed": false
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {}
},
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.7,
"position": [
576,
208
],
"id": "d53997d8-d504-4475-a412-0ab59e57bf24",
"name": "Save to Results Sheet",
"credentials": {
"googleSheetsOAuth2Api": {
"id": "jWatmzLTdYjLY30d",
"name": "Google Sheets account"
}
},
"notes": "Writes all found leads to the timestamped results sheet"
},
{
"parameters": {
"content": "## Requirements\n\n- Google Sheets file **\"Leads\"**\n- Sheet **\"config\"** with fields:\n - leads_query\n - location\n - page_count\n - offset\n- [HasData API](https://hasdata.com/) credentials ",
"height": 224,
"width": 400
},
"type": "n8n-nodes-base.stickyNote",
"position": [
-2096,
-64
],
"typeVersion": 1,
"id": "b95f73c0-42a7-483d-b662-315ebabf8ded",
"name": "Sticky Note"
}
],
"pinData": {},
"connections": {
"Manual Trigger": {
"main": [
[
{
"node": "Create Results Sheet",
"type": "main",
"index": 0
}
]
]
},
"Create Results Sheet": {
"main": [
[
{
"node": "Read Config Sheet",
"type": "main",
"index": 0
}
]
]
},
"Read Config Sheet": {
"main": [
[
{
"node": "Build Search Tasks",
"type": "main",
"index": 0
}
]
]
},
"Build Search Tasks": {
"main": [
[
{
"node": "Search Google Maps",
"type": "main",
"index": 0
}
]
]
},
"Search Google Maps": {
"main": [
[
{
"node": "Split Results",
"type": "main",
"index": 0
}
]
]
},
"Split Results": {
"main": [
[
{
"node": "Remove Duplicates",
"type": "main",
"index": 0
}
]
]
},
"Remove Duplicates": {
"main": [
[
{
"node": "Has Website?",
"type": "main",
"index": 0
}
]
]
},
"Has Website?": {
"main": [
[
{
"node": "Scrape Website",
"type": "main",
"index": 0
}
],
[
{
"node": "Merge for Google Search",
"type": "main",
"index": 1
}
]
]
},
"Scrape Website": {
"main": [
[
{
"node": "Emails Found?",
"type": "main",
"index": 0
}
]
]
},
"Emails Found?": {
"main": [
[
{
"node": "Format Lead with Emails",
"type": "main",
"index": 0
}
],
[
{
"node": "Format Lead without Emails",
"type": "main",
"index": 0
}
]
]
},
"Format Lead with Emails": {
"main": [
[
{
"node": "Merge All Leads",
"type": "main",
"index": 0
}
]
]
},
"Format Lead without Emails": {
"main": [
[
{
"node": "Merge for Google Search",
"type": "main",
"index": 0
}
]
]
},
"Merge for Google Search": {
"main": [
[
{
"node": "Search Google for Emails",
"type": "main",
"index": 0
}
]
]
},
"Search Google for Emails": {
"main": [
[
{
"node": "Extract Emails from Results",
"type": "main",
"index": 0
}
]
]
},
"Extract Emails from Results": {
"main": [
[
{
"node": "Merge All Leads",
"type": "main",
"index": 1
}
]
]
},
"Merge All Leads": {
"main": [
[
{
"node": "Save to Results Sheet",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1",
"binaryMode": "separate"
},
"versionId": "5e34e393-a5f4-4c7a-9235-04891b8f2a1d",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "e1995189ee8c30333c7d7af9a701d255d0078128a18e7c296c4bcf742af75551"
},
"id": "QfqmMXhPJidm7tdH",
"tags": []
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment