8
n8n 中文网amn8n.com

AI驱动的Meta广告周度PDF报告 - 发送至Slack或邮箱

中级

这是一个Market Research, AI Summarization领域的自动化工作流,包含 14 个节点。主要使用 Set, Code, Slack, SplitOut, Pdforge 等节点。 使用GPT-4洞察生成Meta广告周度性能报告并通过Slack发送

前置要求
  • Slack Bot Token 或 Webhook URL
  • 可能需要目标 API 的认证凭证
  • OpenAI API Key
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "OGdHKG9jhEKDrZAC",
  "meta": {
    "instanceId": "33bfcf6494ef5d9b0fac5fde544f7913410659bb957613631505fa8056a7625a",
    "templateCredsSetupCompleted": true
  },
  "name": "AI 驱动的 Meta 广告周度 PDF 报告 – 发送至您的 Slack 或邮箱",
  "tags": [],
  "nodes": [
    {
      "id": "e2a34995-dd70-432e-8bbc-3432421db01d",
      "name": "拆分输出",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        -880,
        40
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "data"
      },
      "typeVersion": 1
    },
    {
      "id": "a7c0b061-00a3-47a8-9ed6-3e8b840cc6a7",
      "name": "HTTP 请求 - Meta 广告",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        -1100,
        40
      ],
      "parameters": {
        "url": "=https://graph.facebook.com/v23.0/act_{{$json.ad_account_id}}/insights",
        "options": {
          "pagination": {
            "pagination": {
              "nextURL": "={{ $response.body.paging.next }}",
              "paginationMode": "responseContainsNextURL",
              "requestInterval": 500,
              "completeExpression": "={{ !$response.body.paging.next }}",
              "paginationCompleteWhen": "other"
            }
          }
        },
        "sendQuery": true,
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "queryParameters": {
          "parameters": [
            {
              "name": "fields",
              "value": "=campaign_id,campaign_name,adset_id,adset_name,ad_id,ad_name,reach,impressions,inline_link_clicks,inline_link_click_ctr,conversions,spend,cpc,cost_per_conversion,action_values,purchase_roas"
            },
            {
              "name": "time_increment",
              "value": "1"
            },
            {
              "name": "date_preset",
              "value": "={{ $json.date_range }}"
            },
            {
              "name": "level",
              "value": "ad"
            }
          ]
        }
      },
      "credentials": {
        "httpHeaderAuth": {
          "id": "Z9SGcPUjCFlHCdPM",
          "name": "Meta Ads Auth"
        }
      },
      "typeVersion": 4.2,
      "alwaysOutputData": true
    },
    {
      "id": "93076858-210e-4142-b7a1-1c0f5ba0d17d",
      "name": "配置",
      "type": "n8n-nodes-base.set",
      "position": [
        -1320,
        40
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "9941e108-1e6c-4050-8401-9e6125a847de",
              "name": "ad_account_id",
              "type": "string",
              "value": "982086037409625232"
            },
            {
              "id": "a9ca0cda-adc0-40fd-8f8c-8aa7437276c5",
              "name": "date_range",
              "type": "string",
              "value": "last_7d"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "252d5491-0ecc-4c87-bce6-fa60e2aba035",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2040,
        -80
      ],
      "parameters": {
        "width": 420,
        "height": 520,
        "content": "## 欢迎使用周度 Meta 广告报告工作流!"
      },
      "typeVersion": 1
    },
    {
      "id": "fa9620e6-cee4-4d75-b19b-c6c25c56a555",
      "name": "便签1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1380,
        -80
      ],
      "parameters": {
        "color": 4,
        "width": 220,
        "height": 320,
        "content": "**在此配置:**"
      },
      "typeVersion": 1
    },
    {
      "id": "7ad3d2d7-571f-4e84-b8d3-a14a39b93a6e",
      "name": "Pdforge",
      "type": "n8n-nodes-pdforge.pdforge",
      "position": [
        100,
        40
      ],
      "parameters": {
        "options": {},
        "variables": "={{ JSON.stringify($json) }}",
        "templateId": "meta_ads"
      },
      "credentials": {
        "pdforgeApi": {
          "id": "fFuvCIsBkIHHd0ct",
          "name": "Pdforge account"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "3ecfcea9-531e-47b2-8074-ed304b35f5e4",
      "name": "格式化数据",
      "type": "n8n-nodes-base.code",
      "position": [
        -660,
        40
      ],
      "parameters": {
        "jsCode": "// 1. extract raw records\nconst records = $input.all().map(item => item.json);\n\n// 2. date utilities\nfunction parseYMD(s){ return new Date(s + 'T00:00:00'); }\nfunction formatYMD(d){\n  const y = d.getFullYear();\n  const m = String(d.getMonth()+1).padStart(2,'0');\n  const da = String(d.getDate()).padStart(2,'0');\n  return `${y}-${m}-${da}`;\n}\nfunction rangeDates(start, end){\n  const a = [];\n  let cur = new Date(start);\n  while(cur <= end){\n    a.push(formatYMD(cur));\n    cur.setDate(cur.getDate()+1);\n  }\n  return a;\n}\n\n// 3. find global start/end\nconst dates = records.flatMap(r=>[r.date_start, r.date_stop]);\nconst minDate = new Date(Math.min(...dates.map(d=>parseYMD(d))));\nconst maxDate = new Date(Math.max(...dates.map(d=>parseYMD(d))));\nconst allLabels = rangeDates(minDate, maxDate);\n\n// 4. overview totals\nconst overviewAgg = records.reduce((acc, r) => {\n  const imp = Number(r.impressions || 0);\n  const clk = Number(r.inline_link_clicks || 0);\n  const spd = Number(r.spend || 0);\n  acc.impressions += imp;\n  acc.clicks     += clk;\n  acc.investment += spd;\n  return acc;\n}, { impressions:0, clicks:0, investment:0 });\n\nconst overview = {\n  investment:  { value: Number(overviewAgg.investment.toFixed(2)) },\n  impressions: { value: overviewAgg.impressions },\n  clicks:      { value: overviewAgg.clicks },\n  ctr:         { \n    value: overviewAgg.impressions\n      ? `${(overviewAgg.clicks / overviewAgg.impressions * 100).toFixed(2)}%`\n      : '0.00%' \n  },\n  cpc:         { \n    value: overviewAgg.clicks\n      ? Number((overviewAgg.investment / overviewAgg.clicks).toFixed(4))\n      : 0 \n  }\n};\n\n// 5. day_by_day\nconst dayMap = records.reduce((m, r) => {\n  const d = r.date_start;\n  if (!m[d]) m[d] = { impressions:0, clicks:0 };\n  m[d].impressions += Number(r.impressions || 0);\n  m[d].clicks     += Number(r.inline_link_clicks || 0);\n  return m;\n}, {});\nconst day_by_day = {\n  labels:      allLabels,\n  impressions: allLabels.map(d => dayMap[d]?.impressions || 0),\n  clicks:      allLabels.map(d => dayMap[d]?.clicks      || 0),\n};\n\n// 6. helper to aggregate by key (com formatação de investimento em 2 casas)\nfunction aggBy(records, keyField){\n  const map = {};\n  records.forEach(r => {\n    const key = r[keyField] || 'N/A';\n    if (!map[key]) map[key] = { \n      name: key, impressions:0, clicks:0, investment:0 \n    };\n    map[key].impressions += Number(r.impressions || 0);\n    map[key].clicks      += Number(r.inline_link_clicks || 0);\n    map[key].investment  += Number(r.spend || 0);\n  });\n  return Object.values(map).map(o => ({\n    name:        o.name,\n    impressions: o.impressions,\n    clicks:      o.clicks,\n    investment:  Number(o.investment.toFixed(2)),               // <-- 2 decimais aqui\n    ctr:         o.impressions\n                   ? `${(o.clicks / o.impressions * 100).toFixed(2)}%`\n                   : '0.00%',\n    cpc:         o.clicks\n                   ? Number((o.investment / o.clicks).toFixed(4))\n                   : 0,\n  }));\n}\n\n// 7. campaigns, ad-sets, ads\nconst byCampaign = aggBy(records, \"campaign_name\");\nconst byAdSet    = aggBy(records, \"adset_name\");\nconst byAd       = aggBy(records, \"ad_name\");\n\n// 8. top 10 cheapest CPC\nconst top10Campaigns = [...byCampaign]\n  .sort((a,b)=>a.cpc - b.cpc)\n  .slice(0,10)\n  .map(c=>({\n    campaign_name: c.name,\n    investment:    c.investment,\n    impressions:   c.impressions,\n    ctr:           c.ctr,\n    clicks:        c.clicks,\n    cpc:           c.cpc\n  }));\nconst top10AdSets = [...byAdSet]\n  .sort((a,b)=>a.cpc - b.cpc)\n  .slice(0,10)\n  .map(a=>({\n    ad_group_name: a.name,\n    investment:    a.investment,\n    impressions:   a.impressions,\n    ctr:           a.ctr,\n    clicks:        a.clicks,\n    cpc:           a.cpc\n  }));\nconst top10Ads = [...byAd]\n  .sort((a,b)=>a.cpc - b.cpc)\n  .slice(0,10)\n  .map(a=>({\n    ad_group_name: a.name,\n    investment:    a.investment,\n    impressions:   a.impressions,\n    ctr:           a.ctr,\n    clicks:        a.clicks,\n    cpc:           a.cpc\n  }));\n\n// 9. comparisons ordered by investment desc\nconst adsComparison    = byAd.sort((a,b)=>b.investment - a.investment);\nconst adSetsComparison = byAdSet.sort((a,b)=>b.investment - a.investment);\n\n// 10. monta o relatório final\nconst report = {\n  report_date: `${formatYMD(minDate)} - ${formatYMD(maxDate)}`,\n  overview,\n  day_by_day,\n  top_10_campaigns:   top10Campaigns,\n  ad_set_comparison: {\n    labels:      adSetsComparison.map(a=>a.name),\n    investment:  adSetsComparison.map(a=>Number(a.investment.toFixed(2))),\n    impressions: adSetsComparison.map(a=>a.impressions),\n    clicks:      adSetsComparison.map(a=>a.clicks),\n  },\n  top_10_ad_sets:     top10AdSets,\n  ads_comparison: {\n    labels:      adsComparison.map(a=>a.name),\n    investment:  adsComparison.map(a=>Number(a.investment.toFixed(2))),\n    impressions: adsComparison.map(a=>a.impressions),\n    clicks:      adsComparison.map(a=>a.clicks),\n  },\n  top_10_ads:        top10Ads\n};\n\n// 11. retorna um único item com o JSON pronto\nreturn [{ json: report }];"
      },
      "typeVersion": 2
    },
    {
      "id": "4fc7c497-470f-4f24-adb2-e0b411c706f3",
      "name": "下载二进制文件",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        320,
        40
      ],
      "parameters": {
        "url": "={{ $json.signedUrl }}",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "06bf787e-7304-401e-b522-4867bf3cec3d",
      "name": "为 Meta 广告生成洞察",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        -460,
        40
      ],
      "parameters": {
        "text": "={{ JSON.stringify($json) }}",
        "options": {
          "systemMessage": "=## Persona\nYou are a data-driven Meta Ads Insights Agent: concise, analytical, and focused on turning raw campaign data into clear, actionable recommendations.\n\n## Objective\n- Ingest structured Meta Ads data (campaigns → adsets → ads).  \n- Generate three tiers of performance insights: campaign, adset, ad.  \n- Keep each insight 200–300 characters, in English, ending with a practical tip.\n\n## Rules\n### Formatting\n- Output **only** a JSON object with keys: `campaign_insights`, `adset_insights`, `ad_insights`.  \n- Do **not** include any additional text or explanation.  \n- Ensure each insight string is between **200 and 300 characters** (inclusive).\n\n### Content\n- All insights must be written in clear, idiomatic English.  \n- Each insight must:\n  1. Summarize performance (e.g., spend vs. CTR/CPC comparisons).  \n  2. Highlight one key strength or weakness.  \n  3. Conclude with a single, concrete, actionable tip.\n\n### Constraints\n- Base insights strictly on the supplied data.  \n- Do not reference hypothetical or external factors.  \n- Do not break JSON syntax or embed markdown in the output.\n\n## Step by Step Instructions\n1. **Parse Input**: Read the incoming JSON payload of `campaigns` → `adsets` → `ads`.  \n2. **Aggregate Metrics**:  \n   - For each campaign: compute total investment, average CTR (weighted by impressions), and average CPC.  \n   - For each adset: compute its CTR and CPC.  \n   - For each ad: read its individual metrics.  \n3. **Identify Highlights**:  \n   - Campaign: find overall high or low performance signal.  \n   - Adset: select the best or worst by CTR or CPC.  \n   - Ad: select the top or bottom ad by CTR or CPC.  \n4. **Compose Insights**: For each level, draft a 200–300 character string that:  \n   - Summarizes performance.  \n   - Notes one key strength or weakness.  \n   - Ends with “Tip: …” giving a clear improvement action.  \n5. **Validate Length**: Confirm each insight is within 200–300 characters.  \n6. **Return Output**: Emit exactly the JSON object described below.\n\n## Output Format\n```json\n{\n  \"campaign_insights\": \"string (200–300 chars)\",\n  \"adset_insights\":  \"string (200–300 chars)\",\n  \"ad_insights\":     \"string (200–300 chars)\"\n}\n```"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 2
    },
    {
      "id": "be4f212e-78a9-4721-82e2-883e1ac02f9e",
      "name": "OpenAI 聊天模型",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        -560,
        300
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1",
          "cachedResultName": "gpt-4.1"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "id": "3hJLI2ahkB4Zjnwd",
          "name": "OpenAi account"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "18aa7809-12f6-4ed4-9523-5b4a8efe8140",
      "name": "结构化输出解析器",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        -200,
        260
      ],
      "parameters": {
        "jsonSchemaExample": "{\n  \"campaign_insights\": \"string (200–300 chars)\",\n  \"adset_insights\":  \"string (200–300 chars)\",\n  \"ad_insights\":     \"string (200–300 chars)\"\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "49f0bd44-ad30-482e-9c05-9027081d7c7a",
      "name": "生成变量",
      "type": "n8n-nodes-base.code",
      "position": [
        -100,
        40
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "const base = $(\"Format data\").first().json\n\nreturn {\n      ...base,                                           \n      insights_content:     $json.output.campaign_insights,\n      insights_ad_set_content:$json.output.adset_insights,\n      insights_ads_content:  $json.output.ad_insights,\n    };\n"
      },
      "typeVersion": 2
    },
    {
      "id": "d2bfb619-b65c-4371-a074-9384bdefd34b",
      "name": "Slack - 发送带文件的消息",
      "type": "n8n-nodes-base.slack",
      "position": [
        540,
        40
      ],
      "webhookId": "aae266d5-f681-4b86-bd2a-fceb1f5c182c",
      "parameters": {
        "options": {
          "fileName": "=Meta ADS Report - {{ $('Generate variables').first().json.report_date }}",
          "channelId": "C093723CAN6",
          "initialComment": "📊 *Here's your Weekly Meta Ads Report* \\n"
        },
        "resource": "file",
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "id": "ombwJbyognfRtMoj",
          "name": "Slack account"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "0bd7647f-1f8b-4478-8e76-2a3f176ad39d",
      "name": "每周一上午 8 点",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -1560,
        40
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 8
            }
          ]
        }
      },
      "typeVersion": 1.2
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "timezone": "America/Sao_Paulo",
    "callerPolicy": "workflowsFromSameOwner",
    "executionOrder": "v1",
    "executionTimeout": -1
  },
  "versionId": "85fcbd4c-7f9b-4aae-b04e-99a0980bd474",
  "connections": {
    "Pdforge": {
      "main": [
        [
          {
            "node": "Download Binary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Out": {
      "main": [
        [
          {
            "node": "Format data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format data": {
      "main": [
        [
          {
            "node": "Generating Insights for Meta Ads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Configuration": {
      "main": [
        [
          {
            "node": "HTTP Request - Meta Ads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download Binary": {
      "main": [
        [
          {
            "node": "Slack - Send Message with File",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Generating Insights for Meta Ads",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Generate variables": {
      "main": [
        [
          {
            "node": "Pdforge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Weekly at monday 8am": {
      "main": [
        [
          {
            "node": "Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request - Meta Ads": {
      "main": [
        [
          {
            "node": "Split Out",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Structured Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Generating Insights for Meta Ads",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Slack - Send Message with File": {
      "main": [
        []
      ]
    },
    "Generating Insights for Meta Ads": {
      "main": [
        [
          {
            "node": "Generate variables",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

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

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

中级 - 市场调研, AI 摘要总结

需要付费吗?

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

工作流信息
难度等级
中级
节点数量14
分类2
节点类型11
难度说明

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

作者
Marcelo Abreu

Marcelo Abreu

@mabreum

founder@pdforge

外部链接
在 n8n.io 查看

分享此工作流