在 Google Ads API 中更改资源时,请遵循以下最佳实践来优化性能、管理跨操作的依赖项以及处理响应。
临时资源名称
GoogleAdsService.Mutate 和
BatchJobService 都支持可在后续操作中引用的临时资源名称。这样,您就可以在单个 mutate 请求或批量作业中创建广告系列及其关联的广告组、广告和关键字。
如需在同一 mutate 请求或批量作业中引用新创建的资源,请在新资源的 resource_name 字段中指定一个负整数 ID(例如 -1 或 -2,但不包括 0)。例如,在批量请求中创建广告系列时,请将其资源名称设置为 customers/CUSTOMER_ID/campaigns/-1。
在同一请求中稍后的操作中创建广告组时,请将 customers/CUSTOMER_ID/campaigns/-1 引用为父级广告系列。API 会自动将 -1 替换为创建时生成的实际广告系列 ID。
使用限制
使用临时资源名称时,请注意以下规则:
- 顺序很重要: 您只能在定义临时资源名称后引用它。在操作列表中,依赖的操作(例如创建广告组)必须出现在创建其父级资源的操作(例如创建广告系列)之后。
- 单次请求或批量作业范围: 临时资源名称不会在不同的作业或更改请求之间保留。如需引用在之前的作业或 mutate 请求中创建的资源,请使用其系统生成的实际资源名称。
- 全局唯一性: 在单个作业或 mutate 请求中,每个临时资源名称都必须在所有资源类型中使用唯一的负整数。例如,您不能在同一请求中同时为广告系列和广告组分配
-1。在同一请求或批量作业中重复使用临时 ID 会返回NewResourceCreationError.DUPLICATE_TEMP_IDS错误。
示例载荷
假设您想在单个 API 请求或批量作业中添加广告系列、广告组和广告。您可以按照以下 REST JSON 示例(为简洁起见,省略了其他必需的资源字段)所示,在 GoogleAdsService.Mutate 或 BatchJobService.AddBatchJobOperations 请求载荷中构建 mutateOperations 数组:
{
"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)。 - 针对同一父级(
AssetGroup或AdGroup)的连续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)操作。
- 一个
从响应中检索可变属性
如果您将 mutate 请求的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]
Ruby
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]; }