內部中介服務未公開供用戶使用
使用 Audience Network 的內部出價目前為封閉測試版,未公開供用戶使用。若有任何變更,我們將提供進一步的更新。
您可以改為透過我們合作的其中一個中介服務平台存取 Audience Network 出價。
本指南說明組建內部競價伺服器的方法。在下方的逐步指南中,我們會使用在插頁廣告出價的範例。請確認您已熟悉 Audience Network 插頁廣告的使用方法。出價同時支援原生、橫幅、插頁、插播影片和獎勵式影片格式。您可以在下方參閱支援的廣告格式),整合不同的廣告格式。
我們提供的程式碼範本只用於示範出價 API 運作方式,以及您將這個 API整合於內部競價伺服器的方法。競價伺服器範例以 Python 加 Flask 寫成,為保持原始碼的簡約程度,只會以最低限度的完整性與安全性為考量。
ORTB 欄位 imp.tagid
中的資料是庫存的 Audience Network 識別碼。在 Audience Network 中是以編號表示庫存。
傳送至 Audience Network 出價端點的 ORTB 要求,必須包含裝置資訊和應用程式資訊。以下是 ORTB 要求中的承載範例:
{ // Device information from client side 'device': { 'ifa': '${DEVICE_ID}', 'dnt': '0', 'ip': '127.0.0.1' }, // Application information 'app': { 'ver': '1.0', 'bundle': 'com.facebook.audiencenetwork.AdUnitsSample', 'publisher': { // For server to server bidding integration, this is your application id on Facebook 'id': '${APP_ID}', } }, // Placement information we can store on server 'imp': [ { 'id': 'banner_test_bid_req_id', // This is the placement id for Audience Network 'tagid': '${PLACEMENT_ID}', 'banner': { 'w': -1, 'h': 50, }, }, ], // Optional regulations object from client side 'regs': { 'coppa': 0, }, // In server to server integration, you can use the Facebook app id as platform id here 'ext': { 'platformid': '${PLATFORM_ID}', // Mediation partner Platform ID or publisher FB app ID, mandatory. 'authentication_id': '${AUTHENTICATION_ID}', // Authentication token to validate the originator of the request. 'security_app_id': '${AN_SECURITY_APP_MY_SECURITY_APP_ID}', // Security app id used to generate authentication_id. 's2s_version': '${VERSION_NUMBER}', // Version of the integration. Max 16 characters. }, // buyeruid is the user bidder token generated on client side, using the `getBidderToken` method from the Audience Network SDK. // It's constant through out app session so you could cache it on the client side 'user': { 'buyeruid': 'mybuyeruid', }, // Test mode flag 'test': '1', // Time out setting we can store on server 'tmax': 1000, // Request ID we can generate on server 'id': 'banner_test_bid_req_id', // Auction setting we can store on server 'at': 1, }
參數 | 類型 | 使用方式 |
---|---|---|
|
| 如果您是中介服務合作夥伴,這就是您的 Facebook 聯絡人提供的合作夥伴編號。 |
如果您是要整合內部解決方案的發佈商,這就是您傳送出價要求所為的應用程式編號。這是版位編號中出現在底線之前的第一個部分。 | ||
|
| 用來驗證要求始發者的驗證權杖。請參閱此頁面。 |
|
| 用來產生 authentication_id 的安全應用程式編號。請參閱此頁面。 |
|
| 整合的版本。上限 16 個字元,由發佈商產生。 |
以上 ORTB 要求承載是 Audience Network 的必要項目。不過,在您自己的競價伺服器上,您其實可以定義一些應用程式規格,例如應用程式編號、版位編號,以及伺服器設定中的一些其他參數,那麼您的用戶端應用程式就只需要傳送簡易版的要求。您的伺服器可以查詢版位資訊並建構最終版的 ORTB 要求。以下是競價伺服器設定範例:
{ "bidding_source_platforms": [ { "platform_name": "audience_network", "end_point": "https://an.facebook.com/${PLATFORM_ID}/placementbid.ortb", "timeout": 1000, "timeout_notification_url": "https://www.facebook.com/audiencenetwork/nurl/?partner=${PARTNER_FBID}&app=${APP_FBID}&auction=${AUCTION_ID}&ortb_loss_code=2" } ], "apps": [ { "app_id": "101", "app_name": "My example app", "placements": [ { "placement_id": "1", "placement_name": "My example placement", "ad_format": "interstitial", "bidding_source_placement_ids":[ { "platform_name": "audience_network", "platform_app_id": "${APP_ID}", "platform_placement_id": "${PLACEMENT_ID}" } ] } ] } ] }
參數 | 使用方式 |
---|---|
| 如果您是中介服務合作夥伴,這就是您的 Facebook 聯絡人提供的合作夥伴編號。 |
如果您是要整合內部解決方案的發佈商,這就是您傳送出價要求所為的應用程式編號。這是版位編號中出現在底線之前的第一個部分。 |
若使用以上競價伺服器設定,用戶端應用程式只需要將包含 "app_id":"101"
和 "placement_id":"1"
的簡易版要求傳送到您的伺服器,您的伺服器就可以查詢完整的版位資訊,例如 placement_id
、ad_format
、platform_name
等等。這是您的用戶端發出的簡易版要求承載範例:
{ // App ID and placement ID are used to look up settings from // server settings 'app_id': '101', 'placement_id': '1', 'bundle': 'com.facebook.samples.s2sbiddingclient', 'bundle_version': '1.0', // Device specifics 'ifa': '${DEVICE_ID}', 'coppa': 0, 'dnt': 0, // buyer_tokens are the user tokens required for different networks 'buyer_tokens': { // Token for audience network from BidderTokenProvider.getBidderToken(context) // This can be cached for the same app session 'audience_network': 'my-buyeruid', }, }
將簡易版要求傳送到您的伺服器之後,您應可以查詢完整的版位、裝置和應用程式資訊,方法是使用競價伺服器設定中的 app_id
和 placement_id
。這是查詢結果範例:
{ "placement_id": "1", "placement_name": "My example placement", "ad_format": "interstitial", "bidding_source_placement_ids": [{ "platform_name": "audience_network", "platform_app_id": "${APP_ID}", "platform_placement_id": "${PLACEMENT_ID}" }] }
競價伺服器收到競價要求之後,需為每一個需求來源建構出價要求。這是 app.py
中用於接收競價要求的端點:
@app.route('/get_bid', methods=['POST']) def get_bid(): ''' The actual endpoint that expects a ClientRequest in the parameters ''' # Initialize the server settings ServerSettings.initialize( app.config['bidding_source_platforms'], app.config['apps'] ) request_params = request.get_json(force=True) # See the code sample below for this function in `bid_manager.py` (code, response) = get_bid_response( request.remote_addr, request.user_agent.string, request_params) app.logger.debug('server response: {0} {1}'.format(code, response)) return Response( json.dumps(response), status=code, mimetype='application/json' )
在 bid_manager.py
中,用戶端傳來的競價要求會包含 app_id
和 placement_id
,其作用是能在伺服器設定中查詢完整版位。
# Return Auction Result - Winner - back to client side def get_bid_response(ip, user_agent, request_params): """Get the winner bid response for the current request""" try: app_id = request_params['app_id'] placement_id = request_params['placement_id'] auction_id = get_auction_id() ... # Find placement in the settings placement = ServerSettings.get_placement(app_id, placement_id) # Collect bid requests for different platforms bid_requests = get_bid_requests( ip, user_agent, auction_id, placement, request_params) except Exception as e: raise ParameterError("Error in request parameters: {}".format(str(e))) ... return final_response # Get all bid requests for different platforms def get_bid_requests(ip, user_agent, auction_id, placement, request_params): """Create bid requests based on the internal placement setting""" ... (end_point, data, timeout_notification_url) = get_bid_request( ip, user_agent, auction_id, platform_name, platform_app_id, platform_placement_id, ad_format, request_params) if data is not None: results.append({ 'platform_name': platform_name, 'end_point': end_point, 'data': data, 'timeout_notification_url': timeout_notification_url, }) # current_app.logger.debug("requests: {}".format(results)) return results # Get bid request for each platform def get_bid_request( ip, user_agent, auction_id, platform_name, platform_app_id, platform_placement_id, ad_format, request_params ): """Create bid request for a specific platform""" if platform_name == 'audience_network': return audience_network.get_bid_request( ip, user_agent, auction_id, platform_app_id, platform_placement_id, ad_format, request_params) else: return (None, None, None)
接著在 audience_network.py
中,get_bid_request
函式會依據完整的版位資訊,產生 Audience Network 所需的最終版 ORTB 要求。
ORTB 欄位 imp.tagid
包含庫存的 Audience Network 識別碼。在 Audience Network 中是以版位編號表示庫存。
def get_bid_request( ip, user_agent, auction_id, platform_app_id, platform_placement_id, ad_format, request_params ): ''' Gather the required bid request parameters for networks. The parameters consist of platform settings like app id, placement ids, ad sizes etc., and client side information such as device information, user agent etc. We use the `settings.json` file to store platform specific settings, and the client request to retrieve the clietn specific information. ''' platform = ServerSettings.get_platform('audience_network') end_point = platform['end_point'] timeout = platform['timeout'] timeout_notification_url = platform['timeout_notification_url'] timeout_notification_url.replace('${PARTNER_FBID}', platform_app_id) timeout_notification_url.replace('${APP_FBID}', platform_app_id) timeout_notification_url.replace('${AUCTION_ID}', auction_id) imp = [] if ad_format == 'native': imp.append({ 'id': auction_id, 'native': { 'w': -1, 'h': -1, }, 'tagid': platform_placement_id, }) elif ad_format == 'banner': imp.append({ 'id': auction_id, 'banner': { 'w': -1, 'h': 50, }, 'tagid': platform_placement_id, }) elif ad_format == 'interstitial': imp.append({ 'id': auction_id, 'banner': { 'w': 0, 'h': 0, }, 'tagid': platform_placement_id, 'instl': 1, }) elif ad_format == 'rewarded_video': imp.append({ 'id': auction_id, 'video': { 'w': 0, 'h': 0, 'linearity': 2, }, 'tagid': platform_placement_id, }) elif ad_format == 'instream_video': imp.append({ 'id': auction_id, 'video': { 'w': 0, 'h': 0, 'linearity': 1, }, 'tagid': platform_placement_id, }) else: raise ParameterError("Incorrect ad format") typed_ip = ipaddress.ip_address(ip) device = { 'ifa': request_params['ifa'], 'ua': user_agent, 'dnt': request_params['dnt'], } if type(typed_ip) is ipaddress.IPv6Address: device['ipv6'] = ip else: device['ip'] = ip # Construct the ORTB request request = { 'id': auction_id, 'imp': imp, 'app': { 'bundle': request_params['bundle'], 'ver': request_params['bundle_version'], 'publisher': { 'id': platform_app_id, } }, 'device': device, 'regs': { 'coppa': request_params['coppa'], }, 'user': { 'buyeruid': request_params['buyer_tokens']['audience_network'], }, 'ext': { ' ': platform_app_id, }, 'at': 1, 'tmax': timeout, 'test': request_params['test'], } return (end_point, request, timeout_notification_url)
我們目前支援四種類型的廣告,可以透過 OpenRTB 進行要求:橫幅廣告、原生廣告(原生或原生橫幅廣告)和影片廣告(獎勵式或插播影片廣告),以及插頁廣告。注意:橫幅、原生和影片物件是相互排斥的,但需要其中任一個。
以下是出價要求中支援的廣告格式清單:
廣告格式 | 出價要求中的參數 |
---|---|
原生廣告 |
|
原生橫幅廣告 |
|
插頁廣告 |
|
獎勵式影片 |
|
獎勵插頁廣告 |
|
橫幅廣告 - 高:50 |
|
橫幅廣告 - 高:250* |
|
*您可以在此廣告格式的營利管理工具中建立橫幅廣告或中型矩形廣告版位
將按照上述步驟建立 ORTB 要求物件後,我們就可以將使用 post
和 Content-Type: application/json
,以 HTTP 將要求傳送至網址在 https://an.facebook.com/${PLATFORM_ID}/placementbid.ortb
的 Audience Network 端點。
在 bid_manager.py
中,待蒐集到每一個平台的所有出價要求之後,就會呼叫各個平台的 exec_bid_requests:
# Return Auction Result - Winner - back to client side def get_bid_response(ip, user_agent, request_params): """Get the winner bid response for the current request""" ... # Execute bid requests by network bid_responses = [] for bid_request in bid_requests: (code, response) = exec_bid_request( bid_request['platform_name'], bid_request['end_point'], bid_request['data'], bid_request['timeout_notification_url']) bid_responses.append({ 'platform_name': bid_request['platform_name'], 'code': code, 'response': response, }) final_response = run_auction(bid_responses, placement) return final_response # Execute bid request for different platform (network) def exec_bid_request( platform_name, end_point, request_params, timeout_notification_url ): ''' Actually run the bid requests for the networks. ''' if platform_name == 'audience_network': return audience_network.exec_bid_request( end_point, request_params, timeout_notification_url, ) else: raise InternalError("Invalid platform: {}".format(platform_name))
要求中會設定下列 HTTP 標頭(針對出價或未出價),其中包含有助於疑難排解且應記錄在競價伺服器上的資訊:
X-FB-AN-Request-ID
:Audience Network 需使用要求編號才能針對特定要求進行偵錯。申請支援時,請務必出示這個編號。X-FB-AN-Errors
:列有所有已知錯誤的清單,有助於瞭解未出價的原因。X-FB-Debug
:與這項要求相關的一些資訊,您可以將這些資訊傳送給您的 Audience Network 客戶代表,由對方進行疑難排解。備註:為盡可能避免誤時,您必須在出價要求中加註 X-FB-Pool-Routing-Token
標頭。
X-FB-Pool-Routing-Token
:這個權杖用於將要求轉傳給我們最近的資料中心,權杖值則與 user.buyeruid
的值相同。 在 audience_network.py
中,會將 ORTB 要求傳送至 Audience Network,並將對應的出價回應傳送至另一個競價伺服器:
def exec_bid_request( end_point, request_params, timeout_notification_url ): try: platform = ServerSettings.get_platform('audience_network') headers = { 'Content-Type': 'application/json; charset=utf-8', 'X-FB-Pool-Routing-Token': request_params['user']['buyeruid'], } timeout = platform['timeout'] r = requests.post( end_point, json=request_params, headers=headers, # Timeout in settings.json in ms timeout=(timeout / 1000), ) except Exception as e: current_app.logger.error(BID_TIMEOUT) # Send time out notification r = requests.get(timeout_notification_url, timeout) return (500, BID_TIMEOUT) if r.status_code == requests.codes.ok: try: data = json.loads(r.text) current_app.logger.debug('Audience Network response: {}'.format( data )) # Parse response from Audience Network with the ORTBResponse ortb_response = ORTBResponse(data) except Exception as e: current_app.logger.error( PARSE_ERROR + "{}".format(e) ) return (500, PARSE_ERROR + "{}".format(e)) return (r.status_code, ortb_response) else: # The error message is stored in the X-FB-AN-Errors header error_header = r.headers.get('x-fb-an-errors') debug_header = r.headers.get('x-fb-debug') bid_request_id = r.headers.get('x-fb-an-request-id') if r.status_code == 400: error_message = INVALID_BID + error_header + INVALID_BID_ADVICE elif r.status_code == 204: error_message = NO_BID + error_header else: error_message = UNEXPECTED_ERROR # Log error information for debugging error = { 'bid_request_id': bid_request_id, 'debug_header': debug_header, 'error_message': error_message, } current_app.logger.error(error) # Respond error status code to client return (r.status_code, error_message)
如果向要求提供 Content-Encoding:gzip
標頭,則可以傳遞二進位 gzip 壓縮格式的要求內文。
現在我們已經收到不同平台(網路)發出的出價回應了。在 bid_manager.py
中,get_bid_response
函式會負責比對平台回應的出價,並且決定最高出價,也就是得標者。
def get_bid_response(ip, user_agent, request_params): """Get the winner bid response for the current request""" ... final_response = run_auction(bid_responses, placement) return final_response def run_auction(bid_responses, placement): """Run auction based on raw responses and create the response object""" other_bid = 1 response = (204, None) # default is 204 no fill for bid_response in bid_responses: if bid_response['platform_name'] == 'audience_network': if bid_response['code'] == 200: ortb_response = bid_response['response'] if ortb_response.price > other_bid: response = create_response(bid_response, placement) current_app.logger.debug( 'Audience Network bid: {} won!'.format( ortb_response.price ) ) notify_result(bid_response) else: current_app.logger.debug( 'Audience Network bid: {} lost!'.format( ortb_response.price ) ) notify_result(bid_response, 102) else: current_app.logger.debug(bid_response['response']) return response
在我們的範例中,Audience Network 是唯一的出價工具,因此,執行競價方法只單純比較傳回的出價與某項價格值,然後決定 Audience Network 是否在競價中順利得標。因此,如果 Audience Network 傳回的出價高於 1 美元,我們的回覆會是 Audience Network 得標,否則就視同本次競價未得標。
我們需要得標、未得標、收費和逾時通知,並備有如 ORTB 中定義的適當未得標代碼。出價回應中會提供 ORTB nurl、lurl 和 burl。請查看上一節的出價回應範例。若發生出價逾時,我們會提供其他回報路線。
出價回應中會提供得標 nurl。您需要在 nurl 中填入出清價格:
"https://www.facebook.com/audiencenetwork/nurl/?partner=${PARTNER_FBID}&app=${APP_FBID}&placement=${PLACEMENT_FBID}&auction=${AUCTION_ID}&impression=${IMPRESSION_ID}&request=${BID_REQUEST_ID}&bid=${BID_ID}&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}"
${AUCTION_PRICE}
:此應替換為與出價相同單位的競價出清價格(即以 CPM 為基礎的美元)。未得標 lurl 包含 2 個需要填入的旗幟:
"https://www.facebook.com/audiencenetwork/nurl/?partner=${PARTNER_FBID}&app=${APP_FBID}&placement=${PLACEMENT_FBID}&auction=${AUCTION_ID}&impression=${IMPRESSION_ID}&request=${BID_REQUEST_ID}&bid=${BID_ID}&ortb_loss_code=${AUCTION_LOSS}&clearing_price=${AUCTION_PRICE}"
${AUCTION_LOSS}
:此應替換為 ORTB 未得標代碼。${AUCTION_PRICE}
:此應替換為與出價相同單位的競價出清價格(即以 CPM 為基礎的美元)。以下列出不同的未得標代碼及其相應的未得標原因。
未得標原因 | 說明 | ORTB 第 2.5 版未得標代碼 |
---|---|---|
出價回應無效 | 出價無效(但準時、不是未出價,且有效性足以讓您擷取 nurl) | 3 |
出價逾時* | 收到出價回應,但趕不上競價截止時間 | 2 |
未出價 | 未出價意指 HTTP 204(即沒有 nurl 可呼叫),但您可以將我們的回應解譯為未出價(可能是整合問題)。您也可以要求多次曝光的出價,我們會進行部分而非全部的出價。 | 9 |
非最高 RTB 出價者 | 另一個出價者得標,包括進入同一競價的綜合出價(例如非 RTB 交易)。 | 102 |
庫存未具現 | 我們的出價贏得競價,但曝光次數沒有具現(例如頁面長度不足以包含此位置,或使用快取廣告前用戶已退出應用程式。)並非所有合作夥伴都能提供此項(其為非事件),若未提供我們會進行推斷。 | 4902 |
已發送到廣告伺服器 | 如果您在決策過程中的最後一個接觸點將我們的高出價發送到廣告伺服器,會發送此項。由於缺少項次、廣告伺服器否決競價,或庫存無法具現,可能仍會失去曝光次數。 | 4900 |
廣告伺服器未挑選 RTB 得標者 | 我們贏得 RTB 競價,但廣告伺服器否決競價(例如直接)。 | 4903 |
得標 | 我們贏得完整的決策樹,並已在頁面(網路)放置標籤或已快取廣告物件(應用程式)。可能仍然不會產生可檢視的曝光次數。 | 0 |
當曝光次數回呼在 Audience Network SDK 中觸發時,我們需要收費通知:您需要在 burl 中填入出清價格:
"https://www.facebook.com/audiencenetwork/burl/?partner=${PARTNER_FBID}&app=${APP_FBID}&placement=${PLACEMENT_FBID}&auction=${AUCTION_ID}&impression=${IMPRESSION_ID}&request=${BID_REQUEST_ID}&bid=${BID_ID}&ortb_loss_code=0&clearing_price=${AUCTION_PRICE}"
${AUCTION_PRICE}
:此應替換為與出價相同單位的競價出清價格(即以 CPM 為基礎的美元)。若發生出價逾時,我們會提供其他回報路線。其為不需等待出價抵達即可接受呼叫的一般 nurl,格式如下所示:
"https://www.facebook.com/audiencenetwork/nurl/?partner=${PARTNER_FBID}&app=${APP_FBID}&auction=${AUCTION_ID}&ortb_loss_code=2"
注意:${PARTNER_FBID}
、${APP_FBID}
和 ${AUCTION_ID}
應填入適當值。下表提供有關這些值的說明。
參數 | 類型 | 說明 |
---|---|---|
PARTNER_FBID | 整數 | Facebook 發佈的廣告競價伺服器編號。如果您沒有專屬的廣告競價合作夥伴,請在此使用您的應用程式編號。 |
APP_FBID | 整數 | Facebook 發佈的應用程式/企業編號,用於初始化競價。 |
AUCTION_ID | 字串 | 用戶端產生的競價編號,用於發佈出價要求。 |
執行競標後,我們會建立回應物件,並將這個物件傳回用戶端應用程式。這是 bid_manager.py
範例中建立競價伺服器最終回應的方法:
def create_response(bid_response, placement): """Create response object based on the auction result""" ad_format = placement['ad_format'] platform_name = bid_response['platform_name'] platform_placement_id = None for bidding_source_placement_id in placement[ 'bidding_source_placement_ids' ]: if bidding_source_placement_id['platform_name'] == platform_name: platform_placement_id = bidding_source_placement_id[ 'platform_placement_id' ] if platform_placement_id is None: raise InternalError("Platform placement ID not found!") bid_payload = None if platform_name == 'audience_network': bid_payload = bid_response['response'].adm else: raise InternalError("Invalid platform") return (200, { 'placement_id': placement['placement_id'], 'ad_format': ad_format, 'platform_name': platform_name, 'platform_placement_id': platform_placement_id, 'bid_payload': bid_payload, })
最後,我們的範例伺服器可以建立這則給用戶端的回應,通知用戶端可以使用平台:
{ 'placement_id': string, // Placement identifier for the auction server 'ad_format': string, // Format of the placement 'platform_name': string, // Which platform won the auction, for example 'audience_network' 'platform_placement_id': string, // Placement ID for the platform, for example the placement ID for Audience network 'bid_payload': string, // The JSON string payload for the platform SDK to load the final ad }