zoukankan      html  css  js  c++  java
  • iOS ASA 的归因技术支持_Swift

    ASA简介

    苹果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 & iAd

    Apple 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+
    ["adGroupId": 1234567890, "creativeSetId": 1234567890, "conversionType": Download, "orgId": 1234567890, "clickDate": 2021-12-07T04:25Z, "keywordId": 12323222,  "countryOrRegion": US, "campaignId": 1234567890,  "attribution": 1]

    iOS14.3-XX
    "Version3.1": {
    "iad-adgroup-id" = 1234567890;
    "iad-adgroup-name" = AdGroupName;
    "iad-attribution" = true;
    "iad-campaign-id" = 1234567890;
    "iad-campaign-name" = CampaignName;
    "iad-click-date" = "2021-12-03T07:32:38Z";
    "iad-conversion-date" = "2021-12-03T07:32:38Z";
    "iad-conversion-type" = Download;
    "iad-country-or-region" = US;
    "iad-creativeset-id" = 1234567890;
    "iad-creativeset-name" = CreativeSetName;
    "iad-keyword" = Keyword;
    "iad-keyword-id" = 12323222;
    "iad-keyword-matchtype" = Broad;
    "iad-lineitem-id" = 1234567890;
    "iad-lineitem-name" = LineName;
    "iad-org-id" = 1234567890;
    "iad-org-name" = OrgName;
    "iad-purchase-date" = "2021-12-03T07:32:38Z";
    }

    这里我是登录后对数据进行了二次加工,把userId和与后台定义的type加进去

    iOS14.3+
    ["adGroupId": 1234567890, "creativeSetId": 1234567890, "conversionType": Download, "orgId": 1234567890, "clickDate": 2021-12-07T04:25Z,

    "keywordId": 12323222,"userId": "20XX", "countryOrRegion": US, "campaignId": 1234567890, "type": 1, "attribution": 1]

    iOS14.3-XX
    ["userId": "20XX";
    "type": 2;
    "Version3.1": {
    "iad-adgroup-id" = 1234567890;
    "iad-adgroup-name" = AdGroupName;
    "iad-attribution" = true;
    "iad-campaign-id" = 1234567890;
    "iad-campaign-name" = CampaignName;
    "iad-click-date" = "2021-12-03T07:32:38Z";
    "iad-conversion-date" = "2021-12-03T07:32:38Z";
    "iad-conversion-type" = Download;
    "iad-country-or-region" = US;
    "iad-creativeset-id" = 1234567890;
    "iad-creativeset-name" = CreativeSetName;
    "iad-keyword" = Keyword;
    "iad-keyword-id" = 12323222;
    "iad-keyword-matchtype" = Broad;
    "iad-lineitem-id" = 1234567890;
    "iad-lineitem-name" = LineName;
    "iad-org-id" = 1234567890;
    "iad-org-name" = OrgName;
    "iad-purchase-date" = "2021-12-03T07:32:38Z";
    }]

    之后对数据进行转json字符串处理然后传给后台

    高版本

     

     低版本

    字段类型说明
    iad-attribution Boolean 如果用户在应用下载前30天点击了Apple Search Ads广告,则为True。
    iad-org-name String 广告系列所属的账户组织名称
    iad-org-id Integer 广告系列所属的账户组织ID
    iad-campaign-id Integer 广告系列ID
    iad-campaign-name String 广告系列名称
    iad-click-date Date/time string 用户点击相应广告的日期和时间
    iad-purchase-date Date/time string 用户首次下载您的应用的日期和时间。 当iad-conversion-type的值为“Redownload”,此字符串表示原始购买日期。 该购买可能与Apple Search广告无关。
    iad-conversion-date Date/time string 用户通过点击Apple搜索广告下载您的应用的日期和时间。
    iad-conversion-type String 表明是否首次下载。"Redownload"说明用户在本设备下载/卸载过,或者用同一账户在其他设备下载过。
    iad-adgroup-id Integer 广告组ID
    iad-adgroup-name String 广告组名称
    iad-country-or-region String 广告系列相关的国家或地区
    iad-keyword String 带来广告展示次数并带来相应广告点击的关键字
    iad-keyword-id String 带来广告展示次数的关键字的ID
    iad-keyword-matchtype String 带来广告展示次数的关键字的匹配类型。 值是广泛匹配、完全匹配或搜索匹配。
    iad-creativeset-id Integer 相应广告所属的广告素材集的ID
    iad-creativeset-name String 相应广告所属的广告素材集的名称

    后台存表处理逻辑参考

    当归因包返回的 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接口

    请参考https://developer.apple.com/documentation/apple_search_ads/implementing_oauth_for_the_apple_search_ads_api

    当然如果不想这样,那就通过批量上传关键词,在上传关键词.csv文件的时候给大数据工程师一份。

     好了,就这样吧!

    参考:https://baijiahao.baidu.com/s?id=1709681570145946947&wfr=spider&for=pc

     

     
     
  • 相关阅读:
    jmeter(46) redis
    jmeter(45) tcp/ip协议
    Codeforces Round #538 (Div. 2)D(区间DP,思维)
    Codeforces Global Round 1D(DP,思维)
    Educational Codeforces Round 57D(DP,思维)
    UPC11073(DP,思维)
    Yahoo Progamming Contest 2019D(DP,思维)
    Atcoder Beginner Contest 118D(DP,完全背包,贪心)
    Xuzhou Winter Camp 1C(模拟)
    Educational Codeforces Round 57 (Rated for Div. 2)D(动态规划)
  • 原文地址:https://www.cnblogs.com/ljcgood66/p/15705298.html
Copyright © 2011-2022 走看看