8
n8n 中文网amn8n.com

竞争对手内容差距分析器:自动化网站主题映射

高级

这是一个Miscellaneous, AI Summarization, Multimodal AI领域的自动化工作流,包含 30 个节点。主要使用 If, Set, Code, Gmail, Merge 等节点。 使用Gemini AI、Apify和Google Sheets分析竞争对手内容差距

前置要求
  • Google 账号和 Gmail API 凭证
  • HTTP Webhook 端点(n8n 会自动生成)
  • 可能需要目标 API 的认证凭证
  • Google Sheets API 凭证
  • Google Gemini API Key
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "MVDc7al8dnpBvlD2",
  "meta": {
    "instanceId": "d1dc073e8e3059a23e2730f69cb1b90065a2ac39039fea0727fdf9bee77a9131",
    "templateCredsSetupCompleted": true
  },
  "name": "竞争对手内容差距分析器:自动化网站主题映射",
  "tags": [],
  "nodes": [
    {
      "id": "8d912fd2-61b3-41df-9631-c7eaf6050bc9",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1696,
        -32
      ],
      "parameters": {
        "text": "=You are a competitive content analyst.\nAnalyze the competitor website page provided in `markdown`.\n\nReturn ONLY valid JSON matching this schema:\n\n{\n  \"page_url\": \"string\",\n  \"title\": \"string\",\n  \"content_type\": \"string\",\n  \"main_topics\": [\n    {\n      \"topic\": \"string\",\n      \"level\": \"number (must start at 1 for top-level)\",\n      \"subtopics\": [\n        {\n          \"topic\": \"string\",\n          \"level\": \"number (increment by 1 for each deeper heading)\",\n          \"subtopics\": []\n        }\n      ]\n    }\n  ],\n  \"key_entities\": [\"string\"],\n  \"depth_score_1_to_5\": \"number\"\n}\n\nRules:\n- Strict JSON output. No extra text, no explanations.\n- content_type: choose from [\"landing\",\"blog\",\"service\",\"about\"].\n- \"main_topics\" must always start at level 1.\n- Subtopics increment level by +1 relative to their parent.\n- If no subtopics, return [].\n- Always close JSON properly.\n\nContent to analyze:\n{{ $json[\"markdown\"] }}\n",
        "options": {},
        "promptType": "define"
      },
      "typeVersion": 2.2
    },
    {
      "id": "d09b94e5-09d8-4477-9034-843079795ea3",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        80,
        -928
      ],
      "parameters": {
        "width": 304,
        "height": 304,
        "content": "## 竞争对手内容差距分析器:自动化网站主题映射"
      },
      "typeVersion": 1
    },
    {
      "id": "a9a4fa59-bc0a-4175-afd8-46855bb271d3",
      "name": "准备 Apify 输入",
      "type": "n8n-nodes-base.code",
      "position": [
        576,
        64
      ],
      "parameters": {
        "jsCode": "// 1) Read POST body (supports Webhook { body: {...} } or raw JSON)\nconst src = items?.[0]?.json ?? {};\nconst bodyRaw = (src.body ?? src ?? {});\nconst body = (typeof bodyRaw === 'string')\n  ? (() => { try { return JSON.parse(bodyRaw); } catch { return {}; } })()\n  : bodyRaw;\n\n// 2) Helpers\nfunction normalizeDomain(input = '') {\n  const s = String(input).trim();\n  if (!s) return '';\n  return s\n    .replace(/^https?:\\/\\//i, '') // remove protocol\n    .replace(/^www\\./i, '')       // remove www\n    .replace(/\\/+$/g, '');        // remove trailing slashes\n}\nfunction splitList(input) {\n  if (Array.isArray(input)) return input.map(String);\n  return String(input || '')\n    .split(/[,\\n\\r;]+/)\n    .map(s => s.trim())\n    .filter(Boolean);\n}\nfunction toGlobs(list) {\n  return splitList(list).map(p => (p.endsWith('*') ? p : `${p}*`));\n}\n\n// 3) Map incoming fields\nconst row = {\n  client_name:   body.client_name ?? body.name ?? '',\n  client_domain: body.client_domain ?? '',\n  competitors:   body.competitors ?? body.competitor ?? body.url ?? '',\n  include_paths: body.include_paths ?? '',\n  exclude_paths: body.exclude_paths ?? '',\n  max_pages:     body.max_pages ?? '',\n  crawl_depth:   body.crawl_depth ?? '',\n  notify_email:  body.notify_email ?? body.email ?? '',\n  processed:     (typeof body.processed === 'boolean'\n                    ? body.processed\n                    : String(body.processed ?? '').toLowerCase() === 'true'),\n  rowIndex:      body.rowIndex ?? null,\n};\n\n// 4) Early exit if sender marked it processed\nif (row.processed === true) return [];\n\n// 5) Build Apify fields\nconst competitorDomain = normalizeDomain(row.competitors);\nconst clientDomain     = normalizeDomain(row.client_domain);\n\nconst startUrls = competitorDomain\n  ? [{ url: `https://${competitorDomain}/`, method: 'GET' }]\n  : [];\n\nif (!startUrls.length) {\n  return [{ json: { error: 'NO_START_URLS', raw_competitors: row.competitors || '' } }];\n}\n\nconst includeArr = toGlobs(row.include_paths);\nconst excludeArr = toGlobs(row.exclude_paths);\n\nconst maxPages   = Number(row.max_pages)   || 20;\nconst crawlDepth = Number(row.crawl_depth) || 1;\n\n// 6) Output for next nodes\nreturn [{\n  json: {\n    client_name:       String(row.client_name || ''),\n    client_domain:     clientDomain,\n    competitor_domain: competitorDomain,\n    startUrls,\n    includeArr,\n    excludeArr,\n    max_pages_num:     maxPages,\n    crawl_depth_num:   crawlDepth,\n    notify_email:      String(row.notify_email || ''),\n    rowIndex:          row.rowIndex,\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "82bce0ee-a888-4e7e-9dc6-47bdcf3e18e9",
      "name": "爬取竞争对手网站",
      "type": "@apify/n8n-nodes-apify.apify",
      "position": [
        1024,
        -32
      ],
      "parameters": {
        "actorId": {
          "__rl": true,
          "mode": "list",
          "value": "aYG0l9s7dbB7j3gbS",
          "cachedResultUrl": "https://console.apify.com/actors/aYG0l9s7dbB7j3gbS/input",
          "cachedResultName": "Website Content Crawler (apify/website-content-crawler)"
        },
        "timeout": {},
        "customBody": "={{ JSON.stringify({\n  crawlerType: \"cheerio\",\n  startUrls: $json.startUrls,                 \n  useSitemaps: false,\n  maxDepth: $json.crawl_depth_num  || 0,\n  maxRequestsPerCrawl: $json.max_pages_num || 20,\nmaxConcurrency:1,\n  respectRobotsTxtFile: true,\n  ignoreHttpsErrors: true,\n  proxyConfiguration: { useApifyProxy: true },\n  removeCookieWarnings: true,\n  removeElementsCssSelector: \"nav, footer, script, style, noscript, svg, img[src^='data:']\",\n  blockMedia: true,\n  saveMarkdown: true,\n  saveHtml: false,\n  saveFiles: false,\n  saveScreenshots: false\n}) }}\n"
      },
      "credentials": {
        "apifyApi": {
          "id": "Uzk0v4R6yNh8OxbO",
          "name": "Apify account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "d7eb2fc0-464d-450e-9be9-ac7e2e9488c3",
      "name": "获取爬取数据集",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1248,
        -32
      ],
      "parameters": {
        "url": "={{   \"https://api.apify.com/v2/datasets/\"   + ($json.data?.defaultDatasetId || $json.defaultDatasetId || \"auD8MlRMr6mZQpRS1\")   + \"/items?clean=true&format=json&offset=0&limit=1\" }}",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "9e970002-63b5-4f1b-8189-b59eb92a9471",
      "name": "提取页面元数据",
      "type": "n8n-nodes-base.code",
      "position": [
        1472,
        -32
      ],
      "parameters": {
        "jsCode": "// Renamed variables for clarity; behavior unchanged.\nconst sourceItem = items?.[0]?.json;\nif (!sourceItem) return [];\n\nconst markdownText = String(sourceItem.markdown || sourceItem.text || '');\nconst wordCount = markdownText.trim().split(/\\s+/).filter(Boolean).length;\n\nfunction getTitleFromMarkdown(markdown, url) {\n  const lines = markdown.split(/\\r?\\n/).map(line => line.trim());\n\n  const h1Line = lines.find(line => /^#\\s+/.test(line));\n  if (h1Line) return h1Line.replace(/^#\\s+/, '').trim();\n\n  const firstMeaningfulLine = lines.find(line => line.length > 0 && line.length < 120);\n  if (firstMeaningfulLine) return firstMeaningfulLine;\n\n  try {\n    const parsedUrl = new URL(url);\n    const pathSegments = parsedUrl.pathname.split('/').filter(Boolean);\n    return (pathSegments.pop() || parsedUrl.hostname).replace(/[-_]/g, ' ');\n  } catch {\n    return url;\n  }\n}\n\nreturn [{\n  json: {\n    page_url: sourceItem.url,\n    title: getTitleFromMarkdown(markdownText, sourceItem.url),\n    word_count: wordCount,\n    reading_time_min: Math.max(1, Math.ceil(wordCount / 200)),\n    excerpt: markdownText.slice(0, 600),\n    markdown: markdownText\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "7e929b2d-89d8-4a36-a13e-f82ae18f3f03",
      "name": "分析页面内容",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1776,
        192
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "googlePalmApi": {
          "id": "qQGrvqnSPqWFH6I6",
          "name": "Google Gemini(PaLM) Api account 5"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "f8c1643b-4382-49c0-9ec3-35876bd52eb2",
      "name": "解析和规范化 Gemini JSON",
      "type": "n8n-nodes-base.code",
      "position": [
        2048,
        -32
      ],
      "parameters": {
        "jsCode": "\nfunction stripCodeFences(text = \"\") {\n  \n  return String(text)\n    .replace(/^\\s*```(?:json)?\\s*/i, \"\")\n    .replace(/\\s*```\\s*$/i, \"\")\n    .trim();\n}\n\nfunction safeParseModelJson(payload) {\n  \n  if (payload && (payload.page_url || payload.main_topics || payload.key_entities)) return payload;\n\n  let raw = payload?.output ?? payload;\n  if (typeof raw !== \"string\") raw = JSON.stringify(raw ?? {});\n  raw = stripCodeFences(raw);\n\n  let obj;\n  try { obj = JSON.parse(raw); } catch {}\n\n\n  if (Array.isArray(obj)) {\n    if (obj[0]?.output) {\n      try { obj = JSON.parse(stripCodeFences(obj[0].output)); } catch { obj = obj[0] || {}; }\n    } else {\n      obj = obj[0] || {};\n    }\n  } else if (obj && typeof obj === \"object\" && typeof obj.output === \"string\") {\n    try { obj = JSON.parse(stripCodeFences(obj.output)); } catch {}\n  }\n\n  return obj || {};\n}\n\nfunction normalizeTopicTree(nodes = [], level = 1) {\n  return nodes.map(node => ({\n    topic: String(node?.topic ?? \"\"),\n    level: Number(node?.level) || level,\n    subtopics: normalizeTopicTree(node?.subtopics || [], (Number(node?.level) || level) + 1),\n  }));\n}\n\nfunction toTopicTree(modelObj) {\n  let t = modelObj.main_topics;\n  if (typeof t === \"string\") {\n    try { t = JSON.parse(t); } catch { t = []; }\n  }\n  if (!Array.isArray(t)) t = [];\n  return normalizeTopicTree(t, 1);\n}\n\nfunction toBulletLines(tree, indent = \"\") {\n  const lines = [];\n  for (const node of tree) {\n    const text = String(node.topic || \"\").trim();\n    if (text) lines.push(`${indent}- ${text}`);\n    if (node.subtopics?.length) lines.push(...toBulletLines(node.subtopics, indent + \"  \"));\n  }\n  return lines;\n}\n\nfunction parseEntitiesToBullets(value) {\n  let arr = value;\n\n  if (typeof arr === \"string\") {\n    const s = arr.trim();\n    if (/^- /m.test(s)) {\n      // Already looks like \"- item\" lines\n      arr = s.split(/\\r?\\n/).map(l => l.replace(/^\\s*-\\s*/, \"\").trim()).filter(Boolean);\n    } else {\n      try { arr = JSON.parse(s); }\n      catch { arr = s.split(\",\").map(x => x.trim()).filter(Boolean); }\n    }\n  }\n\n  if (!Array.isArray(arr)) arr = [];\n  return arr.length ? arr.map(e => `- ${String(e)}`).join(\"\\n\") : \"- (none)\";\n}\n\nfunction chooseUrl(...candidates) {\n  for (const c of candidates) {\n    const v = String(c ?? \"\").trim();\n    if (v && !/^n\\/?a$/i.test(v)) return v; // reject \"N/A\", \"n/a\"\n  }\n  return \"\";\n}\n\n// ---------- main ----------\nconst inputPayload = items?.[0]?.json ?? {};\nconst modelObj     = safeParseModelJson(inputPayload);\n\n// Build bullets\nconst topicTree        = toTopicTree(modelObj);\nconst mainTopicsFlat   = topicTree.length ? toBulletLines(topicTree).join(\"\\n\") : \"- (none)\";\nconst keyEntitiesFlat  = parseEntitiesToBullets(modelObj.key_entities);\n\n// Prefer the true URL from \"Extract Page Metadata\"\nlet urlFromMeta;\ntry {\n  urlFromMeta =\n    $items(\"Extract Page Metadata\", 0, 0)?.json?.page_url ||\n    $items(\"Extract Page Metadata\", 0, 0)?.json?.url;\n} catch {}\n\nconst pageUrl = chooseUrl(\n  urlFromMeta,\n  inputPayload?.page_url,\n  modelObj?.page_url\n);\n\n// Output\nreturn [{\n  json: {\n    page_url: pageUrl,\n    main_topics_flat: mainTopicsFlat,\n    key_entities_flat: keyEntitiesFlat,\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "e5c2649e-d2ea-49a0-a3ad-5f9916843da2",
      "name": "推导工作表名称",
      "type": "n8n-nodes-base.code",
      "position": [
        2272,
        -32
      ],
      "parameters": {
        "jsCode": "\nfunction toSheetName(raw) {\n  let s = String(raw || '').trim();\n  if (!s) return 'Page';\n  if (!/^https?:\\/\\//i.test(s)) s = 'https://' + s;\n\n  try {\n    const u = new URL(s);\n    const host = u.hostname.replace(/^www\\./, '');\n    let path = (u.pathname || '/')\n      .replace(/\\/+/g, '/')\n      .replace(/^\\/|\\/$/g, '');\n    let name = path ? `${host}_${path}` : host;\n    name = name.replace(/[:\\\\\\/\\?\\*\\[\\]]/g, '·').replace(/\\s+/g, ' ').trim();\n    return name || 'Page';\n  } catch {\n    let name = s.replace(/^https?:\\/\\//i, '').replace(/[:\\\\\\/\\?\\*\\[\\]]/g, '·').trim();\n    return name || 'Page';\n  }\n}\n\n// Make N random digits as a string (e.g., \"48291\")\nfunction randDigits(n = 5) {\n  let out = '';\n  for (let i = 0; i < n; i++) out += Math.floor(Math.random() * 10);\n  return out;\n}\n\nconst MAX_LEN = 90;        // Sheets tab name limit\nconst DIGITS = 5;          // how many random digits to append\n\nreturn items.map(({ json }) => {\n  const url = json.page_url ?? json.url ?? json.pageUrl ?? '';\n  const base = toSheetName(url);\n\n  const suffix = '_' + randDigits(DIGITS);          \n  const roomForBase = Math.max(0, MAX_LEN - suffix.length);\n  const safeBase = base.length > roomForBase ? base.slice(0, roomForBase) : base;\n\n  const sheet_name = `${safeBase}${suffix}`;\n\n  return {\n    json: {\n      ...json,\n      sheet_name,\n      _sheet_name_src: url \n    }\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "7b85f71d-7bdf-4de2-a572-494347582699",
      "name": "合并",
      "type": "n8n-nodes-base.merge",
      "position": [
        2720,
        -16
      ],
      "parameters": {},
      "typeVersion": 3.2
    },
    {
      "id": "6182edc0-197d-40b6-bdd0-a00b6d0fa73e",
      "name": "保存请求数据",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1024,
        160
      ],
      "parameters": {
        "columns": {
          "value": {
            "max_pages": "={{ $json.max_pages_num }}",
            "client_name": "={{ $json.client_name }}",
            "competitors": "={{ $json.competitor_domain }}",
            "crawl_depth": "={{ $json.crawl_depth_num }}",
            "notify_email": "={{ $json.notify_email }}",
            "exclude_paths": "={{ $json.excludeArr }}",
            "include_paths": "={{ $json.includeArr }}"
          },
          "schema": [
            {
              "id": "client_name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "client_name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "competitors",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "competitors",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "include_paths",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "include_paths",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "exclude_paths",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "exclude_paths",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "max_pages",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "max_pages",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "crawl_depth",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "crawl_depth",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "notify_email",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "notify_email",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1OvrhykXjzBI0_VqXurrQOMMtvBWfBVlttZVI1LhcRoQ/edit#gid=0",
          "cachedResultName": "CONFIG"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1OvrhykXjzBI0_VqXurrQOMMtvBWfBVlttZVI1LhcRoQ",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1OvrhykXjzBI0_VqXurrQOMMtvBWfBVlttZVI1LhcRoQ/edit?usp=drivesdk",
          "cachedResultName": "Content Gap Analyzer"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "wHDA0XakCCVOLVNe",
          "name": "Google Sheets account 2"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "2b28b4f7-e07d-4d14-950c-61a2d36fdb34",
      "name": "检查数据是否存在",
      "type": "n8n-nodes-base.if",
      "position": [
        800,
        64
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c87f5a6e-e9c8-4c57-a5b3-2c77371cd529",
              "operator": {
                "name": "filter.operator.equals",
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.startUrls[0].method }}",
              "rightValue": "GET"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "7273236a-42f6-4c62-8f79-52e039e15433",
      "name": "是否有内容可邮件发送",
      "type": "n8n-nodes-base.if",
      "position": [
        3392,
        -16
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "f1cdaf14-a4af-4ec9-8db2-4a8ddb78f4c4",
              "operator": {
                "type": "string",
                "operation": "regex"
              },
              "leftValue": "={{ $json.page_url }}",
              "rightValue": "^https?://"
            },
            {
              "id": "8600a236-03c6-45db-91fe-89f93926c97e",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.main_topics }}",
              "rightValue": ""
            },
            {
              "id": "ba4c3349-2b5c-4122-873e-fd93e5f3b944",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.key_words }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "c97679a0-efc5-43ab-b603-c40173c6d4e7",
      "name": "保存收集的数据",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3168,
        -16
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "page_url",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "page_url",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "main_topics",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "main_topics",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "key_words",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "key_words",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "name",
          "value": "={{ $('Derive Sheet Name').item.json.sheet_name }}"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1OvrhykXjzBI0_VqXurrQOMMtvBWfBVlttZVI1LhcRoQ",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1OvrhykXjzBI0_VqXurrQOMMtvBWfBVlttZVI1LhcRoQ/edit?usp=drivesdk",
          "cachedResultName": "Content Gap Analyzer"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "wHDA0XakCCVOLVNe",
          "name": "Google Sheets account 2"
        }
      },
      "executeOnce": true,
      "typeVersion": 4.7
    },
    {
      "id": "ecec493c-7b3b-4746-90f7-1b28ce224d9c",
      "name": "为数据创建工作表",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2512,
        64
      ],
      "parameters": {
        "title": "={{ $json.sheet_name }}",
        "options": {},
        "operation": "create",
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1OvrhykXjzBI0_VqXurrQOMMtvBWfBVlttZVI1LhcRoQ",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1OvrhykXjzBI0_VqXurrQOMMtvBWfBVlttZVI1LhcRoQ/edit?usp=drivesdk",
          "cachedResultName": "Content Gap Analyzer"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "wHDA0XakCCVOLVNe",
          "name": "Google Sheets account 2"
        }
      },
      "executeOnce": false,
      "notesInFlow": false,
      "retryOnFail": false,
      "typeVersion": 4.7
    },
    {
      "id": "0964d490-2039-4658-be5f-c9b374fab9dc",
      "name": "发送报告",
      "type": "n8n-nodes-base.gmail",
      "position": [
        3616,
        -32
      ],
      "webhookId": "1d1f6479-dd01-4dee-97cc-ff1a3b706dcb",
      "parameters": {
        "sendTo": "={{ $('Prepare Apify Input').item.json.notify_email }}",
        "message": "=<div style=\"font-family: Arial, sans-serif; color: #333;\">\n  <h2 style=\"color:#4CAF50;\">📊 Competitor SEO Content Analysis Report</h2>\n\n  <p><b>Page URL:</b> <a href=\"{{ $json.page_url }}\" target=\"_blank\">{{ $json.page_url }}</a></p>\n\n  <h3>Main Topics</h3>\n  <pre style=\"background:#f9f9f9; padding:10px; border:1px solid #ddd; white-space:pre-wrap; margin:0;\">\n{{ $json.main_topics }}\n  </pre>\n\n  <h3>Keywords Identified</h3>\n  <pre style=\"background:#f9f9f9; padding:10px; border:1px solid #ddd; white-space:pre-wrap; margin:0;\">\n{{ $json.key_words }}\n  </pre>\n</div>",
        "options": {},
        "subject": "=SEO Audit Report: {{ $json.page_url }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "jkKHvU2Pb9X5WJk5",
          "name": "Gmail account"
        }
      },
      "executeOnce": true,
      "typeVersion": 2.1
    },
    {
      "id": "f5ee629d-0845-4680-9104-672f7e2a3570",
      "name": "便签2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        960,
        336
      ],
      "parameters": {
        "color": 5,
        "height": 112,
        "content": "### 保存请求数据"
      },
      "typeVersion": 1
    },
    {
      "id": "b846b79a-f3ab-4b05-9bbe-41ecf82f8626",
      "name": "便签3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        944,
        -192
      ],
      "parameters": {
        "color": 5,
        "width": 256,
        "height": 144,
        "content": "### 爬取竞争对手网站"
      },
      "typeVersion": 1
    },
    {
      "id": "a684c3ba-f7a2-446f-9f27-5fc7dade6d3c",
      "name": "便签4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1680,
        320
      ],
      "parameters": {
        "color": 5,
        "width": 272,
        "height": 112,
        "content": "### 分析页面"
      },
      "typeVersion": 1
    },
    {
      "id": "0a3c9884-2fe0-4759-941f-7fe2af68b682",
      "name": "便利贴5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3536,
        128
      ],
      "parameters": {
        "color": 5,
        "width": 256,
        "height": 128,
        "content": "### 发送报告"
      },
      "typeVersion": 1
    },
    {
      "id": "81fa829e-90ef-41a4-aa23-c65887297f6d",
      "name": "便签1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        480,
        -928
      ],
      "parameters": {
        "color": 3,
        "width": 336,
        "height": 304,
        "content": "## 优势"
      },
      "typeVersion": 1
    },
    {
      "id": "df5d7ff4-6e19-48fd-b3ec-7e9a302a7d81",
      "name": "便签 6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        912,
        -928
      ],
      "parameters": {
        "color": 6,
        "width": 288,
        "height": 176,
        "content": "## 目标受众"
      },
      "typeVersion": 1
    },
    {
      "id": "b74a1d37-14b2-4cf4-8b1e-5ffc0c54ec84",
      "name": "便签 7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1280,
        -928
      ],
      "parameters": {
        "color": 5,
        "width": 406,
        "height": 176,
        "content": "## 所需 API"
      },
      "typeVersion": 1
    },
    {
      "id": "c8dde663-90f0-46e7-bebe-de63a7c58f74",
      "name": "便签8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1744,
        -928
      ],
      "parameters": {
        "color": 2,
        "width": 496,
        "height": 176,
        "content": "## 轻松自定义"
      },
      "typeVersion": 1
    },
    {
      "id": "f3181117-2221-4b73-9632-2572ab4f2a89",
      "name": "便签 9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2448,
        208
      ],
      "parameters": {
        "color": 5,
        "width": 224,
        "height": 112,
        "content": "### 为数据创建工作表"
      },
      "typeVersion": 1
    },
    {
      "id": "874709c8-edd9-4084-b3bd-ad925b039f40",
      "name": "便签10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        96,
        -48
      ],
      "parameters": {
        "color": 5,
        "width": 208,
        "height": 208,
        "content": "### Webhook — 由此开始"
      },
      "typeVersion": 1
    },
    {
      "id": "00661d71-a7b1-485e-bff7-a1888df54b85",
      "name": "便签11",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        416,
        -496
      ],
      "parameters": {
        "color": 5,
        "width": 448,
        "height": 336,
        "content": "### 提交表单"
      },
      "typeVersion": 1
    },
    {
      "id": "cd5b853c-8331-442b-b4c4-d772c6bdc4f8",
      "name": "由此开始",
      "type": "n8n-nodes-base.webhook",
      "position": [
        352,
        -32
      ],
      "webhookId": "300e7628-acda-4e87-ad89-6179f6d89260",
      "parameters": {
        "path": "competitors",
        "options": {},
        "responseMode": "responseNode",
        "multipleMethods": true
      },
      "typeVersion": 2.1
    },
    {
      "id": "be10836c-5727-4607-9dfb-12d50ad1f12b",
      "name": "提交表单",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        576,
        -128
      ],
      "parameters": {
        "options": {},
        "respondWith": "text",
        "responseBody": "=<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\" />\n  <title>Competitor Crawl Request</title>\n  <meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n  <style>\n    :root{\n      --bg-0:#0b1220;\n      --bg-1:#0f172a;\n      --bg-2:#111827;\n      --card:#0b1220;\n      --text:#e5e7eb;\n      --muted:#94a3b8;\n      --line:#1f2937;\n      --accent:#7c3aed;      /* violet */\n      --accent-2:#06b6d4;    /* cyan */\n      --ok:#10b981;          /* green */\n      --err:#ef4444;         /* red */\n    }\n    *{box-sizing:border-box}\n    html,body{height:100%}\n    body{\n      margin:0;\n      background:\n        radial-gradient(1200px 600px at 20% -10%, rgba(124,58,237,.18), transparent 60%),\n        radial-gradient(1000px 400px at 120% 10%, rgba(6,182,212,.20), transparent 60%),\n        linear-gradient(180deg, #0b1220, #0b1220 60%, #0a1020 100%);\n      color:var(--text);\n      font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, \"Noto Sans\", \"Apple Color Emoji\",\"Segoe UI Emoji\";\n      display:flex; align-items:center; justify-content:center;\n      padding:24px;\n    }\n    .card{\n      width:100%;\n      max-width: 720px;\n      background: linear-gradient(180deg, rgba(255,255,255,.03), rgba(255,255,255,.01));\n      border:1px solid var(--line);\n      border-radius: 16px;\n      box-shadow: 0 10px 30px rgba(0,0,0,.45), inset 0 1px 0 rgba(255,255,255,.03);\n      backdrop-filter: blur(8px);\n      overflow: hidden;\n    }\n    .card header{\n      padding:20px 24px;\n      border-bottom:1px solid var(--line);\n      display:flex; align-items:center; gap:12px;\n      background: linear-gradient(180deg, rgba(124,58,237,.15), rgba(124,58,237,.02));\n    }\n    .logo{\n      width:36px; height:36px; border-radius:10px;\n      background: conic-gradient(from 210deg, var(--accent), var(--accent-2));\n      box-shadow: 0 0 0 2px rgba(255,255,255,.06), 0 8px 16px rgba(124,58,237,.2);\n    }\n    h1{font-size:1.15rem; margin:0}\n    .sub{font-size:.9rem; color:var(--muted); margin-top:2px}\n    form{padding:20px 24px 24px}\n    .grid{\n      display:grid;\n      grid-template-columns: 1fr;\n      gap:16px;\n    }\n    @media (min-width: 720px){\n      .grid{ grid-template-columns: 1fr 1fr; }\n      .grid .full{ grid-column: 1 / -1; }\n    }\n    label{\n      display:block; font-size:.92rem; font-weight:600; margin-bottom:8px; color:#c7d2fe;\n    }\n    input, select, textarea{\n      width:100%;\n      background:#0d1426;\n      color:var(--text);\n      border:1px solid #1e293b;\n      border-radius:10px;\n      padding:12px 12px;\n      font-size: 0.98rem;\n      outline:none;\n      transition: border .15s, box-shadow .15s, transform .02s;\n    }\n    input::placeholder, textarea::placeholder{ color:#6b7280 }\n    input:focus, select:focus, textarea:focus{\n      border-color: var(--accent);\n      box-shadow: 0 0 0 3px rgba(124,58,237,.2);\n    }\n    select[multiple]{\n      min-height:132px;\n      padding:10px;\n    }\n    small.help{ display:block; color:var(--muted); margin-top:6px; }\n    .row{\n      display:flex; gap:10px; align-items:center; margin-top:6px;\n    }\n    .row input{ flex:1 }\n    .pill{\n      display:inline-flex; align-items:center; gap:8px;\n      padding:10px 14px; border-radius:999px;\n      border:1px solid #233046; background:#0e1629;\n      font-weight:700; letter-spacing:.2px;\n      color:#e9d5ff;\n      cursor:pointer; user-select:none;\n      transition: transform .02s, background .15s, border .15s;\n    }\n    .pill:hover{ background:#121b31; border-color:#2a3a57 }\n    .submit{\n      display:flex; gap:12px; align-items:center; justify-content:flex-end;\n      padding:18px 24px; border-top:1px solid var(--line);\n      background: linear-gradient(180deg, rgba(6,182,212,.08), rgba(6,182,212,.02));\n    }\n    button{\n      all:unset;\n      background: linear-gradient(90deg, var(--accent), var(--accent-2));\n      padding:12px 18px; border-radius:12px; cursor:pointer; font-weight:700;\n      box-shadow: 0 10px 22px rgba(124,58,237,.25);\n    }\n    button[disabled]{ opacity:.7; cursor:not-allowed; box-shadow:none }\n    .msg{ padding:12px 16px; font-weight:600; }\n    .success{ color: var(--ok); }\n    .error{ color: var(--err); }\n    .inline-note{ font-size:.85rem; color:var(--muted); margin-left:10px }\n  </style>\n</head>\n<body>\n  <div class=\"card\" role=\"region\" aria-label=\"Competitor Crawl Request\">\n    <header>\n      <div class=\"logo\" aria-hidden=\"true\"></div>\n      <div>\n        <h1>Competitor Crawl Request</h1>\n        <div class=\"sub\">Send a domain and optional scoping rules for the crawl</div>\n      </div>\n    </header>\n\n    <form id=\"requestForm\" novalidate>\n      <div class=\"grid\">\n        <div class=\"full\">\n          <label for=\"competitors\">Competitor URL / Domain</label>\n          <input id=\"competitors\" name=\"competitors\" type=\"text\" required placeholder=\"https://example.com or example.com\" />\n          <small class=\"help\">A full URL or bare domain is fine — we’ll normalize it.</small>\n        </div>\n\n        <div>\n          <label for=\"name\">Your Name</label>\n          <input id=\"name\" name=\"name\" type=\"text\" required placeholder=\"Your name…\" autocomplete=\"name\" />\n        </div>\n\n        <div>\n          <label for=\"email\">Email (for report)</label>\n          <input id=\"email\" name=\"email\" type=\"email\" required placeholder=\"you@domain.com\" autocomplete=\"email\" inputmode=\"email\" />\n          <small class=\"help\">We’ll email the summary here.</small>\n        </div>\n\n        <div class=\"full\">\n          <label for=\"include_paths\">Include paths (multi-select)</label>\n          <select id=\"include_paths\" multiple>\n            <option value=\"/\">/</option>\n            <option value=\"/blog/\">/blog/</option>\n            <option value=\"/services/\">/services/</option>\n            <option value=\"/products/\">/products/</option>\n            <option value=\"/news/\">/news/</option>\n            <option value=\"/portfolio/\">/portfolio/</option>\n          </select>\n          <div class=\"row\">\n            <input id=\"include_custom\" type=\"text\" placeholder=\"Custom paths (comma-separated, e.g. /guide/, /docs/ )\" />\n            <span class=\"pill\" id=\"add_include\">+ Add</span>\n          </div>\n          <small class=\"help\">Hold Ctrl/⌘ to select multiple. Custom items are appended automatically.</small>\n        </div>\n\n        <div class=\"full\">\n          <label for=\"exclude_paths\">Exclude paths (multi-select)</label>\n          <select id=\"exclude_paths\" multiple>\n            <option value=\"/privacy\">/privacy</option>\n            <option value=\"/terms\">/terms</option>\n            <option value=\"/login\">/login</option>\n            <option value=\"/cart\">/cart</option>\n            <option value=\"/checkout\">/checkout</option>\n            <option value=\"/wp-admin\">/wp-admin</option>\n            <option value=\"/tag/\">/tag/</option>\n            <option value=\"/category/\">/category/</option>\n            <option value=\"/feed\">/feed</option>\n          </select>\n          <div class=\"row\">\n            <input id=\"exclude_custom\" type=\"text\" placeholder=\"Custom paths (comma-separated, e.g. /search, /admin )\" />\n            <span class=\"pill\" id=\"add_exclude\">+ Add</span>\n          </div>\n          <small class=\"help\">Use this to skip utility/low-value areas.</small>\n        </div>\n\n        <div>\n          <label for=\"max_pages\">Max pages</label>\n          <select id=\"max_pages\">\n            <option value=\"5\">5</option>\n            <option value=\"10\">10</option>\n            <option value=\"20\" selected>20</option>\n            <option value=\"50\">50</option>\n            <option value=\"100\">100</option>\n            <option value=\"200\">200</option>\n          </select>\n          <span class=\"inline-note\">Default 20</span>\n        </div>\n\n        <div>\n          <label for=\"crawl_depth\">Crawl depth</label>\n          <select id=\"crawl_depth\">\n            <option value=\"0\">0 (only start URL)</option>\n            <option value=\"1\" selected>1</option>\n            <option value=\"2\">2</option>\n            <option value=\"3\">3</option>\n          </select>\n          <span class=\"inline-note\">Default 1</span>\n        </div>\n      </div>\n\n      <div class=\"submit\">\n        <div id=\"response\" class=\"msg\" aria-live=\"polite\"></div>\n        <button id=\"submitBtn\" type=\"submit\">Send Request</button>\n      </div>\n    </form>\n  </div>\n\n  <script>\n    const $ = (id) => document.getElementById(id);\n\n    function showMsg(text, type) {\n      const el = $('response');\n      el.className = 'msg ' + (type || '');\n      el.textContent = text;\n    }\n\n    function normalizeUrl(input) {\n      const s = (input || '').trim();\n      if (!s) return '';\n      return /^https?:\\/\\//i.test(s) ? s : `https://${s}`;\n    }\n\n    function selectedValues(selectEl) {\n      return Array.from(selectEl.options)\n        .filter(o => o.selected)\n        .map(o => o.value.trim())\n        .filter(Boolean);\n    }\n\n    function parseCustom(inputEl) {\n      return (inputEl.value || '')\n        .split(',')\n        .map(s => s.trim())\n        .filter(Boolean);\n    }\n\n    function addCustomToSelect(inputEl, selectEl) {\n      parseCustom(inputEl).forEach(v => {\n        const opt = document.createElement('option');\n        opt.value = v;\n        opt.textContent = v;\n        opt.selected = true;\n        selectEl.appendChild(opt);\n      });\n      inputEl.value = '';\n    }\n\n    // Wire add buttons\n    $('add_include').addEventListener('click', () => addCustomToSelect($('include_custom'), $('include_paths')));\n    $('add_exclude').addEventListener('click', () => addCustomToSelect($('exclude_custom'), $('exclude_paths')));\n\n    $('requestForm').addEventListener('submit', async (e) => {\n      e.preventDefault();\n      showMsg('', '');\n\n      if (!e.target.checkValidity()) {\n        e.target.reportValidity();\n        return;\n      }\n\n      const name  = $('name').value.trim();\n      const email = $('email').value.trim();\n      const competitors = normalizeUrl($('competitors').value.trim());\n\n      const emailOk = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email);\n      if (!emailOk) { $('email').focus(); return showMsg('❌ Please enter a valid email.', 'error'); }\n\n      if (!competitors) { $('competitors').focus(); return showMsg('❌ Please enter a competitor URL.', 'error'); }\n\n      // Merge dropdown selections + any custom text entries\n      const includeSel = selectedValues($('include_paths'));\n      const excludeSel = selectedValues($('exclude_paths'));\n      const includeCustom = parseCustom($('include_custom'));\n      const excludeCustom = parseCustom($('exclude_custom'));\n\n      const include_paths = [...includeSel, ...includeCustom].join(', ');\n      const exclude_paths = [...excludeSel, ...excludeCustom].join(', ');\n\n      const max_pages   = $('max_pages').value;\n      const crawl_depth = $('crawl_depth').value;\n\n      const submitBtn = $('submitBtn');\n      submitBtn.setAttribute('disabled','true');\n      submitBtn.textContent = 'Sending…';\n\n      try {\n        const webhookUrl = 'https://n8nworkflow.eu/webhook-test/competitors'; // use production /webhook/... when you deploy\n        const res = await fetch(webhookUrl, {\n          method: 'POST',\n          headers: { 'Content-Type': 'application/json' },\n          body: JSON.stringify({\n            name,\n            email,\n            competitors,\n            include_paths,\n            exclude_paths,\n            max_pages,\n            crawl_depth\n          })\n        });\n\n        if (res.ok) {\n          showMsg('✅ Request sent. We’ll email you when it’s processed.', 'success');\n          e.target.reset();\n        } else {\n          const txt = await res.text().catch(() => '');\n          showMsg('❌ Failed to send. ' + (txt || ''), 'error');\n        }\n      } catch (err) {\n        showMsg('❌ Network error. Please try again.', 'error');\n      } finally {\n        submitBtn.removeAttribute('disabled');\n        submitBtn.textContent = 'Send Request';\n      }\n    });\n  </script>\n</body>\n</html>"
      },
      "executeOnce": false,
      "retryOnFail": true,
      "typeVersion": 1.4
    },
    {
      "id": "76b234af-da62-4017-a648-e5a8d7a61c51",
      "name": "准备工作表行",
      "type": "n8n-nodes-base.set",
      "position": [
        2944,
        -16
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "367b45c6-c47d-43de-ae52-ea269cdbe938",
              "name": "page_url",
              "type": "string",
              "value": "={{ $('Parse and Normalize Gemini JSON').item.json.page_url }}"
            },
            {
              "id": "420d1ace-2bb3-4fbf-ada2-9301ca570f9a",
              "name": "main_topics",
              "type": "string",
              "value": "={{ $('Parse and Normalize Gemini JSON').item.json.main_topics_flat }}"
            },
            {
              "id": "707356b5-ae9f-4706-820d-213916363049",
              "name": "key_words",
              "type": "string",
              "value": "={{ $('Parse and Normalize Gemini JSON').item.json.key_entities_flat }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "timezone": "Europe/Helsinki",
    "callerPolicy": "workflowsFromSameOwner",
    "executionOrder": "v1"
  },
  "versionId": "30265337-2486-4efa-90c8-7dfb0cca682e",
  "connections": {
    "Merge": {
      "main": [
        [
          {
            "node": "Prepare Sheet Row",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "Parse and Normalize Gemini JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start Here": {
      "main": [
        [
          {
            "node": "Submission form",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Prepare Apify Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Submission form": {
      "main": [
        []
      ]
    },
    "Derive Sheet Name": {
      "main": [
        [
          {
            "node": "Create sheet for the data",
            "type": "main",
            "index": 0
          },
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Sheet Row": {
      "main": [
        [
          {
            "node": "Save the data collected",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Prepare Apify Input": {
      "main": [
        [
          {
            "node": "Check if the data exist",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Page Content": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Has content to email": {
      "main": [
        [
          {
            "node": "Send report ",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Page Metadata": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Crawled Dataset": {
      "main": [
        [
          {
            "node": "Extract Page Metadata",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check if the data exist": {
      "main": [
        [
          {
            "node": "Crawl Competitor Website",
            "type": "main",
            "index": 0
          },
          {
            "node": "Save Data Of The request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save the data collected": {
      "main": [
        [
          {
            "node": "Has content to email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Crawl Competitor Website": {
      "main": [
        [
          {
            "node": "Fetch Crawled Dataset",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Save Data Of The request": {
      "main": [
        []
      ]
    },
    "Create sheet for the data": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Parse and Normalize Gemini JSON": {
      "main": [
        [
          {
            "node": "Derive Sheet Name",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

高级 - 杂项, AI 摘要总结, 多模态 AI

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
高级
节点数量30
分类3
节点类型13
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

外部链接
在 n8n.io 查看

分享此工作流