游戏内广告变现工程 2026:eCPM 预测与广告负载决策
游戏内广告收入不由「放多少广告」决定,而由三个可分离的决策层决定:何时请求、向谁请求、这一枪值多少钱。本文逐层拆解并现场计算:填充率饱和下的次线性收益、eCPM 重尾下用均值当底价少收 5.01%、最优广告负载只由留存疲劳系数决定,以及最关键的一层——观测上「看广告的人留存高 15.7 个百分点」,真实因果却是掉了 9.1 个百分点,方向完全相反。
约 44 分钟阅读12,919 字3 次阅读博主

游戏内广告收入不由「放多少广告」决定,而由三个可分离的决策层决定:何时请求、向谁请求、这一枪值多少钱。本文逐层拆解并现场计算:填充率饱和下的次线性收益、eCPM 重尾下用均值当底价少收 5.01%、最优广告负载只由留存疲劳系数决定,以及最关键的一层——观测上「看广告的人留存高 15.7 个百分点」,真实因果却是掉了 9.1 个百分点,方向完全相反。

一句话摘要:游戏内广告(IAA)的收入不是由「放多少广告」决定的,而是由三个可分离的决策层决定——在什么时候向谁请求一次广告(请求与填充)、这一次请求值多少钱(eCPM 与竞价)、以及在多少人身上花掉这次曝光(负载与人群);把这三层各自的最优解拆开,你会发现真正需要 AI 的地方既不是生成素材,也不是调 eCPM 公式,而是回答一个被混淆了太久的问题:这一次曝光对这个玩家而言,究竟是净收益还是净损失。
游戏内广告变现,英文缩写 IAA(in-app advertising),是很多团队绕不开又不愿深谈的话题。绕不开,是因为它是免费游戏和小游戏最主要的收入来源;不愿谈,是因为它牵扯着一堆不好量化、不好汇报、又不敢轻易优化的灰色地带的指标——填充率、曝光率、eCPM、广告频次、玩家留存、激励视频的完播与放弃率。
这篇文章想做的,是把 IAA 拆成一组可以逐层验证、每层都能自算的决策问题,并且明确指出每一层里 AI 到底该做什么、不该做什么。全程使用可以复现的数字:每一个硬数字都来自自写脚本的现场计算,或者来自一手文档里抓下来的原始样例,而不是经验值。
先把常见的混淆拆开。一次「广告曝光」在工程上会经过这样一条链路:
玩家触发某个场景(复活 / 翻倍 / 领取)
→ 客户端调用 createRewardedVideoAd().show()
→ 聚合层(mediation)向多个广告源发请求
→ 某个广告源返回填充(fill),返回这次曝光的报价(eCPM)
→ 玩家看完 / 中途关闭
→ 平台回调 onClose(res),res.isEnded 标记是否完整观看
→ 开发者按 isEnded 发放奖励(复活卡 / 金币 / 体力)
这条链路上至少有三次独立的决策,它们经常被塞进同一个「广告策略」配置项里,结果就是谁都调不动:
第一层是「要不要请求」。 在玩家点「看广告复活」的时候,客户端是不是该真的去拉一次广告?这取决于当下的填充率、库存、玩家所在时段、玩家画像。填不上的时候,这次请求不只是白跑一次网络往返——它还占用了主线程、消耗了电量,并且在很多实现里会让按钮进入 loading 态,让玩家误以为游戏卡了。
第二层是「这一枪值多少钱」。 一次填充的 eCPM 不是常数,它由竞价结果决定,受时段、地域、供需、广告源库存影响。这一层的定价权大部分掌握在广告平台手里,开发者能施加影响的地方是:选瀑布流还是实时竞价(in-app bidding)、设不设底价(floor)、以及是否接入多聚合层。
第三层是「这一枪该打给谁」。 这是最关键、也是最容易被忽略的一层:同一个激励视频广告位,展示给不同的玩家,对玩家的长期价值完全不同。有的玩家看广告是「白嫖了一个复活卡」,有的玩家是被硬塞了一条广告、被打断了节奏、并且从此对游戏产生反感。这两者的差别不会出现在任何一条 eCPM 报表里,因为它只发生在留存曲线上。
这三层里,第一层和第三层是你独有的、平台看不到的——只有你自己知道这个玩家刚刚死了三次、刚刚通关、刚刚充了值。这正是 AI 在 IAA 里真正有位置的地方。而第二层,更像是在跟专业拍卖行打交道,你的杠杆有限。
很多团队的现状是:第一层拍脑袋定「每 3 关插一次」,第二层交给聚合层默认配置,第三层根本不看——所有玩家拿到的广告频次和时机完全一样。整条链路上没有任何一个环节使用了玩家层面的信息。
在讨论任何优化之前,先做一件看起来无聊但能省掉后续 80% 扯皮的事:验证你的收入数字是不是自洽的。
微信小游戏的《广告数据接口》文档给出了广告分析数据的字段定义,其中「小游戏广告汇总数据」接口(publisher_adpos_general)的返回里包含 req_succ_count(拉取量)、exposure_count(曝光量)、exposure_rate(曝光率)、click_count(点击量)、click_rate(点击率)、income(收入,单位分)、ecpm(广告千次曝光收益,单位分)。
文档给的返回样例是这样一个数据集:
| 字段 | 值 |
|---|---|
| ad_slot | SLOT_ID_WEAPP_INTERSTITIAL |
| date | 2020-04-13 |
| req_succ_count | 443610 |
| exposure_count | 181814 |
| exposure_rate | 0.409850995 |
| click_count | 10095 |
| click_rate | 0.055523777 |
| income | 52175 |
| ecpm | 286.969100289 |
这组数字看起来很平常,但它是可验证的。我把它代入三个恒等式跑了一遍:
exposure_rate = exposure_count / req_succ_count
= 181814 / 443610
= 0.409850995 ✓ 与文档一致(偏差 2.44e-10)
click_rate = click_count / exposure_count
= 10095 / 181814
= 0.055523777 ✓ 与文档一致(偏差 4.69e-11)
income = exposure_count × ecpm / 1000
= 181814 × 286.969100289 / 1000
= 52175.0000 分 ✓ 与文档一致(偏差 0.000000)
三个恒等式全部闭合。这看起来是废话,但它在你自己的数据仓库里未必成立——很多团队自己算出来的 eCPM 和平台报表对不上,原因就是曝光口径不一致:客户端上报的「展示」是 show() 调用成功,而平台记账的「曝光」是广告素材真正渲染完成。这两者在有加载失败、有切后台、有低电量跳过的场景里差得很远。
建议把这条链路的口径校验做成一个每日自动跑的断言脚本:拉取 publisher_adpos_general 的数据,逐日校验上面三个恒等式,偏差超过一个数量级就告警。这个脚本本身不需要 AI,但它会挡住所有后续优化——因为如果曝光口径本身是错的,你连「优化了什么」都说不清楚。
顺带提一句这份文档里几个容易漏掉的细节:
total_num 是 list 返回总条数;第一层决策是「什么时候去请求一次广告」。这里有一个反直觉的结论:拉取量增加带来的收入增长,必然是次线性的,因为填充率会饱和。
我用一个显式的假设模型来量化这件事(注意:这是自建模型,参数是假设值,不是平台真实曲线):
fill(rq) = FMAX × (1 − e^(−rq/KT)) FMAX = 0.62, KT = 12
ARPDAU_ads(rq) = rq × fill(rq) × eCPM / 1000
代入 eCPM = 286.9691 分(即上面文档样例的插屏 eCPM):
| 日请求数 rq | 填充率 | ARPDAU(分) | 环比增量 |
|---|---|---|---|
| 1 | 0.0496 | 0.0142 | — |
| 2 | 0.0952 | 0.0546 | +284.0% |
| 4 | 0.1758 | 0.2017 | +269.3% |
| 8 | 0.3017 | 0.6926 | +243.3% |
| 12 | 0.3919 | 1.3496 | +94.9% |
| 16 | 0.4566 | 2.0963 | +55.3% |
| 24 | 0.5361 | 3.6922 | +76.1% |
| 32 | 0.5769 | 5.2979 | +43.5% |
看最后两列:请求量翻倍带来的 ARPDAU 增幅在持续下降。在 rq = 8 处,一次额外请求的边际 ARPDAU 是 0.15e-3 分;到 rq = 24 时是 0.20e-3 分。而这个模型的渐近上界是 FMAX × eCPM / 1000 = 0.1779 分/请求——填充率饱和之后,每多请求一次的边际收益趋近于常数,不会趋近于零,但也不会爆炸。
把这张表翻译成工程决策,结论有三条:
第一,不要用「拉取量」当增长 KPI。 把「每 DAU 请求次数」拉高很容易,改个配置就行;但这个指标和收入的关系是次线性的,而且拉高它会消耗电量、占用主线程、并且在填充率低的时候制造大量无效请求。真正该看的是 income / req_succ_count,也就是每一次请求的平均变现效率。
第二,无效请求要按「机会成本」计价。 一次填充失败的请求,在游戏内的真实成本不是一次网络往返,而是:一次 show() 的 Promise reject、一次按钮 loading 态的进出、一段玩家等待、以及可能一次「游戏卡了」的投诉。填不上的时候干脆不请求,是一个纯收益的优化——但前提是你有办法预测「这次大概率填不上」,而这恰恰是 AI 能介入的地方(用玩家画像 + 时段 + 场景预测填充率)。
第三,请求的时机必须和场景绑定,而不是和定时器绑定。 激励视频的请求应该由玩家行为触发(他点了复活按钮),而不是由「每 60 秒检查一次」触发。前者的填充率和转化率都显著更高,因为用户已经处在明确的「我愿意看广告换这个东西」的意图状态里。微信《IAA 预测组件》文档里那个「复活场景」的案例,把这条讲得很直白:开发者按 gift_scene(广告触发场景)和 gift_name(广告设计点)分类型上报当天的点击次数与成功看完次数,平台才能给出这个玩家的潜力档位。场景标签不干净,后面的模型就没有输入。
第二个反直觉的结论:eCPM 的分布是重尾的,用均值做任何决策都是错的。
用一个 lognormal 分布(μ = 4.2, σ = 0.8)做压力测试,30 万次采样:
mean = 91.60
median = 66.45
mean / median = 1.3784
p95 = 247.7
p99 = 429.9
p99 / median = 6.47 倍
也就是说,1% 的高价曝光把均值拉高了 38%。这在三个地方会直接坑到你:
坑一:底价(floor)设成均值。 在 OpenRTB 的语义下,底价决定了一次请求是否参与竞价;设得太高会直接损失填充,设得太低会让高价请求拿不到应得的溢价。在上面的分布下,比较几种底价设定的期望单次收益:
| 底价设定 | E单次请求收入 |
|---|---|
| 中位数 66.45 | 33.2268 |
| 均值 91.60 | 31.5637 |
| 最优(在候选分位中枚举) | 33.2268 |
用均值当底价,比最优底价少收 5.01%。 5% 听起来不多,但这是纯参数选择的损耗,没有任何工程复杂度成本。
坑二:均值算出来的「保本曝光量」偏乐观。 收入预估模型里的曝光量往往用均值反推,导致实际的曝光分布比预估更集中在低分位——收入比你以为的少,而且方差比你以为的大。
坑三:拿 eCPM 的均值去做 A/B 显著性检验。 重尾分布的均值估计收敛极慢,两组各几万次曝光的对比,eCPM 均值的置信区间可能宽到毫无意义。对这类指标,要么用分位数(中位数、P75)做主指标,要么做 bootstrap 重采样,要么直接改成对「每次请求收入」做非参数检验。
一个具体的落地建议:你的收入报表里应该同时有均值和中位数,并且监控两者的比值。 这个比值本身就是填充质量的指标——比值突然从 1.2 跳到 1.9,说明你的流量结构变了(可能是某个高价广告源开始抢量,也可能是某个低填充时段被混进来了)。
第二层里你能施加影响的地方,主要是聚合(mediation)的配置。Unity LevelPlay 的官方文档对这件事的表述非常清楚:
Use a Bidding Placement for third-party-mediated projects to receive the most competitive bid for your ad space. Unlike traditional waterfalls, where mediation platforms prioritize ad networks based on manual targets set by the publisher, bidding allows for a fair auction where multiple ad networks can compete in real-time for impressions. The ad network with the highest bid wins the bid request.
要点有三个:瀑布流是发布商手工设优先级(谁排前面谁先被调用,填不上才轮到下一个),竞价是实时拍卖,最高出价者胜出。
文档里还有两条非常实用的迁移约束,值得单独摘出来,因为踩过的团队不少:
文档还记录了一个具体的报错,来自把 waterfall 切成 bidding 时参数没配对:
Header bidding load invocation failed: adMarkup is missing; objectId is missing
这个错误信息本身很有信息量——它告诉你 bidding 路径需要 adMarkup 和 objectId 两个参数,而 waterfall 路径不需要。
对照 AdMob Mediation 的官方文档,可以看到行业已经收敛到「bidding 适配器需要显式初始化」这个模式:文档要求 bidding 场景使用 Google Mobile Ads SDK (Legacy) 18.3.0 或更高版本,并且强调 必须等所有 mediation adapter 的初始化完成之后再 load ads,否则第一次广告请求会缺少部分广告源的参与,直接损失那部分广告源的填充。AdMob 文档列出的可选广告源有二十多家,从 AdColony、AppLovin、BidMachine 到 Mintegral、Pangle、Unity Ads、Vpon 等。
这两件事加在一起,指向一个明确的工程动作:升级到竞价前,先做一次「初始化顺序」和「首次请求参与率」的专项验证。 具体做法是:在测试环境用 Ad Inspector(AdMob 提供的调试工具)看一次真实的广告请求,确认参与竞价的广告源数量符合预期,再放量。
我得强调一句:这一层的定价权主要在广告平台手里,你的手牌是「选竞价还是瀑布流、设多少底价、接哪些广告源、怎么切量」——而不是「让 eCPM 变高」。任何声称能靠 AI 直接把 eCPM 提升 30% 的方案,在架构上就不成立,因为 eCPM 是竞价的结果,不是你能设定的参数。
第三层是本文的核心,也是大多数团队真正应该动手的地方:给定一个固定的曝光预算,把它花在哪些玩家身上。
先说一个反直觉但可以严格证明的结论:在这个模型下,最优广告负载与 eCPM 无关。
我用一个显式的留存衰减模型(同样是自建假设模型):
surv(t) = r0^t × e^(−k·a·t) r0 = 0.35, T = 30 天
LTV(a) = Σ_{t=1..T} surv(t) × a × e e = 单次曝光收入
对 a 做扫描,结果:
| k(每次广告的留存折损) | 最优负载 a* | 固定 a=6 的 LTV 损失 | LTV(e=60)/LTV(e=1000) |
|---|---|---|---|
| 0.02 | 30.00 | 62.13% | 0.0600 |
| 0.04 | 21.25 | 39.02% | 0.0600 |
| 0.06 | 14.15 | 22.21% | 0.0600 |
| 0.10 | 8.50 | 4.60% | 0.0600 |
| 0.15 | 5.65 | 0.14% | 0.0600 |
| 0.25 | 3.40 | 15.02% | 0.0600 |
最后一列在所有 k 下都等于 0.0600,正好是 60/1000。因为 LTV 对 e 是严格线性的,eCPM 翻十几倍只会等比例放大 LTV,不会改变最优负载的位置。a* 只由两个东西决定:留存折损系数 k 和基线留存 r0。
这个结论的工程含义非常强:
「我们这款游戏适合一天放 6 次广告」这种结论是错的,除非你同时知道 k。 你必须先估出 k——玩家每多看一次广告,次留下降多少——才能算出你自己的 a*。k 大的游戏(重度单机、玩家对打断极敏感)a* 只有 3.4 次;k 小的游戏(放置挂机、玩家本来就在等)a* 可以到 30 次以上。用同一个「每 N 次关卡插一次」的规则覆盖所有项目,是把一个可计算的决策变成了一个拍脑袋的常数。
而且注意 k = 0.25 那一行:固定 a = 6 反而比最优值损失 15.02%,因为此时 a* = 3.4,固定值已经超出了最优区间——多投的那部分广告纯粹在消耗留存。超载和欠载的损失形态是不一样的,但报表上看起来一样:ARPDAU 涨了,留存跌了。
怎么估 k? 这是本层唯一需要 AI 的地方,而且答案不是回归,是实验。k 是一个因果量:你需要知道「多展示一次广告 → 次留变化多少」,而观测数据里存在严重的混淆——爱看广告的玩家往往本身就更活跃、留存也更高。下一节专门处理这个坑。
这是我认为整篇文章里最值得写的一段。
先看一个我现场算出来的数字。构造一个模拟场景:200,000 个玩家,潜在活跃度 z ~ N(0,1);展示广告的概率随 z 上升(爱玩的人更可能点广告);基线留存随 z 上升;广告对留存的真实因果效应是异质的、且平均为负(delta = −0.06 − 0.05z,z 低的玩家受损小甚至受益,z 高的玩家受损大)。
算出来的两组数字是这样的:
NAIVE gap E[ret|exposed] − E[ret|control] = +15.743 pp ← 观测到的
TRUE ATE over exposed = −9.071 pp ← 真实因果效应
方向差 = 24.81 pp
朴素分析告诉你「看广告的人留存高 15.7 个百分点」,真实因果效应却是「广告让留存掉了 9.1 个百分点」。方向完全相反。
原因不玄学:E[z|exposed] = +0.614 而 E[z|control] = −0.328,被展示广告的玩家本身就是更活跃的一批人(selection on observables)。你测到的「广告效果」里,混着的是「本来就更活跃」这个选择效应。
如果团队用这个朴素结论做决策,得到的建议是「多投广告」——正好是反的。这不是小误差,是 180 度掉头。
那正确的方法是什么?Uplift / CATE 建模。 我在同一份数据上跑了一个 T-learner(处理组和对照组各训一个逻辑回归,特征用 [1, z, z², z³]):
mean CATE|exposed = −1.450 pp (模型估的)
TRUE ATE|exposed = −9.071 pp (真的)
corr(CATE, true delta) = 0.9462 ← 排序几乎正确
CATE > 0 的占比 = 2.1% vs 真实正向占比 2.1%
这组数字给出了两条很实用的结论:
T-learner 的量级被严重压缩了(−1.45 vs −9.07),但排序几乎完全正确(相关系数 0.9462,CATE>0 的比例 2.1% 与真实完全一致)。 这意味着:用 uplift 模型做「谁该看广告」的排序是可靠的,但拿它的绝对值去预测「这次曝光会让留存掉几个点」是不可靠的。 前者可以上线,后者只能作为相对参考。这直接对应到工程上:把 CATE 当作一个分配用的分数(谁优先给他看),而不是当作收益预估(他看了能赚多少留存)。
正向收益的人群是极少数(2.1%)。 也就是说,对绝大部分玩家,展示广告在因果上是净损失——只是这个损失小到淹没在噪声里。这意味着「按人数平均地投广告」这个默认策略,从因果角度看是明确错误的。
那么,把曝光预算定向给高 CATE 的人,效果如何?我在**固定曝光预算(每 1000 用户 6 次曝光,收入完全相同)**的条件下比较三种分配策略:
| 分配策略 | 曝光/1000 用户 | 留存 | 收入/DAU(分) |
|---|---|---|---|
| 不投放 | 0 | 60.5052% | 0.0000 |
| 随机分配 | 6 | 60.5040% | 0.0017 |
| uplift 定向 | 6 | 60.5103% | 0.0017 |
同样的收入,uplift 定向比随机分配多留住 0.0063 个百分点的玩家;比完全不投还多 0.0051 个百分点。 而无差别随机投放相比不投,是 −0.0012 pp 的净损失——收入换不来留存。
0.0063 pp 在生产环境里非常小,但请注意这个数字是在「平均伤害为 −9 pp」的最坏情形、以及「只有 2.1% 的人真的受益」的极端结构下算出来的。真实产品里,广告场景的类型差异(复活场景 vs 签到场景 vs 关卡结算)会带来更大的异质性,uplift 的可操作空间也会更大。
顺带说清楚这里的统计陷阱:如果你在报表上看到「看过广告的用户留存是 60%,没看广告的是 55%」,正确的反应既不是「广告提升留存 5 个点」,也不是「广告降低留存 5 个点」,而是「我不知道」。要回答「广告对留存的因果影响」,只有三条路:
工程上还有两个必须做的前置动作,缺一个上面的结论都不成立:
gift_scene(触发场景,如玩法单局内 / 局外签到 / 局外数值养成)、gift_name(广告设计点,如复活卡、刷新卡、养成资源)、gift_value(点击拉起次数)、gift_value_success(成功看完次数)。没有场景标签,uplift 模型只能学出一个「广告 vs 不广告」的粗粒度效应,而广告位之间的差异才是最大的收益来源。isEnded 为准。 微信 RewardedVideoAd.onClose 的回调参数 res.isEnded 字段明确表示「视频是否是在用户完整观看的情况下被关闭的」(最低版本 2.1.0)。按 isEnded 发放奖励是基本要求;反过来,把它也当作训练信号(gift_value 与 gift_value_success 的差值就是该玩家对该场景的放弃率),能直接把「哪些玩家/场景的激励视频设计有问题」暴露出来。前面说过,最值得用 AI 的一层是「这一枪打给谁」。这一层上有一个现成的、被大量团队忽略的资源:微信小游戏的 IAA 预测组件。
按官方文档,这是一个需要单独申请权限的插件(文档里给出的申请入口与 game.json 声明方式都写得很清楚,provider 为 wxaed5ace05d92b218,通过 requirePlugin('MiniGameCommon') 调用),核心能力是 getUserLabel。
它的返回结构里有两个关键字段族:
一是档位 value,体现「预测用户潜力从小到大」。文档给出了两套查询口径:
| labelId | 时间窗口 | value 区间 | 潜力系数区间 |
|---|---|---|---|
| iaa_feature | 近 30 分钟 | 0–10 | 0–300 |
| iaa_feature | 近 30 分钟 | 10–20 | 300–1000 |
| iaa_feature | 近 30 分钟 | 20–30 | 1000–3000 |
| iaa_feature_v2 | 当日 0 点至今 | 0–500 | 0–5000 |
| iaa_feature_v2 | 当日 0 点至今 | 501 | >5000 |
二是用户活跃属性:today_play_duration(当天总游玩时长,秒)、weekly_login_cnt(周登录次数)、continuous_login_days(连续登录天数)。v2 口径还额外返回 value_exposure(当日 0 点至今广告曝光次数)。
这本质上就是一个平台侧训练好的用户变现潜力分档模型。免费用,而且它已经在用「活跃 / 分享 / 潜力」这类跨游戏属性做特征了——这正是你自建模型很难补齐的部分。
但用它的方式决定效果。文档里有三条约束,每一条都会直接影响你的收益:
那怎么用这些档位?我算了一下「一刀切」的错误有多严重。显式假设:潜力系数与该用户的预期变现能力成正比(这个假设文档没有明说,是我为了让计算可执行而设的,实际关系需要你自己用数据校准)。按各档系数区间的中点(150 / 650 / 2000,全量均值 933.3)估算:
| 投放策略 | 覆盖率 | 单用户收入倍数 | 总收入倍数 |
|---|---|---|---|
| 只投档位 0 | 33% | 0.161 | 0.054 |
| 只投档位 1 | 33% | 0.696 | 0.232 |
| 只投档位 2(最高档) | 33% | 2.143 | 0.714 |
| 只投档位 1+2 | 67% | 1.420 | 0.946 |
| 全量投放 | 100% | 1.000 | 1.000 |
只投最高档的总收入只有全量的 71.4%——「高潜力用户就多投、低潜力用户就不投」这种直觉策略,在这个模型下会让总收入少掉近三成。 因为最高档只覆盖三分之一的人,把另外三分之二的钱直接扔掉了。
正确的做法是分层投放:低档位用户降低频次(而不是降到零)、中档位维持默认、高档位适度提高。同时这跟第六节的结论是一致的——真正该调的不是「给不给」,而是「给多少」。
还要提一个同样属于平台能力、但几乎没人用的接口:RewardedVideoAd.setMiniGameAutoIntelligenceInfo。按文档,它用于「设置小游戏智能托管关卡信息」,字段包括:
curveIdStr:当前用户所使用 5-15 关整体难度轨迹levelId:当前所处的关卡 IDdurationMs:本关实际游戏时间failCount:本关内失误次数retryCount:本关重试次数hintUsed:本关消耗提示数文档明确要求「须在 load() 前调用,下次广告请求会带上」。这等于是在告诉广告系统:这个玩家当前卡在哪一关、失败了几次、消耗了几个提示。 有了这份画像,广告侧(尤其是激励视频的激励对齐)就能做出更贴合的呈现——对一个连续失败 5 次的玩家和一个一路通关的玩家,展示同样的「双倍奖励」广告,前者的接受度显然不同。这份数据不需要你自己训练模型,代价只是几行调用和一次字段填充。
安全侧也有一层现成设施:微信小游戏文档里有独立的《广告反作弊》章节;激励视频还支持 setServerSideVerificationData,用于向平台回传 userId、rewardItem(奖励品类)、rewardAmount(奖励个数)、customData(自定义数据)——把「谁看了广告、换了什么」这条链路做成服务端可校验的形式,而不是纯客户端自证。
把上面这些压成一份按依赖顺序排列的清单。前三步不需要 AI,但没做完后面全是空中楼阁。
第 0 步:口径自证。 建一个每日跑的断言脚本,校验 exposure_rate、click_rate、income 三个恒等式在自有数据仓库里闭合。不闭合就不要往下走。
第 1 步:把广告负载从常数变成可计算的量。 用随机实验估出你自己的 k(次留对广告次数的因果弹性),代进第六节的模型算出 a*,再和现有的固定策略做对比。这个数字会成为你向产品方汇报的唯一核心指标。
第 2 步:给广告位打场景标签。 按微信 IAA 组件的字段规范,把 gift_scene / gift_name / gift_value / gift_value_success 四个字段落进自有数据。没有这四个字段,后面所有模型都做不了。
第 3 步:奖励发放严格以 isEnded 为准,并同步调用 setServerSideVerificationData 做服务端留痕。
第 4 步:接入平台档位,但按分层而非一刀切使用。 申请 IAA 预测组件权限;明确处理「返回 0」的两语义区分;针对「体验版无模型」设计降级路径并单独埋点;用第 4 节的测算结果设计分层频次表。
第 5 步:把关卡难度画像喂给广告请求。 调 setMiniGameAutoIntelligenceInfo,注意必须在 load() 前调用。
第 6 步:上 uplift 模型做分配决策,但只把它当排序分数用。 保留一个小流量的随机桶做持续校准,防止模型的绝对量级随时间漂移。
第 7 步:重估聚合层配置。 考虑从瀑布流切到竞价,但先验证 adapter 初始化顺序与首次请求参与率;切换前确认好 waterfall Placement 的归档与迁移路径,别在切换过程中才发现 Ad Unit 的 Placement 类型互斥。
第 8 步:监控指标体系。 收入侧用中位数与 P75 而非均值做主指标,监控 mean/median 比值作为填充质量信号;成本侧盯留存曲线(而不是只看次留);决策侧盯「无效请求占比」和「各场景的 CATE 分布」。
四个不要:
最后说一句:这篇里所有模型都是自建的、参数是显式假设,所有一手事实(API 字段、档位区间、结算比例、竞价与瀑布流的迁移约束、报错文案)都来自官方文档原文。而这恰恰是 IAA 这块最该有的态度——平台给你的数据是黑箱,你的模型是白箱;只有当你的白箱能被自己的恒等式证伪时,你才知道该信它多少。
wx.createRewardedVideoAd(参数 adUnitId / multiton / disableFallbackSharePage,小游戏端基础库要求 ≥ 2.0.4,单例语义说明)https://developers.weixin.qq.com/minigame/dev/api/ad/wx.createRewardedVideoAd.htmlRewardedVideoAd.onClose(回调参数 res.isEnded,最低版本 2.1.0)https://developers.weixin.qq.com/minigame/dev/api/ad/RewardedVideoAd.onClose.htmlRewardedVideoAd.setServerSideVerificationData(userId / rewardItem / rewardAmount / customData)https://developers.weixin.qq.com/minigame/dev/api/ad/RewardedVideoAd.setServerSideVerificationData.htmlRewardedVideoAd.setMiniGameAutoIntelligenceInfo(curveIdStr / levelId / durationMs / failCount / retryCount / hintUsed,须在 load() 前调用)https://developers.weixin.qq.com/minigame/dev/api/ad/RewardedVideoAd.setMiniGameAutoIntelligenceInfo.htmlgetUserLabel、iaa_feature 与 iaa_feature_v2 档位与潜力系数区间、30s 延迟上报、返回 0 的双语义、体验版无模型、错误码 −10000401、变现数据上报字段 gift_scene / gift_name / gift_value / gift_value_success)https://developers.weixin.qq.com/minigame/dev/guide/open-ability/ad/iaa.htmlpublisher_adpos_general / publisher_adunit_general / get_adunit_list / publisher_settlement,90 天跨度、每页 90 条、字段 req_succ_count / exposure_count / exposure_rate / click_count / click_rate / income / ecpm、返回样例数据、广告位枚举 SLOT_ID_WEAPP_*)https://developers.weixin.qq.com/minigame/dev/guide/open-ability/ad/ad-data-interface.htmladMarkup 与 objectId、切换聚合方时的报错文案、bidding A/B 测试方法、LevelPlay 适配器版本要求)https://docs.unity.com/en-us/grow/dashboard/bidding/levelplaybid 对象与底价 bidfloor 语义;GitHub 仓库 InteractiveAdvertisingBureau/openrtb2.x,181★,最后推送 2026-10-29)https://github.com/InteractiveAdvertisingBureau/openrtb2.x/blob/main/2.6.mdConversation
0 条