8
n8n 中文网amn8n.com

完整的SEO审计工作流,带自动化HTML摘要

中级

这是一个Market Research领域的自动化工作流,包含 7 个节点。主要使用 Set, Code, Html, Gmail, HttpRequest 等节点。 每日SEO审计工作流,通过Gmail/Slack发送HTML报告

前置要求
  • Google 账号和 Gmail API 凭证
  • 可能需要目标 API 的认证凭证
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "ak2jPqnpYMKMo8Bp",
  "meta": {
    "instanceId": "e0fd1578cd5453d4707742d6846391bbf1c4d865d540d8236161c7a6376cbd50",
    "templateCredsSetupCompleted": true
  },
  "name": "完整的 SEO 审计工作流,带自动化 HTML 摘要",
  "tags": [],
  "nodes": [
    {
      "id": "2fa7bca3-02c4-4ded-b4ea-b8dca5314257",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        144,
        -560
      ],
      "parameters": {
        "width": 796,
        "height": 644,
        "content": "## 通过 Gmail 或 Slack 实现带自动化 HTML 报告的完整 SEO 审计工作流"
      },
      "typeVersion": 1
    },
    {
      "id": "b64d81da-cc22-4fdd-9146-b76a29721047",
      "name": "每日审计触发器",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        128,
        144
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "b12cf011-73cc-4890-bf15-1292201d3f60",
      "name": "配置目标和收件人",
      "type": "n8n-nodes-base.set",
      "position": [
        320,
        144
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "44337ce3-4e65-4d7b-9b1a-cf77f8412169",
              "name": "siteUrl",
              "type": "string",
              "value": "https://example.com/"
            },
            {
              "id": "60e53201-5d1b-4665-9f13-aad194cd653c",
              "name": "emailFrom",
              "type": "string",
              "value": "exampleFrom@example.com"
            },
            {
              "id": "dc19de25-3f64-4244-9a3e-65d5178181c5",
              "name": "emailTo",
              "type": "string",
              "value": "exampleTo@example.com"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "d84cbda7-a9e0-4c40-86da-e8914cf99e8f",
      "name": "HTTP 获取页面内容",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        528,
        144
      ],
      "parameters": {
        "url": "={{$node[\"Configure Target & Recipients\"].json.siteUrl}}\n",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "f25b7ba0-54e9-4e5e-85f8-936f9ffd3930",
      "name": "解析页面内元素",
      "type": "n8n-nodes-base.html",
      "position": [
        736,
        144
      ],
      "parameters": {
        "options": {},
        "operation": "extractHtmlContent",
        "dataPropertyName": "=data",
        "extractionValues": {
          "values": [
            {
              "key": "title",
              "cssSelector": "title"
            },
            {
              "key": "metaDesc",
              "attribute": "content",
              "cssSelector": "meta[name=\"description\"]",
              "returnValue": "attribute"
            },
            {
              "key": "h1Tags",
              "cssSelector": "h1",
              "returnArray": true
            },
            {
              "key": "h2Tags",
              "cssSelector": "h2",
              "returnArray": true
            },
            {
              "key": "imgAlts",
              "attribute": "alt",
              "cssSelector": "img",
              "returnArray": true,
              "returnValue": "attribute"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "f097edcb-fb09-4eb7-96dc-398f8e3dbecd",
      "name": "运行 SEO 审计逻辑",
      "type": "n8n-nodes-base.code",
      "position": [
        944,
        144
      ],
      "parameters": {
        "jsCode": "// Collect results for each URL\nconst output = [];\n\nfor (const item of items) {\n  const d = item.json;\n  const issues = [];\n  const suggestions = {};\n\n  function addIssue(code, message, suggestion) {\n    issues.push({ code, message });\n    suggestions[code] = suggestion;\n  }\n\n  // 1. Title length\n  if (!d.title) {\n    addIssue('missing_title', 'Missing <title>', 'Add a descriptive <title> (30–60 chars).');\n  } else if (d.title.length < 30) {\n    addIssue('short_title', `Title too short (${d.title.length} chars)`, 'Increase title length to ≥30 chars.');\n  } else if (d.title.length > 60) {\n    addIssue('long_title', `Title too long (${d.title.length} chars)`, 'Reduce title to ≤60 chars.');\n  }\n\n  // 2. Meta description\n  if (!d.metaDesc) {\n    addIssue('missing_meta_desc', 'Missing meta description', 'Add <meta name=\"description\"> (50–160 chars).');\n  } else if (d.metaDesc.length < 50) {\n    addIssue('short_meta_desc', `Meta description too short (${d.metaDesc.length} chars)`, 'Increase description to ≥50 chars.');\n  } else if (d.metaDesc.length > 160) {\n    addIssue('long_meta_desc', `Meta description too long (${d.metaDesc.length} chars)`, 'Shorten description to ≤160 chars.');\n  }\n\n  // 3. Robots, viewport, canonical, lang\n  if (!d.metaRobots) {\n    addIssue('missing_robots', 'Missing meta robots tag', 'Add <meta name=\"robots\" content=\"index,follow\">.');\n  }\n  if (!d.metaViewport || !d.metaViewport.includes('width=device-width')) {\n    addIssue('viewport', 'Missing/incorrect viewport tag', 'Add <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">.');\n  }\n  if (!d.linkCanonical) {\n    addIssue('canonical', 'Missing canonical', 'Add <link rel=\"canonical\" href=\"YOUR_URL\">.');\n  }\n  if (!d.htmlLang) {\n    addIssue('lang', 'Missing lang attribute', 'Add <html lang=\"en\">.');\n  }\n\n  // 4. Word count\n  if (typeof d.wordCount === 'number' && d.wordCount < 300) {\n    addIssue('low_word_count', `Low word count (${d.wordCount} words)`, 'Aim for ≥300 words.');\n  }\n\n  // 5. Headings\n  const h1 = (d.h1Tags || []).length;\n  const h2 = (d.h2Tags || []).length;\n  const h3 = (d.h3Tags || []).length;\n  if (h1 !== 1) {\n    addIssue('h1', `${h1} <h1> tags`, 'Use exactly one <h1>.');\n  }\n  if (h2 === 0) {\n    addIssue('h2', 'No <h2> tags', 'Add ≥1 <h2> for sections.');\n  }\n  if (h3 === 0) {\n    addIssue('h3', 'No <h3> tags', 'Use <h3> for subsections.');\n  }\n\n  // 6. Images alt text\n  if (Array.isArray(d.imgAlts)) {\n    const missing = d.imgAlts.filter(a => !a || !a.trim()).length;\n    if (missing) {\n      addIssue('img_alt', `${missing} images missing alt`, 'Provide descriptive alt text.');\n    }\n  }\n\n  // 7. Broken links\n  if (Array.isArray(d.links)) {\n    const broken = d.links.filter(l => l.status >= 400).length;\n    if (broken) {\n      addIssue('broken_links', `${broken} broken links`, 'Fix or remove broken URLs.');\n    }\n  }\n\n  // 8. Structured data\n  if (!d.structuredData || d.structuredData.length === 0) {\n    addIssue('schema', 'No structured data', 'Implement JSON-LD schema markup.');\n  }\n\n  // 9. Core Web Vitals\n  if (d.metrics) {\n    const { LCP, INP, CLS } = d.metrics;\n    if (LCP > 2500) {\n      addIssue('lcp', `LCP too slow (${LCP} ms)`, 'Optimize images and server response.');\n    }\n    if (INP > 200) {\n      addIssue('inp', `INP high (${INP} ms)`, 'Minimize JavaScript execution.');\n    }\n    if (CLS > 0.1) {\n      addIssue('cls', `CLS unstable (${CLS})`, 'Set image dimensions and pre-load fonts.');\n    }\n  }\n\n  // Summary\n  const pass = issues.length === 0;\n  const summary = pass ? 'All checks passed! 🎉' : `${issues.length} issue${issues.length > 1 ? 's' : ''} detected`;\n\n  // Markdown report\n  let markdown = `## SEO Audit for ${d.siteUrl || d.url}\\n`;\n  markdown += `**Status:** ${pass ? 'PASS ✅' : 'FAIL ❌'}\\n`;\n  markdown += `**Summary:** ${summary}\\n`;\n  if (!pass) {\n    markdown += `\\n### Issues & Suggestions\\n`;\n    issues.forEach(({ code, message }) => {\n      markdown += `- **${message}**  \\n  _Suggestion:_ ${suggestions[code]}\\n`;\n    });\n  }\n\n  output.push({\n    json: {\n      siteUrl: d.siteUrl || d.url,\n      pass,\n      summary,\n      issues,\n      suggestions,\n      markdownReport: markdown,\n    },\n  });\n}\n\nreturn output;\n"
      },
      "typeVersion": 2
    },
    {
      "id": "81cd1593-0669-4f68-bc12-cac12e30f327",
      "name": "电子邮件审计报告",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1168,
        144
      ],
      "webhookId": "5394c49f-09fb-4f0f-ba2f-0f312d197b53",
      "parameters": {
        "sendTo": "={{$node[\"Configure Target & Recipients\"].json.emailTo}}\n\n",
        "message": "=<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"UTF-8\" />\n  <style>\n    body { font-family: Arial, sans-serif; color: #333; line-height: 1.4; padding: 20px; }\n    .header { display: flex; align-items: center; margin-bottom: 20px; }\n    .logo { height: 40px; margin-right: 15px; }\n    .title { font-size: 24px; color: #2a6ebb; margin: 0; }\n    .meta { margin: 5px 0 20px; }\n    .status-pass { color: green; font-weight: bold; }\n    .status-fail { color: red; font-weight: bold; }\n    .summary-table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }\n    .summary-table td { padding: 8px; border: 1px solid #eee; }\n    .issues-container { display: flex; gap: 20px; }\n    .column { flex: 1; }\n    .column h4 { border-bottom: 1px solid #ddd; padding-bottom: 5px; }\n    .issues-list { list-style: none; padding: 0; margin: 0; }\n    .issues-list li { margin: 8px 0; }\n    .suggestion { display: block; margin-left: 15px; font-style: italic; color: #555; }\n    .priorities { background: #f9f9f9; padding: 15px; border-left: 4px solid #2a6ebb; margin-bottom: 20px; }\n    .next-steps { margin-top: 30px; font-size: 14px; }\n    .next-steps a { color: #2a6ebb; text-decoration: none; }\n    code { background: #eef; padding: 2px 4px; border-radius: 3px; }\n  </style>\n</head>\n<body>\n\n  <div class=\"header\">\n    <div>\n      <h1 class=\"title\">SEO Audit Report</h1>\n      <div class=\"meta\">Date: {{ new Date().toLocaleDateString() }}</div>\n    </div>\n  </div>\n\n  <p>\n    <strong>Page:</strong> {{ $node[\"Run SEO Audit Logic\"].json.siteUrl }}<br/>\n    <strong>Status:</strong>\n    <span class=\"{{ $node[\"Run SEO Audit Logic\"].json.pass ? 'status-pass' : 'status-fail' }}\">\n      {{ $node[\"Run SEO Audit Logic\"].json.pass ? 'PASS ✅' : 'FAIL ❌' }}\n    </span>\n  </p>\n\n  <div class=\"priorities\">\n    <strong>🚩 Top 3 Priorities:</strong>\n    <ol>\n      {{\n        $node[\"Run SEO Audit Logic\"].json.issues\n          .slice(0, 3)\n          .map(i => `<li>${i.message.replace(/</g,'&lt;').replace(/>/g,'&gt;')}</li>`)\n          .join('')\n      }}\n    </ol>\n  </div>\n\n  <table class=\"summary-table\">\n    <tr>\n      <td><strong>Total Issues</strong></td>\n      <td>{{ $node[\"Run SEO Audit Logic\"].json.issues.length }}</td>\n    </tr>\n    <tr>\n      <td><strong>Broken Links</strong></td>\n      <td>\n        {{\n          $node[\"Run SEO Audit Logic\"].json.issues.filter(i => i.code === 'broken_links').length\n        }}\n      </td>\n    </tr>\n    <tr>\n      <td><strong>CWV Alerts</strong></td>\n      <td>\n        {{\n          $node[\"Run SEO Audit Logic\"].json.issues\n            .filter(i => ['lcp','inp','cls'].includes(i.code))\n            .length\n        }}\n      </td>\n    </tr>\n  </table>\n\n  <div class=\"issues-container\">\n    <!-- On‑Page Issues -->\n    <div class=\"column\">\n      <h4>📝 On‑Page Issues</h4>\n      <ul class=\"issues-list\">\n        {{\n          $node[\"Run SEO Audit Logic\"].json.issues\n            .filter(i => [\n              'missing_title','short_title','long_title',\n              'missing_meta_desc','short_meta_desc','long_meta_desc',\n              'h1','h2','h3','img_alt','schema'\n            ].includes(i.code))\n            .map(i => {\n              const msg = i.message.replace(/</g,'&lt;').replace(/>/g,'&gt;');\n              const sug = $node[\"Run SEO Audit Logic\"].json.suggestions[i.code]\n                            .replace(/</g,'&lt;').replace(/>/g,'&gt;');\n              return `\n                <li>\n                  ⚠️ <strong>${msg}</strong>\n                  <span class=\"suggestion\">\n                    Suggestion: <code>${sug}</code>\n                  </span>\n                </li>`;\n            })\n            .join('')\n        }}\n      </ul>\n    </div>\n\n    <!-- Technical Issues -->\n    <div class=\"column\">\n      <h4>🔧 Technical Issues</h4>\n      <ul class=\"issues-list\">\n        {{\n          $node[\"Run SEO Audit Logic\"].json.issues\n            .filter(i => ![\n              'missing_title','short_title','long_title',\n              'missing_meta_desc','short_meta_desc','long_meta_desc',\n              'h1','h2','h3','img_alt','schema'\n            ].includes(i.code))\n            .map(i => {\n              const msg = i.message.replace(/</g,'&lt;').replace(/>/g,'&gt;');\n              const sug = $node[\"Run SEO Audit Logic\"].json.suggestions[i.code]\n                            .replace(/</g,'&lt;').replace(/>/g,'&gt;');\n              return `\n                <li>\n                  ⚙️ <strong>${msg}</strong>\n                  <span class=\"suggestion\">\n                    Suggestion: <code>${sug}</code>\n                  </span>\n                </li>`;\n            })\n            .join('')\n        }}\n      </ul>\n    </div>\n  </div>\n\n  <div class=\"next-steps\">\n    <p><strong>Next Steps:</strong></p>\n    <ol>\n      <li>Review the above suggestions and assign tickets in your tracker.</li>\n      <li>Re‑run this audit after fixes are deployed to confirm all PASS.</li>\n    </ol>\n  </div>\n\n</body>\n</html>\n",
        "options": {},
        "subject": "={{$node[\"Configure Target & Recipients\"].json.siteUrl}}"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "jkKHvU2Pb9X5WJk5",
          "name": "Gmail account"
        }
      },
      "typeVersion": 2.1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "bc8a99b9-d397-4881-a02e-6aab78b364dc",
  "connections": {
    "Daily Audit Trigger": {
      "main": [
        [
          {
            "node": "Configure Target & Recipients",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run SEO Audit Logic": {
      "main": [
        [
          {
            "node": "Email Audit Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Fetch Page Content": {
      "main": [
        [
          {
            "node": "Parse On‑Page Elements",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse On‑Page Elements": {
      "main": [
        [
          {
            "node": "Run SEO Audit Logic",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configure Target & Recipients": {
      "main": [
        [
          {
            "node": "HTTP Fetch Page Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

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

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

中级 - 市场调研

需要付费吗?

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

工作流信息
难度等级
中级
节点数量7
分类1
节点类型7
难度说明

适合有一定经验的用户,包含 6-15 个节点的中等复杂度工作流

外部链接
在 n8n.io 查看

分享此工作流