iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0
Build on Google AI

AI-Driven MarTech:用 Google Cloud + Vertex AI 打造全自動廣告歸因與多模態素材分析系統系列 第 8 篇

Day 08 | 客人看了三支廣告才下單,功勞到底該算誰的?

  • 分享至 

  • xImage
  •  

1. 前言:最後點擊報表說 Meta 沒用?砍預算之前先看完整路徑

打開 GA4 的流量來源報表或購物車後台的訂單來源統計,很常看到這樣的畫面:Google 搜尋廣告帶來一大堆成交,Meta 的轉換數少得可憐,換算下來 ROAS 也難看,直覺反應是把 Meta 的預算挪去搜尋,但這份報表用的多半是最後點擊歸因,也就是整筆訂單的功勞全部算給顧客下單前最後一次點進來的那個通路。

問題在於顧客很少只看一次廣告就下單,常見的路徑是先在 Meta 滑到廣告點進來逛逛,隔幾個小時又被 LINE 的廣告喚回,最後想買的時候直接去 Google 搜尋品牌名稱結帳,最後點擊只看得到最後一步,前面兩支廣告做的事在報表上完全不存在,這時候砍掉 Meta,最後一步的搜尋量也可能跟著慢慢變少卻沒有人發現。

Day 05 植入合成器的訊號 S6 正是這個現象:meta 多出現在購買路徑的開頭、google 搜尋廣告多出現在最後一步,今天要用 Day 07 整理好的 fct_events 把它找回來。

今日核心目標:

  1. 把每一次造訪串成每筆訂單的購買路徑,並處理下單後的造訪、回購、資料起點截斷這些邊界狀況
  2. 實作第一次接觸、最後接觸與時間衰減三種分配規則,結果寫成一張 mart_attribution 表
  3. 用同一批訂單比較三種規則的答案,確認 S6 有沒有被找回來,並整理不同的預算決策該看哪一種

2. 系統架構全景與設計理念

圖一:fct_events 的造訪與 fct_orders 的訂單串成購買路徑,經過三種規則分配功勞,寫進 mart_attribution 後產出通路功勞對照表

歸因要回答的問題只有一個:一筆訂單的功勞要怎麼分給路徑上的每一個通路,整個流程分成三步:

步驟 輸入 輸出
串路徑 fct_orders 的每一筆訂單、fct_events 的每一次造訪 每筆訂單下單前的造訪清單,依時間排序
分功勞 購買路徑 路徑上每個觸點分到的功勞,三種規則各一個數字
看結果 mart_attribution 各通路在三種規則下分到的訂單數與營收

這裡的觸點指的是一次造訪,也就是一個 session_start 事件,來源取它身上的 utm_source 與 utm_medium,例如 meta / paid_social,同一次造訪裡看了幾個商品都只算一個接觸點。

💡 核心工程理念:

  1. 功勞守恆:不管用哪一種規則,一筆訂單的功勞加總一定是 1,所以每種規則的功勞總和都等於訂單數,營收也一樣,漏算或重複算的錯誤一檢查就會現形
  2. 一張表存全部規則:mart_attribution 一列是一筆訂單的一個觸點,三種規則再各分 direct 算與不算兩個版本,六個功勞欄位並排放在同一列,要比較時就不用重算
  3. 轉換以訂單為準:Day 05 植入的 S2 讓 8/27 的 purchase 事件整天沒送出但訂單照常成立,用訂單表當轉換才不會漏掉那 37 筆
  4. 參數寫在最前面:回溯天數與半衰期是 build.sql 開頭的兩個變數,想換設定改一行就能重跑

3. 核心技術深度拆解

3.1 把造訪串成購買路徑

串路徑的做法是拿每一筆訂單,去 fct_events 找同一位訪客在下單前的 session_start,照時間排好就是這筆訂單的路徑,但有三條界線要先畫清楚:

界線 設定 沒畫清楚會怎樣
下單之後 只取下單時間之前的造訪 付款完成後回來查訂單的造訪會被當成促成成交的觸點
回溯窗口 下單前 30 天 三個月前來過一次的造訪也分到功勞
回購 從上一筆訂單之後重新起算 第二筆訂單會把第一筆的路徑再算一次功勞

實際跑完 3,304 筆訂單全部串得到路徑,總共 9,043 個觸點,平均一筆訂單約 2.7 個,路徑長度的分佈如下:

路徑長度 訂單數 其中回購
1 個觸點 1,042 448
2 個觸點 884 277
3 個觸點 535 84
4 個觸點 344 29
5 個以上 499 5

只有一個觸點的訂單占 31.5%,這些訂單不管用哪一種規則,功勞都是同一個通路拿走,三種規則可能給出不同答案的是另外 68.5% 的多觸點訂單。

另外有 666 筆訂單下單日在 7/19 之前,往前推 30 天會超過合成資料的起點 6/19,路徑可能被截斷,表裡用 window_truncated 標記起來,後面的主表會把它們排除。

3.2 三種分配規則

以一筆實際跑出來的首購訂單為例,這位訪客的路徑是 meta → line → google 搜尋廣告,下單金額 540 元:

觸點 距離下單 第一次接觸 最後接觸 時間衰減
meta / paid_social 約 7 小時 1 0 0.328
line / display 約 2 小時 0 0 0.335
google / cpc 約 4 分鐘 0 1 0.338
  • 第一次接觸:功勞全部給路徑上的第一個觸點,回答的是「這位顧客是被誰帶進來的」
  • 最後接觸:功勞全部給下單前的最後一個觸點,回答的是「誰促成了最後的成交」,也是最常見、最直覺的算法
  • 時間衰減:每個觸點都分到功勞,離下單越近分得越多,權重是 0.5 的「距離下單天數 ÷ 7」次方,也就是每往前推 7 天權重減半,算完再除以總和讓整條路徑加起來等於 1

時間衰減的三個數字幾乎一樣,這不是算錯而是這條路徑從第一次點進來到下單只花了 7 個小時,對半衰期 7 天來說三個觸點都算「剛剛發生」,這件事在 3.3 最後會再回來談。

SQL 的寫法不複雜,先用 ROW_NUMBER() 替路徑上的觸點編號,第一次接觸是編號等於 1 的那一列拿 1,最後接觸是編號等於路徑長度的那一列拿 1,時間衰減則是先算每個觸點的權重,再用視窗函式除以同一筆訂單的權重總和,完整的 SQL 放在儲存庫的 attribution/build.sql,欄位定義放在同一個目錄的 README.md。

3.3 同一批訂單,三種答案

主表只看首購、而且回溯窗口完整的 1,833 筆訂單,回購訂單的路徑主要是電子報和舊客回訪,混進來會蓋掉廣告帶新客的樣子,這張表 direct 不算觸點,原因放在避坑指南第 1 點:

通路 第一次接觸 最後接觸 時間衰減
meta / paid_social 610 385 507.2
line / display 545 324 454.5
google / organic 333 404 355.2
google / cpc 295 670 466.1
(direct) / (none) 50 50 50

每一欄加總都是 1,833,功勞守恆成立,差別只在同一批訂單分給誰:

  • meta 在第一次接觸分到 610 筆,換成最後接觸只剩 385 筆,少了 37%,換算營收從 407,700 元掉到 262,460 元
  • google 搜尋廣告剛好相反,從 295 筆變成 670 筆,變成原來的 2.3 倍
  • line 的走勢和 meta 一樣,都是負責把人帶進來的通路,自然搜尋則介於中間

這就是 S6 被找回來的樣子,同樣的 1,833 筆訂單,只因為換了一種分功勞的規則,meta 從第一名掉到第三名,google 搜尋廣告從第四名升到第一名,如果只看最後點擊報表,就會得出前言那個「Meta 沒用」的結論。

時間衰減的數字落在兩者之間,但這份合成資料有一個要交代清楚的地方:主表這批首購訂單裡,多觸點路徑從第一個觸點到下單的中位數只有約 4.5 小時,90% 在約 13 小時內走完,換個角度看,2,461 位買家裡只有 41 位的首購距離第一次造訪超過一天。

原因出在合成器挑回訪者的方式,Day 05 設定回訪名單保留近 14 天來過、還沒下單的訪客,但每次挑人時只從名單最後的 400 筆裡挑,合成資料一天有一千多次造訪,最後 400 筆大約就是最近幾個小時的訪客,回訪自然集中在同一天,半衰期 7 天放在幾個小時的路徑上,權重幾乎平均分配,時間衰減在這裡的效果接近把功勞平分給每個觸點,真實電商的考慮期通常以天計,半衰期應該先查自家從第一次造訪到下單的天數分佈再決定。

S7 秋日專案的專案商品占比要和專案開始前比較才看得出變化,這部分留給 Day 09 和 ROAS 異常一起對照。

3.4 什麼決策看哪一種

三種規則沒有對錯,是在回答不同的問題,選哪一種要看手上的決策:

要做的決策 建議看 原因
開發新客的預算要不要加 第一次接觸 它衡量的是誰把陌生人帶進來
關鍵字出價與收割型廣告的成效 最後接觸 它衡量的是誰促成最後一步
跨通路的整體預算分配 時間衰減,並和另外兩種並看 每個通路都有分到功勞,不會有通路被完全抹掉

實務上最穩的做法是三種一起看,某個通路在三種規則下排名差很多,就代表它在路徑上有固定的位置,砍預算前要先想清楚它負責的是哪一段。

GA4 在 2023 年移除了第一次點擊、線性、時間衰減與根據位置四種規則式模型,目前只剩數據驅動與最後點擊兩類,數據驅動歸因由 Google 依自家模型分配功勞,看不到計算過程,自己在 BigQuery 用規則式模型算一次,至少每一個數字都知道是怎麼來的。


4. FinOps 成本防護實踐:三道防線體系

  1. 第一道防線:善用 Google Cloud 每月免費額度:建表實際處理 58.9 MB、計費 80.7 MB,檢查處理約 1.4 MB,mart_attribution 整張表不到 10 MiB,五段報表每段都只會以最低的 10 MiB 計費,連同建表與檢查的計費量合計約 150 MB,大約是每月 1 TiB 免費查詢額度的萬分之一點四,這張表只有 9,043 列,儲存量可以忽略
  2. 第二道防線:架構層被動成本防護:fct_events 開啟了必須指定分區條件,build.sql 先從訂單表算出最早與最晚的下單日,再往前多讀一個回溯窗口,只讀需要的日期,另外每個陳述式讀到的每一張表都至少以 10 MiB 計費,建表腳本裡有好幾個陳述式,所以計費量會比處理量多,今天沒有呼叫 Gemini,沒有任何 Token 成本
  3. 第三道防線:Cloud Billing 預算警報:沿用 Day 03 由 Terraform 建立的預算警報(新台幣帳戶 NT$ 300/美元帳戶 US$ 10),50%、80%、100% 三段通知,今天的操作不會觸發

5. Cloud Shell 實戰演練:一行指令算出三種歸因

5.1 事前準備

  • 已完成 Day 07,martech_dw 裡有 fct_events 與 fct_orders
  • 確認 gcloud 有登入中的帳號,輸入 gcloud auth list,帳號前面要有星號

5.2 路線 A|懶人包:一行指令建表、檢查並出報表

cd ~/ai-driven-martech-pipeline && git pull && bash attribution/run.sh

看到「14 項通過、0 項不通過」與五段報表就完成了,報表的第二段就是 3.3 的主表。

5.3 路線 B|逐步教學:理解每一個步驟

步驟 1:取得最新程式碼

cd ~/ai-driven-martech-pipeline
git pull
ls attribution

會看到 build.sql、check.sql、report.sql、run.sh 與說明文件 README.md 五個檔案。

步驟 2:建立歸因表

bq query --nouse_legacy_sql < attribution/build.sql

想換回溯天數或半衰期,改 build.sql 開頭的 lookback_days 與 half_life_days 再執行一次,表會整張重建,改了回溯天數的話 check.sql 裡檢查天數上限的 30 也要一起改。

步驟 3:功勞守恆檢查

bq query --nouse_legacy_sql --format=pretty --max_rows=100 < attribution/check.sql

這份檢查逐筆確認六個功勞欄位的加總都是 1、訂單數與營收和 fct_orders 一致、沒有下單之後的觸點、沒有超過 30 天的觸點,另外檢查第一次與最後接觸的功勞落在正確的位置、回購路徑沒有跨過上一筆訂單,ok 欄位全部是 OK 或 INFO 就代表沒有問題。

步驟 4:看一條路徑

bq query --nouse_legacy_sql --format=pretty "
SELECT touch_seq, channel, days_before_order, credit_first, credit_last, ROUND(credit_decay, 3) AS credit_decay
FROM martech_dw.mart_attribution
WHERE order_date = '2026-07-19' AND transaction_id = 'SY2026071910141634F7'
ORDER BY touch_seq"

這就是 3.2 的那筆訂單,三列分別是三個觸點,可以直接看到同一條路徑在三種規則下各自怎麼分。

步驟 5:印出主表

sed -n '/-- ②/,/;/p' attribution/report.sql | bq query --nouse_legacy_sql --format=pretty

report.sql 裡有五段查詢,這行只取出第二段執行,也就是 3.3 的主表,路線 A 的 run.sh 則會把五段逐一印出。

5.4 驗證成果

  • 3,304 筆訂單全部歸因,營收加總 2,056,430 元,和 fct_orders 相同
  • 首購且回溯窗口完整的訂單共 1,833 筆,meta 的功勞從第一次接觸的 610 筆變成最後接觸的 385 筆
  • google 搜尋廣告從 295 筆變成 670 筆

5.5 不用了?指令全部清除

bq rm -f -t martech_dw.mart_attribution

這張表 Day 21 讓 AI 查資料庫時會用到,建議保留,想重建隨時再跑一次 run.sh 即可。


6. 工程實務避坑指南

  1. direct 會吃掉最後接觸:很多人想下單時會直接打網址或點書籤進站,全部訂單用最後接觸算,direct 拿走 856 筆,改成 direct 不算觸點只剩 241 筆,其餘 615 筆還給路徑上的其他通路,做法和 GA 的最終非直接點擊相同,只有整條路徑都是 direct 時才把功勞給 direct
  2. 轉換不要用 purchase 事件:追蹤碼壞掉的那天事件會整批消失,訂單卻照常成立,用訂單表當轉換才不會讓那一天的通路功勞全部歸零
  3. 下單之後的造訪要排除:付款完成後回來查出貨進度的造訪時間在下單之後,沒有排除就會變成最後接觸
  4. 資料起點會截斷路徑:前 30 天的訂單看不到更早的造訪,第一次接觸會被錯算給資料裡最早出現的那一次,比較通路時先排除這些訂單
  5. 同一時間的觸點要固定排序:兩個觸點時間相同時,沒有指定第二排序鍵的話每次跑出來的第一次接觸可能不一樣,這裡依時間排序後再依通路名稱與活動排序
  6. 半衰期要配合自家的購買週期:半衰期遠大於路徑長度時,時間衰減會退化成平均分配,先查第一次造訪到下單的天數分佈再決定

7. 總結與明日預告

今天把 fct_events 的造訪串成 3,304 筆訂單的購買路徑,用第一次接觸、最後接觸與時間衰減三種規則分配功勞,結果存進 mart_attribution,14 項檢查全部通過,每種規則的功勞加總都剛好等於訂單數。

回頭看前言的問題,同樣 1,833 筆首購訂單,meta 在第一次接觸分到 610 筆,在最後接觸只剩 385 筆,google 搜尋廣告則從 295 筆升到 670 筆,Day 05 植入的 S6 完整找回來了,最後點擊報表裡的 Meta 不是沒用而是它負責的那一段剛好不在報表看得到的地方。

明日預告:Day 09《廣告成效突然跳水?直接在 SQL 裡叫 AI 找出原因》,我們將在 BigQuery 裡建立連到 Gemini 的遠端模型,直接用 SQL 把 ROAS 異常的資料交給 AI 判讀,看它能不能分辨 8/12 起競價變貴和 8/27 追蹤碼失效這兩種完全不同的狀況!


上一篇
Day 07 | 倉儲建模:廣告成效星狀綱要設計
下一篇
Day 09 | 廣告成效突然跳水?直接在 SQL 裡叫 AI 找出原因
系列文
AI-Driven MarTech:用 Google Cloud + Vertex AI 打造全自動廣告歸因與多模態素材分析系統 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言