Aby optymalizować skuteczność, zarządzać zależnościami między operacjami i obsługiwać odpowiedzi podczas modyfikowania zasobów w interfejsie Google Ads API, stosuj te sprawdzone metody.
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. Umożliwia to utworzenie kampanii oraz powiązanych z nią grup reklam, reklam i słów kluczowych 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 wyjątkiem 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łuj się do customers/CUSTOMER_ID/campaigns/-1 jako kampanii nadrzędnej. Interfejs API automatycznie zastępuje symbol -1 faktycznym identyfikatorem kampanii wygenerowanym podczas jej tworzenia.
Ograniczenia użytkowania
Korzystając 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 niepowtarzalność: w ramach jednego zadania lub żądania modyfikacji każdy tymczasowy identyfikator zasobu musi używać unikalnej liczby całkowitej ujemnej we wszystkich typach zasobów.
Nie możesz na przykład przypisać
-1do kampanii i grupy reklam w tym samym żądaniu. Ponowne użycie tymczasowego identyfikatora w ramach tej samej prośby lub zadania wsadowego zwraca błądNewResourceCreationError.DUPLICATE_TEMP_IDS.
Przykładowy ładunek
Załóżmy, że chcesz dodać kampanię, grupę reklam i reklamę w ramach jednego żądania API lub zadania wsadowego. Tablicę mutateOperations możesz ustrukturyzować w treści żądania GoogleAdsService.Mutate lub BatchJobService.AddBatchJobOperations, jak pokazano w tym przykładzie JSON 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ż identyfikator-1jest już przypisany do kampanii. - Grupa reklam odwołuje się do wartości
customers/CUSTOMER_ID/campaigns/-1, aby połączyć się z kampanią utworzoną w poprzedniej operacji. 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 odczytuje operacje sekwencyjnie, 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 np. w powtarzanym polu mutate_operations umieścisz 5 operacji dotyczących kampanii, a potem 10 operacji dotyczących grup reklam, system wykona 2 wywołania backendu: jedno do CampaignService w przypadku 5 operacji dotyczących kampanii, a drugie do AdGroupService w przypadku 10 operacji dotyczących grup reklam.
Z kolei przeplatanie operacji przez uporządkowanie ich w postaci [campaign, ad group,
campaign, ad group] powoduje wykonanie 4 osobnych wywołań backendu. Przeplatane wywołania obniżają wydajność interfejsu API i mogą powodować przekroczenie limitu czasu żądania w przypadku dużych partii.
Obsługa częściowych niepowodzeń i limitów pakietów
Domyślnie GoogleAdsService.Mutate wycofuje całe żądanie, jeśli nie powiedzie się jakakolwiek operacja. Aby zatwierdzić prawidłowe operacje nawet wtedy, gdy inne operacje w tym samym żądaniu zakończą się niepowodzeniem, ustaw w żądaniu wartość partial_failure na true i sprawdź partial_failure_error w odpowiedzi. Gdy wartość parametru
partial_failure to 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, które odwołują się do tego tymczasowego identyfikatora w tym samym żądaniu, również nie przejdą weryfikacji i zwrócą błąd NewResourceCreationError.TEMP_ID_RESOURCE_HAD_ERRORS. Więcej informacji znajdziesz w przewodniku po częściowych awariach.
Pamiętaj też o rozmiarze żądania, podziale na mniejsze partie i limitach częstotliwości:
- Limity rozmiaru żądania i fragmentu: pojedyncze żądanie
GoogleAdsService.Mutatewymusza limit 10 000 operacji mutacji (lub do 20 000, jeśli wszystkie operacje w żądaniu sąAdGroupCriterionOperation, zwracającRequestError.TOO_MANY_MUTATE_OPERATIONSw przypadku przekroczenia limitu) i maksymalnie 100 operacji działania (RequestError.TOO_MANY_ACTION_OPERATIONS).BatchJobService.AddBatchJobOperationswymusza maksymalnie 10 000 operacji na wywołanie, 10 484 504 bajty na pojedynczyMutateOperationi 41 937 920 bajtów naAddBatchJobOperationsRequest(zwracającBatchJobError.REQUEST_TOO_LARGEw przypadku przekroczenia dowolnego limitu, przy czym łączna liczba operacji w zadaniu wsadowym może wynosić do 1 000 000). Jednoczesne zmiany skierowane na tę samą kampanię lub konto mogą powodować błędyDatabaseError.CONCURRENT_MODIFICATION. BatchJobServiceniepodzielne dzielenie na mniejsze partie: chociaż zadania wsadowe są wykonywane w ramach semantyki częściowej awarii (domyślnie 1000 operacji na wewnętrzną mniejszą partię),BatchJobServiceautomatycznie grupuje niektóre sąsiadujące operacje zależne o tym samym identyfikatorze nadrzędnym w niepodzielne mniejsze partie:- Operacja
AssetGroupOperation(create) z kolejnymi operacjamiAssetGroupAssetOperation(create) dla tego samego identyfikatoraAssetGroup(maksymalnie 1000 operacji łącznie, nieudane operacje są wycofywane w całości);BatchJobError.ASSET_GROUP_AND_ASSET_GROUP_ASSET_TRANSACTION_FAILUREkażda operacjaAssetGroupOperationupdatelubremovejest wykonywana w samodzielnej podgrupie z jedną operacją. - Kampania Performance Max
CampaignOperation(create, gdy włączone są wytyczne dotyczące marki, co jest ustawieniem domyślnym, chyba że parametrbrand_guidelines_enabledma wartośćfalselub parametrhotel_property_asset_setjest ustawiony), a następnie ciągłe operacjeCampaignAssetOperation(create) dla tego samego identyfikatoraCampaign(maksymalnie 1000 operacji łącznie, w przypadku niepowodzenia wszystkie operacje są wycofywane z użyciem koduBatchJobError.CAMPAIGN_AND_CAMPAIGN_ASSET_TRANSACTION_FAILURE). - Kolejne operacje
AssetGroupListingGroupFilterOperation(max 10,000, nieudane atomowo zBatchJobError.ASSET_GROUP_LISTING_GROUP_FILTER_TRANSACTION_FAILURE) lubAdGroupCriterionOperation(listing_group,max 20,000, nieudane atomowo zCriterionError.LISTING_GROUP_ERROR_IN_ANOTHER_OPERATION) dla tego samego elementu nadrzędnego (AssetGrouplubAdGroup).
- Operacja
Pobieranie z odpowiedzi atrybutów, które można zmieniać
Jeśli w żądaniu modyfikacji ustawisz wartość response_content_type na MUTABLE_RESOURCE, odpowiedź będzie zawierać resource_name i obiekt zasobu wypełniony jego polami zmiennymi (a także kluczowymi polami wypełnionymi przez system w zwróconym zasobie, np. 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 zwraca tylko resource_name. Użyj tej funkcji, aby uniknąć wysyłania dodatkowego żądania Search lub SearchStream po każdym wywołaniu mutate.
Jeśli nie ustawisz parametru response_content_type, interfejs Google Ads API domyślnie przyjmie wartość RESOURCE_NAME_ONLY i zwróci tylko resource_name każdego zmodyfikowanego zasobu.
Poniższy przykład pokazuje, jak pobrać zasób podlegający zmianom z wywołania mutate:
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]; }