請按照下列最佳做法,在 Google Ads API 中變更資源時,盡可能提升效能、管理各項作業的依附元件,以及處理回應。
暫時資源名稱
GoogleAdsService.Mutate 和 BatchJobService 都支援臨時資源名稱,可在後續作業中參照。您可以在單一變更要求或批次工作中,建立廣告活動及其相關聯的廣告群組、廣告和關鍵字。
如要在同一個變更要求或批次作業中參照新建立的資源,請在新資源的 resource_name 欄位中指定負整數 ID (例如 -1 或 -2,不包括 0)。舉例來說,在批次要求中建立廣告活動時,請將資源名稱設為 customers/CUSTOMER_ID/campaigns/-1。
在同一項請求的後續作業中建立廣告群組時,請將 customers/CUSTOMER_ID/campaigns/-1 參照為父項廣告活動。API 會自動將 -1 替換為建立時產生的實際廣告活動 ID。
使用限制
使用臨時資源名稱時,請注意下列規則:
- 順序很重要:您只能在定義臨時資源名稱後參照該名稱。在作業清單中,相依作業 (例如建立廣告群組) 必須出現在建立父項資源 (例如建立廣告活動) 的作業之後。
- 單一要求或批次工作範圍:臨時資源名稱不會在不同工作或變動要求之間保留。如要參照先前作業或變更要求中建立的資源,請使用系統產生的實際資源名稱。
- 全域專屬性:在單一工作或變更要求中,每個暫時資源名稱在所有資源類型中都必須使用專屬的負整數。舉例來說,您無法在同一個要求中,同時為廣告活動和廣告群組指派
-1。在相同要求或批次作業中重複使用臨時 ID 會傳回NewResourceCreationError.DUPLICATE_TEMP_IDS錯誤。
酬載範例
假設您想在單一 API 要求或批次工作新增廣告活動、廣告群組和廣告,您可以在 GoogleAdsService.Mutate 或 BatchJobService.AddBatchJobOperations 要求酬載中,建構 mutateOperations 陣列,如下列 REST JSON 範例所示 (為求精簡,省略其他必要資源欄位):
{
"mutateOperations": [
{
"campaignOperation": {
"create": {
"resourceName": "customers/CUSTOMER_ID/campaigns/-1"
}
}
},
{
"adGroupOperation": {
"create": {
"resourceName": "customers/CUSTOMER_ID/adGroups/-2",
"campaign": "customers/CUSTOMER_ID/campaigns/-1"
}
}
},
{
"adGroupAdOperation": {
"create": {
"adGroup": "customers/CUSTOMER_ID/adGroups/-2"
}
}
}
]
}
這個範例示範了下列重要詳細資料:
- 廣告群組使用新的臨時 ID (
-2),因為-1已指派給廣告活動。 - 廣告群組會參照
customers/CUSTOMER_ID/campaigns/-1,將自己連結至前一個作業中建立的廣告活動。 - 由於要求中的後續作業未參照新廣告,因此
adGroupAdOperation會參照customers/CUSTOMER_ID/adGroups/-2並省略resourceName。
將相同類型的作業分組
使用 GoogleAdsService.Mutate 時,請在重複的 mutate_operations 陣列中,依資源類型將作業分組,同時遵守父項和子項的依附元件。這個方法會依序讀取作業,直到遇到不同資源類型為止,然後將所有相同類型的連續作業批次處理為單一後端服務要求。
舉例來說,如果您在重複的 mutate_operations 欄位中加入 5 個廣告活動作業,然後加入 10 個廣告群組作業,系統會執行兩次後端呼叫:一次是針對 5 個廣告活動作業呼叫 CampaignService,另一次是針對 10 個廣告群組作業呼叫 AdGroupService。
相反地,如果將作業交錯排序為 [campaign, ad group,
campaign, ad group],則會產生四個不同的後端呼叫。交錯呼叫會降低 API 效能,並可能導致大型批次作業的要求逾時。
處理部分失敗和批次限制
根據預設,如果任何單一作業失敗,GoogleAdsService.Mutate 會復原整個要求。如要提交有效作業,即使同一要求中的其他作業失敗也沒關係,請在要求中將 partial_failure 設為 true,並檢查回應中的 partial_failure_error。當 partial_failure 為 true 時,如果定義臨時 ID 的父項作業 (例如 customers/CUSTOMER_ID/campaigns/-1) 驗證失敗,則在同一要求中參照該臨時 ID 的任何子項作業也會失敗,並顯示 NewResourceCreationError.TEMP_ID_RESOURCE_HAD_ERRORS。詳情請參閱部分失敗指南。
此外,請留意要求大小、子批次處理和速率限制:
- 要求和區塊大小限制:單一
GoogleAdsService.Mutate要求最多可強制執行 10,000 項變動作業 (如果要求中的所有作業都是AdGroupCriterionOperation,則最多可執行 20,000 項,超過上限會傳回RequestError.TOO_MANY_MUTATE_OPERATIONS),以及最多 100 項動作作業 (RequestError.TOO_MANY_ACTION_OPERATIONS)。BatchJobService.AddBatchJobOperations每次呼叫最多可強制執行 10,000 項作業,每個MutateOperation最多 10,484,504 個位元組,每個AddBatchJobOperationsRequest最多 41,937,920 個位元組 (如果超過任何限制,會傳回BatchJobError.REQUEST_TOO_LARGE,每個批次工作最多可執行 1,000,000 項作業)。如果多項變動同時以相同廣告活動或帳戶為目標,可能會觸發DatabaseError.CONCURRENT_MODIFICATION錯誤。 BatchJobService不可分割的子批次作業:雖然批次作業會根據部分失敗語意執行 (每個內部子批次預設為 1,000 項作業),但BatchJobService會自動將相同父項 ID 的特定連續相依作業分組為不可分割的子批次作業:AssetGroupOperation(create),後接相同AssetGroupID 的連續AssetGroupAssetOperation(create) 作業 (最多 1,000 項作業,會以原子方式失敗,並顯示BatchJobError.ASSET_GROUP_AND_ASSET_GROUP_ASSET_TRANSACTION_FAILURE;每個AssetGroupOperationupdate或remove都會在獨立的單一作業子批次中執行)。- 最高成效
CampaignOperation(create,啟用品牌宣傳指南時為預設值,除非brand_guidelines_enabled設為false或hotel_property_asset_set設為其他值) 後接相同CampaignID 的連續CampaignAssetOperation(create) 作業 (最多 1,000 項作業,會以原子方式失敗並顯示BatchJobError.CAMPAIGN_AND_CAMPAIGN_ASSET_TRANSACTION_FAILURE)。 - 連續
AssetGroupListingGroupFilterOperation(max 10,000,以BatchJobError.ASSET_GROUP_LISTING_GROUP_FILTER_TRANSACTION_FAILURE原子方式失敗) 或AdGroupCriterionOperation(listing_group、max 20,000,以CriterionError.LISTING_GROUP_ERROR_IN_ANOTHER_OPERATION原子方式失敗) 作業,適用於相同父項 (AssetGroup或AdGroup)。
從回應中擷取可變動的屬性
如果將變更要求中的 response_content_type 設為 MUTABLE_RESOURCE,回應會包含 resource_name 和資源物件,並填入其可變動欄位 (以及傳回資源中系統填入的索引鍵欄位,例如 ExperimentArm.in_design_campaigns),適用於要求建立或更新 (未移除) 的每個支援物件。如果是 remove 作業,或是資源類型不支援傳回 MUTABLE_RESOURCE,回應一律只會傳回 resource_name。使用這項功能,即可避免在每次變動呼叫後,傳送額外的 Search 或 SearchStream 請求。
如未設定 response_content_type,Google Ads API 會預設為 RESOURCE_NAME_ONLY,且只會傳回每個變動資源的 resource_name。
以下範例說明如何從變動呼叫中擷取可變動的資源:
Java
private String createExperimentArms( GoogleAdsClient googleAdsClient, long customerId, long campaignId, String experiment) { List<ExperimentArmOperation> operations = new ArrayList<>(); operations.add( ExperimentArmOperation.newBuilder() .setCreate( // The "control" arm references an already-existing campaign. ExperimentArm.newBuilder() .setControl(true) .addCampaigns(ResourceNames.campaign(customerId, campaignId)) .setExperiment(experiment) .setName("control arm") .setTrafficSplit(40) .build()) .build()); operations.add( ExperimentArmOperation.newBuilder() .setCreate( // In standard campaign experiments, creating the treatment arm automatically // generates a draft campaign that you can modify before starting the experiment. ExperimentArm.newBuilder() .setControl(false) .setExperiment(experiment) .setName("experiment arm") .setTrafficSplit(60) .build()) .build()); try (ExperimentArmServiceClient experimentArmServiceClient = googleAdsClient.getLatestVersion().createExperimentArmServiceClient()) { // Constructs the mutate request. MutateExperimentArmsRequest mutateRequest = MutateExperimentArmsRequest.newBuilder() .setCustomerId(Long.toString(customerId)) .addAllOperations(operations) // We want to fetch the draft campaign IDs from the treatment arm, so the easiest way // to do that is to have the response return the newly created entities. .setResponseContentType(ResponseContentType.MUTABLE_RESOURCE) .build(); // Sends the mutate request. MutateExperimentArmsResponse response = experimentArmServiceClient.mutateExperimentArms(mutateRequest); // Results always return in the order that you specify them in the request. Since we created // the treatment arm last, it will be the last result. If you don't remember which arm is the // treatment arm, you can always filter the query in the next section with // `experiment_arm.control = false`. MutateExperimentArmResult controlArmResult = response.getResults(0); MutateExperimentArmResult treatmentArmResult = response.getResults(response.getResultsCount() - 1); System.out.printf( "Created control arm with resource name '%s'%n", controlArmResult.getResourceName()); System.out.printf( "Created treatment arm with resource name '%s'%n", treatmentArmResult.getResourceName()); return treatmentArmResult.getExperimentArm().getInDesignCampaigns(0); } }
C#
private static (MutateExperimentArmResult, MutateExperimentArmResult) CreateExperimentArms(GoogleAdsClient client, long customerId, long baseCampaignId, string experimentResourceName) { // Get the ExperimentArmService. ExperimentArmServiceClient experimentService = client.GetService( Services.V25.ExperimentArmService); // Create the control arm. The control arm references an already-existing campaign. ExperimentArmOperation controlArmOperation = new ExperimentArmOperation() { Create = new ExperimentArm() { Control = true, Campaigns = { ResourceNames.Campaign(customerId, baseCampaignId) }, Experiment = experimentResourceName, Name = "Control Arm", TrafficSplit = 40 } }; // Create the non-control arm. // In standard campaign experiments, creating the treatment arm automatically // generates a draft campaign that you can modify before starting the experiment. ExperimentArmOperation treatmentArmOperation = new ExperimentArmOperation() { Create = new ExperimentArm() { Control = false, Experiment = experimentResourceName, Name = "Experiment Arm", TrafficSplit = 60 } }; // We want to fetch the draft campaign IDs from the treatment arm, so the // easiest way to do that is to have the response return the newly created // entities. MutateExperimentArmsRequest request = new MutateExperimentArmsRequest { CustomerId = customerId.ToString(), Operations = { controlArmOperation, treatmentArmOperation }, ResponseContentType = ResponseContentType.MutableResource }; MutateExperimentArmsResponse response = experimentService.MutateExperimentArms( request ); // Results always return in the order that you specify them in the request. // Since we created the treatment arm last, it will be the last result. MutateExperimentArmResult controlArm = response.Results.First(); MutateExperimentArmResult treatmentArm = response.Results.Last(); Console.WriteLine($"Created control arm with resource name " + $"'{controlArm.ResourceName}'."); Console.WriteLine($"Created treatment arm with resource name" + $" '{treatmentArm.ResourceName}'."); return (controlArm, treatmentArm); }
PHP
private static function createExperimentArms( GoogleAdsClient $googleAdsClient, int $customerId, int $campaignId, string $experimentResourceName ): string { $operations = []; $experimentArm1 = new ExperimentArm( [ // The "control" arm references an already-existing campaign. 'control' => true, 'campaigns' => [ResourceNames::forCampaign($customerId, $campaignId)], 'experiment' => $experimentResourceName, 'name' => 'control arm', 'traffic_split' => 40 ] ); $operations[] = new ExperimentArmOperation(['create' => $experimentArm1]); $experimentArm2 = new ExperimentArm( [ // The non-"control" arm, also called a "treatment" arm, will automatically // generate draft campaigns that you can modify before starting the // experiment. 'control' => false, 'experiment' => $experimentResourceName, 'name' => 'experiment arm', 'traffic_split' => 60 ] ); $operations[] = new ExperimentArmOperation(['create' => $experimentArm2]); // Issues a request to create the experiment arms. $experimentArmServiceClient = $googleAdsClient->getExperimentArmServiceClient(); $response = $experimentArmServiceClient->mutateExperimentArms( MutateExperimentArmsRequest::build($customerId, $operations) // We want to fetch the draft campaign IDs from the treatment arm, so the easiest // way to do that is to have the response return the newly created entities. ->setResponseContentType(ResponseContentType::MUTABLE_RESOURCE) ); // Results always return in the order that you specify them in the request. // Since we created the treatment arm last, it will be the last result. $controlArmResourceName = $response->getResults()[0]->getResourceName(); $treatmentArm = $response->getResults()[count($operations) - 1]; print "Created control arm with resource name '$controlArmResourceName'" . PHP_EOL; print "Created treatment arm with resource name '{$treatmentArm->getResourceName()}'" . PHP_EOL; return $treatmentArm->getExperimentArm()->getInDesignCampaigns()[0]; }
Python
def create_experiment_arms( client: GoogleAdsClient, customer_id: str, base_campaign_id: str, experiment: str, ) -> str: """Creates a control and treatment experiment arms. Args: client: an initialized GoogleAdsClient instance. customer_id: a client customer ID. base_campaign_id: the campaign ID to associate with the control arm of the experiment. experiment: the resource name for an experiment. Returns: the resource name for the new treatment experiment arm. """ operations: List[ExperimentArmOperation] = [] campaign_service: CampaignServiceClient = client.get_service( "CampaignService" ) # The "control" arm references an already-existing campaign. operation_1: ExperimentArmOperation = client.get_type( "ExperimentArmOperation" ) exa_1: ExperimentArm = operation_1.create exa_1.control = True exa_1.campaigns.append( campaign_service.campaign_path(customer_id, base_campaign_id) ) exa_1.experiment = experiment exa_1.name = "control arm" exa_1.traffic_split = 40 operations.append(operation_1) # In standard campaign experiments, creating the treatment arm automatically # generates a draft campaign that you can modify before starting the experiment. operation_2: ExperimentArmOperation = client.get_type( "ExperimentArmOperation" ) exa_2: ExperimentArm = operation_2.create exa_2.control = False exa_2.experiment = experiment exa_2.name = "experiment arm" exa_2.traffic_split = 60 operations.append(operation_2) experiment_arm_service: ExperimentArmServiceClient = client.get_service( "ExperimentArmService" ) request: MutateExperimentArmsRequest = client.get_type( "MutateExperimentArmsRequest" ) request.customer_id = customer_id request.operations = operations # We want to fetch the draft campaign IDs from the treatment arm, so the # easiest way to do that is to have the response return the newly created # entities. request.response_content_type = ( client.enums.ResponseContentTypeEnum.MUTABLE_RESOURCE ) response: MutateExperimentArmsResponse = ( experiment_arm_service.mutate_experiment_arms(request=request) ) # Results always return in the order that you specify them in the request. # Since we created the treatment arm second, it will be the second result. control_arm_result: Any = response.results[0] treatment_arm_result: Any = response.results[1] print( f"Created control arm with resource name {control_arm_result.resource_name}" ) print( f"Created treatment arm with resource name {treatment_arm_result.resource_name}" ) return treatment_arm_result.experiment_arm.in_design_campaigns[0]
小茹
def create_experiment_arms(client, customer_id, base_campaign_id, experiment) operations = [] operations << client.operation.create_resource.experiment_arm do |ea| # The "control" arm references an already-existing campaign. ea.control = true ea.campaigns << client.path.campaign(customer_id, base_campaign_id) ea.experiment = experiment ea.name = 'control arm' ea.traffic_split = 40 end operations << client.operation.create_resource.experiment_arm do |ea| # The non-"control" arm, also called a "treatment" arm, will automatically # generate draft campaigns that you can modify before starting the # experiment. ea.control = false ea.experiment = experiment ea.name = 'experiment arm' ea.traffic_split = 60 end response = client.service.experiment_arm.mutate_experiment_arms( customer_id: customer_id, operations: operations, # We want to fetch the draft campaign IDs from the treatment arm, so the # easiest way to do that is to have the response return the newly created # entities. response_content_type: :MUTABLE_RESOURCE, ) # Results always return in the order that you specify them in the request. # Since we created the treatment arm last, it will be the last result. control_arm_result = response.results.first treatment_arm_result = response.results.last puts "Created control arm with resource name #{control_arm_result.resource_name}." puts "Created treatment arm with resource name #{treatment_arm_result.resource_name}." treatment_arm_result.experiment_arm.in_design_campaigns.first end
Perl
sub create_experiment_arms { my ($api_client, $customer_id, $base_campaign_id, $experiment) = @_; my $operations = []; push @$operations, Google::Ads::GoogleAds::V25::Services::ExperimentArmService::ExperimentArmOperation ->new({ create => Google::Ads::GoogleAds::V25::Resources::ExperimentArm->new({ # The "control" arm references an already-existing campaign. control => "true", campaigns => [ Google::Ads::GoogleAds::V25::Utils::ResourceNames::campaign( $customer_id, $base_campaign_id ) ], experiment => $experiment, name => "control arm", trafficSplit => 40 })}); push @$operations, Google::Ads::GoogleAds::V25::Services::ExperimentArmService::ExperimentArmOperation ->new({ create => Google::Ads::GoogleAds::V25::Resources::ExperimentArm->new({ # The non-"control" arm, also called a "treatment" arm, will automatically # generate draft campaigns that you can modify before starting the # experiment. control => "false", experiment => $experiment, name => "experiment arm", trafficSplit => 60 })}); my $response = $api_client->ExperimentArmService()->mutate({ customerId => $customer_id, operations => $operations, # We want to fetch the draft campaign IDs from the treatment arm, so the # easiest way to do that is to have the response return the newly created # entities. responseContentType => MUTABLE_RESOURCE }); # Results always return in the order that you specify them in the request. # Since we created the treatment arm last, it will be the last result. my $control_arm_result = $response->{results}[0]; my $treatment_arm_result = $response->{results}[1]; printf "Created control arm with resource name '%s'.\n", $control_arm_result->{resourceName}; printf "Created treatment arm with resource name '%s'.\n", $treatment_arm_result->{resourceName}; return $treatment_arm_result->{experimentArm}{inDesignCampaigns}[0]; }