{
  "name": "My workflow copy",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {}
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        -2592,
        -800
      ],
      "id": "fc73f795-40f9-4fa0-8352-9a5db4af1072",
      "name": "Schedule Trigger"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "8b6187de-5add-4b59-a035-e1a751d5f89b",
              "leftValue": "={{ $('提取微博内容').item.json.postTime }}",
              "rightValue": "={{ $json.lastDate }}",
              "operator": {
                "type": "dateTime",
                "operation": "after"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -272,
        -496
      ],
      "id": "0933d7fe-a862-4dab-a583-68974eef36c9",
      "name": "If"
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "n8n-nodes-base.dateTime",
      "typeVersion": 2,
      "position": [
        -240,
        -992
      ],
      "id": "1dbe4604-7563-41a4-85af-389b8a38ace6",
      "name": "CurrentDate"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=你是一位顶尖的数字舆情分析师，拥有社会学、传播学、消费者行为学和自然语言处理的复合背景。现在，请基于我提供的以下两项信息，执行一次深度、客观、结构化的舆情分析：\n\n### 工作流程\n\n1、先分析一下原始用户发帖内容：\"{{ $('微博内容拆分').item.json.content }}\"讨论的主体是不是目标产品名称：\"腾讯地图\"，如果是则继续执行下一步，如果不是，则本条情感分析结果直接标记为neutral\n2、判定整体情感极性（正面 / 中性 / 负面）、给出置信度（0–100%）、指出关键情感触发词或语句\n\n\n⚠️ 注意事项：\n\n不臆测未在文本中体现的信息\n避免主观情绪化语言，保持专业中立\n若发帖内容模糊或信息不足，请明确说明“信息不足，无法准确判断”，并指出缺失的关键要素\n\n请严格按照以下 JSON Schema 输出结果，不得包含任何额外文本、解释、Markdown 或注释。\n\n  \"sentiment\": {\n    \"polarity\": \"positive|neutral|negative\",\n    \"confidence\": 0.0,\n    \"trigger_phrases\": [\"string\"],\n    \"summary\":[\"string\"]\n  }",
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 3,
      "position": [
        224,
        -336
      ],
      "id": "a56fce21-3cc0-4a5a-9a47-2d5d040ef666",
      "name": "AI Agent1"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "python",
        "pythonCode": "# Loop over input items and add a new field called 'myNewField' to the JSON of each one\nimport json;\n\nreturn json.loads(_input.item.json.output)\n"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        672,
        -288
      ],
      "id": "da8b0a05-66e8-45a3-bdb7-4f7fb256c3a7",
      "name": "Code in Python (Beta)"
    },
    {
      "parameters": {
        "operation": "update",
        "table": {
          "__rl": true,
          "value": "user_sentiment_posts",
          "mode": "list",
          "cachedResultName": "user_sentiment_posts"
        },
        "dataMode": "defineBelow",
        "columnToMatchOn": "post_id",
        "valueToMatchOn": "={{ $('微博内容拆分').item.json.weiboId }}",
        "valuesToSend": {
          "values": [
            {
              "column": "sentiment_label",
              "value": "={{ $json.sentiment.polarity }}"
            },
            {
              "column": "keywords",
              "value": "=\"{{ $json.sentiment.trigger_phrases }}\""
            },
            {
              "column": "summary",
              "value": "={{ $json.sentiment.summary }}"
            },
            {
              "column": "confidence",
              "value": "={{ $json.sentiment.confidence }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.mySql",
      "typeVersion": 2.5,
      "position": [
        992,
        -80
      ],
      "id": "5324dbcf-fa60-454e-8a05-b0b06a3abf69",
      "name": "Update rows in a table1",
      "credentials": {
        "mySql": {
          "id": "nyt5Hl6LlXn7qrOi",
          "name": "MySQL account"
        }
      }
    },
    {
      "parameters": {
        "operation": "upsert",
        "table": {
          "__rl": true,
          "value": "user_sentiment_posts",
          "mode": "list",
          "cachedResultName": "user_sentiment_posts"
        },
        "dataMode": "defineBelow",
        "columnToMatchOn": "post_id",
        "valueToMatchOn": "={{ $('提取微博内容').item.json.weiboId }}",
        "valuesToSend": {
          "values": [
            {
              "column": "username",
              "value": "={{ $('提取微博内容').item.json.username }}"
            },
            {
              "column": "platform",
              "value": "weibo"
            },
            {
              "column": "post_url",
              "value": "={{ $('提取微博内容').item.json.postTimeLink }}"
            },
            {
              "column": "content",
              "value": "={{ $('提取微博内容').item.json.content }}"
            },
            {
              "column": "post_time",
              "value": "={{ $('提取微博内容').item.json.postTime }}"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.mySql",
      "typeVersion": 2.5,
      "position": [
        512,
        -672
      ],
      "id": "5c672121-a812-4d01-990f-87aa9d72b967",
      "name": "Insert or update rows in a table1",
      "credentials": {
        "mySql": {
          "id": "nyt5Hl6LlXn7qrOi",
          "name": "MySQL account"
        }
      }
    },
    {
      "parameters": {
        "model": "deepseek-r1:7b",
        "options": {
          "format": "json"
        }
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOllama",
      "typeVersion": 1,
      "position": [
        192,
        -160
      ],
      "id": "6cd6af68-3628-4e56-a3a3-c64620422a6c",
      "name": "Ollama Chat Model",
      "credentials": {
        "ollamaApi": {
          "id": "9nWs7XUJz2fyGXDw",
          "name": "Ollama account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "return [\n  { json: { page: 1 } },\n  { json: { page: 2 } },\n  { json: { page: 3 } },\n  { json: { page: 4 } },\n  { json: { page: 5 } }\n];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -2144,
        -752
      ],
      "id": "f26b5c1e-4e51-427f-8a4f-370477279259",
      "name": "Code in JavaScript"
    },
    {
      "parameters": {
        "fieldToSplitOut": "content",
        "include": "selectedOtherFields",
        "fieldsToInclude": "weiboId",
        "options": {}
      },
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        -704,
        -896
      ],
      "id": "dbb9aea4-1b31-4b09-bf41-162f5eb4e1ad",
      "name": "微博内容拆分"
    },
    {
      "parameters": {
        "operation": "scrape",
        "url": "",
        "additionalFields": {
          "customProperties": "={\n  \"url\": \"https://s.weibo.com/realtime?q=腾讯地图&rd=realtime&tw=realtime&Refer=weibo_realtime&page= {{ $json.page }}\",\n  \"formats\": [\n   {\n      \"type\": \"markdown\"\n    }\n  ],\n  \"onlyMainContent\": true,\n \n  \"maxAge\": 172800000,\n  \"headers\": {\n    \"Cookie\": \"XSRF-TOKEN=Vpllcum96mWkoZUnTDUPd3Lq; _s_tentry=passport.weibo.com; Apache=8041587221385.712.1763972929386; SINAGLOBAL=8041587221385.712.1763972929386; ULV=1763972929387:1:1:1:8041587221385.712.1763972929386:; wb_view_log_3093094175=1512*9822; webim_unReadCount=%7B%22time%22%3A1764559323255%2C%22dm_pub_total%22%3A0%2C%22chat_group_client%22%3A0%2C%22chat_group_notice%22%3A0%2C%22allcountNum%22%3A8%2C%22msgbox%22%3A0%7D; ALF=1767174963; SUB=_2A25EKRZhDeRhGeVO4lER-SrNzDmIHXVnRxeprDV8PUJbkNAbLVbikW1NTXwxtwuVrr8SU2IXt6B0GARRr09VdsZ0; SUBP=0033WrSXqPxfM725Ws9jqgMF55529P9D9WF_Da88lxSPdGiWKJiIfHaO5JpX5KMhUgL.Foe71Ke71KBpS0-2dJLoI7pzdcpjIPShIGHydJMt; WBPSESS=8CcMmWu8i823I0m0tutp2OQ7eG_tRsF3Zv_q6LNsZD8mircqph1Jx7VmyZDFN9Nia7NBhC3L7v-iGnMsbRlKFmVetQWxu_QYMSKrit3hiFpoeC5ZsD7B5RtfiX41vFfNBlr7zP5Xg2R69xGLjuwA3A==\",\n    \"User-Agent\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36\"\n  },\n  \"waitFor\": 0,\n  \"mobile\": false,\n  \"skipTlsVerification\": true,\n  \"timeout\": 100000,\n  \"parsers\": [\n    \"pdf\"\n  ],\n  \"location\": {\n    \"country\": \"US\",\n    \"languages\": [\n      \"en-US\"\n    ]\n  },\n  \"removeBase64Images\": false,\n  \"blockAds\": true,\n  \"proxy\": \"auto\",\n  \"storeInCache\": true,\n  \"zeroDataRetention\": false\n}"
        },
        "useCustomBody": true,
        "requestOptions": {}
      },
      "type": "@mendable/n8n-nodes-firecrawl.firecrawl",
      "typeVersion": 1,
      "position": [
        -1696,
        -784
      ],
      "id": "b012bcdc-d1e1-41d7-beb6-c17a515e1c74",
      "name": "爬取网页信息",
      "credentials": {
        "firecrawlApi": {
          "id": "Gx82yvk4YGHTvQee",
          "name": "Firecrawl account"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Loop over input items and add a new field called 'myNewField' to the JSON of each one\n// 基准当前时间\n  const offsetHours = 8; \n  const now = new Date();\n  const utc = now.getTime() + (now.getTimezoneOffset() * 60000);\n  const NOW = new Date(utc + (offsetHours * 3600000));\n\n  function parseWeiboTime(raw) {\n    raw = raw.trim();\n\n    // 1. 已是标准格式：2023-10-23 21:48\n    if (/^\\d{4}-\\d{2}-\\d{2}\\s+\\d{1,2}:\\d{2}$/.test(raw)) {\n      return raw + ':00';\n    }\n\n    // 2. X秒前\n  const secMatch = raw.match(/^(\\d+)秒前$/);\n  if (secMatch) {\n    const secs = parseInt(secMatch[1], 10);\n    const dt = new Date(NOW.getTime() - secs * 1000);\n    return dt.toISOString().replace('T', ' ').substring(0, 19);\n  }\n\n  // 3. X分钟前 \n  const minMatch = raw.match(/^(\\d+)分钟前$/);\n  if (minMatch) {\n    const mins = parseInt(minMatch[1], 10);\n    const dt = new Date(NOW.getTime() - mins * 60 * 1000);\n    return dt.toISOString().replace('T', ' ').substring(0, 19);\n  }\n\n  // 4. X小时前\n  const hourMatch = raw.match(/^(\\d+)小时前$/);\n  if (hourMatch) {\n    const hours = parseInt(hourMatch[1], 10);\n    const dt = new Date(NOW.getTime() - hours * 60 * 60 * 1000);\n    return dt.toISOString().replace('T', ' ').substring(0, 19);\n  }\n\n    // 5. 今天HH:mm\n    const todayMatch = raw.match(/^今天(\\d{1,2}):(\\d{2})$/);\n    if (todayMatch) {\n      const h = parseInt(todayMatch[1], 10);\n      const m = parseInt(todayMatch[2], 10);\n      const dt = new Date(NOW);\n      dt.setHours(h + 8, m, 0, 0);\n      return dt.toISOString().replace('T', ' ').substring(0, 19);\n    }\n\n    // 6. MM月DD日 HH:mm\n    const monthDayMatch = raw.match(/^(\\d{1,2})月(\\d{1,2})日\\s+(\\d{1,2}):(\\d{2})$/);\n    if (monthDayMatch) {\n      const month = parseInt(monthDayMatch[1], 10) - 1; // JS 月份从 0 开始\n      const day = parseInt(monthDayMatch[2], 10);\n      const h = parseInt(monthDayMatch[3], 10) + 8;\n      const m = parseInt(monthDayMatch[4], 10);\n\n      let year = NOW.getFullYear();\n      const candidate = new Date(year, month, day, h, m, 0);\n\n      // 如果解析出的日期比 NOW 还晚（比如 12月2日 在 12月1日看就是未来），则减一年\n      if (candidate > NOW) {\n        year -= 1;\n      }\n\n      const dt = new Date(year, month, day, h, m, 0);\n      const pad = n => n.toString().padStart(2, '0');\n      return `${dt.getFullYear()}-${pad(dt.getMonth() + 1)}-${pad(dt.getDate())} ${pad(dt.getHours())}:${pad(dt.getMinutes())}:00`;\n    }\n\n    // 5. 无法识别，返回原始 + 补00秒（保守处理）\n    if (/^\\d{4}-\\d{2}-\\d{2}\\s+\\d{1,2}:\\d{2}:\\d{2}$/.test(raw)) {\n      return raw;\n    }\n    // 兜底：尝试直接补 :00\n    if (raw.includes(':') && !raw.includes(':00')) {\n      return raw + ':00';\n    }\n    return raw; // 保留原样\n  }\nconst posts = [];\n\nfor (const item of $input.all()) {\n  // === 主提取逻辑 ===\n  const blocks = item.json.data.markdown.split(/\\n-\\s*转发\\s*\\n/);\n  \n  const inlineImageRegex = /\\[\\]\\((https?:\\/\\/[^\\s\\)]+\\.(?:jpg|jpeg|png|gif))\\)/gi;\n\n  for (const block of blocks) {\n    const userPattern = /\\[!\\[.*?\\]\\((https?:\\/\\/[^\\s\\)]+)\\)\\]\\((https?:\\/\\/[^\\s\\)]+)\\)\\s*\\n\\s*\\[([^\\]]+)\\]\\((https?:\\/\\/[^\\s\\)]+)\\)(?:\\s*\\n\\s*\\[!\\[.*?\\]\\(.*?\\)\\])?\\s*\\n\\s*\\[([^\\]]+)\\]\\((https?:\\/\\/[^\\s\\)]+)\\)/;\n    const userMatch = block.match(userPattern);\n    if (!userMatch) continue;\n\n    const username = userMatch[3];\n    const userLink = userMatch[4];\n    const rawPostTime = userMatch[5];\n    const postTimeLink = userMatch[6];\n\n    // ✅ 标准化时间\n    const postTime = parseWeiboTime(rawPostTime);\n\n    let rawContent = block.slice(userMatch.index + userMatch[0].length).trim();\n    const cutOffIndex = rawContent.search(/\\n-\\s*(\\[\\]\\(https?:\\/\\/.*?\\.(?:jpg|png|gif)\\)|\\d+|转发|评论|赞)/i);\n    if (cutOffIndex !== -1) {\n      rawContent = rawContent.slice(0, cutOffIndex).trim();\n    }\n\n    let content = rawContent\n      .replace(/\\[展开 _c_\\]\\([^)]*\\)/g, '')\n      .replace(/收起 _d_/g, '')\n      .replace(/\\s+/g, ' ')\n      .trim();\n\n    content = content.replace(/^来自\\s*\\[[^\\]]+\\]\\([^)]*\\)\\s*/, '').trim();\n\n    // 提取并移除内联图片\n    const inlineImages = [];\n    let imgMatch;\n    while ((imgMatch = inlineImageRegex.exec(content)) !== null) {\n      inlineImages.push(imgMatch[1]);\n    }\n    content = content.replace(inlineImageRegex, '').replace(/\\s+/g, ' ').trim();\n\n    // 提取列表图片\n    const listImages = [];\n    const listImageRegex = /-\\s*\\[\\]\\((https?:\\/\\/[^\\s\\)]+\\.(?:jpg|jpeg|png|gif))\\)/gi;\n    let listMatch;\n    while ((listMatch = listImageRegex.exec(block)) !== null) {\n      listImages.push(listMatch[1]);\n    }\n\n    const imageUrls = [...new Set([...inlineImages, ...listImages])];\n\n    const hashtags = [...new Set(\n      content.match(/#([^#\\s]+)#?/g)?.map(tag => tag.replace(/^#|#$| $/g, '')) || []\n    )];\n\n    // ✅ 新增：从微博链接提取唯一ID\n  function extractWeiboId(url) {\n    try {\n      const cleanUrl = url.split('?')[0]; // 移除查询参数\n      const parts = cleanUrl.split('/');\n      return parts[parts.length - 1] || null;\n    } catch (e) {\n      return null;\n    }\n  }\n\n    const weiboId = extractWeiboId(postTimeLink); // ✅ 提取唯一ID\n\n    posts.push({\n      weiboId,\n      username,\n      userLink,\n      postTime,          // ← 已标准化为 YYYY-MM-DD HH:mm:ss\n      postTimeLink,\n      content,\n      imageUrls,\n      hashtags\n    });\n  }\n\n}\n\nreturn posts;"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1216,
        -832
      ],
      "id": "ba68cac4-9a9f-4941-95b7-64f56e4fd07c",
      "name": "提取微博内容"
    },
    {
      "parameters": {
        "operation": "subtractFromDate",
        "magnitude": "={{ $json.currentDate }}",
        "duration": 1,
        "outputFieldName": "lastDate",
        "options": {}
      },
      "type": "n8n-nodes-base.dateTime",
      "typeVersion": 2,
      "position": [
        -32,
        -992
      ],
      "id": "74c29dfd-9b1a-4b9d-b8f5-c3a60f68df7d",
      "name": "LastDate"
    }
  ],
  "pinData": {},
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If": {
      "main": [
        [
          {
            "node": "AI Agent1",
            "type": "main",
            "index": 0
          },
          {
            "node": "Insert or update rows in a table1",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "CurrentDate": {
      "main": [
        [
          {
            "node": "LastDate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent1": {
      "main": [
        [
          {
            "node": "Code in Python (Beta)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in Python (Beta)": {
      "main": [
        [
          {
            "node": "Update rows in a table1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update rows in a table1": {
      "main": [
        []
      ]
    },
    "Insert or update rows in a table1": {
      "main": [
        []
      ]
    },
    "Ollama Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent1",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript": {
      "main": [
        [
          {
            "node": "爬取网页信息",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "微博内容拆分": {
      "main": [
        [
          {
            "node": "CurrentDate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "爬取网页信息": {
      "main": [
        [
          {
            "node": "提取微博内容",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "提取微博内容": {
      "main": [
        [
          {
            "node": "微博内容拆分",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LastDate": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "timezone": "Asia/Shanghai",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": false
  },
  "versionId": "5513217d-a0d9-4116-8aa2-45813bf3aef9",
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "e2f1810a3d343b4cb91647ff32c3720de7e17c264aa766bc8c994b1cbb80b056"
  },
  "id": "Otf1gnbMitsLtmK2",
  "tags": []
}