This workflow corresponds to n8n.io template #17515 — we link there as the canonical source.
This workflow follows the Google Sheets → Informationextractor 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 →
{
"id": "uIqLYqCvhQuPnTuI",
"name": "Business Directory Extractor \u2192 Google Sheets",
"tags": [],
"nodes": [
{
"id": "0acbb599-4dd9-467c-8963-afd3748a564f",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
0
],
"parameters": {
"width": 520,
"height": 1320,
"content": "## Business Directory Extractor \u2192 Google Sheets\n\n### How it works\n\nEvery day at 12 PM the workflow reads a list of public business-directory page URLs from the **Directory Sources** tab and processes them one at a time. For each URL, Snapshot Site captures the fully-rendered page HTML (`format: html`, `fullSize`, 5s settle delay), a Code node strips scripts/styles/SVG/comments and truncates the DOM, and an AI Information Extractor returns an array of structured business listings. Each listing becomes one row in the **Businesses** tab, deduplicated on `directoryUrl`. Capture or extraction failures are written to the **Extraction Errors** tab and the loop moves on \u2014 one bad page never stops the run.\n\n### Compliance note\n\nOnly extract data from directory pages you're allowed to process \u2014 check the source site's terms of service and robots directives before pointing this workflow at a new directory.\n\n### Setup steps\n\n- Install the `n8n-nodes-snapshot-site` community node (Settings \u2192 Community nodes \u2192 Install \u2192 `n8n-nodes-snapshot-site`) \u2014 verified by n8n.\n- Add a Snapshot Site credential, a Google Sheets credential, and an AI chat model credential (e.g. OpenAI) for the Information Extractor.\n- Create three tabs in one spreadsheet: **Directory Sources**, **Businesses**, **Extraction Errors**. In **Directory Sources**, cell A1 must be the literal header text `url`, with one directory listing page URL per row from row 2 (full URLs including `https://` \u2014 the API rejects bare domains).\n- Paste this exact header row into row 1 of the **Businesses** tab (tab-separated, one column per cell): `companyName\tcategory\tdescription\taddress\tpostalCode\tcity\tcountry\tphone\temail\twebsite\topeningHours\trating\treviews\tdirectoryUrl`\n- Paste this header row into row 1 of the **Extraction Errors** tab: `url\tmessage\toccurredAt`\n- After importing, open the two Google Sheets write nodes (Log Extraction Error, Save Businesses) and switch Mapping Column Mode from \"Map Each Column Manually\" to \"Map Automatically\" \u2014 it always imports blank regardless of the JSON, but since the sheet headers above already match the incoming field names, this needs no manual typing. On Save Businesses, also pick `directoryUrl` under \"Column to match on\".\n- Point the three Google Sheets nodes at your spreadsheet and adjust the schedule.\n\n### Customization\n\nAdd more source columns (e.g. an `industry` tag), swap the AI model, edit the extractor's JSON schema to change which fields are pulled, or point the same Clean HTML \u2192 Extractor pattern at a different page type \u2014 this is the base pattern for every extraction workflow in this collection."
},
"typeVersion": 1
},
{
"id": "96b495c2-41b0-4fa2-81d8-2437317d0448",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
528,
288
],
"parameters": {
"color": 7,
"width": 820,
"height": 460,
"content": "## 1. Read sources and loop\n\nRuns daily at 12 PM and reads every directory page URL from the **Directory Sources** tab.\n\nThe loop processes one URL per iteration \u2014 both the save path and the error path come back here, so the run always continues to the next source."
},
"typeVersion": 1
},
{
"id": "a810ce95-2e4f-4e33-9f46-61b8e85b806d",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
1392,
32
],
"parameters": {
"color": 7,
"width": 780,
"height": 928,
"content": "## 2. Capture, validate and clean\n\n**Get Page HTML** returns the fully-rendered DOM (3 retries, hard failures leave via the error output). **Check for API Error** catches soft errors \u2014 HTTP 200 with `error: true` \u2014 that arrive on the success output.\n\nEither failure builds an error record and appends it to **Extraction Errors**. Clean pages go to **Clean HTML**, which strips scripts/styles/SVG/comments and truncates to 100k characters before the AI step. When the last source is done, the loop exits at **All Sources Processed**."
},
"typeVersion": 1
},
{
"id": "fd2f6f3b-1fde-4a9a-8904-81cf329cde2f",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
2224,
288
],
"parameters": {
"color": 7,
"width": 820,
"height": 600,
"content": "## 3. Extract and save\n\n**Extract Business Fields** sends the cleaned HTML to the chat model with a JSON schema and gets back `output.businesses`. **Split Businesses** turns that array into one item per company.\n\n**Save Businesses** appends or updates rows in the **Businesses** tab, matching on `directoryUrl` so re-running the same source updates existing rows instead of duplicating them."
},
"typeVersion": 1
},
{
"id": "a6330d26-3f31-40f4-b74e-81d88a3ef953",
"name": "When Every Day at 12PM",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
608,
528
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 12
}
]
}
},
"typeVersion": 1.2
},
{
"id": "0aa54c92-0929-4ec9-936f-bf5f1c3d8ac6",
"name": "Read Directory Sources",
"type": "n8n-nodes-base.googleSheets",
"position": [
880,
528
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Directory Sources"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/PLACEHOLDER/edit"
}
},
"typeVersion": 4.5
},
{
"id": "e510e059-704b-49a4-81bc-a873937f2b3e",
"name": "Loop Over Directory Sources",
"type": "n8n-nodes-base.splitInBatches",
"position": [
1168,
528
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "ea6816b0-fc0e-4936-8966-ba15922079e7",
"name": "All Sources Processed",
"type": "n8n-nodes-base.noOp",
"position": [
1440,
368
],
"parameters": {},
"typeVersion": 1
},
{
"id": "221cb2d8-f364-4c38-b12c-3c98e0c25aa0",
"name": "Get Page HTML",
"type": "n8n-nodes-snapshot-site.snapshotSite",
"onError": "continueErrorOutput",
"maxTries": 3,
"position": [
1440,
608
],
"parameters": {
"url": "={{ $json.url }}",
"requestOptions": {},
"additionalFields": {
"delay": 5,
"format": "html",
"fullSize": true
}
},
"retryOnFail": true,
"typeVersion": 1,
"waitBetweenTries": 5000
},
{
"id": "e8b5291d-4f17-4def-a287-0f576822c380",
"name": "Check for API Error",
"type": "n8n-nodes-base.if",
"position": [
1728,
608
],
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{ $json.error === true }}",
"value2": true
}
]
}
},
"typeVersion": 1
},
{
"id": "dd2b47d1-8d12-4acb-a4f2-30a5c2da158b",
"name": "Build Error Record",
"type": "n8n-nodes-base.set",
"position": [
1728,
800
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"name": "url",
"type": "string",
"value": "={{ $('Loop Over Directory Sources').item.json.url }}"
},
{
"name": "message",
"type": "string",
"value": "={{ $json.error?.message || $json.message }}"
},
{
"name": "occurredAt",
"type": "string",
"value": "={{ $now.toISO() }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "d39ec79b-c1fd-4ffc-beff-c72ea69ba9a8",
"name": "Log Extraction Error",
"type": "n8n-nodes-base.googleSheets",
"position": [
2000,
800
],
"parameters": {
"columns": {
"value": {},
"schema": [],
"mappingMode": "autoMapInputData",
"matchingColumns": []
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Extraction Errors"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/PLACEHOLDER/edit"
}
},
"typeVersion": 4.5
},
{
"id": "9c03fd7a-b571-4fe6-bfc6-29297538740e",
"name": "Clean HTML",
"type": "n8n-nodes-base.code",
"position": [
2000,
528
],
"parameters": {
"jsCode": "const MAX_CHARS = 100000;\nconst raw = $input.item.json.html || '';\nlet cleaned = raw\n .replace(/<script[\\s\\S]*?<\\/script>/gi, '')\n .replace(/<style[\\s\\S]*?<\\/style>/gi, '')\n .replace(/<svg[\\s\\S]*?<\\/svg>/gi, '')\n .replace(/<!--[\\s\\S]*?-->/g, '')\n .replace(/\\s+/g, ' ')\n .trim();\nif (cleaned.length > MAX_CHARS) {\n cleaned = cleaned.slice(0, MAX_CHARS);\n}\nreturn [{ json: { url: $input.item.json.url, cleanedHtml: cleaned } }];"
},
"typeVersion": 2
},
{
"id": "34752775-3dce-41d6-8856-abe78180529f",
"name": "Extract Business Fields",
"type": "@n8n/n8n-nodes-langchain.informationExtractor",
"position": [
2288,
528
],
"parameters": {
"text": "={{ $json.cleanedHtml }}",
"options": {},
"schemaType": "manual",
"inputSchema": "{\n \"type\": \"object\",\n \"properties\": {\n \"businesses\": {\n \"type\": \"array\",\n \"items\": {\n \"type\": \"object\",\n \"properties\": {\n \"companyName\": { \"type\": \"string\" },\n \"category\": { \"type\": \"string\" },\n \"description\": { \"type\": \"string\" },\n \"address\": { \"type\": \"string\" },\n \"postalCode\": { \"type\": \"string\" },\n \"city\": { \"type\": \"string\" },\n \"country\": { \"type\": \"string\" },\n \"phone\": { \"type\": \"string\" },\n \"email\": { \"type\": \"string\" },\n \"website\": { \"type\": \"string\" },\n \"openingHours\": { \"type\": \"string\" },\n \"rating\": { \"type\": \"string\" },\n \"reviews\": { \"type\": \"string\" },\n \"directoryUrl\": { \"type\": \"string\" }\n }\n }\n }\n }\n}"
},
"typeVersion": 1
},
{
"id": "cf3ebfca-e6bb-4def-a067-4761742cfa6a",
"name": "Extraction Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
2288,
720
],
"parameters": {
"model": "gpt-4o-mini",
"options": {}
},
"typeVersion": 1
},
{
"id": "d794dabd-6272-4ee5-816f-efb36b5df2f4",
"name": "Split Businesses",
"type": "n8n-nodes-base.splitOut",
"position": [
2560,
528
],
"parameters": {
"options": {},
"fieldToSplitOut": "output.businesses"
},
"typeVersion": 1
},
{
"id": "297ce3c0-53e7-48a8-b9ff-b0f4cc3d963e",
"name": "Save Businesses",
"type": "n8n-nodes-base.googleSheets",
"position": [
2848,
528
],
"parameters": {
"columns": {
"value": {},
"schema": [],
"mappingMode": "autoMapInputData",
"matchingColumns": [
"directoryUrl"
]
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Businesses"
},
"documentId": {
"__rl": true,
"mode": "url",
"value": "https://docs.google.com/spreadsheets/d/PLACEHOLDER/edit"
}
},
"typeVersion": 4.5
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "79249d45-248c-46a8-84f3-7642d53fed4f",
"nodeGroups": [],
"connections": {
"Clean HTML": {
"main": [
[
{
"node": "Extract Business Fields",
"type": "main",
"index": 0
}
]
]
},
"Get Page HTML": {
"main": [
[
{
"node": "Check for API Error",
"type": "main",
"index": 0
}
],
[
{
"node": "Build Error Record",
"type": "main",
"index": 0
}
]
]
},
"Save Businesses": {
"main": [
[
{
"node": "Loop Over Directory Sources",
"type": "main",
"index": 0
}
]
]
},
"Extraction Model": {
"ai_languageModel": [
[
{
"node": "Extract Business Fields",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Split Businesses": {
"main": [
[
{
"node": "Save Businesses",
"type": "main",
"index": 0
}
]
]
},
"Build Error Record": {
"main": [
[
{
"node": "Log Extraction Error",
"type": "main",
"index": 0
}
]
]
},
"Check for API Error": {
"main": [
[
{
"node": "Build Error Record",
"type": "main",
"index": 0
}
],
[
{
"node": "Clean HTML",
"type": "main",
"index": 0
}
]
]
},
"Log Extraction Error": {
"main": [
[
{
"node": "Loop Over Directory Sources",
"type": "main",
"index": 0
}
]
]
},
"Read Directory Sources": {
"main": [
[
{
"node": "Loop Over Directory Sources",
"type": "main",
"index": 0
}
]
]
},
"When Every Day at 12PM": {
"main": [
[
{
"node": "Read Directory Sources",
"type": "main",
"index": 0
}
]
]
},
"Extract Business Fields": {
"main": [
[
{
"node": "Split Businesses",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Directory Sources": {
"main": [
[
{
"node": "All Sources Processed",
"type": "main",
"index": 0
}
],
[
{
"node": "Get Page HTML",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow runs daily, reads business-directory page URLs from Google Sheets, captures fully rendered HTML via Snapshot Site, uses OpenAI (via n8n’s Information Extractor) to extract structured business listings, and writes results back to Google Sheets while logging…
Source: https://n8n.io/workflows/17515/ — 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.
This workflow runs daily, reads real estate listing URLs from Google Sheets, captures rendered HTML with Snapshot Site, extracts structured listing details using OpenAI via n8n’s Information Extractor
This workflow runs daily, captures fully rendered careers pages with Snapshot Site, uses an OpenAI chat model to extract structured job-offer fields, and writes the results to Google Sheets while logg
Sign up for Decodo — get better pricing here
Eliminate 90% of manual work in procurement by automating quote requests, response tracking, price extraction, and supplier follow-ups. This complete automation handles everything from sending persona
Transform your Gmail sent folder into a comprehensive, enriched contact database automatically. This workflow processes hundreds or thousands of sent emails, extracting and enriching contact informati