AutomationFlowsAI & RAG › Aggregate Web Product Reviews and Sentiment with Decodo and Google Gemini

Aggregate Web Product Reviews and Sentiment with Decodo and Google Gemini

ByAbdullah Alshiekh @abdullah01 on n8n.io

Brands and marketers spend hours manually searching Google for product reviews. Reading through multiple websites to gauge general sentiment is tedious and inefficient. It is difficult to spot recurring customer complaints or praises without aggregating data. This workflow…

Event trigger★★★★☆ complexityAI-powered18 nodesGoogle Gemini ChatTelegram TriggerAgentTelegram@Decodo/N8N Nodes Decodo
AI & RAG Trigger: Event Nodes: 18 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow corresponds to n8n.io template #12068 — we link there as the canonical source.

This workflow follows the Agent → Google Gemini Chat 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 →

Download .json
{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "ba38a1f6-c843-466d-b531-bdd9e99cc3e5",
      "name": "Google Gemini Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        11504,
        112
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "9945101a-46f5-4bf5-a139-1474d939866e",
      "name": "Loop Over Items",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        11280,
        -96
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "9d264dc4-fb1a-4fc4-97ad-0868fe835053",
      "name": "Google Gemini Chat Model1",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        11792,
        -352
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "915e1239-4e78-413a-8901-7cf1cc86f032",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9872,
        -208
      ],
      "parameters": {
        "color": 7,
        "width": 528,
        "height": 272,
        "content": "## Input & Search\nReceives the company name from Telegram and performs a Google search for relevant review pages using Decodo.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "8eabfbdc-4925-4243-864e-b1b527f47d40",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        10432,
        -208
      ],
      "parameters": {
        "color": 7,
        "width": 544,
        "height": 272,
        "content": "## URL Extraction & Scraping\nExtracts all review URLs from search results, fetches each page, and cleans the content for analysis.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "e633c1a7-614e-469b-a623-0ef4f004c4be",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        11024,
        -192
      ],
      "parameters": {
        "color": 7,
        "width": 784,
        "height": 400,
        "content": "## Sentiment Analysis\nProcesses each review page individually to classify sentiment, assign a score, and extract key topics.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "52b39c26-9802-4993-988c-d1897aecfe31",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        11424,
        -608
      ],
      "parameters": {
        "color": 7,
        "width": 848,
        "height": 384,
        "content": "## Final Report\nCombines all sentiment results into a single summary and generates an actionable verdict sent via Telegram.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "a19c275a-6fb5-47d0-9762-fd266e7c0efa",
      "name": "Get Message",
      "type": "n8n-nodes-base.telegramTrigger",
      "position": [
        9904,
        -96
      ],
      "parameters": {
        "updates": [
          "message"
        ],
        "additionalFields": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "7f3c7e04-ce05-4ea3-9cb8-8d0b38aeb48c",
      "name": "Set Search Query",
      "type": "n8n-nodes-base.set",
      "position": [
        10064,
        -96
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "ba5eae0e-6e9e-4f2b-84cb-8e792aaa33ff",
              "name": "query",
              "type": "string",
              "value": "={{ $json.message.text }} reviews"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "dc96a499-b8f7-4e02-a685-aeb0049c4b3e",
      "name": "Extract URLs",
      "type": "n8n-nodes-base.code",
      "position": [
        10544,
        -96
      ],
      "parameters": {
        "jsCode": "// Recursively collect all URLs in ANY depth\n\nfunction extractUrls(obj, urls) {\n  if (!obj || typeof obj !== 'object') return;\n\n  // If object has a URL, collect it\n  if (obj.url && typeof obj.url === 'string' && obj.url.startsWith('http')) {\n    urls.push(obj.url);\n  }\n\n  // Recursively scan inner values\n  if (Array.isArray(obj)) {\n    for (const item of obj) extractUrls(item, urls);\n  } else {\n    for (const key of Object.keys(obj)) {\n      extractUrls(obj[key], urls);\n    }\n  }\n}\n\nlet urls = [];\n\n// extract from all decodo items\nfor (const item of items) {\n  extractUrls(item.json, urls);\n}\n\n// remove duplicates\nurls = [...new Set(urls)];\n\n// SKIP the first URL (Decodo's own search URL)\nurls = urls.slice(1);\n\n// return one item per URL\nreturn urls.map(url => ({\n  json: { url }\n}));\n"
      },
      "typeVersion": 2
    },
    {
      "id": "96148a7d-6376-4b98-ae06-cb66f547acee",
      "name": "Clean HTML",
      "type": "n8n-nodes-base.code",
      "position": [
        11072,
        -96
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const item = $input.item.json;\n\nlet html = \"\";\n\ntry {\n  html = item.results[0].content || \"\";\n} catch (e) {\n  html = \"\";\n}\n\nlet cleaned = html\n  .replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, \"\")\n  .replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, \"\")\n  .replace(/<noscript[^>]*>[\\s\\S]*?<\\/noscript>/gi, \"\")\n  .replace(/<\\/?[^>]+>/g, \" \")\n  .replace(/&nbsp;/gi, \" \")\n  .replace(/&amp;/gi, \"&\")\n  .replace(/&quot;/gi, '\"')\n  .replace(/&#39;/gi, \"'\")\n  .replace(/&lt;/gi, \"<\")\n  .replace(/&gt;/gi, \">\")\n  .replace(/\\s+/g, \" \")\n  .trim();\n\nreturn {\n  json: {\n    text_clean: cleaned\n  }\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "0f022eee-85c0-4c1f-8fd8-122c2ee04e64",
      "name": "Analyze Sentiment",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        11504,
        -80
      ],
      "parameters": {
        "text": "=You are a sentiment engine. Analyze the review below and return JSON.\n\nReview:\n{{ $json.text_clean }}\n\nReturn:\n{\n  \"sentiment\": \"positive | neutral | negative\",\n  \"score\": 0-1,\n  \"topics\": [\"...\"]\n}\n",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "0626aefd-a66c-4864-b76a-c583ef326115",
      "name": "Merge Results",
      "type": "n8n-nodes-base.code",
      "position": [
        11520,
        -528
      ],
      "parameters": {
        "jsCode": "// 1. Get all results from the previous AI Agent\nconst items = $input.all();\n\n// 2. Start a generic variable\nlet combinedData = \"Here is the combined analysis data gathered from the search results:\\n\\n\";\n\n// 3. Loop and stack the text\nitems.forEach((item, index) => {\n  // We grab the 'output' field from your previous AI node\n  combinedData += `--- Search Result ${index + 1} ---\\n`;\n  combinedData += (item.json.output || \"No data\") + \"\\n\\n\";\n});\n\n// 4. Return one single item to the next AI\nreturn [{\n  json: {\n    aggregated_report: combinedData\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "7bb60ae6-815e-49d7-b505-f8987653cff9",
      "name": "Draft Final Report",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        11792,
        -528
      ],
      "parameters": {
        "text": "=Role: You are a strict data analyst. You do not use flowery language. You only extract facts.\n\nData Source: {{ $json.aggregated_report }}\n\nConstraints:\n1. STRICTLY use the provided \"Data Source\" only. Do not invent features or sentiment.\n2. If the data mentions specific numbers or scores, quote them.\n3. Max length: 200 words.\n4. Format: Clean bullet points optimized for a Telegram message.\n\nOutput Format:\n**Sentiment Score:** [Extract the average score or dominant sentiment, e.g., \"Positive (0.95)\"]\n\n**Customer Voice:**\n\u2022 [One specific, distinct quote or observation from the data]\n\u2022 [Another specific observation]\n\n**Actionable Verdict:**\n[One sentence recommendation based on the data]",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 3
    },
    {
      "id": "48ff4042-3c0b-46c6-99ef-aeb1cd2d3daa",
      "name": "Reply to User",
      "type": "n8n-nodes-base.telegram",
      "position": [
        12144,
        -528
      ],
      "parameters": {
        "text": "={{ $json.output }}",
        "chatId": "={{ $('Get Message').first().json.message.chat.id }}",
        "additionalFields": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "e459df98-8bb9-4b7d-b836-8c34321d04ff",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9184,
        -464
      ],
      "parameters": {
        "width": 656,
        "height": 576,
        "content": "## How it works\nWhen a user sends a company name to the Telegram bot, the workflow automatically performs a Google search for review-related pages using Decodo. All discovered URLs are extracted dynamically, regardless of their depth in the search response. Each page is then fetched and cleaned by removing HTML, scripts, and unnecessary markup to produce readable text.\n\nThe workflow processes each cleaned review page individually using an AI sentiment agent. For every source, it determines the sentiment (positive, neutral, or negative), assigns a confidence score, and extracts key discussion topics. All individual analyses are then merged into a single aggregated dataset.\n\nA final AI analysis step reviews the combined data and generates a structured executive-style report. The report includes an overall sentiment score, direct customer voice observations, and a clear actionable verdict. The final result is sent back to the user via Telegram.\n\n## Setup steps\n1. Connect your Telegram Bot credentials.\n2. Add your Decodo API key for Google search and page fetching.\n3. Configure your Google Gemini API credentials.\n4. Deploy the workflow and send a company name via Telegram to start analysis.\n\n## Customization\nYou can adjust prompt strictness, sentiment thresholds, or limit the number of analyzed pages to control cost and depth.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "ea960533-c286-47ac-9afe-07692f4e2652",
      "name": "Google Search1",
      "type": "@decodo/n8n-nodes-decodo.decodo",
      "position": [
        10272,
        -96
      ],
      "parameters": {
        "query": "={{ $json.query }}",
        "operation": "google_search"
      },
      "credentials": {
        "decodoApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "44d6cfe0-b68e-4f26-830b-98b28a10a8ce",
      "name": "Scrape Page Content1",
      "type": "@decodo/n8n-nodes-decodo.decodo",
      "position": [
        10752,
        -96
      ],
      "parameters": {
        "url": "={{ $json.url }}"
      },
      "credentials": {
        "decodoApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Clean HTML": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Message": {
      "main": [
        [
          {
            "node": "Set Search Query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract URLs": {
      "main": [
        [
          {
            "node": "Scrape Page Content1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Results": {
      "main": [
        [
          {
            "node": "Draft Final Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Search1": {
      "main": [
        [
          {
            "node": "Extract URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Items": {
      "main": [
        [
          {
            "node": "Merge Results",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Analyze Sentiment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Search Query": {
      "main": [
        [
          {
            "node": "Google Search1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Sentiment": {
      "main": [
        [
          {
            "node": "Loop Over Items",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Draft Final Report": {
      "main": [
        [
          {
            "node": "Reply to User",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Scrape Page Content1": {
      "main": [
        [
          {
            "node": "Clean HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Analyze Sentiment",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model1": {
      "ai_languageModel": [
        [
          {
            "node": "Draft Final Report",
            "type": "ai_languageModel",
            "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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Brands and marketers spend hours manually searching Google for product reviews. Reading through multiple websites to gauge general sentiment is tedious and inefficient. It is difficult to spot recurring customer complaints or praises without aggregating data. This workflow…

Source: https://n8n.io/workflows/12068/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

Sign up for Decodo HERE for Discount

@Decodo/N8N Nodes Decodo, Memory Buffer Window, Telegram +5
AI & RAG

This workflow creates a multi-talented AI assistant named Simran that interacts with users via Telegram. It can handle text and voice messages, understand the user's intent, and perform various tasks.

MongoDB, Chain Llm, Google Gemini Chat +11
AI & RAG

This project is a template for building a complete academic virtual assistant using n8n. It connects to Telegram, answers frequently asked questions by querying MongoDB, keeps the community informed a

Telegram, MongoDB, Telegram Trigger +6
AI & RAG

Telegram Trigger receives incoming messages (text, voice, photo, document). Switch routes by message type to appropriate processors: Text → forwarded as-is. Voice → downloaded and sent to Transcribe a

Memory Buffer Window, Telegram Trigger, Telegram +12
AI & RAG

Transform your Telegram messenger into a powerful, multi-modal personal or team assistant. This n8n workflow creates an intelligent agent that can understand text, voice, images, and documents, and ta

Memory Buffer Window, Telegram Trigger, Telegram +10