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

 

 
 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM