This workflow corresponds to n8n.io template #8917 — we link there as the canonical source.
This workflow follows the Google Sheets → HTTP Request recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"nodes": [
{
"id": "cf7497dc-a934-41d5-9860-e27599d3bf90",
"name": "When clicking 'Execute workflow'",
"type": "n8n-nodes-base.manualTrigger",
"position": [
-832,
-64
],
"parameters": {},
"typeVersion": 1
},
{
"id": "f0e7adde-83ce-428d-9590-f1f35b6c0ba3",
"name": "Get Keywords from Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
-544,
-64
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1VXVbyqRPPHgO0tyoxpEajCyPX1DZdY3hRu_jCOdDriE/edit#gid=0",
"cachedResultName": "Keywords"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1VXVbyqRPPHgO0tyoxpEajCyPX1DZdY3hRu_jCOdDriE",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1VXVbyqRPPHgO0tyoxpEajCyPX1DZdY3hRu_jCOdDriE/edit?usp=drivesdk",
"cachedResultName": "Competitor tracker"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.6
},
{
"id": "2b8e096f-cd26-4886-9cc2-78d67b49c76d",
"name": "URL Encode Keywords",
"type": "n8n-nodes-base.code",
"position": [
-272,
-64
],
"parameters": {
"jsCode": "const encode = encodeURIComponent;\nfor (const item of $input.all()) {\n item.json.Keyword = encode(item.json.Keyword);\n}\nreturn $input.all();"
},
"typeVersion": 2
},
{
"id": "2c743dde-c8f3-4ce8-9a11-89d95accffc3",
"name": "Fetch Google Search Results",
"type": "n8n-nodes-base.httpRequest",
"position": [
80,
-96
],
"parameters": {
"url": "=https://api.scrape.do/?token={{$vars.SCRAPEDO_TOKEN}}&url={{ encodeURIComponent('https://www.google.com/search?q=' + $json.Keyword) }}&geoCode={{ $json['Target Country'] || 'us' }}&render=true",
"options": {
"timeout": 60000,
"redirect": {
"redirect": {}
}
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Accept",
"value": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
}
]
}
},
"typeVersion": 4.2,
"alwaysOutputData": true
},
{
"id": "581d6f6b-b4b7-460b-8d99-f61de812f2eb",
"name": "Extract Competitor Data from HTML",
"type": "n8n-nodes-base.code",
"position": [
368,
-96
],
"parameters": {
"jsCode": "// Parse the HTML response from Scrape.do\nconst html = $input.first().json.data || $input.first().json.body || '';\n\nconst results = [];\n\n// Simple regex patterns to extract search results\n// Pattern for organic results\nconst resultPattern = /<div[^>]*class=\\\"[^\\\"]*Ww4FFb[^\\\"]*\\\"[^>]*>([\\s\\S]*?)<\\/div>/gi;\nconst matches = html.matchAll(resultPattern);\n\nlet position = 1;\nfor (const match of matches) {\n const resultHtml = match[1];\n \n // Extract title (h3 tag)\n const titleMatch = resultHtml.match(/<h3[^>]*>([^<]*)<\\/h3>/i);\n const title = titleMatch ? titleMatch[1].replace(/<[^>]*>/g, '').trim() : '';\n \n // Extract URL\n const urlMatch = resultHtml.match(/href=\\\"([^\\\"]+)\\\"/i);\n const url = urlMatch ? urlMatch[1] : '';\n \n // Extract description\n const descMatch = resultHtml.match(/<div[^>]*class=\\\"[^\\\"]*VwiC3b[^\\\"]*\\\"[^>]*>([^<]*)</i);\n const description = descMatch ? descMatch[1].replace(/<[^>]*>/g, '').trim() : '';\n \n if (title && url) {\n results.push({\n position: position++,\n websiteTitle: title,\n websiteUrl: url,\n websiteDescription: description || 'No description available',\n keyword: $input.first().json.originalKeyword || $input.first().json.Keyword || ''\n });\n }\n \n // Stop after 10 organic results\n if (position > 10) break;\n}\n\n// If no results found with the above method, try a simpler approach\nif (results.length === 0) {\n // Look for any links that appear to be search results\n const linkPattern = /<a[^>]*href=\\\"(https?:\\/\\/[^\\\"]+)\\\"[^>]*>([^<]+)<\\/a>/gi;\n const linkMatches = html.matchAll(linkPattern);\n \n position = 1;\n for (const linkMatch of linkMatches) {\n const url = linkMatch[1];\n const title = linkMatch[2].replace(/<[^>]*>/g, '').trim();\n \n // Filter out Google's own links\n if (url && !url.includes('google.com') && !url.includes('googleapis.com')) {\n results.push({\n position: position++,\n websiteTitle: title || 'No title',\n websiteUrl: url,\n websiteDescription: 'Description not available',\n keyword: $input.first().json.originalKeyword || $input.first().json.Keyword || ''\n });\n \n if (position > 10) break;\n }\n }\n}\n\n// Return results or a default if none found\nreturn results.length > 0 ? results.map(r => ({json: r})) : [{json: {\n position: 1,\n websiteTitle: 'No results found',\n websiteUrl: 'N/A',\n websiteDescription: 'Please check the HTML response',\n keyword: $input.first().json.originalKeyword || $input.first().json.Keyword || '',\n htmlLength: html.length\n}}];"
},
"typeVersion": 2,
"alwaysOutputData": true
},
{
"id": "fe0422c4-be3e-4a41-aa36-0e631c4450b4",
"name": "Append Results to Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
640,
-96
],
"parameters": {
"columns": {
"value": {},
"schema": [],
"mappingMode": "autoMapInputData",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1113333162,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1VXVbyqRPPHgO0tyoxpEajCyPX1DZdY3hRu_jCOdDriE/edit#gid=1113333162",
"cachedResultName": "Competitor Results"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1VXVbyqRPPHgO0tyoxpEajCyPX1DZdY3hRu_jCOdDriE",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1VXVbyqRPPHgO0tyoxpEajCyPX1DZdY3hRu_jCOdDriE/edit?usp=drivesdk",
"cachedResultName": "Competitor tracker"
}
},
"typeVersion": 4.6
},
{
"id": "1fbe216c-35dc-4eb1-bed9-ef130ffa15b5",
"name": "Workflow Start",
"type": "n8n-nodes-base.stickyNote",
"position": [
-880,
96
],
"parameters": {
"height": 192,
"content": "This workflow is triggered manually by clicking 'Execute workflow'.\n\nIt fetches keywords and target countries from a Google Sheet to perform SERP analysis using Scrape.do API."
},
"typeVersion": 1
},
{
"id": "6dc607b7-d5f4-4fcd-809e-9545e1488972",
"name": "Read Keywords",
"type": "n8n-nodes-base.stickyNote",
"position": [
-576,
112
],
"parameters": {
"height": 192,
"content": "Reads data from the specified Google Sheet.\n\nExpects columns:\n- 'Keyword' (search term)\n- 'Target Country' (2-letter country code like 'us', 'uk', 'de')\n\n**Remember to replace:**\n- YOUR_GOOGLE_SHEET_ID\n- YOUR_GOOGLE_SHEETS_CREDENTIAL_ID"
},
"typeVersion": 1
},
{
"id": "9868274a-1c01-459b-bdf9-47cf1ef14d7a",
"name": "Encode Keywords",
"type": "n8n-nodes-base.stickyNote",
"position": [
-304,
80
],
"parameters": {
"height": 192,
"content": "Encodes the 'Keyword' value using `encodeURIComponent`.\n\nThis is necessary to ensure keywords with special characters are correctly formatted for the URL."
},
"typeVersion": 1
},
{
"id": "a9158b83-ce5a-45d2-908c-9a504990dd71",
"name": "Fetch SERP Data with Scrape.do",
"type": "n8n-nodes-base.stickyNote",
"position": [
16,
64
],
"parameters": {
"content": "**Scrape.do Configuration:**\n\n- **API Endpoint**: https://api.scrape.do/\n- **Parameters**:\n - `token`: Your Scrape.do API token\n - `url`: The Google search URL (encoded)\n - `geoCode`: Target country code\n - `render`: Set to true for JavaScript rendering\n\n**Remember to replace 'YOUR_SCRAPEDO_TOKEN'**\n\nGet your token at: https://dashboard.scrape.do/"
},
"typeVersion": 1
},
{
"id": "4b9de746-a1ae-4e43-91d6-3b42524f7ddf",
"name": "Extract Competitor Data",
"type": "n8n-nodes-base.stickyNote",
"position": [
320,
80
],
"parameters": {
"content": "Parses the HTML response from Scrape.do to extract:\n- Website titles\n- URLs\n- Descriptions\n- Position in search results\n\nThe code looks for organic search results and filters out ads and Google's own links."
},
"typeVersion": 1
},
{
"id": "b51511f4-0d7c-4411-a5a4-36cc976fcf6a",
"name": "Append to Results sheet",
"type": "n8n-nodes-base.stickyNote",
"position": [
640,
80
],
"parameters": {
"content": "Appends parsed SERP rows into the 'Results' sheet. Columns expected:\n- keyword\n- position\n- websiteTitle\n- websiteUrl\n- websiteDescription\n\nIf the sheet does not exist, create a tab named 'Results' in the same spreadsheet."
},
"typeVersion": 1
}
],
"connections": {
"URL Encode Keywords": {
"main": [
[
{
"node": "Fetch Google Search Results",
"type": "main",
"index": 0
}
]
]
},
"Get Keywords from Sheet": {
"main": [
[
{
"node": "URL Encode Keywords",
"type": "main",
"index": 0
}
]
]
},
"Fetch Google Search Results": {
"main": [
[
{
"node": "Extract Competitor Data from HTML",
"type": "main",
"index": 0
}
]
]
},
"When clicking 'Execute workflow'": {
"main": [
[
{
"node": "Get Keywords from Sheet",
"type": "main",
"index": 0
}
]
]
},
"Extract Competitor Data from HTML": {
"main": [
[
{
"node": "Append Results to Sheet",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleSheetsOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This template requires a self-hosted n8n instance to run.
Source: https://n8n.io/workflows/8917/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
TrackCollect_deeper. Uses googleSheets, httpRequest, @n-octo-n/n8n-nodes-json-database, itemLists. Event-driven trigger; 80 nodes.
This template is ideal for solo store owners, eCommerce marketers, automation beginners, or anyone using Shopify and Gmail who wants to recover lost revenue without coding.
PCN. Uses googleSheets, httpRequest, @n-octo-n/n8n-nodes-json-database, itemLists. Event-driven trigger; 60 nodes.
Product Ad Machine. Uses googleDrive, googleSheets, httpRequest, @ffmpeg-micro/n8n-nodes-ffmpeg-micro. Event-driven trigger; 60 nodes.
Basic AI SEO KW Research n8n DataForSEO Gumroad 030125. Uses googleSheets, googleDrive, httpRequest. Event-driven trigger; 56 nodes.