内部中介不公开提供
内部 Audience Network 竞价现在处于封测阶段,不公开提供。如有变化,我们将提供最新信息。
作为替代方案,您可以通过与我们合作的任一中介平台访问 Audience Network 竞价。
本指南将向您介绍如何构建内部竞拍服务器。在下面的分步指南中,我们将会使用在插屏广告中竞价的示例。请确保您已经熟悉 Audience Network 插屏广告的使用方法。竞价还支持原生广告、横幅广告、插屏广告、视频插播广告和激励视频广告格式。您可以查看下方支持的广告格式,以集成其他广告格式。
我们提供的示例代码仅用于演示竞价 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 个。 |
Audience Network 需要以上 ORTB 请求负载。不过,在您自己的竞拍服务器上,您实际上可以定义其中一些应用参数(如应用编号、版位编号以及服务器配置中的一些其他参数);然后您的客户端应用仅需要发送简化请求。您的服务器可以循环版位信息,并构建最终 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 请求后,可以使用 HTTP 请求、post
和 Content-Type: application/json
向位于 https://an.facebook.com/${PLATFORM_ID}/placementbid.ortb
的 Audience Network 端点发送请求。
在 bid_manager.py
中,应用收集到每个平台的所有竞价请求后,将调用每个平台的 exec_bid_request:
# 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 返回的竞价高于 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}
:您应将此值替换为采用我们相同竞价单位的竞拍结算价格(即基于千次展示费用的美元金额)。我们的失标 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}
:您应将此值替换为采用我们相同竞价单位的竞拍结算价格(即基于千次展示费用的美元金额)。下方展示了各种失标代码和相应失标原因的列表。
失标原因 | 说明 | ORTB v2.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}
:您应将此值替换为采用我们相同竞价单位的竞拍结算价格(即基于千次展示费用的美元金额)。如果竞价超时,我们会向您提供替代报告路径。此为通用的 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 }