廣告歸因-讓你徹底弄歸因架構實現

seth-shi發表於2023-01-12

解釋

  • 這裡會引用神策資料很多的介紹,然後進行總結

歸因方法

  • 自歸因
    • 渠道商幫我們做歸因,有的是每個使用者開啟app都回傳給渠道商,渠道商自己歸因
    • 有的如華為是從應用商店安裝時, 應用商店把歸因資訊寫入到app, 然後首次安裝啟動時能從本地儲存獲取到歸因資料
  • 曝光歸因
    • 曝光歸因由於有資料量極大、不會使用此項
  • 點選歸因(常用)
    • 所謂點選歸因, 就是點選廣告之後首個轉化, 基本都是用這種方式歸因

      歸因模型

  • 末次歸因模型 (常用, 因為比較好實現)
    • 多個歸因源事件時,認為最後一個歸因源事件的功勞為100%
  • 首次歸因模型
    • 多個歸因源事件時,認為第一個歸因源事件的功勞為100%。理由是第一個觸點給使用者建立了認知,與使用者形成了連線。
  • 平均歸因模型
    • 多個歸因源事件時,認為每個歸因源事件平均分配此次功勞。
  • 時間衰減歸因模型
    • 加上了時間的影響因素,最後1次觸達的貢獻更高。
  • 位置歸因模型
    • 多個歸因源事件時,認為第一個歸因源事件和最後一個歸因源事件各佔40%功勞,其餘平分剩餘的20%功勞。兼顧最初的線索和最終的決策。
  • 價值加權歸因模型
    • 多個歸因源事件時,對不同渠道的貢獻價值進行加權,將轉化功勞根據權重進行劃分。

匹配方式

  • 精確匹配
    • OAID: OAID全稱是Open Anonymous Device Identifier,中文名是匿名裝置識別符號。 OAID是一種非永久性裝置識別符號,最長64位,在系統首次啟動的時候生成
    • AndroidID: ANDROID_ID是裝置首次啟動時由系統隨機生成的一串64位的十六進位制數字
    • IMEI: 國際移動裝置識別碼(International Mobile Equipment Identity,IMEI),即通常所說的手機序列號、手機“串號”,用於在行動電話網路中識別每一部獨立的手機等行動通訊裝置,相當於行動電話的身份證。
    • Mac: 手機的網路卡地址
  • 模糊匹配
    • IP: 分配給使用者上網使用的網際協議(全稱Internet Protocol, 簡稱IP)的裝置的數字標籤
    • User_Agent: 一個特殊字串頭,使得伺服器能夠識別客戶使用的作業系統及版本、CPU型別、瀏覽器及版本、瀏覽器渲染引擎、瀏覽器語言、瀏覽器外掛等

      服務架構實現

  • 接下來會參考這個圖來整體說明

廣告歸因-讓你徹底弄歸因架構實現

根據上圖, 我們先給一個最基礎的表結構,大家可以根據具體業務增減欄位

# 應用表(apps)
id, appid(客戶端使用), name, os, attribute_cycle_days(歸因週期), attribute_white_list(JSON 白名單列表)
# 渠道表(channels)
id, name, template_query(此欄位預先組裝好格式)
# 監測連結表(links)
id, app_id, channel_id, channel_name(自定義渠道名), events(JSON 需要回傳的事件, 方便後續動態增加回傳事件), exp(有效期)
# 廣告點選表:按天分表(click_logs)
id, appid, ad_name, [oaid, imei, android_id, mac, ip, ua](這些匹配方式看各自需要儲存), exp, callback, data(JSON冗餘欄位), attributed_at(歸因成功時間',')
# 歸因成功日誌表(這個表按各自日誌需要設計)
# 回撥日誌表(這個表按各自日誌需要設計)

根據時序圖, 來說明實際場景(以下為虛擬碼, 所有資料庫查詢自行做好快取處理)

    1. 點選廣告(這一步是不需要我們處理的, 使用者點選廣告的請求直達渠道商)
    1. 點選監測, 渠道商會請求我們的監測連結
    • 監測連結說明
    • 由於每一家的引數不一樣, 我的建議是不要針對每一個渠道開發, 而是應該適配一個通用的輸入
    • 然後根據通用的輸入, 設計一個模板,這就是為什麼要在渠道表加一個template_query欄位的原因
    • 比如oppo商店的格式是這樣ad_id=__ADID__&android_id=__ANDROIDID__&imei_md5=__IMEI__&oaid=__OAID__
    • 比如頭條的格式是這樣ad_name=__AID_NAME__&android_md5=__ANDROIDID__&callback=__CALLBACK_PARAM__&idfa=__IDFA__&imei_md5=__IMEI__&ip=__IP__&mac_md5=__MAC1__&oaid=__OAID__&site=__CSITE__&ua=__UA__
    • 然後生成監測連線的時候,應該是生成像下面這樣的
    • https://api.domain.com/api/v1/links/{id}/click_logs?ad_id=__ADID__&android_id=__ANDROIDID__&imei_md5=__IMEI__&oaid=__OAID__
    • 介面處理
    • 當請求到達https://api.domain.com/api/v1/links/{id}/click_logs?ad_id=__ADID__&android_id=__ANDROIDID__&imei_md5=__IMEI__&oaid=__OAID__介面時
    • 引數中的宏會替換成實際點選使用者的裝置值, 如:https://api.domain.com/api/v1/links/{id}/click_logs?ad_id=123456789&android_id=123456789&imei_md5=123456789&oaid=123456789
    • 介面虛擬碼:
// 統一的請求結構
class AdClickRequest { public $oaid;
 public $imei;
 public $imei_md5;
 public $andoird_md5;
 public $ad_name;
 public $callback;
 // xxx 更多欄位
}

const FIELDS = ['oaid', 'imei', 'imei_md5', 'xxx'];
function clickLogs($id)
{
 // 1. 根據不同框架, 把資料解析到統一請求上
 $req = new AdClickRequest(); // 2. 查詢監測連結表
 $link = "select * from links where id = {$id}"; // $id
 if (is_null($link)) { return 'FAIL'; } // 3. 寫入點選日誌表, 點選量大走佇列插入
 $logModel = "insert into click_logs(`oaid`, `imei`, `events`, `exp`, 'xxx更多欄位') values({$req->oaid}, {$req->imei}, {$link->events}, {$link->exp}, 'xxx更多欄位')";
 // 4. 寫入 redis $pipe = \Redis::pipeline();
 $value = $logModel . '.' . $logModel->id;
 foreach (FIELDS as $key) { $redisKey = sprintf('attributes:%d_%s', $link->app_id, $req->{$key});
 $pipe->set($redisKey, $value, $logModel->exp*60*60*24);
 } $pipe->exec();
     return 'OK';
}
    1. 使用者首次開啟app上報介面
class AppReportRequest { public $deviceKey;
 public $oaid;
 public $imei;
 public $mac;
 // xxx 更多欄位
}
const FIELDS = ['oaid', 'imei', 'imei_md5', 'xxx'];function appReport($appId)
{
 // 0. 如果是 deepLink 拉起, 最好加一個延遲 10s 的佇列歸因, 防止`app`請求先於渠道商監測連結請求
 // 1. 根據不同框架, 把資料解析到統一請求上
 $req = new AppReportRequest(); // 2. 查詢 app $app = "select * apps where id={$appId}";
 // 3. 查詢 redis $pipe = \Redis::pipeline();
 $keys = []; foreach (FIELDS as $key) { $redisKey = sprintf('attributes:%d_%s', $app->app_id, $req->{$key});
 $keys[] = $redisKey; $pipe->get($redisKey);
 } // result 為一個陣列, 如果匹配到了裡面就是日誌表的表名和主鍵
 $result = $pipe->exec();
 $value = collect($result)->filter()->first();
 if (is_null($value)) { return '歸因失敗';
 }
 $logModel = "select * from click_logs{$value->table} where id = {$value->id}";

 // 接下來可以用佇列事件解耦之後的流程
 \Redis::set("attribute_devices:{$appId}_{$req->deviceKey}", $logModel, 60*60*24*7);
 // 儲存歸因成功日誌表
 // 修改點選日誌狀態等等
 // 刪除所有歸因的 $keys, 防止重複歸因
 // 根據 $app->attribute_cycle_days 設定歸因週期

 return 'SUCCESS';}
  • 4.5.6. 這三步根據具體業務來實現即可

    1. 客戶端事件回傳
function appCallback($appId, $deviceKey)
{
 $logModel = \Redis::get("attribute_devices:{$appId}_{$deviceKey}");
 if (is_null($logModel)) { return 'FAIL'; } // 透過 $logModel->attributed_at 判斷次留是否有效, 判斷是否七日內付費    

 // 處理回傳的邏輯
 return 'SUCCESS';}

客戶端架構

  • 客戶端應該儘量不要發起沒必要的請求, 減少伺服器的壓力
  • 比如播放回傳的時候, 不要每次都回傳, 應該用一下虛擬碼實現
// 假設這個是客戶端的方法, 在需要打點的地方每次都呼叫這個方法
function eventReport(event) {
 // 從本地儲存獲取資料, 一定要存成 json 格式, 繼續反序列化
 var data = storage.get('attribute_events');
 // 如果已經上報過了, 不要上報
 if (data[event]) {
 return; }
 // 上報介面
 var res = api.post('/api/v1/event_callback', '引數');
 if (res.code !== 200 || ! res.data.status) {
 return; }

  data[event] = true;
  storage.set('attribute_events', data);
}

// 歸因上報的介面,
function attributeReport() {
 // 請求介面
 var res = api.post('/api/v1/attributes', '引數')
 if (res.code !== 200 || ! res.data.status) {
 return; }
 // 把整個事件快取刪除掉,這樣子才能繼續上報
  storage.delete('attribute_events');
}

後臺

  • 後臺應該提供一個便捷的聯調頁面

  • 輸入裝置號如oaid, mac, imei

廣告歸因-讓你徹底弄歸因架構實現

  • 如果渠道商日誌沒傳送來, 那就輪詢

廣告歸因-讓你徹底弄歸因架構實現

  • 如果收到日誌, 和API相同的匹配流程查詢到日誌ID

廣告歸因-讓你徹底弄歸因架構實現

  • 設定裝置白名單, 解除重複歸因限制

廣告歸因-讓你徹底弄歸因架構實現

  • 提示投放, 是透過什麼歸因成功的(oaid), 等等其它資訊

廣告歸因-讓你徹底弄歸因架構實現

  • 事件回傳聯調, 把所有可能的事件列出來, 事件建議用JSON儲存, 存成{"event1": "status1", "event2": "status2"}這樣

廣告歸因-讓你徹底弄歸因架構實現

  • 事件回傳成功, 整個歸因聯調完成

廣告歸因-讓你徹底弄歸因架構實現

本作品採用《CC 協議》,轉載必須註明作者和本文連結
當神不再是我們的信仰,那麼信仰自己吧,努力讓自己變好,不辜負自己的信仰!

相關文章