在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
苹果ASA搜索广告服务已全面上线,在App Store中搜索关键词,搜索结果的顶部会出现带有“广告”标识的App展示。 ASA拥有高转化率、低成本、用户精准、流量安全等优势,是一个相当重要的获量渠道。ASA不知道是什么请看这里 https://ads.apple.com/cn/?cid=BD-BZ-Desktop-SC-CN-001 ASA投放创建ASA账号,以及投放广告是BD和运营的事情,这些不清楚的请参考百度。 ASA归因这里是该文章的重点。 ASA的自归因和第三方归因主要差异是什么ASA自归因:App直接和广告媒体做数据对接,并进行归因。 第三方归因:App通过第三方归因平台处理数据统计业务。
ASA后台和第三方归因的数据是实时的吗? ASA后台不是。正常情况下Apple Ads后台的数据有3-6个小时的延迟,有时甚至会达到24小时。 第三方归因平台则要取决于各自的方案。通常获取曝光和点击数据的延时性取决于ASA的API反馈速度。 一个关键词下广告多次展示,却没有用户点击会有影响吗? 影响ASA广告展示的因素有三个:相关性、竞价价格、用户行为。用户点击属于用户行为的一部分,对于用户行为表现较差的广告,会被认为App广告和关键词的相关性并不强,从而减少广告展示权重。 教育行业是否支持投放ASA? 目前已支持投放。近期,苹果发布了《适用于中国大陆的Apple广告指南》文档的版本更新。在本次的更新内容中,就新增了“教育”类别的资质需求,可以自行查看详情。 ASA的自归因我公司合作的数据统计平台如果开通第三方归因服务每年需要8W,所有自然就是自己处理,虽然效果差些,但分析数据现阶段够了。下面进入正题 Apple Ads 归因 API 说明AdServices & iAdApple Ads 的归因 API 包括两个,分别是 iAd Framework 和 AdServices
Framework 。AdServices 是必须实施的,为兼容 14.2 及以下设备建议实施 iAd,
以充分准确地归因来自 ASA 广告的安装。
注:iAd Framework 及 AdServices Framework 必须 App 版本更新进行集成
两个方案的版本支持及成功率对于 iOS 的版本支持以及用户隐私相关的限制:
iOS 14.3 (含)以及更高版本的设备,优先使用 AdServices API 获取归因,该方案不涉
及用户隐私限制,理论上归因成功率超过 90%;
iAd API 可支持所有版本的设备(iOS 4.0+),但仅限于【允许跟踪】的设备;
在 iOS 13 以及更低版本的设备中,隐私设置中的【限制广告跟踪】为关闭状态,在 iOS 14
以及更高版本的设备中,隐私设置中的【允许应用程序请求跟踪】为开启状态,且如果 App
已实施 App Tracking Transparency 框架,还需用户点击“允许跟踪”。
注:如存在复杂归因逻辑的场景,例如多渠道归因、新老用户、网络异常等,上述归因
成功率预估或存在偏差。
集成步骤1、将框架添加到您的项目工程
AdServices.framework 框架是用于 ASA 归因的,不受 ATT 约束,即无论用户是否
允许跟踪都可以归因,仅支持 14.3 及更高版本系统,需 XCode 12.3 及更高版本 支
持。
iAd.framework 框架亦是用于 ASA 归因的,受 ATT 以及 LAT 约束,如果用户允许
跟踪方可以归因。支持当前所有 iOS 版本,但未来可能废弃。
AppTrackingTransparency.framework 是在iOS 14 及更高版本用于征求用户跟踪许
可的框架,即弹窗询问用户是否同意跟踪,在 iOS 14.5 苹果将强制要求开发者实施,
也是获取 IDFA 的前提。
AdSupport.framework 是用于获取 IDFA,以及在低于 iOS 14 的版本中获取 LAT 信
息。
以上 framework 在添加到项目中后,均设置为 Optional。
客户端处理逻辑参考由于各种原因导致的获取归因包失败时,需要做容错处理,及时进行重试,
重试多次仍然失败的,应用在下次启动时再进行获取;
XCode 版本必需 12.3 及以上;
归因数据包格式说明iOS14.3+ iOS14.3-XX 这里我是登录后对数据进行了二次加工,把userId和与后台定义的type加进去 iOS14.3+ "keywordId": 12323222,"userId": "20XX", "countryOrRegion": US, "campaignId": 1234567890, "type": 1, "attribution": 1] iOS14.3-XX 之后对数据进行转json字符串处理然后传给后台 高版本
低版本
后台存表处理逻辑参考当归因包返回的 attribution 为 false,其他数据字段没有,后台存表需注意。
客户端获取归因数据示意代码
// LXADSHelper.swift // Psybot // // Created by 李俊成LX on 2021/12/3. // Copyright © 2021 lianxin. All rights reserved. // import AdSupport import AdServices import iAd import AppTrackingTransparency import Foundation import RxSwift import RxCocoa import NSObject_Rx class LXADSHelper{ static let disposeBag = DisposeBag() class func initSDK() { //苹果ASA;延迟4秒再发送,等ATT用户操作结果,可能有IDFA DispatchQueue.main.asyncAfter(deadline: .now() + 7) { let defaultStand = UserDefaults.standard let pushedADS = defaultStand.value(forKey: "pushedADS") if Platform.isSimulator { }else { if pushedADS == nil { self.logAds() } } } } /// 苹果Ads广告 /// TODO:有些旧设备新系统(iPhone8),会出现token为空的问题 class func logAds() { if #available(iOS 14.3, *) { var token: String? = nil do { token = try AAAttribution.attributionToken() } catch { } LXSimpleLogs("LogAds:AdServces,Token: \(token ?? "")") if let token = token { // 1、发送POST给苹果得到归因数据 sendToken(getANullableString("token", content: token)) { attrData in // 异步,会延后 LXSimpleLogs("LogAds:14.3+ Dict: \(attrData ?? [:])") // TODO::发送数据给服务端 // ... ... if attrData != nil { var attrDataL:[String:Any] = attrData! // 添加userId attrDataL["type"] = "1" let defaultStand = UserDefaults.standard defaultStand.set(attrDataL, forKey:"pushADSDic") defaultStand.synchronize() self.logOpen() } } } }else{ if ADClient.shared().responds(to: #selector(ADClient.requestAttributionDetails(_:))) { LXSimpleLogs("LogAds:iAd called") ADClient.shared().requestAttributionDetails({ attrData, error in // 异步,会延后 LXSimpleLogs("LogAds:14- Dict: \(attrData ?? [:])") // TODO::发送数据给服务端 if attrData != nil { var haveVersion :Int = 0 var haveVersionStr :String = "Version" for keystr in attrData!.keys { if keystr.contains("Version") { haveVersion = 1 haveVersionStr = keystr } } if haveVersion == 1 { var attrDataLL : [String:Any] = [:] let attrDataL:[String:Any] = attrData![haveVersionStr] as! [String : Any] // 旧数据统一处理一下 attrDataLL["iadPurchaseDate"] = attrDataL["iad-purchase-date"] attrDataLL["iadLineitemId"] = attrDataL["iad-lineitem-id"] attrDataLL["iadOrgName"] = attrDataL["iad-org-name"] attrDataLL["iadCreativesetId"] = attrDataL["iad-creativeset-id"] attrDataLL["iadCreativesetName"] = attrDataL["iad-creativeset-name"] attrDataLL["iadOrgId"] = attrDataL["iad-org-id"] attrDataLL["iadLineitemName"] = attrDataL["iad-lineitem-name"] attrDataLL["iadAdgroupName"] = attrDataL["iad-adgroup-name"] attrDataLL["iadConversionDate"] = attrDataL["iad-conversion-date"] attrDataLL["iadClickDate"] = attrDataL["iad-click-date"] attrDataLL["iadKeywordMatchtype"] = attrDataL["iad-keyword-matchtype"] attrDataLL["iadCountryOrRegion"] = attrDataL["iad-country-or-region"] attrDataLL["iadConversionType"] = attrDataL["iad-conversion-type"] attrDataLL["iadKeywordId"] = attrDataL["iad-keyword-id"] attrDataLL["iadCampaignId"] = attrDataL["iad-campaign-id"] attrDataLL["iadAttribution"] = attrDataL["iad-attribution"] attrDataLL["iadCampaignName"] = attrDataL["iad-campaign-name"] attrDataLL["iadKeyword"] = attrDataL["iad-keyword"] attrDataLL["iadAdgroupId"] = attrDataL["iad-adgroup-id"] // attrDataLL["type"] = "2" let defaultStand = UserDefaults.standard defaultStand.set(attrDataLL, forKey:"pushADSDic") defaultStand.synchronize() self.logOpen() } } }) } } } /// 读取可能为空的字符串 class func getANullableString(_ desc: String?, content: String?) -> String? { if content == nil { return "" } return "\(content ?? "")" } /// 发送归因token得到数据 class func sendToken(_ token: String?, completeBlock: @escaping (_ data: [String : Any]?) -> Void) { let url = "https://api-adservices.apple.com/api/v1/" var request: URLRequest? = nil if let url1 = URL(string: url) { request = URLRequest(url: url1) } request?.httpMethod = "POST" request?.addValue("text/plain", forHTTPHeaderField: "Content-Type") let postData = token?.data(using: .utf8) request?.httpBody = postData // 发出请求 URLSession.shared.dataTask(with: request!) { data, response, error in var result: [String : Any]? = nil if error != nil { // 请求失败 LXSimpleLogs("LogAds:sendToken ERR") let nulldict: [String : Any] = [:] completeBlock(nulldict) }else{ // 请求成功 var resDic: [String : Any]? = nil do { resDic = try JSONSerialization.jsonObject(with: data!, options: []) as? [String : Any] } catch _ { } result = resDic completeBlock(result) } }.resume() } /// 激活日志,这里登录后发送 class func logOpen() { let defaultStand = UserDefaults.standard let pushedADS = defaultStand.value(forKey: "pushedADS") if pushedADS == nil && LXUserModel.islogined , let userId = LXUserModel.localModel()?.userId{ LXSimpleLogs("LogOpen") var attrData:[String:Any] = defaultStand.value(forKey: "pushADSDic") as! [String : Any] if attrData.keys.count > 0 { // 添加userId attrData["userId"] = userId LXSimpleLogs("LogOpenAds Dict: \(attrData)") // 上传数据 let params = ["asaData":attrData.jsonString] as [String : Any] LXSimpleLogs("LogOpenAds Dict_params: \(params)") DispatchQueue.main.async { //code 上传数据伪代码 // 上传数据后OK let defaultStand = UserDefaults.standard defaultStand.set(true, forKey:"pushedADS") defaultStand.synchronize() } } } } struct Platform { static let isSimulator: Bool = { var isSim = false #if arch(i386) || arch(x86_64) isSim = true #endif return isSim }() } }
大数据分析归因处理参考以上就把相关的归因数据存表了,但是有的只有关键词ID,并没有对应的关键词,这里就会用到获取对应关键词的官方API接口 当然如果不想这样,那就通过批量上传关键词,在上传关键词.csv文件的时候给大数据工程师一份。
好了,就这样吧! 参考:https://baijiahao.baidu.com/s?id=1709681570145946947&wfr=spider&for=pc
|
请发表评论