Aby zoptymalizować skuteczność, zarządzać zależnościami między operacjami i obsługiwać odpowiedzi podczas modyfikowania zasobów w interfejsie Google Ads API, postępuj zgodnie z tymi sprawdzonymi metodami.
Tymczasowe nazwy zasobów
Zarówno GoogleAdsService.Mutate, jak i
BatchJobService obsługują tymczasowe nazwy zasobów, do których można
się odwoływać w kolejnych operacjach. Dzięki temu możesz utworzyć kampanię oraz powiązane z nią grupy reklam, reklamy i słowa kluczowe w ramach jednego żądania modyfikacji lub zadania wsadowego.
Aby odwołać się do nowo utworzonego zasobu w ramach tego samego żądania modyfikacji lub zadania wsadowego, w polu resource_name nowego zasobu podaj ujemny identyfikator liczby całkowitej (np. -1 lub -2, z wyłączeniem 0). Na przykład podczas tworzenia kampanii w żądaniu zbiorczym ustaw jej nazwę zasobu na customers/CUSTOMER_ID/campaigns/-1.
Podczas tworzenia grupy reklam w późniejszej operacji w ramach tego samego żądania odwołaj się do customers/CUSTOMER_ID/campaigns/-1 jako kampanii nadrzędnej. Interfejs API automatycznie zastępuje -1 rzeczywistym identyfikatorem kampanii wygenerowanym podczas tworzenia.
Ograniczenia użytkowania
Podczas korzystania z tymczasowych nazw zasobów pamiętaj o tych regułach:
- Kolejność ma znaczenie: możesz odwoływać się do tymczasowej nazwy zasobu tylko po jej zdefiniowaniu. Na liście operacji operacja zależna (np. utworzenie grupy reklam) musi występować po operacji, która tworzy jej zasób nadrzędny (np. utworzenie kampanii).
- Zakres pojedynczego żądania lub zadania wsadowego: tymczasowe nazwy zasobów nie są zachowywane w przypadku oddzielnych zadań ani żądań modyfikacji. Aby odwołać się do zasobu utworzonego w poprzednim zadaniu lub żądaniu modyfikacji, użyj jego rzeczywistej nazwy zasobu wygenerowanej przez system.
- Globalna unikalność: w ramach jednego zadania lub żądania modyfikacji każda tymczasowa nazwa zasobu musi używać unikalnej ujemnej liczby całkowitej we wszystkich typach zasobów.
Na przykład nie możesz przypisać
-1zarówno do kampanii, jak i do grupy reklam w tym samym żądaniu. Ponowne użycie tymczasowego identyfikatora w ramach tego samego żądania lub zadania wsadowego spowoduje zwrócenie błęduNewResourceCreationError.DUPLICATE_TEMP_IDS.
Przykładowy ładunek
Załóżmy, że chcesz dodać kampanię, grupę reklam i reklamę w ramach jednego żądania do interfejsu API lub zadania wsadowego. Możesz utworzyć tablicę mutateOperations w ładunku żądania GoogleAdsService.Mutate lub BatchJobService.AddBatchJobOperations, jak pokazano w tym przykładzie JSON w interfejsie REST (inne wymagane pola zasobu zostały pominięte dla zwięzłości):
{
"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"
}
}
}
]
}
Ten przykład pokazuje te kluczowe szczegóły:
- Grupa reklam używa nowego tymczasowego identyfikatora (
-2), ponieważ-1jest już przypisany do kampanii. - Grupa reklam odwołuje się do
customers/CUSTOMER_ID/campaigns/-1, aby połączyć się z kampanią utworzoną w poprzedniej operacji. - Element
adGroupAdOperationodwołuje się docustomers/CUSTOMER_ID/adGroups/-2i pomijaresourceName, ponieważ żadna kolejna operacja w żądaniu nie odwołuje się do nowej reklamy.
Grupowanie operacji tego samego typu
Gdy używasz GoogleAdsService.Mutate, grupuj operacje według
typu zasobu w powtarzającej się tablicy mutate_operations, zachowując zależności między elementami nadrzędnymi
i podrzędnymi. Ta metoda sekwencyjnie odczytuje operacje, dopóki nie napotka innego typu zasobu, a następnie łączy wszystkie sąsiadujące operacje tego samego typu w jedno żądanie usługi backendu.
Jeśli na przykład w powtarzającym się polu mutate_operations umieścisz 5 operacji na kampanii, a następnie 10 operacji na grupie reklam, system wykona 2 wywołania backendu: jedno do CampaignService w przypadku 5 operacji na kampanii, a drugie do AdGroupService w przypadku 10 operacji na grupie reklam.
Z kolei przeplatanie operacji przez ich uporządkowanie w postaci [campaign, ad group,
campaign, ad group] powoduje 4 oddzielne wywołania backendu. Przeplatane wywołania obniżają wydajność interfejsu API i mogą prowadzić do przekroczenia limitu czasu żądania w przypadku dużych partii.
Obsługa częściowych błędów i limitów partii
Domyślnie GoogleAdsService.Mutate wycofuje całe
żądanie, jeśli jakakolwiek operacja się nie powiedzie. Aby zatwierdzić prawidłowe operacje, nawet jeśli
inne operacje w tym samym żądaniu się nie powiodą, ustaw partial_failure na true w
żądaniu i sprawdź partial_failure_error w odpowiedzi. Gdy partial_failure ma wartość true, jeśli operacja nadrzędna, która definiuje tymczasowy identyfikator (np. customers/CUSTOMER_ID/campaigns/-1), nie przejdzie weryfikacji, wszystkie zależne operacje podrzędne odwołujące się do tego tymczasowego identyfikatora w tym samym żądaniu również się nie powiodą z powodu błędu NewResourceCreationError.TEMP_ID_RESOURCE_HAD_ERRORS. Więcej informacji znajdziesz w przewodniku dotyczącym częściowych błędów.
Pamiętaj też o rozmiarze żądania, podziale na partie i ograniczaniu liczby żądań:
- Limity rozmiaru żądania i fragmentu: pojedyncze żądanie
GoogleAdsService.Mutatema limit 10 000 operacji modyfikacji (lub do 20 000, jeśli wszystkie operacje w żądaniu toAdGroupCriterionOperation, a w przypadku przekroczenia limitu zwracany jest błądRequestError.TOO_MANY_MUTATE_OPERATIONS) i co najwyżej 100 operacji działania (RequestError.TOO_MANY_ACTION_OPERATIONS).BatchJobService.AddBatchJobOperationsma limit 10 000 operacji na wywołanie, 10 484 504 bajtów na pojedyncząMutateOperationi 41 937 920 bajtów naAddBatchJobOperationsRequest(w przypadku przekroczenia dowolnego limitu zwracany jest błądBatchJobError.REQUEST_TOO_LARGE, a łączna liczba operacji na zadanie wsadowe wynosi maksymalnie 1 000 000). Jednoczesne modyfikacje dotyczące tej samej kampanii lub konta mogą powodować błędyDatabaseError.CONCURRENT_MODIFICATION. BatchJobServiceniepodzielny podział na partie: chociaż zadania wsadowe są wykonywane zgodnie z semantyką częściowych błędów (domyślnie 1000 operacji na wewnętrzną podgrupę),BatchJobServiceautomatycznie grupuje niektóre sąsiadujące operacje zależne dla tego samego identyfikatora nadrzędnego w niepodzielne podgrupy:- Operacja
AssetGroupOperation(create) poprzedzona sąsiadującymiAssetGroupAssetOperation(create) operacjami dla tego samegoAssetGroupID (łącznie do 1000 operacji, które nie powiodą się niepodzielnie z powodu błęduBatchJobError.ASSET_GROUP_AND_ASSET_GROUP_ASSET_TRANSACTION_FAILURE; każdaAssetGroupOperationupdatelubremovejest wykonywana w osobnej podgrupie z jedną operacją). - Operacja Performance Max
CampaignOperation(create, gdy włączone są wytyczne dotyczące marki – jest to ustawienie domyślne,chyba żebrand_guidelines_enabledma wartośćfalselub ustawionohotel_property_asset_set) poprzedzona sąsiadującymi operacjamiCampaignAssetOperation(create) dla tego samego identyfikatoraCampaign(łącznie do 1000 operacji, które nie powiodą się niepodzielnie z powodu błęduBatchJobError.CAMPAIGN_AND_CAMPAIGN_ASSET_TRANSACTION_FAILURE). - Kolejne
AssetGroupListingGroupFilterOperation(max 10,000, które nie powiodą się niepodzielnie z powodu błęduBatchJobError.ASSET_GROUP_LISTING_GROUP_FILTER_TRANSACTION_FAILURE) lubAdGroupCriterionOperation(listing_group,max 20,000, które nie powiodą się niepodzielnie z powodu błęduCriterionError.LISTING_GROUP_ERROR_IN_ANOTHER_OPERATION) operacje dla tego samego elementu nadrzędnego (AssetGrouplubAdGroup).
- Operacja
Pobieranie atrybutów, które można modyfikować, z odpowiedzi
Jeśli ustawisz response_content_type żądania modyfikacji na
MUTABLE_RESOURCE, odpowiedź będzie zawierać
resource_name i obiekt zasobu wypełniony jego polami mutable (a także kluczowymi polami wypełnionymi przez system w zwracanym zasobie, takimi jak
ExperimentArm.in_design_campaigns) dla każdego obsługiwanego obiektu utworzonego lub
zaktualizowanego (nie usuniętego) przez żądanie. W przypadku operacji remove lub typów zasobów, które nie obsługują zwracania MUTABLE_RESOURCE, odpowiedź zawsze zawiera tylko resource_name. Dzięki tej funkcji nie musisz wysyłać
dodatkowego Search ani SearchStream żądania po każdym wywołaniu modyfikacji.
Jeśli nie ustawisz response_content_type, interfejs Google Ads API domyślnie użyje RESOURCE_NAME_ONLY i zwróci tylko resource_name każdego zmodyfikowanego zasobu.
Ten przykład pokazuje, jak pobrać zasób, który można modyfikować, z wywołania modyfikacji:
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]; }