給外包資料分析師的入門文件——GliaCloud 影音廣告聯播網怎麼運作、資料怎麼流、錢怎麼分。
← 回文件首頁
GliaCloud 提供一個可嵌入媒體(publisher)文章頁的影音播放器,播放器向 Google Ad Manager(GAM)、Prebid header bidding 供應商、以及 60+ 家區域型廣告聯播網 發送廣告請求,用 waterfall/即時競價決定播出哪支廣告、賺取廣告營收,再依合約比例分潤給媒體。gstudio 這個 repo 同時是「文章轉影片」內容生產工具,也是這整套廣告聯播網的後台——本文件只講後者。
換句話說:媒體不需要自己去談廣告、串接十幾個廣告平台的 SDK,把 GliaCloud 的播放器嵌進頁面,GliaCloud 就代為處理廣告競價、政策合規(ads.txt / sellers.json)、營收拆帳與付款。這是一個典型的 video ad network(影音廣告聯播網)商業模式:對媒體是「一站式影音變現」,對廣告主/廣告平台是「一個可以買到大量優質影音版位的供應商」。
錢的流向,由右到左依序被切分:
Demand(廣告平台)先依 CPM/eCPM 付費 → 依 Demand 設定的 margin 預扣一部分 → 得到 Glia 真正入帳的 revenue_shard → 再依 Channel 的 share%(分潤比例)算出媒體應得的淨營收。margin 是因應 IVT(Invalid Traffic)事後被廣告平台追討(clawback)的風險,先保守預扣一部分不計入淨營收。
結算與付款是月結、以 Agency(對外簽約單位)為單位進行:
播放器在頁面上曝光、發送廣告請求
GAM waterfall/Prebid 競價後由某個 demand 得標並回報營收
60+ 家 demand 的原始 report 定期匯入,寫進 Performance(各 demand 頻率不同,相關 cron 記錄各自設定)
依 Channel share% 算出媒體淨營收,彙總到 Agency
每個 Agency 每月一張,觸發實際匯款
整個系統可以看成一條由「誰付錢」到「誰收錢」的階層鏈。上層是商業/合約關係,下層是實際發送廣告請求的技術單位:
每一層在系統裡負責的事情不同,整理如下:
| 實體 | 白話角色 | 誰在維護 | 對應顆粒度 |
|---|---|---|---|
| Agency | 簽約與收付款的商業單位,每月產出一張 Payment Notice | BD / AM | 月 |
| Channel | 分潤比例(share%)的設定單位;一個 Agency 可以有多個 Channel | BD / AM | 月 |
| Account | 一個媒體網站,帶 ads.txt/MCM child publisher 等合規資訊 | AM / RD | 站台 |
| Slot | 頁面上的一個播放器版位,name 建立後不可改(報表 key) | RD / AM | 版位 |
| SlotSetting | Slot 的設定版本;Performance/AdUnitMetric 都對這層計費 | RD | 版位 × 版本 |
| AdUnit | 連接 Slot 與 Placement,決定分多少流量給這個 demand 通路 | RD / AM | 版位 × 通路 |
| Placement | 某個廣告格式的設定(GAM / Prebid / Standard,依技術類型分好幾種子類別) | RD / AM | 通路 |
| Demand | 廣告聯播網品牌本身,帶自己的 ads.txt 樣板與 IVT 預扣比例(margin) | BD | 廣告平台 |
以下依「使用者打開頁面、播放器實際發送廣告請求」的即時發生順序來寫。
一個 Slot 底下可能同時存在好幾個 SlotSetting 版本:一個當作 control(現行版本),其他是 ExperimentSetting(實驗組),各自帶一個 traffic 權重(0~100)。Slot 發布給 player 的設定(Slot.entity_model)會把 traffic > 0 的版本全部列出來,player 載入頁面時依權重從這份清單挑一個版本來服務這次瀏覽——挑到哪個版本,接下來就照那個版本當下已經算好的 ad unit 順序繼續往下走。
一個版位(Slot)底下的 SlotSetting 可以同時掛好幾個 demand 通路(AdUnit),每個 AdUnit 依 priority 排出順序、分組(groupId)。播放器初始化後會依序試打廣告請求:先打優先權最高的一組,該組沒有廣告回應(no fill)才往下一組打,逐層往下——這就是「waterfall」名稱的由來。播放時機(waterfall_mode:interval/pre-roll/mid-roll)、每輪間隔秒數,以及最多試幾輪(maxRounds/maxFalls)都可以設定。
waterfall 裡有一種 AdUnit 類型是 Prebid(GamPrebidVideoAdUnit/StandardPrebidVideoAdUnit 等):輪到它那一組時,會同時向多個 SSP/DSP(Magnite、Openx、InMobi 等)發起即時競價,出價最高者成交——同一組裡原本要一個個問的多家 demand,改成同時比價,避免漏接願意出更高價的買家。
waterfall 裡另一種常見的 AdUnit 類型會直接打 Google Ad Manager,由 GAM 依 LineItem/底價決定播出哪支廣告。Glia 同時營運兩個獨立的 GAM 網路,各自也跑一套 MCM(Multiple Customer Management),代表規模不足以自己申請 AdX/GAM 的「child publisher」去賣廣告版位:
| Network | 網路代碼 | 角色 |
|---|---|---|
| Glia Publisher | 21818843116 | Glia 自己直營的 GAM 帳號;廣告版位依 GAM 的 inventory_management 設定分兩種 demand:Glia AdExchange Direct(版位庫存直接算在這個 network 自己名下)與 Glia MCM(版位委派給某個 MCM child publisher) |
| Dormknight | 22825748039 | 另一個獨立的 GAM 帳號;同樣依 inventory_management 分成 Dormknight AdExchange Direct 與 Dormknight MCM 兩種 demand |
MCM 媒體要先通過一套上線審核,才會有 demand 可以打——審核流程跟卡關會怎樣,寫在「合規與監控機制」一節。
版位自動優化,指的是系統自動調整版位設定(廣告排序、要不要繼續跑某個設定)來提高營收,而這件事是透過實驗系統做到的。實驗系統是用真實流量做 A/B 測試的機制:把一個版位的一小部分流量分給新設定(實驗組 ExperimentSetting),其餘留在現行設定(control),兩邊同時跑、直接比營收成效。實驗組的設定會由以下兩個排程任務依成效數據持續自動調整:
排程任務 ad.tasks.UpdateDynamicExperimentAlgorithm(cron id 40)會套用掛在 ExperimentSetting 上的 ExperimentFunction——一段後台可編輯的排序演算法,依歷史 RPM/eCPM 把 ad units 重新排序,讓歷史表現好的通路排前面優先搶單,也可以排除掉後段低效的通路(要不要排除、門檻多少都是演算法程式碼裡自己決定,因實驗而異)。目標很單純:在每一次曝光上,盡量賣到最高的價錢。這個排程多久重跑一次,就在上面 cron id 40 那筆記錄裡調整。
排程任務 ad.tasks.UpdateExperimentTraffic(cron id 41)會比較每個 ExperimentSetting 跟它 control 在同一段觀察期的 RPM/CPM 等指標:每個實驗組可以設定 AutoPauseCondition(累計自實驗開始,或只看最近一天,門檻在後台設定),一旦成效比 control 差到觸發門檻,系統就自動把該實驗組的 traffic 砍到 0(停用,不會再被挑中服務任何頁面瀏覽),並發 Slack 通知。
最核心的事實表是 Performance,一列資料的顆粒度是:
SlotSetting × time_unit_date(日)× Demand × purpose
| 欄位 | 意義 |
|---|---|
| ad_request_shard | 廣告請求數(fill rate 的分母) |
| ad_impr_shard | 實際曝光數 |
| revenue_raw | Demand 回報的原始金額(未扣 IVT 預扣) |
| revenue_shard | revenue_raw × margin,扣掉 IVT 預扣、再扣事後回溯調整(rebate)後,Glia 實際入帳的金額。margin 套用順序是 Account 覆寫 > MCM Child Publisher 覆寫 > Demand 預設值:同一個 Account 底下所有 Slot 對同一個 demand 用同一個 margin,但不同 Account 之間即使是同一個 demand,margin 也可能不同 |
| purpose | 區分同一格資料是「正常營收」還是「事後回溯調整(rebate)」 |
更細顆粒(per ad unit)的 AdUnitMetric 才有現成算好的 eCPM、RPM、fill_rate 欄位;Performance 本身不存這些比率,要自己用 revenue/impression/request 算。
向各 demand 平台拉 revenue/impression,寫進 BigQuery;各 demand 頻率不同,相關 cron 記錄各自設定
依播放器端事件比例(如 ad.start 次數),把 demand 層級的營收拆分攤回各個 slot
排程讀回 BigQuery,upsert 進 Django 的 Performance 表
這 60+ 家 driver 涵蓋 Google GAM/AdX、日本/東南亞區域型影音聯播網(FreakOut、Fluct、Aniview)、Prebid 供應商(Magnite、Openx、InMobi)、以及多家歐洲/全球型影音聯播網(Sparteo、SmartAdServer、ShowHeroes)——反映 GliaCloud 是一個橫跨多國市場、同時對接 waterfall 與 header bidding 的區域型影音聯播網。
report/ app 只管廣告營收與投放,跟 analytics/ app(Instagram/Facebook 貼文的社群經營成效)是完全不同的兩件事,只是剛好都叫「數據」。
月結時,先在 Channel 層算淨營收:net_revenue = gross_revenue × share / 100。同一個 Agency 底下所有 Channel 的淨營收加總後,系統自動產生一張 PaymentNotice(每個 Agency 每月一張)。若加總金額低於該 Agency 設定的 min_payment 門檻,就不會觸發付款,金額會累積到下個月。
| 法人 | 用途 |
|---|---|
| GliaCloud(台灣) | 本地媒體合約,TWD 計價模板 |
| SG Dormknight(新加坡) | 海外合約,USD/JPY 計價模板 |
| BVI Dormknight(英屬維京群島) | 海外合約,USD/JPY 計價模板 |
每個 Agency 綁定一個 contract_entity,決定用哪個法人簽約、開哪一種付款模板(幣別/稅務格式)——目的是讓海外媒體的合約與付款不必全部走台灣公司,簡化跨境稅務與匯款流程。
部分 Agency 有「上月總 gross revenue 達到某個級距,就退一定比例回饋金」的自動 rebate 設定(金額級距 → 回饋 %),每月結算時會反推回「分潤前」金額計算,確保分潤(share)套用後金額仍然正確。
ads.txt/sellers.json 是 IAB 制定的反詐騙標準,用來對抗「假冒媒體賣廣告版位」:ads.txt 由媒體自己公開宣告「哪些廣告平台有權賣我的版位」;sellers.json 反過來,由廣告平台公開列出「我代理哪些賣方」。廣告主的競價系統下單前會核對兩者是否對得起來——對不起來,廣告平台就悄悄不出價,不會報錯,只會讓某個 demand 的營收無聲無息掉到 0。這也是為什麼系統有一整組自動檢查,各自的執行頻率都在後台 Cron 設定裡調整(連結見下表各筆):
| Checker | 檢查什麼 |
|---|---|
| check_adstxt | 媒體實際 ads.txt 跟 GAM 授權報表是否一致 |
| check_sellers_json | MCM parent 的 sellers.json 是否有新增/異動的賣方 |
| check_lineitem | GAM line item 是否被誤設成「對整個 Run of Network 投放」 |
| check_mcm_ad_tag_url | MCM child publisher 的廣告標籤網路代碼是否正確 |
| check_placement_mirror_slots | 該有對應 mirror placement 的版位是否都建好,避免漏掛 demand |
| check_std_ad_tag_url | 非 GAM 的標準 VAST 廣告標籤格式是否正確 |
| detect_ad_abnormal | 拿當下數據跟過去 7 天/前兩週同星期中位數比對,抓「營收突然異常下降」(Dormknight/Gliacloud 各分 Daily/Hourly,共 4 筆記錄) |
此外,GamViolation 會匯入 Google 寄來的政策違規報告,追蹤哪些站台/頁面被 Google 停止投放(政策違規、可疑流量等),提供 AM 主動處理。
媒體要能真正吃到 MCM demand(見「廣告投放機制」一節),得先通過一套審核流程,一關卡住就沒有 MCM 營收:
READY,這時才真的能吃到 AdX demand這是分析媒體「為什麼這個月營收突然跟其他 slot 落差很大」時第一個該查的地方。
| 名詞 | 白話解釋 |
|---|---|
| Account | 一個媒體站台,掛在某個 Agency 底下 |
| Agency | 對外簽約、收付款的商業單位,每月一張 Payment Notice |
| Channel | 分潤結算單位(決定 share%)。注意:report app 裡也有一個同名但已棄用的 Channel model,實際在用的是 publisher app 那個 |
| Slot | 頁面上的播放器嵌入點;name 建立後不可改 |
| SlotSetting | Slot 的一個設定版本;報表實際對這層計費 |
| AdUnit | 把 Slot 接到 Placement 的中介,決定分多少流量(traffic%)給這個通路 |
| Placement | 某種廣告格式/通路設定(GAM Video、Prebid Outstream、Standard tag…) |
| Demand | 廣告聯播網品牌本身(Google AdX、FreakOut、Sparteo…60+ 家) |
| LineItem | GAM/Prebid 裡具體的出價/合約設定(CPM、起訖時間) |
| Waterfall | 依優先權分組、依序試打廣告請求的機制:一組沒回應才往下一組打,跟 Header Bidding 的同時競價相對 |
| Header Bidding / Prebid | 讓多個 demand 在 waterfall 跑之前先同時競價一次 |
| Experiment 系統 | SlotSetting 的 A/B 測試框架:一個 control(現行版本)搭配多個 ExperimentSetting(實驗組),成效比 control 差到門檻會自動停用(traffic 砍到 0) |
| MCM | Google 讓大帳號代管中小型媒體的機制,Glia Publisher 與 Dormknight 兩個 network 各自有一套(Glia MCM / Dormknight MCM),都扮演 parent 角色 |
| ads.txt | 媒體公開宣告「哪些平台有權賣我版位」的文字檔(IAB 標準) |
| sellers.json | 廣告平台公開列出「我代理哪些賣方」的 JSON 檔,跟 ads.txt 互相對照 |
| revenue_raw | Demand 回報的原始金額(未扣 IVT 預扣) |
| revenue_shard | 扣掉 IVT 預扣(margin)/rebate 後,實際入帳的營收 |
| margin | Demand 層針對 IVT(無效流量)風險的預扣保留比例;跟 Channel 的 share(分潤)是完全不同層的概念,兩者疊加才是媒體最終拿到多少 |
| share | Channel 的分潤比例(0~100%),net_revenue = gross × share/100 |
| PaymentNotice | 每個 Agency 每月一張的對帳/付款通知單,是實際觸發匯款的單位 |
SlotSetting(例如流量分配比例改版),營收其實是掛在 SlotSetting 上,不是 Slot 本身。Performance 資料會被鎖住不可再改——回填或修正歷史資料前先確認是否已經結過帳。revenue_shard 是「Glia 入帳後」的金額,要算「媒體實際拿到的錢」還要再乘上 Channel 的 share%;同一個 Channel 底下不同使用者(user)看到的分潤比例還可能被個別覆寫,不等於 Channel 本身的 share。