AutomationFlowsWeb Scraping › Pulsemosaic-v1.1-main

Pulsemosaic-v1.1-main

PulseMosaic-v1.1-Main. Uses rssFeedRead, httpRequest. Event-driven trigger; 18 nodes.

Event trigger★★★★☆ complexity18 nodesRSS Feed ReadHTTP Request
Web Scraping Trigger: Event Nodes: 18 Complexity: ★★★★☆ Added:

This workflow follows the HTTP Request → RSS Feed Read 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
{
  "name": "PulseMosaic-v1.1-Main",
  "nodes": [
    {
      "id": "a1-001",
      "name": "Manual Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        240,
        300
      ],
      "parameters": {}
    },
    {
      "id": "a1-002",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        240,
        420
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 4
            }
          ]
        }
      }
    },
    {
      "id": "a1-003",
      "name": "Init",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        360
      ],
      "parameters": {
        "jsCode": "return [{\n  json: {\n    topics: [\"ai safety\", \"model context protocol\", \"n8n automation\", \"vector databases\", \"prompt injection\"],\n    endpoints: {\n      stackoverflow: \"https://api.stackexchange.com/2.3/search/advanced?order=desc&sort=activity&site=stackoverflow&q=\",\n      github: \"https://api.github.com/search/repositories?q=\",\n      arxiv: \"https://export.arxiv.org/api/query?search_query=all:\"\n    },\n    rss_feeds: [\n      \"https://hnrss.org/frontpage\",\n      \"https://www.nasa.gov/rss/dyn/breaking_news.rss\"\n    ],\n    config: {\n      cache_ttl_minutes: 45,\n      dedupe_ttl_days: 10,\n      min_score: 5,\n      max_items_total: 30,\n      enable_llm_summary: true,\n      ZAI_API_KEY: $env.ZAI_API_KEY || \"\",\n      ZAI_BASE_URL: $env.ZAI_BASE_URL || \"https://api.z.ai/api/paas/v4\",\n      ZAI_MODEL: $env.ZAI_MODEL || \"glm-5\"\n    }\n  }\n}];"
      }
    },
    {
      "id": "a1-004",
      "name": "Split Out RSS",
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        680,
        240
      ],
      "parameters": {
        "fieldToSplitOut": "rss_feeds",
        "destinationFieldName": "rss_url"
      }
    },
    {
      "id": "a1-005",
      "name": "RSS Feed Read",
      "type": "n8n-nodes-base.rssFeedRead",
      "typeVersion": 1,
      "position": [
        900,
        240
      ],
      "parameters": {
        "url": "={{ $json.rss_url }}"
      }
    },
    {
      "id": "a1-006",
      "name": "Normalize RSS",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1120,
        240
      ],
      "parameters": {
        "jsCode": "const items = [];\nconst data = $input.all();\nfor (const item of data) {\n  const feedUrl = item.json.rss_url;\n  const entries = Array.isArray(item.json) ? item.json : (item.json.items || []);\n  for (const entry of entries) {\n    items.push({\n      json: {\n        source: \"rss\",\n        topic: null,\n        title: entry.title || \"Untitled\",\n        url: entry.link || entry.url || \"\",\n        published_at: entry.pubDate || entry.isoDate || null,\n        raw_score: 0,\n        score: 5,\n        summary_raw: entry.contentSnippet || entry.description || null,\n        summary: null,\n        meta: { author: entry.creator || null, venue: feedUrl, cached: false }\n      }\n    });\n  }\n}\nreturn items.length > 0 ? items : [{ json: { __empty: true, __stream: \"rss\" } }];"
      }
    },
    {
      "id": "a1-007",
      "name": "Split Out Topics",
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        680,
        480
      ],
      "parameters": {
        "fieldToSplitOut": "topics",
        "destinationFieldName": "topic"
      }
    },
    {
      "id": "a1-008",
      "name": "Prepare Worker Input",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        900,
        480
      ],
      "parameters": {
        "jsCode": "const init = $('Init').first().json;\nreturn [{\n  json: {\n    topic: $json.topic,\n    config: init.config,\n    endpoints: init.endpoints\n  }\n}];"
      }
    },
    {
      "id": "a1-009",
      "name": "Execute Worker",
      "type": "n8n-nodes-base.executeWorkflow",
      "typeVersion": 1,
      "position": [
        1120,
        480
      ],
      "parameters": {
        "workflowId": "PulseMosaic-v1.1-Worker",
        "options": {}
      }
    },
    {
      "id": "a1-010",
      "name": "Merge Streams",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 2.1,
      "position": [
        1340,
        360
      ],
      "parameters": {
        "mode": "append"
      }
    },
    {
      "id": "a1-011",
      "name": "Finalize",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1560,
        360
      ],
      "parameters": {
        "jsCode": "const st = $getWorkflowStaticData('global');\nif (!st.seen) st.seen = {};\nconst now = Date.now();\nconst config = $('Init').first().json.config;\nconst ttl = config.dedupe_ttl_days * 24 * 60 * 60 * 1000;\n\n// Prune old entries\nfor (const url in st.seen) {\n  if (now - st.seen[url] > ttl) delete st.seen[url];\n}\n\nlet candidates = 0;\nlet newItems = 0;\nlet cachedHits = 0;\nlet apiCalls = 0;\nlet failedCalls = 0;\nconst bySource = {};\nconst items = [];\n\nconst input = $input.all();\nfor (const item of input) {\n  const i = item.json;\n  if (i.__empty) continue;\n  candidates++;\n  if (!bySource[i.source]) bySource[i.source] = 0;\n  bySource[i.source]++;\n  if (i.meta && i.meta.cached) cachedHits++;\n  if (i.__meta) {\n    if (i.__meta.api_calls) apiCalls += i.__meta.api_calls;\n    if (i.__meta.failed_calls) failedCalls += i.__meta.failed_calls;\n  }\n  \n  if (i.score >= config.min_score && i.url && !st.seen[i.url]) {\n    st.seen[i.url] = now;\n    newItems++;\n    items.push(i);\n  }\n}\n\nitems.sort((a, b) => b.score - a.score);\nconst finalItems = items.slice(0, config.max_items_total);\n\nreturn [{\n  json: {\n    config,\n    stats: {\n      candidates_total: candidates,\n      new_items: newItems,\n      cached_hits: cachedHits,\n      api_calls: apiCalls,\n      failed_calls: failedCalls,\n      by_source: bySource\n    },\n    items: finalItems,\n    run_id: $execution.id,\n    generated_at: new Date().toISOString()\n  }\n}];"
      }
    },
    {
      "id": "a1-012",
      "name": "Check LLM",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1780,
        360
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true
          },
          "conditions": [
            {
              "leftValue": "={{ $json.config.enable_llm_summary }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            },
            {
              "leftValue": "={{ $json.config.ZAI_API_KEY }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "isNotEmpty"
              }
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "a1-013",
      "name": "Prepare LLM",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2000,
        260
      ],
      "parameters": {
        "jsCode": "const topItems = $json.items.slice(0, 12).map(i => ({ url: i.url, title: i.title, summary_raw: i.summary_raw || '' }));\nconst content = `Summarize these items in JSON format {\"summaries\":[{\"url\",\"summary\"}]}. Max 20 words per summary.\\n` + JSON.stringify(topItems);\nreturn [{\n  json: {\n    ...$json,\n    llm_payload: {\n      model: $json.config.ZAI_MODEL,\n      messages: [{ role: \"user\", content }],\n      temperature: 0.2,\n      response_format: { type: \"json_object\" }\n    }\n  }\n}];"
      }
    },
    {
      "id": "a1-014",
      "name": "HTTP LLM",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2220,
        260
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $json.config.ZAI_BASE_URL + '/chat/completions' }}",
        "authentication": "none",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "={{ 'Bearer ' + $json.config.ZAI_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { model: $json.llm_payload.model, messages: $json.llm_payload.messages, temperature: $json.llm_payload.temperature, response_format: $json.llm_payload.response_format } }}",
        "options": {
          "continueOnFail": true
        }
      }
    },
    {
      "id": "a1-015",
      "name": "Apply Summaries",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2440,
        260
      ],
      "parameters": {
        "jsCode": "const prev = $('Prepare LLM').first().json;\nlet items = prev.items;\nconst resp = $json;\n\nif (resp?.choices?.[0]?.message?.content) {\n  try {\n    const parsed = JSON.parse(resp.choices[0].message.content);\n    if (parsed.summaries && Array.isArray(parsed.summaries)) {\n      const map = new Map(parsed.summaries.map(s => [s.url, s.summary]));\n      items = items.map(i => ({ ...i, summary: map.get(i.url) || i.summary_raw || i.title }));\n    }\n  } catch (e) {\n    items = items.map(i => ({ ...i, summary: i.summary_raw || i.title }));\n  }\n} else {\n  items = items.map(i => ({ ...i, summary: i.summary_raw || i.title }));\n}\n\nreturn [{ json: { ...prev, items } }];"
      }
    },
    {
      "id": "a1-016",
      "name": "Fallback Summary",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2000,
        460
      ],
      "parameters": {
        "jsCode": "const items = $json.items.map(i => ({ ...i, summary: i.summary_raw || i.title }));\nreturn [{ json: { ...$json, items } }];"
      }
    },
    {
      "id": "a1-017",
      "name": "QA Lint",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2660,
        360
      ],
      "parameters": {
        "jsCode": "const data = $json;\nif (!Array.isArray(data.items)) throw new Error('Items must be array');\nfor (const i of data.items) {\n  if (!i.url || typeof i.url !== 'string') throw new Error('Invalid URL');\n  if (typeof i.score !== 'number') throw new Error('Invalid score');\n  const s = JSON.stringify(i);\n  if (s.includes('{{')) throw new Error('Contains mustache');\n}\nreturn [{ json: data }];"
      }
    },
    {
      "id": "a1-018",
      "name": "Build Final",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2880,
        360
      ],
      "parameters": {
        "jsCode": "const data = $json;\nconst lines = [];\nconst groups = {};\nfor (const i of data.items) {\n  if (!groups[i.source]) groups[i.source] = [];\n  groups[i.source].push(i);\n}\nfor (const [src, items] of Object.entries(groups)) {\n  lines.push(`## ${src}`);\n  for (const i of items) {\n    lines.push(`- [${i.title}](${i.url}) (score ${i.score})`);\n  }\n}\nreturn [{\n  json: {\n    run_id: data.run_id,\n    generated_at: data.generated_at,\n    topics: $('Init').first().json.topics,\n    stats: data.stats,\n    items: data.items,\n    digest_markdown: lines.join('\\n')\n  }\n}];"
      }
    }
  ],
  "connections": {
    "Manual Trigger": {
      "main": [
        [
          {
            "node": "Init",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Init",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Init": {
      "main": [
        [
          {
            "node": "Split Out RSS",
            "type": "main",
            "index": 0
          },
          {
            "node": "Split Out Topics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out RSS": {
      "main": [
        [
          {
            "node": "RSS Feed Read",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "RSS Feed Read": {
      "main": [
        [
          {
            "node": "Normalize RSS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize RSS": {
      "main": [
        [
          {
            "node": "Merge Streams",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out Topics": {
      "main": [
        [
          {
            "node": "Prepare Worker Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Worker Input": {
      "main": [
        [
          {
            "node": "Execute Worker",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute Worker": {
      "main": [
        [
          {
            "node": "Merge Streams",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge Streams": {
      "main": [
        [
          {
            "node": "Finalize",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Finalize": {
      "main": [
        [
          {
            "node": "Check LLM",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check LLM": {
      "main": [
        [
          {
            "node": "Prepare LLM",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Fallback Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare LLM": {
      "main": [
        [
          {
            "node": "HTTP LLM",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP LLM": {
      "main": [
        [
          {
            "node": "Apply Summaries",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Apply Summaries": {
      "main": [
        [
          {
            "node": "QA Lint",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fallback Summary": {
      "main": [
        [
          {
            "node": "QA Lint",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "QA Lint": {
      "main": [
        [
          {
            "node": "Build Final",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

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

About this workflow

PulseMosaic-v1.1-Main. Uses rssFeedRead, httpRequest. Event-driven trigger; 18 nodes.

Source: https://github.com/turtir-ai/n8n-workflow-studio/blob/main/public/fixtures/PulseMosaic-v1.1-Main.json — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

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

Web Scraping

GoldPulse-v1.1-Single. Uses rssFeedRead, httpRequest. Event-driven trigger; 33 nodes.

RSS Feed Read, HTTP Request
Web Scraping

AegisPulse-v1.1-Single. Uses rssFeedRead, httpRequest. Event-driven trigger; 31 nodes.

RSS Feed Read, HTTP Request
Web Scraping

QuorumPulse-v1.1-Single. Uses rssFeedRead, httpRequest. Event-driven trigger; 29 nodes.

RSS Feed Read, HTTP Request
Web Scraping

PulseMosaic-v2-Single. Uses rssFeedRead, httpRequest. Event-driven trigger; 25 nodes.

RSS Feed Read, HTTP Request
Web Scraping

PulseMosaic-v3.1-Single. Uses rssFeedRead, httpRequest. Event-driven trigger; 25 nodes.

RSS Feed Read, HTTP Request