AI Tools

多 Agent 竞品情报:3 个子 Agent 分盯站点/广告/社媒,周五自动出 PDF 简报

多 Agent 竞品情报:3 个子 Agent 分盯站点/广告/社媒,周五自动出 PDF 简报
目录

去年三月一个周三的下午,一个客户发来一张竞品首页的截图问我:"他们什么时候加的这一整层新套餐?"我在 Wayback Machine 上翻了 20 分钟才给出一个干净的答案:大概 11 天前,周二下午。那天我们三个人都在 Slack 房间里,没一个注意到。等截图到我邮箱的时候,竞品那一档新套餐已经上线快两周,我们的销售团队正在输掉四笔我们不知道自己已经输掉的单子。

那一周我搭了那条每周跑的竞品情报流水线。它已经跑了 14 个月,产出了 60 份周报,从第 3 周开始就抓住了清单上每一个真正有意义的竞品动作。整套东西是 3 个子 Agent 加一个"笨"父级。笨是设计。

一段话讲清楚架构

一个父级 Claude Agent 跑在每周五早上 6:00 的 cron(定时任务——靠时钟触发,每周固定时间跑)上。它从一个 Git 仓库的 competitors.json 文件里读 5 个竞品的清单。它并行派出 3 个子 Agent——站点监视、广告监视、社媒监视——给的是同一份清单,但活儿完全不同。每个子 Agent 返回一个 JSON 对象,结构必须严格符合父级校验过的 schema。父级把三份 JSON 合并成一份 Markdown 简报,喂给 Pandoc(一个文档转换工具,命令行把 Markdown 编译成 PDF),吐出一份 8 页 PDF。PDF 传到一个 Notion 页面,Slack 在 6:14 往 #competitive-intel 频道发一条通知。整条流水线 8 到 14 分钟跑完,取决于几个竞品站点的 JavaScript 有多肥。我周一早上喝着咖啡读这份简报——它是我每周打开的第一份营销物料。

真正有意思的部分不是 Agent。是它们之间的边界。三个子 Agent 互相不知道彼此存在;它们都不写散文;它们都不做任何排版。每个子 Agent 拿到的就是一份名字清单和 URL 清单,吐出来就是一个 JSON 文件。父级不知道什么是爬虫、什么是广告库、什么是社媒 API。它只懂一件事:把三份 JSON 文件拼到一份 Markdown 模板里。这就是设计——而且这个设计是故意做笨的。

为什么是 3 个子 Agent,而不是一个超级 Agent

我先试过"一个 Claude Agent 全包"的版本。当时想得很优雅:一份 prompt、一套工具集(Playwright、Meta Ads Library、社媒爬虫)、一个输出。跑了两个礼拜。输出来说是一塌糊涂也不算,但坏得很具体:Agent 把上下文窗口(context window——模型一次能"看到"的文本量)全花在格式化 JSON 契约上了,真正花在情报本身上的注意力就稀了。当站点监视子 Agent 抓到一次价格变动,它需要做好三件事:渲染页面、对比快照、对变动做分类。它不需要知道什么叫"hook angle"(钩子角度,广告开头吸引注意力的角度),它也不需要知道执行摘要长什么样。把这三件活儿塞进同一份 prompt 的结果就是每件活儿都只拿 60% 的注意力。拆开以后,每件活儿都拿到完整的上下文窗口。

更深一层的原因是失败隔离。超级 Agent 失败的时候,失败是模糊的——有时候社媒段是空的,有时候广告段是模型自己编的,有时候两段都是。我根本没法判断是哪个工具坏了。换成 3 个子 Agent 之后,父级拿到 3 份 JSON。如果 ads.json 缺了 meta_ads 字段,我就知道是 Meta 的 API token 过期了,简报剩下的部分没问题。如果 social.json 返回一个空 posts 数组,我就知道是 LinkedIn 爬虫被限流了(rate limit——平台在短时间内接受太多请求时会切断你),那这一段不能信。失败的颗粒度就是信任的颗粒度。

第三个原因是成本。一份又长又全的超级 Agent prompt,光 system message 就要烧 4,000 token 来描述三套工具和三件活儿。换成三份短 prompt,每份 800 token。每周跑一次的话其实没省几个钱,但是形态对了——只做一件事的 Agent 更好维护、更好替换、调用也更便宜。

子 Agent 1:站点监视

站点监视的活儿是本周在竞品自有的资产上发现了什么变化。它对一个竞品固定的 URL 清单跑 Playwright(无头浏览器——一个没有可见窗口的 Chrome,用来脚本化地操作网页)——首页、定价页、Top 3 产品页、招聘页。每个 URL 截一张图,给截图算一个感知哈希(perceptual hash——一种短"指纹"字符串,捕捉图片的视觉特征;两个几乎一样的页面会算出几乎一样的哈希),跟上周的哈希对比;哈希距离过了阈值,就把页面完整 HTML 拉下来,用一个 Python diff 库跟上一次快照做结构对比。

它返回的就是一份 JSON,schema 是这样:

json{
  "agent": "site-watcher",
  "run_id": "2026-03-12",
  "competitors": [
    {
      "name": "Acme Cloud",
      "urls_watched": ["https://acme.com", "https://acme.com/pricing"],
      "changes": [
        {
          "url": "https://acme.com/pricing",
          "change_type": "pricing_update",
          "severity": "high",
          "summary": "在 Pro 和 Enterprise 之间多了一档 $49/seat 的 'Team'",
          "before_excerpt": "Pro: $99/seat ... Enterprise: Contact sales",
          "after_excerpt": "Pro: $99/seat ... Team: $49/seat (min 5) ... Enterprise: Contact sales",
          "detected_at": "2026-03-11T14:22:00Z"
        }
      ],
      "no_change_urls": ["https://acme.com/about"]
    }
  ]
}

prompt 本身很短,整段大概 600 token:

You are a competitive site-watcher. For each competitor in the list, render the provided URLs, compare against last week's snapshot, and return a JSON object conforming exactly to this schema: {...}. For each change, classify the change_type as one of: pricing_update, new_feature, copy_shift, layout_change, unknown. Set severity to high if the change affects pricing, packaging, or a top-of-funnel page; medium for product page changes; low for footer / nav / careers. Do not invent changes. If the page failed to render, return error for that URL with the failure reason. No prose, no markdown, no commentary outside the JSON.

两个细节最关键。第一个是显式的 error 字段——当 Playwright 撞上爬虫从来没见过的版式,子 Agent 不会假装页面没变化。它对那个 URL 直接返回错误,父级把这一段标记为"覆盖有缺口",读到简报的人就知道这份周报里这个竞品得自己手动复查一遍。第二个是 severity 的规则。否则模型打 high 会非常宽松——没有显式规则,它会把每个 footer 的变化都标成 high。

子 Agent 2:广告监视

广告监视的活儿是扫 Meta 广告库(Meta Ads Library,Meta 公开的广告投放档案库)里每个竞品当前在跑的所有广告。它拉出活跃广告清单,按钩子角度和优惠类型给广告文案分类,然后按竞品报本周 vs 上周的广告数量变化。广告数量是真正能用的信号——Meta 不公开花费数据,但活跃广告数涨 40% 是投放预算增加的一个很强代理指标。

schema:

json{
  "agent": "ad-watcher",
  "run_id": "2026-03-12",
  "competitors": [
    {
      "name": "Acme Cloud",
      "active_ad_count": 47,
      "ad_count_delta_pct": 38.5,
      "new_ads": [
        {
          "ad_id": "abc123",
          "first_seen": "2026-03-09",
          "hook_angle": "fear_of_missing_out",
          "offer_type": "free_trial_extension",
          "copy_excerpt": "Q2 is closing. Extend your trial another 30 days, no card.",
          "platform": "instagram_feed"
        }
      ],
      "hook_distribution": {
        "fear_of_missing_out": 12,
        "social_proof": 8,
        "feature_callout": 15,
        "price_anchor": 7,
        "other": 5
      },
      "low_confidence": false
    }
  ]
}

有意思的字段是 low_confidence。Meta 广告库对中小竞品返回的数据非常少——有时候总共就 3 条广告,有时候只给 30 天以上的老广告。子 Agent 被显式告知:如果一个竞品活跃广告少于 10 条,就把 low_confidence 设成 true,然后把 hook_distribution 清零。父级在那个竞品的广告那一段就渲染成"低置信度,详见原始数据"——而不是假装这个分布有意义。从 3 条广告里编一个 hook 分布是竞品广告分析最常见的失败模式。schema 强制要求诚实。

子 Agent 3:社媒监视

社媒监视的活儿是三个里面最糙的,也是我最不信的那个。它爬每个竞品最近 14 天的 LinkedIn 帖子和推文,给帖子按主题分类,然后报主题分布的相对上周的偏移。

json{
  "agent": "social-watcher",
  "run_id": "2026-03-12",
  "competitors": [
    {
      "name": "Acme Cloud",
      "platforms_scanned": ["linkedin", "twitter"],
      "post_count": 23,
      "theme_distribution": {
        "product_launch": 4,
        "thought_leadership": 11,
        "customer_story": 3,
        "hiring": 5
      },
      "theme_shift_vs_last_run": {
        "thought_leadership": 6.0,
        "product_launch": -2.0
      },
      "notable_posts": [
        {
          "url": "https://linkedin.com/posts/acme-...",
          "date": "2026-03-08",
          "theme": "thought_leadership",
          "excerpt": "We rebuilt our entire billing system. Here's what we learned about event-driven architecture...",
          "engagement": { "likes": 1240, "comments": 87 }
        }
      ],
      "data_quality_note": "LinkedIn full-post scrape throttled at 14 days for 2 of 5 competitors"
    }
  ]
}

data_quality_note 是真正起决定性作用的字段。LinkedIn 爬虫即便带了认证,对全文内容也只能回看 14 天。超过 14 天的帖子拉回来就是被截断的预览。子 Agent 被明确告知:要把这件事写在 note 里,不能拿被截断的数据装作没事。父级在社媒段加个脚注把这件事标出来。知道自己拿到的数据是不完整的,比以为拿到一份完整图景、但其实建立在 14 天前被截断的碎片上,强得多。

父级 Agent 和那 8 页 PDF

父级 Agent 是整条流水线里最"笨"的一块。它不爬虫、不分类、不做综合,只做一件事——把三份 JSON 拼到一份 Markdown 模板里。它的完整 prompt 比任何一个子 Agent 都短:

You are a competitive intel brief writer. You will receive three JSON files: site.json, ads.json, social.json. Each conforms to a strict schema. Your only job is to:

  1. Read all three.
  2. Write page 1: a 200-word exec summary naming the most important change per competitor and the one recommended action for our team this week.
  3. Write pages 2–6: one page per competitor, with a fixed structure: "What changed on their site", "What they are advertising", "What they are posting about", "What this means for us" (3–5 sentences).
  4. Write page 7: a cross-competitor "this week's themes" page (e.g. "3 of 5 launched a new free-trial extension this week").
  5. Write page 8: the recommended actions list, ranked by impact. Use the provided Markdown template. Do not invent data. If a section is empty, write "No data this week" — do not pad. Return only Markdown, no commentary.

被 Pandoc 编译成 PDF 的就是这份 Markdown。它是一个 110 行的模板文件,里面定义了标题大小、强制分页的位置、固定的封面页。一行 Pandoc 命令搞定:

bashpandoc brief.md \
  --pdf-engine=xelatex \
  --template=template.tex \
  -V geometry:margin=1in \
  -V fontsize=10pt \
  -o brief-2026-03-12.pdf

视觉的部分全在 template.tex 里——V3 风格的几何 header、角落里等宽字体的日期、章节分隔线上的强调色。我从博客的设计系统扒过来再调过打印字号。

8 页严格对应:1 封面 + 执行摘要,5 个竞品各 1 页,1 跨竞品主题页,1 行动建议页。如果哪天变成 7 个竞品,简报就变 10 页,模板能撑开。父级只被告知页数,不知道模板细节,所以结构对清单的小变化是健壮的。

14 个月里真实踩过的坑

有个竞品把整个定价页重做成 React 单页应用。 Playwright 渲染出来没问题——感知哈希一模一样,肉眼看页面也没变——但真正的定价数据藏在一个 hydration(水合——服务端渲染好的 HTML 被 JavaScript"激活"变成可交互页面的过程)步骤后面。子 Agent 把定价页归到了 no_change_urls,简报写"本周无定价变动",下一周的运行才抓到新一档 $30/seat 的"Growth"。我丢了一周。修法是截图前加一个 200ms 的 waitForSelector。现在每个站点监视的 URL 都会等 200ms,整轮多 4 秒,值。

第 11 周 Meta 广告库 API 把广告监视子 Agent 限流了。 广告监视 5 个竞品全部返回 active_ad_count: 0,因为整轮跑到第 8 分钟就撞到了 rate limit。schema 里的 low_confidence: true 救了一回——父级把广告那段渲染成"低置信度,详见原始数据",简报照常出。没有这个置信度标志的话,简报会写"本周 5 个竞品全部暂停了所有投放"——一个五级火警,也是假的。真正的故事是 rate-limit 报错;schema 让我们能把这件事说清楚。

第 23 周 LinkedIn 爬虫悄悄改了认证要求。 子 Agent 对 5 个竞品里的 4 个返回了空 posts 数组,对第 5 个返回了部分数组。又是 data_quality_note 字段救了一回——父级在社媒段加了一个覆盖脚注。简报照常出。认证是第二天修的,那一周社媒覆盖范围是一个"已知的未知"。

第 31 周一个竞品把首页文案从 "AI-powered" 换成了 "agentic"。 站点监视标了 copy_shiftseverity: low。父级的执行摘要没提。我读了简报不认同这个降级,加了一条显式规则:任何包含 "AI"、"agent"、"automation"、"GPT" 这几个字符串的 copy_shift 都要标 severity: high。下一周抓到同一家竞品把博客全面转向"agentic workflows"——这一回在执行摘要页露了面。

老实账

14 个月。60 份周报。312 次子 Agent 运行(60 周 × 大约 5 个竞品 × 3 个 Agent,含重试)。第一个月有过一次值得写 postmortem 的漏报。总共 Anthropic 花费:84 美元。Pandoc + Playwright + Apify 总成本:每份周报大约 0.20 美元。占用的真人时间:每周读简报约 20 分钟,每月维护监控清单和调 prompt 约 30 分钟。相比人工扫竞品省下来的时间:每周大约 6 小时。光是那个我本会漏掉的"竞品加新套餐"那一周,就够把这条流水线一直用到 2027 年。

这份周报最擅长的不是抓大动作。大动作迟早有人会看到。它擅长的是抓小动作——一次 7% 的降价、一个改了名的功能、活跃广告数涨 40%——人工扫的时候人会一眼带过,但一个季度累积下来就不一样了。到第 4 个月,我在 3 月做的定价决策比 11 月好了一截,这里面大部分功劳是"每周每个竞品的每个动作都看到"——而不是我变聪明了。

如果让我重来:我会把父级 prompt 也放进版本控制里,跟流水线其余部分放一起;另外再加一个 prompts/ 目录,把三个子 Agent 的 prompt 各自存成单独的文件。现在它们跟 n8n 工作流 JSON 里其他配置挤在一起,改 prompt 等于一次部署。一个流水线无所谓,三个就错了。

如果你要搭这个,先把 JSON 契约写对。schema 写对了,prompt 中等水平流水线也跑得起来。schema 是契约——松了,父级就开始编;紧了,父级就只能说真话。花一个下午打磨 schema。prompt 是周二的活儿。