ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

OpenRTB 2.5实时竞价接入指南:从Bid Request到宏替换

OpenRTB 2.5实时竞价接入指南:从Bid Request到宏替换 简介OpenRTB 2.5是程序化广告领域的重要开放协议标准本资源为其中文翻译文档主要面向计算广告与实时竞价相关的开发者、算法工程师及广告平台运营人员旨在降低英文原版规范的阅读门槛方便国内从业者理解Ad Exchange与SSP、DSP之间的通信机制和实时拍卖流程。文档系统覆盖了Bid Request、Bid Response、Impression、User、Device、Site/App、Ext扩展字段、Bidder Seat、Price Floors等核心对象与概念同时阐述了GDPR隐私合规支持、地理位置精确化、视频/音频/富媒体广告扩展等2.5版本的重要更新便于对照英文原文快速定位关键技术条款。压缩包共1个PDF文件约1.36MB适合桌面端或移动端随时查阅作为项目开发、接口联调或方案设计时的常备参考手册。目前已有708人下载学习适合需要系统梳理OpenRTB协议全貌的初中级计算广告从业者。1. 别把 OpenRTB 2.5 读成小说这是一份接 RTB 的施工图纸在计算广告里做实时竞价RTB迟早要跟 OpenRTB 2.5 打照面。它是 IAB 定的实时竞价接口规范市面上绝大多数 ADX/SSP 对外接 DSP 的文档都是照着它裁剪出来的。可惜原版是英文字段嵌套又深指望通读一遍就记住 imp、banner、seatbid 各自该放什么基本不现实。这份中文翻译文档能做的是把图纸给你铺开Bid Request 里哪些字段必填、哪些可以缺省Bid Response 里 seatbid 和 bid 是怎样的从属关系宏替换在哪个环节发生翻译得直白、能对着代码查。它适合刚转入计算广告的 RD、正在跟 ADX/SSP 联调的客户端开发以及要写接口对接文档的 PM。我不建议把它当协议百科从头啃。更好的用法是接到一家流量平台的接入文档后拿它当字典翻。下面拆的几个部分都是这份翻译文档里高频翻页的位置。2. 翻译文档的使用姿势先分清 Request 与 Response 的字段边界2.1 顶层对象只有三个Bid Request、Bid Response、No-Bid先说结论OpenRTB 2.5 整份规范翻译过来就是在讲三件事——请求方ADX/SSP把一次展示机会描述成 Bid Request 发给各家 DSPDSP 在限时内决定出不出价出价就回 Bid Response不出就是 No-BidHTTP 层面可能直接回 200 空 body也可能是 204。很多新手一上来就抠 imp 里的 banner 尺寸忽略了这三层关系结果连“为什么对方返回空 body”都想不明白。翻译文档里最容易忽略的是顶层字段的约束。比如 id 是请求唯一 ID必填imp 是曝光数组也必填site 和 app 描述流量来源二选一不能同时出现device 和 user 分别描述设备和用户是可选对象但不是可省对象广告主定向靠的就是这两个。常用顶层字段我列成表对接时直接对着查字段类型必填含义idstring是请求唯一标识DSP 方用于日志关联impobject[]是一次曝光机会可多个site / appobject二选一网页或应用上下文deviceobject否设备信息定向和反作弊用userobject否用户信息含 yob、gender 等atint否拍卖类型1 一价2 二价默认 2tmaxint否DSP 处理时限单位毫秒bcatstring[]否屏蔽的内容分类badvstring[]否屏蔽的广告主域名curstring[]否结算货币默认 USDregsobject否合规相关如 coppa这张表在英文原版里是散落各段的翻译文档把它集中在了一块。实际联调时我建议先把这张表贴在显示器边上比反复翻页快。2.2 imp 是请求的重心banner、video、native 怎么选imp 数组是 Bid Request 的心脏一次请求里可以出现多个 imp但每个 imp 只能有一个 id且必须在请求内唯一。imp 内部最关键的是曝光类型banner、video、native 三者的字段差异很大翻译文档如果把这部分分错后面照着配必翻车。banner 对应普通图片/HTML 横幅核心字段是尺寸。规范里 w 和 h 必须成对出现要么都传要么都不传只传一边会被判定请求非法。想表达自适应尺寸用 wmax/hmax/wmin/hmin 表达范围而不是把 w 随便传成 0。video 对应视频广告必填 mimes、protocols还要给 minduration 和 maxduration它是所有类型里字段最多的很多平台在 video 上裁字段反而让你少踩坑。native 在 2.5 里是一包 JSONrequest 字段本身是个 JSON 字符串广告位要求、所需资源都塞在里面解析时注意别当成普通对象遍历。我一般建议媒体形态单一就让 imp 里只出现一种类型对象只有当一次曝光确实存在多规格竞争时才在同一个 imp 里用多尺寸组合表达。文档里叫“多尺寸”实际是把 banner 的 w/h 换成范围组合不是塞一堆 banner 对象。2.3 site/app、device、user上下文、设备与人的边界很多刚接触 RTB 的人分不清 site 和 app 该填什么。site 描述网页流量里面一般有 id、name、publisherapp 描述应用流量除了 id、name还常带 bundle包名和 storeurl。两者选哪个由请求来源决定不能为了拿全量信息同时塞。注意 publisher 对象里也有 id 和 name它和 site.id 不是一回事publisher.id 对应媒体主site.id 对应具体广告位所在页面。device 和 user 的边界也是翻译文档的重点。device 是硬件的描述ua、ip、ifa、os、geo反作弊和频控主要靠它user 是人的描述buyeruid、yob、gender、keywords定向人群画像靠它。实操中很多团队把 user.id 当设备号传这在协议层面不报错但下游统计分析会乱因为 user.id 在广告主视角里应该是同一用户跨媒体识别的标识。合规上2.5 原文里有 regs.coppa表示流量是否面向 13 岁以下用户。GDPR 不是 2.5 正文而是 IAB 后来出的扩展常见做法是把 gdpr 和 consent 放在 regs.ext 和 user.ext 里。这属于文档里 ext 扩展位的典型用途——协议没覆盖的各家拿 ext 补。注意ext 是扩展位凡是在翻译文档里标了“扩展字段”的都不建议跨平台通用除非两家平台私下约定过同一个 key。这句话能帮你少背几个黑锅。3. 照着文档走一遍竞价闭环解析请求、构造响应与宏替换3.1 最小 Bid Requestw/h 成对、site/app 二选一翻文档不如跑一遍。以 DSP 接入方视角第一步是收到平台发来的 Bid Request先做协议校验。下面这段代码负责解析并校验最关键的必填约束import json def parse_bid_request(raw_body): req json.loads(raw_body) if id not in req or imp not in req: raise ValueError(请求缺少 id 或 imp按规范两者必填) imp req[imp][0] if banner in imp: banner imp[banner] # w 和 h 必须成对出现只传一个属于非法请求 if (w in banner) ! (h in banner): raise ValueError( banner 的 w 和 h 必须成对出现只传一个属于非法请求 ) return req这里只挑了三个高频校验点id 和 imp 必填、banner 尺寸成对。实际对接时还要看 site/app 二选一是否满足、tmax 是否超过本地预算、cur 里有没有自己支持的币种。解析函数不要吞异常把它打在链路日志里后面排查“平台说发了请求、你没出价”的问题时这段日志就是第一现场。3.2 构造最小 Bid Responseseatbid 与 bid 的组织方式解析完请求DSP 要回一个 Bid Response。响应结构看起来只有一层但 seatbid 和 bid 的从属关系经常被搞反def build_bid_response(request_id, imp_id, bid_id, price, adm, nurl): return { id: request_id, # 回显请求 id方便双方按日志关联 seatbid: [ { seat: demo-seat, bid: [ { id: bid_id, # 本次竞价生成的 bid 标识 impid: imp_id, # 指向请求里的 imp.id price: price, # CPM 单位不是单次展示价格 adm: adm, # 创意代码HTML/VAST 等 nurl: nurl # 胜出通知 URL可带宏 } ] } ], cur: USD }id 建议回显请求里的 id平台侧日志关联靠它。seatbid 是个数组一个 seatbid 里可以挂多个 bid每条 bid 必须通过 impid 精确指向请求里的某一次曝光bid 里的 price 是 CPM 价格不是单次展示价格这个单位搞错会出大事。seatbid 里还有个 group 字段0 表示各 bid 独立1 表示这些 bid 属于同一个竞价组合普通场景下不需要拆开特殊处理。3.3 宏替换${AUCTION_PRICE} 不能原样发出去nurl 是胜出通知地址你在 Bid Response 里返回的是一条模板里面可以带宏。规范约定这些宏由请求方ADX/SSP在胜出后展开再回调你填的 URL。写模板时不要自己先把宏替换成实际值否则平台展开时会发现宏已经不存在。2.5 里常见的宏有这些宏含义${AUCTION_ID}拍卖 ID${AUCTION_BID_ID}胜出 bid 的 id${AUCTION_IMP_ID}对应 imp 的 id${AUCTION_SEAT_ID}胜出的 seat 标识${AUCTION_PRICE}结算价二价拍卖下不等于你的出价${AUCTION_CURRENCY}结算货币${AUCTION_LOSS}败北通知2.5 新增用于 loss 回调离线自测时没有平台环境可以用一个本地展开器模拟平台行为顺便验证模板 URL 格式对不对def expand_macros(template, auction_id, bid_id, price): mapping { ${AUCTION_ID}: auction_id, ${AUCTION_BID_ID}: bid_id, ${AUCTION_PRICE}: f{price:.2f}, ${AUCTION_CURRENCY}: USD } expanded template for macro, value in mapping.items(): expanded expanded.replace(macro, value) return expanded注意 AUCTION_PRICE 是最终结算价。一价拍卖里它是你的出价二价拍卖里它是第二高价对账时别拿原始出价去比。宏替换后还残留 ${ 开头的串说明模板或映射表有一边不完整直接报错别放行。4. 参数取舍bidfloor、bcat 与 device 字段的四个决策点4.1 bidfloor 不是门槛是信号bidfloor 是曝光底价单位是 CPM即千次展示计费。新手容易犯的错拿自己在 DSP 里的单次出价去填比如单次曝光出价 0.01 元就把 bidfloor 填成 0.01结果请求要么被拒要么进来全是低质流量。正确理解是它在一个数量级CPM 1.0 意味着千次展示至少 1 美元按单次算约 0.001 美元。DSP 侧最常见的用法是把 bidfloor 当成流量质量信号。收到高底价的请求说明媒体敢把库存标到那个价可以适当提高出价上限低底价长尾流量则压价。我常用的参考区间流量类型常见 bidfloor 区间DSP 应对高星媒体 / 首页头部1.0 - 2.0 USD CPM放宽出价上限参与竞争中长尾页面0.3 - 0.8 USD CPM正常预算按 ROI 控制激励视频单独定价需单独策略不套用横幅标准调整节奏上我习惯每次升降不超过 50%观察两小时填充率与均价再动。bidfloor 不是越高越好也不是越低越安全它更像媒体和 DSP 之间的价格握手信号。4.2 bcat 与 badv屏蔽的粒度和判定时机bcat 和 badv 是请求方给 DSP 的约束经常被混用。bcat 屏蔽内容分类值是 IAB 分类编号比如 IAB1 代表“艺术与娱乐”IAB25 代表“健康”badv 屏蔽广告主域名比如不让某竞品投自家流量。判定场景也不同bcat 更多由内容分类体系驱动平台在请求分发阶段就会过滤badv 主要是广告主级黑名单在竞价审核阶段直接剔除。翻译文档里这两个字段都放在顶层不在 imp 里。有人把它塞进 imp.ext平台解析不到等于白配。配置经验bcat 按媒体受众画像来成人、赌博、武器通常是默认屏蔽badv 按竞品名单维护别把分类当成关键词否则英文原版里四个字母的分类编号容易看错。4.3 device 与 userIFA、UA 与隐私的取舍device.ifa 在 2.5 里是广告主标识符iOS 上通常是 IDFAAndroid 上是 GAID。但现实接口里很多平台要求的是经加密或脱敏的标识直接传明文 ifa 会被风控拦。devicetype 是个 int 枚举2 代表 PC4 代表 Phone5 代表 Tablet翻译文档附表里有联调时经常有人把 PC 流量填成 4导致下游定向全偏。隐私边界也要舍得“能少传就少传”是这几年接广告协议的默认习惯。regs.coppa 标记是否涉 13 岁以下流量GDPR 扩展的 consent 大多放在 user.ext.consent 里user.data 里带的用户分段如果不是自研 DMP 的数据建议不要明文放请求里。这份翻译文档不会帮你做合规决策但字段边界看得越清楚越知道哪些数据其实可以不进请求链路。5. 避坑手册五个用 OpenRTB 2.5 最容易翻车的地方第一个坑请求被平台判定非法日志里 banner 只有 w 没有 h。现象是平台回 400你去翻请求样例发现 w320h 字段没了。原因多半是内部字段映射时把 h 当可选字段或取媒体配置时 h 为 0 被序列化丢掉。解决方式是组装 Bid Request 前做一次 schema 校验w 和 h 要么同有要么用 wmax/hmax 组合单个出现直接抛异常。第二个坑win notice 宏未替换价格对不上。现象是通知 URL 里出现字面量 ${AUCTION_PRICE}或对账时发现结算价与记的价差一个量级。原因是把 nurl 当普通 URL 透传没做宏展开。要注意nurl 里的宏由 ADX/SSP 展开你的任务是保证模板原样放进 Bid Response但如果下游还有二次分发宏就必须在己方展开并且替换前后各打一条日志。第三个坑site 和 app 同时出现在一个请求里。现象是流量在报表里时有时无归因对不上。原因是一些聚合 SDK 在拼接请求时把两套上下文都塞了进去而协议要求二选一。有些平台会忽略 site 只认 app有些直接拒掉。解决就是按流量来源只保留一个对象混合流量就分开建两套请求模板。第四个坑tmax 填成自己的处理时长。现象是平台侧永远看不到你的出价或者压测时发现 DSP 响应全部超时。原因是把内部超时当成 tmax 直接填。tmax 是请求方给 DSP 的整体预算含网络往返和队列时间。常规做法是填 50-120ms自己代码在 tmax 的一半内完成解析和出价而不是顶着上限跑。第五个坑bidfloor 按单次出价填。现象是大量请求被过滤填充率掉到个位数。原因是把 DSP 内部的单次出价单位和协议 CPM 单位搞混。解决是统一走“CPM 单次价格 × 1000”的换算在配置层就定死货币单位不做隐式转换。这个坑几乎每个新团队都踩一遍翻译文档里字段注释写得再清楚不如自己在代码里加一道单位断言。6. 用最小请求验证你的解析器构造样例与边界断言把协议文档读熟不等于能上线。我每次拿到翻译文档后的最后一个动作是构造一个最小请求专门用来验证解析器对边界的处理。样例长这样{ id: req-min-0001, imp: [ { id: 1, banner: {w: 300, h: 250}, bidfloor: 0.0 } ], site: {id: test-site}, device: { ua: Mozilla/5.0 (Linux; Android 12), ip: 198.51.100.23, devicetype: 4 }, tmax: 120, at: 2, cur: [USD] }这个样例只保留协议必填项没有 site.name没有 user适合做单元测试基线。拿到它之后我至少跑四个断言把 imp 数组置空解析器应该返回结构化错误而不是直接抛异常把响应里的 seatbid 设为空数组或直接回空 body应该被判定为 no-bid 而不是解析中断一次响应里出现两个 seatbid两个都要遍历展开不能只取第一个把响应 cur 改成 EUR价格归一化要按汇率计算不能直接忽略币种。有精力再加一条adm 和 nurl 里同时出现多个宏断言全部被替换不能留下任何一个 ${ 开头。从那以后我每次接新协议都强制走一遍这个最小请求流程先跑合法请求再跑空 imp、空 seatbid、双 seatbid最后对一遍宏替换结果。一遍下来能挡掉八成低级事故。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进