Paid Media

Make.com + Gemini:每周自动抓一次竞品广告库的完整方案(Meta Ad Library 到 Airtable)

Make.com + Gemini:每周自动抓一次竞品广告库的完整方案(Meta Ad Library 到 Airtable)
目录

上个月的一个周二早上,Airtable 库里一个标记为 competitor-ads/this-week(本周竞品广告) 的视图多了一行:一条来自某直接竞品的视频广告,Gemini 给出的分类是 ugc-mirror(UGC 镜子视角) / skincare-routine(护肤日常) / 25-34-female(25-34 岁女性) / new-creative(全新创意)。Gemini 在这行下面附了一句话备注:"POV 风格的前后对比,第 6 秒出现一个核心成分口播,Hook 是 '我用了一个月'。" 就在上个周五,我盯的六个竞品主页里,还没有人看到过这条广告。等我周二下午再去看的时候,Meta 后台显示这条创意的曝光量已经过 8 万。把这一行写进表格的 Make 场景,每周一早 7 点自动跑,上周是 7:11:42 跑完的。

这个场景不是某个研究项目。它是一个跑了 11 个月的生产工具,覆盖 6 个竞品账号。也是我搭建过的最便宜的每周市场情报。6 个账号一次完整跑下来,总成本大约 $0.34。它平均每个账号、每周能抓到 3 条我手工会漏掉的竞品新广告。下面是这个场景的完整形态——每个模块、那条 Gemini 提示词、成本算账,还有头一个月里崩过的三个地方。

这个场景在做什么

每周一早 7 点,一次 Make.com 跑完,做这五件事:

  1. 调一次 Meta 广告资料库 API (Meta Ad Library API)——这是 Meta 公开的、广告主必须披露的广告数据库,可以按主页、国家、广告状态检索——拿到固定清单上 6 个竞品主页 ID 在过去 7 天新投放的所有广告,过滤到一个国家、ad_active_status = "ACTIVE"(仅展示投放中)。
  2. 对每条新广告,下载广告素材(图或视频首帧缩略图),把素材丢给 Gemini 2.5 Flash,配一条结构化提示词。
  3. Gemini 返回一个 JSON 对象:Hook 风格、格式、受众信号、Offer 类型、关键文案转写,以及一句"这条广告为什么值得看"的理由。
  4. 解析 JSON,把一行写进 Airtable 的 competitor-ads 表,通过主页 ID 关联到另一张 competitors 表。
  5. 最后,往另一张 weekly-digest(每周摘要)表里追加一行,周一早上我一边喝咖啡一边看的就是它。

整个流程到 Airtable 就停了。它不往 Slack 推,不自动发邮件,也不自动写回复。目的是 把信号浮出来,不是去 反应。反应是我看完摘要之后,自己做的判断。

动手前要准备的东西

组件 用途 近似成本
Meta 广告资料库 API 访问权限 免费,但要上 facebook.com/ads/library/api 申请一个带 ads_read 权限、审核通过的 App ID。我等了 4 天,有人等更久。 免费
Make.com Core 或 Pro 套餐 免费版(1,000 ops/月)不够用——Iterator(迭代器)模块会把操作数放大。Core($10.59/月,10K ops)够覆盖 6 个竞品账号每周一次的跑量。 约 $10.59/月
Google Gemini API Key 用 Gemini 2.5 Flash。它原生支持多模态(能接图像输入)、便宜、JSON 模式输出足够稳定,够这个活儿用。 按量计费,每百万输入 token 约 $0.30
Airtable Pro 套餐 至少要 Pro 才能突破单 base 1,200 行的限制。我跑了 9 个月后,base 里已经有 4,800 行。 $20/席位/月
首次搭建 ~2 小时 之后每次微调 15 分钟。

最被低估的隐性成本其实是 Meta API 申请。提前规划。如果你的开发者账号是全新的,留出一周时间。

Airtable 的表结构

表结构决定一切。我刻意保持扁平——一条广告一行,不嵌套。关联记录只指向 competitors 一张表。

competitors(每个竞品主页一行):

  • name(文本)
  • page_id(文本,Meta 的数字主页 ID)
  • category(单选:e-com、SaaS、retail、DTC、other)
  • monitored_since(日期)

competitor-ads(每条广告一行):

  • ad_id(文本,Meta 广告档案 ID,用作去重键)
  • competitor(关联记录 → competitors)
  • first_seen(日期)
  • creative_url(URL,Meta CDN 原始链接)
  • local_creative(附件,Make.com 下载下来的本地副本)
  • format(单选:image、video、carousel、collection、other)
  • hook_style(单选,固定词表见下方提示词)
  • offer_type(单选:discount、bundle、free-shipping、social-proof、lead-magnet、none-detected)
  • audience_signal(文本,1 个短语)
  • key_phrases(长文本,逗号分隔)
  • gemini_rationale(长文本,1-2 句)
  • manual_priority(单选:high、medium、low、ignore)

前三个单选字段是真正干活的列。Airtable 视图里我可以两下点击就筛出"竞品 X 最近 60 天的所有 ugc-mirror 广告"。去重键是 ad_id——把"Upsert(有则更新、无则创建)"动作配上去,就算 Iterator 跑两次,也不会产生重复行。

Make.com 场景的模块结构

自上而下,11 个模块,每周一早 7 点触发:

  1. Schedule(定时)——每周一 07:00,America/Los_Angeles 时区
  2. HTTP — Make an API call——请求 https://graph.facebook.com/v19.0/ads_archive,查询串为 access_tokensearch_page_ids={{1.page_id}}ad_reached_countries=["US"]ad_active_status=ACTIVEfields=id,ad_snapshot_url,ad_creation_time,ad_delivery_start_time,page_id,page_name,bylines,media_type,creative_thumbnail_url
  3. JSON — Parse JSON——解析上面那次响应
  4. Iterator(迭代器)——遍历 data 数组
  5. Filter(过滤)——ad_creation_time >= 现在-7d AND ad_id 不在 Airtable 已收录的 ad_id 集合里(去重门)
  6. HTTP — Download a file——从 creative_thumbnail_url 把素材拉下来
  7. Google Gemini — Create a Completion——把下载到的二进制文件作为图像输入送进去
  8. JSON — Parse JSON——解析 Gemini 的结构化输出
  9. Set Variable(设变量)——把解析出来的字段压扁成一个 bundle
  10. Airtable — Upsert a Record——在 competitor-ads 表里以 ad_id 为键,有则更新、无则创建
  11. Airtable — Create a Record——往 weekly-digest 表写一行,记录每个竞品的新增广告数

每条广告穿过 Iterator 一次,大概消耗 4 个 operation(HTTP、调用 Gemini、解析、upsert)。6 个竞品账号每周平均发现 5 条新广告,一次完整跑量约 120 个 operation。10K ops/月的套餐下,我能跑 43 次这样的周循环。

第 1 步——Meta 广告资料库的调用

端点是 https://graph.facebook.com/v19.0/ads_archive,参数集是官方文档里固定的。Make 的 HTTP 模块里,这次调用长这样:

Method: GET
URL: https://graph.facebook.com/v19.0/ads_archive
Headers: (空,token 放在查询串里)
Query String:
  access_token={{2.meta_token}}
  search_page_ids={{1.page_id}}
  ad_reached_countries=["US"]
  ad_active_status=ACTIVE
  fields=id,ad_snapshot_url,ad_creation_time,page_id,page_name,media_type,creative_thumbnail_url
  limit=50

我用的是 search_page_ids 而不是 search_terms——我要的就是竞品自家主页的广告,不是所有提到他们品牌的广告。media_type 字段是关键判别器:值是 VIDEOIMAGE 的我留下,TEXT 的我丢掉(纯文字广告没有素材可分类)。

ad_snapshot_url 是 Meta 广告资料库的公开页面,任何人点进去都能在原环境里看这条广告。我把这个 URL 存进 Airtable 行,因为我视图里点击率最高的就是这一列——看到 Gemini 说"interesting"的时候,我就点过去看广告原貌。

第 2 步——下载素材

模块 6 用另一个 HTTP 模块请求 creative_thumbnail_url,关键是把它配置成 把响应作为二进制文件返回,而不是字符串。这是头一个月坑了我两次的地方:Make 的 HTTP 模块默认返回 JSON,你得手动关掉"Parse response"、打开"Download file"。响应一旦正确以二进制返回,Make 会把文件存到 bundle 里,下一个模块就可以把它当图像输入了。

VIDEO 广告的 creative_thumbnail_url 是视频的静态封面帧,不是视频本身。这对 Gemini 分类视觉风格和屏幕文字已经够用——80% 的判断就靠这些。真要视频本体的话,ad_snapshot_url 那个 HTML 页面里通常会嵌一个 .mp4 链接,但那是一个独立、更脆弱的抓取,我懒得做。

第 3 步——调 Gemini(杠杆就出在这)

模块 7 把二进制文件交给 Gemini,搭配这条系统提示词。9 个月没改过:

你是一名广告创意分析师。你将看到一张广告图片,请对它分类。
只返回一个 JSON 对象,严格按下面的结构:

{
  "format": "image | video-still | carousel-first-frame | unknown",
  "hook_style": "ugc-mirror | talking-head | voiceover-over-b-roll |
                 text-overlay-heavy | product-hero | lifestyle-soft |
                 before-after | testimonial | founder-led |
                 comparison | listicle | meme | other",
  "offer_type": "discount | bundle | free-shipping | social-proof |
                lead-magnet | free-trial | none-detected",
  "audience_signal": "一句话短语,描述隐含的目标观众",
  "key_phrases": ["最多 6 个从广告里转写的短语"],
  "rationale": "1-2 句:这条广告想做什么、为什么可能有效",
  "confidence": "high | medium | low"
}

规则:
- 文字看不清就在 rationale 里说看不清,不要瞎猜。
- "ugc-mirror" 仅指自拍镜子视角的内容。
- 不要编造 Offer。如果看不出来,填 "none-detected"。
- key_phrases 必须原样转写,不要概括。
- 如果图片不是广告,confidence 填 "low" 并在 rationale 里解释。

提示词里的两个关键决定是 固定的 hook_styleoffer_type 词表。自由分类听起来很爽,但会把数据库搞死——我需要每一行都能被筛选。一个 13 项固定的 hook_style 词表配单选列,让我在 Airtable 一个公式里就能数出"竞品 X 第一季度投了多少 talking-head 广告"。

confidence: low 的行会被筛进一个叫 review-needed(待复核)的 Airtable 视图。大约 7% 的行落进去,几乎都是缩略图分辨率太低的视频广告,屏幕文字读不清。我每周日晚上批量复核一次。9 个月下来,没有一条真的"无法分类"——Gemini 只是对输入诚实地表达了不确定性。

第 4 步——Upsert 进 Airtable

模块 10 是 Make.com 原生的 Airtable "Upsert a Record" 动作,去重键是 Meta 给的 ad_id。这意味着这个场景是 幂等的——周二重新跑一遍周一的同一个场景,不会产生重复行;如果 Gemini 的分类有变化,会更新已有行,否则保持原样。幂等性是这套定时任务宽容度的来源。Make 调度器如果漏掉了一次周一,我周二手工重新触发,数据就接上了。

最后那张 weekly-digest 表,每次跑完追加一行:

字段
run_date (自动)
competitors_checked 6
new_ads_found 32
new_ads_by_format {"video": 19, "image": 11, "carousel": 2}
new_ads_by_hook {"ugc-mirror": 11, "talking-head": 7, ...}
top_competitor_this_week (Airtable 公式自动算)

周一早上我先打开这个视图,扫一眼 top_competitor_this_week 那一格,然后跳进 competitor-ads 表看新增行。一次过完一周,大概 9 分钟。

单次跑量的成本

6 个竞品账号、本周约 30 条新广告的一次完整跑量:

  • Make.com ops: 约 250 个 operation(HTTP + Iterator + Airtable 开销)。10K 套餐下,占月度配额的 2.5%。可忽略。
  • Gemini 2.5 Flash 调用: 30 次,平均 800 input token(图编码后约 600 token)+ 250 output token。按当前价格:单次约 $0.012
  • Airtable 记录: 每次 30 条新行,每月约 150 条,远低于任何上限。
  • Meta 广告资料库 API: 免费,但有速率限制,约 200 次/小时。我跑 6 个账号一周只发 6 次请求,速率限制从来不是瓶颈。扩到 40 个账号才会撞上。

合计:每次跑量约 $0.012,加上 Make 固定 $10.59/月、Airtable $20/月。 这条"情报"开支——LLM 带来的那部分新成本——基本就是个四舍五入的小数。

三个崩过的地方

1. HTTP 模块返回了文本而不是二进制。 我在这里栽了 2 周。修法是 HTTP 模块的 Advanced 里把"Download file"勾上。症状:Gemini 每一行都返回 "error: image not recognized"

2. Meta 广告资料库的 media_type 字段不一致。 同一支 carousel 广告,响应里有时是 media_type: "IMAGE",有时是 CAROUSEL。我在模块 5 加了一道门,直接丢掉 media_type: "TEXT" 的,剩下的放行。大约 4% 的行被这道门丢掉,会落进 review-needed 视图,等手工复核。

3. Gemini 在没 Offer 的广告上"脑补"出 Offer。 提示词第一版让 Gemini"识别 Offer"。它在一张完全没提优惠的广告上写出了"限时 30% off",因为广告里通常都有这种元素。加上"不要编造 Offer,看不出来就填 'none-detected'"这条规则、再加上 confidence: low 兜底,幻觉率从 22% 降到 3% 左右。我每个月检查一次 offer_type 字段的 none-detected 比例;如果对一个正常打价格战的竞品,这个比例低于 12%,我就知道 Gemini 又开始编了。

这个东西到底是干嘛用的

我不相信那种号称"基于竞品自动给你写广告"的 swiping file 工具(竞品素材库工具)。那是另一回事,大多数出来的都是同质化垃圾。我信的是 看到正在跑什么、便宜地分类、推送到我本来就会看的地方。在这套场景之前,我每周五手工去 Meta 广告资料库看一遍,经常漏。等上了这个场景,判断不是被替换了——只是从基于"不完整信息"的判断,变成了基于"完整视图"的判断。

如果你也要搭这个东西,两点提醒。第一,从 2 个竞品账号 开始,不是 6 个。首次搭建才是最慢的部分。第二,不要跳过 confidence: low 的复核环节。它是让系统保持诚实的唯一反馈回路。三个月后,low-confidence 比例会稳定在 6-8%,你那时的每周复核就是 5 分钟扫一遍的事。