完整的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,'<').replace(/>/g,'>')}</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,'<').replace(/>/g,'>');\n const sug = $node[\"Run SEO Audit Logic\"].json.suggestions[i.code]\n .replace(/</g,'<').replace(/>/g,'>');\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,'<').replace(/>/g,'>');\n const sug = $node[\"Run SEO Audit Logic\"].json.suggestions[i.code]\n .replace(/</g,'<').replace(/>/g,'>');\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)可能需要您自行付费。
相关工作流推荐
竞争对手内容差距分析器:自动化网站主题映射
使用Gemini AI、Apify和Google Sheets分析竞争对手内容差距
If
Set
Code
+10
30 节点Mychel Garzon
杂项
社区问题监控器与OpenRouter AI、Reddit和论坛爬取
使用OpenRouter AI、Reddit和论坛爬取监控社区问题
Set
Code
Html
+13
29 节点Julian Kaiser
市场调研
GA4异常检测与自动Slack和邮件提醒
GA4异常检测与自动Slack和邮件提醒
If
Set
Code
+5
9 节点Artur
市场调研
AI简历优化器
使用Gemini分析和邮件报告将简历匹配到职位描述
Set
Code
Gmail
+9
18 节点Mychel Garzon
AI 摘要总结
新抓取器_TechCrunch新闻-AI1
TechCrunch AI文章抓取与分类器,使用GPT-4.1-nano到Sheets和Telegram
Set
Code
Html
+12
18 节点Mujahid Kabae
市场调研
自动化SEO健康监控与报告
网站SEO健康分析(Google Sheets、PDF报告和Gmail告警)
If
Set
Code
+8
16 节点WeblineIndia
市场调研