أفضل الممارسات المتعلّقة بتعديل البيانات

اتّبِع أفضل الممارسات التالية لتحسين الأداء وإدارة التبعيات بين العمليات والتعامل مع الردود عند تعديل الموارد في Google Ads API.

أسماء الموارد المؤقتة

يتيح كل من GoogleAdsService.Mutate وBatchJobService استخدام أسماء موارد مؤقتة يمكن الرجوع إليها في العمليات اللاحقة. يتيح لك ذلك إنشاء حملة ومجموعاتها الإعلانية وإعلاناتها وكلماتها الرئيسية المرتبطة بها في طلب تغيير واحد أو مهمة مجمّعة.

للإشارة إلى مورد تم إنشاؤه حديثًا ضمن طلب تعديل أو مهمة مجمّعة نفسها، حدِّد رقم تعريف عدد صحيح سالبًا (مثل -1 أو -2، باستثناء 0) في حقل resource_name الخاص بالمرجع الجديد. على سبيل المثال، عند إنشاء حملة في طلب مجمّع، اضبط اسم المورد على customers/CUSTOMER_ID/campaigns/-1. عند إنشاء مجموعة إعلانية في عملية لاحقة ضمن الطلب نفسه، استخدِم customers/CUSTOMER_ID/campaigns/-1 كحملة رئيسية. تستبدل واجهة برمجة التطبيقات تلقائيًا -1 بمعرّف الحملة الفعلي الذي يتم إنشاؤه عند الإنشاء.

قيود الاستخدام

يجب مراعاة القواعد التالية عند استخدام أسماء الموارد المؤقتة:

  • الترتيب مهم: لا يمكنك الإشارة إلى اسم مورد مؤقت إلا بعد تعريفه. في قائمة العمليات، يجب أن تظهر العملية التابعة (مثل إنشاء مجموعة إعلانية) بعد العملية التي تنشئ المورد الرئيسي (مثل إنشاء حملة).
  • نطاق الطلب الفردي أو مهمة الدفعات: لا تبقى أسماء الموارد المؤقتة متاحة في مهام منفصلة أو طلبات تعديل. للإشارة إلى مرجع تم إنشاؤه في مهمة سابقة أو طلب تغيير، استخدِم اسم المرجع الفعلي الذي أنشأه النظام.
  • التفرّد على مستوى العالم: ضمن مهمة واحدة أو طلب تغيير، يجب أن يستخدم كل اسم مورد مؤقت عددًا صحيحًا سالبًا فريدًا على مستوى جميع أنواع الموارد. على سبيل المثال، لا يمكنك تعيين -1 لكلّ من الحملة والمجموعة الإعلانية في الطلب نفسه. تؤدي إعادة استخدام معرّف مؤقت ضمن الطلب نفسه أو مهمة الدفعة إلى عرض الخطأ NewResourceCreationError.DUPLICATE_TEMP_IDS.

مثال على الحمولة

لنفترض أنّك تريد إضافة حملة ومجموعة إعلانية وإعلان في طلب واحد من واجهة برمجة التطبيقات أو مهمة مجمّعة. يمكنك تنظيم مصفوفة mutateOperations في حمولة طلب GoogleAdsService.Mutate أو BatchJobService.AddBatchJobOperations كما هو موضّح في مثال JSON التالي الخاص بـ REST (مع حذف حقول الموارد الأخرى المطلوبة للاختصار):

{
  "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"
        }
      }
    }
  ]
}

يوضّح هذا المثال التفاصيل الرئيسية التالية:

  • تستخدِم المجموعة الإعلانية رقم تعريف مؤقتًا جديدًا (-2) لأنّ -1 سبق أن تم تعيينه للحملة.
  • تشير المجموعة الإعلانية إلى customers/CUSTOMER_ID/campaigns/-1 لربط نفسها بالحملة التي تم إنشاؤها في العملية السابقة.
  • يشير adGroupAdOperation إلى customers/CUSTOMER_ID/adGroups/-2 ويحذف resourceName لأنّ أي عملية لاحقة في الطلب تشير إلى الإعلان الجديد.

تجميع العمليات من النوع نفسه

عند استخدام GoogleAdsService.Mutate، يجب تجميع العمليات معًا حسب نوع المورد في مصفوفة mutate_operations المتكررة مع مراعاة التبعيات بين العناصر الرئيسية والعناصر الفرعية. تقرأ هذه الطريقة العمليات بالتسلسل إلى أن تصادف نوعًا مختلفًا من الموارد، ثم تجمع كل العمليات المتجاورة من النوع نفسه في طلب واحد من خدمة الخلفية.

على سبيل المثال، إذا أدرجت 5 عمليات على مستوى الحملة متبوعة بـ 10 عمليات على مستوى المجموعة الإعلانية في الحقل المتكرّر mutate_operations، سيُجري النظام استدعاءَين في الخلفية: أحدهما إلى CampaignService للعمليات الخمس على مستوى الحملة، والآخر إلى AdGroupService للعمليات العشر على مستوى المجموعة الإعلانية.

في المقابل، يؤدي ترتيب العمليات على النحو التالي [campaign, ad group, campaign, ad group] إلى إجراء أربع طلبات منفصلة من الخلفية. تؤدي الطلبات المتداخلة إلى تدهور أداء واجهة برمجة التطبيقات، وقد تؤدي إلى انتهاء مهلة الطلبات في الدفعات الكبيرة.

التعامل مع حالات الفشل الجزئي وحدود الدفعات

بشكلٍ تلقائي، تتراجع عملية GoogleAdsService.Mutate عن الطلب بأكمله في حال تعذُّر تنفيذ أي عملية فردية. لتنفيذ عمليات صالحة حتى في حال تعذُّر تنفيذ عمليات أخرى في الطلب نفسه، اضبط partial_failure على true في الطلب وافحص partial_failure_error في الردّ. عندما تكون قيمة partial_failure هي true، إذا تعذّر التحقّق من صحة عملية رئيسية تحدّد معرّفًا مؤقتًا (مثل customers/CUSTOMER_ID/campaigns/-1)، ستتعذّر أيضًا أي عمليات فرعية تابعة تشير إلى هذا المعرّف المؤقت في الطلب نفسه، وسيظهر الخطأ NewResourceCreationError.TEMP_ID_RESOURCE_HAD_ERRORS. لمزيد من التفاصيل، يُرجى الاطّلاع على دليل حالات الفشل الجزئي.

يجب أيضًا مراعاة حجم الطلب والتقسيم إلى دفعات فرعية وحدود المعدّل:

  • حدود حجم الطلب والتقسيم: يفرض طلب GoogleAdsService.Mutate واحد حدًا يبلغ 10,000 عملية تغيير (أو ما يصل إلى 20,000 عملية عندما تكون جميع العمليات في الطلب هي AdGroupCriterionOperation، مع عرض RequestError.TOO_MANY_MUTATE_OPERATIONS في حال تجاوز الحد) و100 عملية إجراء على الأكثر (RequestError.TOO_MANY_ACTION_OPERATIONS). يفرض BatchJobService.AddBatchJobOperations حدًا أقصى يبلغ 10,000 عملية لكل مكالمة، و10,484,504 بايت لكل MutateOperation فردي، و41,937,920 بايت لكل AddBatchJobOperationsRequest (مع عرض BatchJobError.REQUEST_TOO_LARGE في حال تجاوز أي حد، مع ما يصل إلى 1,000,000 عملية إجمالية لكل مهمة مجمّعة). يمكن أن تؤدي التغييرات المتزامنة التي تستهدف الحملة أو الحساب نفسهما إلى حدوث أخطاء DatabaseError.CONCURRENT_MODIFICATION.
  • BatchJobService التقسيم إلى دفعات فرعية ذرية: على الرغم من أنّ مهام الدفعات يتم تنفيذها ضمن دلالات الفشل الجزئي (مع ضبط القيمة التلقائية على 1,000 عملية لكل دفعة فرعية داخلية)، BatchJobService يتم تلقائيًا تجميع عمليات متجاورة معيّنة تعتمد على بعضها البعض لمعرّف العنصر الأصلي نفسه في دفعات فرعية ذرية:
    • AssetGroupOperation (create) متبوعًا بعمليات AssetGroupAssetOperation (create) متجاورة لمعرّف AssetGroup نفسه (ما يصل إلى 1,000 عملية إجمالاً، مع تعذُّر التنفيذ بشكل ذري باستخدام BatchJobError.ASSET_GROUP_AND_ASSET_GROUP_ASSET_TRANSACTION_FAILURE؛ يتم تنفيذ كل AssetGroupOperation update أو remove في مجموعة فرعية مستقلة من عملية واحدة).
    • CampaignOperation في "حملات الأداء الأفضل" (create، عندما تكون "إرشادات العلامة التجارية" مفعّلة، وهو الإعداد التلقائي ما لم يتم ضبط brand_guidelines_enabled على false أو ضبط hotel_property_asset_set) متبوعًا بعمليات CampaignAssetOperation (create) متجاورة لمعرّف Campaign نفسه (ما يصل إلى 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 لكل مورد تم تعديله.

يوضّح المثال التالي كيفية استرداد مورد قابل للتعديل من خلال طلب تعديل:

جافا

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];
}
      

curl