广告归因-让你彻底弄归因架构实现

解释

  • 这里会引用神策数据很多的介绍,然后进行总结

归因方法

  • 自归因
    • 渠道商帮我们做归因,有的是每个用户打开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 协议》,转载必须注明作者和本文链接
当神不再是我们的信仰,那么信仰自己吧,努力让自己变好,不辜负自己的信仰!
本帖由系统于 1年前 自动加精
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 13

讲得不错,我们就是这么搞得

1年前 评论
seth-shi (楼主) 1年前
邢闯洋 (作者) 1年前
seth-shi (楼主) 1年前
seth-shi (楼主) 1年前

要是早两个月看到,不至于现在这么痛苦

1年前 评论
seth-shi (楼主) 1年前
╰ゝSakura

讲得不错,归因都给你讲明白了

1年前 评论
seth-shi (楼主) 1年前
QIN秦同学
又涨见识了,手动支持,学习下、
1年前 评论
GDDD

快手磁力聚星,或者其他广告平台,都这样的。基本操作

1年前 评论

当没有oaid的时候一个用户访问多个账号的广告然后有归因怎么区分是那个账号 我们当初通过User-Agent+ip 还是有重复的

1年前 评论
seth-shi (楼主) 1年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!