广告系列草稿和实验

您是否想过:“如果我更改广告的着陆页,会不会获得更多转化?”又或者,“如果我改变广告措辞会怎么样?会对提高流量有帮助么?”通过设置测试分离出相应的特定变量可能是件麻烦事,但如果您使用广告系列草稿和实验,系统将替您完成所有繁杂工作(复制数据、设置等)。

使用 DraftServiceTrialService,可以轻松快捷地设置新的广告系列实验。以下是您设置实验所需遵循的步骤:

  1. 利用现有的基本广告系列创建草稿。草稿是现有广告系列的镜像,不会投放广告。
  2. 修改广告系列草稿使其符合您的实验需要。
  3. 利用草稿创建试验。试验让您可以使用新的试验广告系列(一开始是广告系列草稿的一个副本),并在试验广告系列和基本广告系列之间分配流量。这些试验广告系列又称为实验。
  4. 比较试验广告系列和基本广告系列的统计信息,以了解哪个广告系列的效果更好。

上述步骤代表最常用的工作流程,不过 DraftService 和 TrialService 非常灵活,还有其他一些使用方式。例如,您可以使用草稿暂时存放对基本广告系列做出的更改,然后将这些更改整合到基本广告系列中,甚至不需要使用试验。或者,如果您确实使用了某个试验,并且对试验效果感到满意,您可以选择将它的属性推广到基本广告系列中,或将该试验升级为完整的广告系列,与基本广告系列一同使用。

以下流程图显示了您在使用广告系列草稿和实验时可采用的工作流程:

草稿

草稿是一个对象,用于维持广告系列草稿与基本广告系列之间的关联。您可以通过 DraftService 创建草稿,只要提供要用作基本广告系列的现有广告系列的 ID 即可。DraftService 会自动创建新的广告系列草稿,并将其与草稿关联。“草稿”对象本身不是广告系列草稿,只是将广告系列草稿与基本广告系列关联到一起。广告系列草稿作为一个实际的“广告系列”对象而存在。广告系列草稿具有 campaignId,也就是草稿的 draftCampaignId。您可以修改广告系列草稿,就像修改真实的广告系列一样,比如修改它的条件、广告组、出价和广告。不过,广告系列草稿不会投放广告。

现有广告系列必须满足若干要求,才能用作草稿的基本广告系列。它必须是“搜索网络”或“搜索网络和精选展示广告网络”广告系列,并且没有共享预算(该广告系列预算的 isExplicitlyShared 值为 false)。尽管实验支持广告系列的大部分功能,但也存在几种例外情况

创建草稿

要创建草稿,需设置 baseCampaignId 并为草稿取名。请注意,您帐户中所有草稿的名称都必须是唯一的。以下是一个 Ruby 代码示例:

Java

// Get the DraftService.
DraftServiceInterface draftService = adWordsServices.get(session, DraftServiceInterface.class);
Draft draft = new Draft();
draft.setBaseCampaignId(baseCampaignId);
draft.setDraftName("Test Draft #" + System.currentTimeMillis());

DraftOperation draftOperation = new DraftOperation();
draftOperation.setOperator(Operator.ADD);
draftOperation.setOperand(draft);

draft = draftService.mutate(new DraftOperation[] {draftOperation}).getValue(0);

VB

Dim draft As New Draft()
draft.baseCampaignId = baseCampaignId
draft.draftName = "Test Draft #" + ExampleUtilities.GetRandomString()

Dim draftOperation As New DraftOperation()
draftOperation.operator = [Operator].ADD
draftOperation.operand = draft

C#

Draft draft = new Draft() {
  baseCampaignId = baseCampaignId,
  draftName = "Test Draft #" + ExampleUtilities.GetRandomString()
};

DraftOperation draftOperation = new DraftOperation() {
  @operator = Operator.ADD,
  operand = draft
};

PHP

// Get the DraftService, which loads the required classes.
$draftService = $user->GetService('DraftService', ADWORDS_VERSION);

// Create a draft.
$draft = new Draft();
$draft->baseCampaignId = $baseCampaignId;
$draft->draftName = 'Test Draft #' . uniqid();

// Create an operation.
$operation = new DraftOperation();
$operation->operand = $draft;
$operation->operator = 'ADD';
$operations[] = $operation;

// Make the mutate request.
$result = $draftService->mutate($operations);
$draft = $result->value[0];

Perl

my $draft = Google::Ads::AdWords::v201609::Draft->new({
    baseCampaignId => $base_campaign_id,
    draftName      => sprintf("Test Draft #%s", uniqid())});

# Create operation.
my $draft_operation = Google::Ads::AdWords::v201609::DraftOperation->new({
    operator => "ADD",
    operand  => $draft
});

Python

draft_service = client.GetService('DraftService', version='v201609')

draft = {
    'baseCampaignId': base_campaign_id,
    'draftName': 'Test Draft #%s' % uuid.uuid4()
}

draft_operation = {'operator': 'ADD', 'operand': draft}
draft = draft_service.mutate([draft_operation])['value'][0]
draft_campaign_id = draft['draftCampaignId']

Ruby

draft_srv = adwords.service(:DraftService, API_VERSION)

draft = {
  :base_campaign_id => base_campaign_id,
  :draft_name => 'Test Draft #%d' % (Time.new.to_f * 1000).to_i
}
draft_operation = {:operator => 'ADD', :operand => draft}

draft_result = draft_srv.mutate([draft_operation])

draft = draft_result[:value].first
draft_id = draft[:draft_id]
draft_campaign_id = draft[:draft_campaign_id]

草稿中包含 draftCampaignIddraftIdbaseCampaignId 等详细信息,所有这些信息都很重要。draftCampaignId 用于修改广告系列草稿及其广告组、条件和广告。draftIdbaseCampaignId 用于在创建试验或推广草稿时对草稿进行引用。

对广告系列草稿进行定制

draftCampaignId 字段可作为真实的广告系列 ID 来使用,即可以提供给任何需要提供此类 ID 的服务(例如 CampaignService、AdGroupService、CampaignCriterionService 等)。例如,要向广告系列添加新的语言条件,只需使用从草稿中获取的 draftCampaignId 作为 campaignId

Java

CampaignCriterionServiceInterface campaignCriterionService =
    adWordsServices.get(session, CampaignCriterionServiceInterface.class);

Language language = new Language();
language.setId(1003L); // Spanish

// Make sure to use the draftCampaignId when modifying the virtual draft campaign.
CampaignCriterion campaignCriterion = new CampaignCriterion();
campaignCriterion.setCampaignId(draft.getDraftCampaignId());
campaignCriterion.setCriterion(language);

CampaignCriterionOperation criterionOperation = new CampaignCriterionOperation();
criterionOperation.setOperator(Operator.ADD);
criterionOperation.setOperand(campaignCriterion);

campaignCriterion =
    campaignCriterionService
        .mutate(new CampaignCriterionOperation[] {criterionOperation})
        .getValue(0);

VB

campaign_criterion_srv =
    adwords.service(:CampaignCriterionService, API_VERSION)

criterion = {
  :xsi_type => 'Language',
  :id => 1003 # Spanish
}

criterion_operation = {
  # Make sure to use the draft_campaign_id when modifying the virtual draft
  # campaign.
  :operator => 'ADD',
  :operand => {
    :campaign_id => draft_campaign_id,
    :criterion => criterion
  }
}

criterion_result = campaign_criterion_srv.mutate([criterion_operation])

C#

Language language = new Language() {
  id = 1003L // Spanish
};

// Make sure to use the draftCampaignId when modifying the virtual draft
// campaign.
CampaignCriterion campaignCriterion = new CampaignCriterion() {
  campaignId = draft.draftCampaignId,
  criterion = language
};

CampaignCriterionOperation criterionOperation = new CampaignCriterionOperation() {
  @operator = Operator.ADD,
  operand = campaignCriterion
};

campaignCriterion = campaignCriterionService.mutate(
    new CampaignCriterionOperation[] {criterionOperation}).value[0];

PHP

$campaignCriterionService =
    $user->GetService('CampaignCriterionService', ADWORDS_VERSION);

// Create a criterion.
$language = new Language();
$language->id = 1003; // Spanish
$campaignCriterion = new CampaignCriterion();
$campaignCriterion->campaignId = $draft->draftCampaignId;
$campaignCriterion->criterion = $language;

// Create an operation.
$operations = array();
$operation = new CampaignCriterionOperation();
$operation->operand = $campaignCriterion;
$operation->operator = 'ADD';
$operations[] = $operation;

// Make the mutate request.
$result = $campaignCriterionService->mutate($operations);
$campaignCriterion = $result->value[0];

Perl

my $criterion = Google::Ads::AdWords::v201609::Language->new({
    id => 1003    # Spanish
});

my $operation =
  Google::Ads::AdWords::v201609::CampaignCriterionOperation->new({
    operator => "ADD",
    operand  => Google::Ads::AdWords::v201609::CampaignCriterion->new({
        campaignId => $draft_campaign_id,
        criterion  => $criterion
      })});

$result =
  $client->CampaignCriterionService()->mutate({operations => [$operation]});

$criterion = $result->get_value()->[0];

Python

campaign_criterion_service = client.GetService('CampaignCriterionService',
                                               version='v201609')

criterion = {
    'xsi_type': 'Language',
    'id': 1003  # Spanish
}

criterion_operation = {
    # Make sure to use the draftCampaignId when modifying the virtual draft
    # campaign.
    'operator': 'ADD',
    'operand': {
        'campaignId': draft_campaign_id,
        'criterion': criterion
    }
}

criterion = campaign_criterion_service.mutate([criterion_operation])[
    'value'][0]

Ruby

campaign_criterion_srv =
    adwords.service(:CampaignCriterionService, API_VERSION)

criterion = {
  :xsi_type => 'Language',
  :id => 1003 # Spanish
}

criterion_operation = {
  # Make sure to use the draft_campaign_id when modifying the virtual draft
  # campaign.
  :operator => 'ADD',
  :operand => {
    :campaign_id => draft_campaign_id,
    :criterion => criterion
  }
}

criterion_result = campaign_criterion_srv.mutate([criterion_operation])

对于广告系列草稿中的广告组,也可以执行类似操作。例如,您可以使用过滤条件从广告系列草稿中提取广告组,如下所示:

Java

// Get the AdGroupService.
AdGroupServiceInterface adGroupService =
    adWordsServices.get(session, AdGroupServiceInterface.class);

// Create a selector that limits to ad groups in the draft campaign.
Selector selector =
    new SelectorBuilder()
        .fields(AdGroupField.Id)
        .equals(AdGroupField.CampaignId, Long.toString(draftCampaignId))
        .limit(100)
        .build();

// Make a 'get' request.
AdGroupPage adGroupPage = adGroupService.get(selector);

// Display the results.
if (adGroupPage.getEntries() != null && adGroupPage.getEntries().length > 0) {
  System.out.printf(
      "Found %d of %d ad groups.%n",
      adGroupPage.getEntries().length, adGroupPage.getTotalNumEntries());
} else {
  System.out.println("No ad groups found.");
}

VB

' Get the AdGroupService.
Dim adGroupService As AdGroupService = CType(user.GetService( _
    AdWordsService.v201609.AdGroupService), AdGroupService)

' Create a selector that limits to ad groups in the draft campaign.
Dim selector As New Selector
selector.fields = New String() {
  AdGroup.Fields.Id
}
selector.predicates = New Predicate() {
  Predicate.Equals(AdGroup.Fields.CampaignId, draftCampaignId)
}
selector.paging = Paging.Default

Dim page As AdGroupPage = adGroupService.get(selector)

' Display the results.
If ((Not page Is Nothing) AndAlso (Not page.entries Is Nothing)) Then
  Console.WriteLine("Fetched {0} of {1} ad groups.", page.entries.Length,
      page.totalNumEntries)
End If

PHP

// Get the AdGroupService.
$adGroupService = $user->GetService('AdGroupService', ADWORDS_VERSION);

// Create selector.
$selector = new Selector();
$selector->fields = array('Id');

// Create predicates.
$selector->predicates[] =
    new Predicate('CampaignId', 'EQUALS', $draftCampaignId);

// Create paging controls.
$selector->paging = new Paging(0, AdWordsConstants::RECOMMENDED_PAGE_SIZE);

// Make the get request.
$page = $adGroupService->get($selector);

// Display results.
if ($page->totalNumEntries !== null && $page->totalNumEntries > 0) {
  printf("Found %d ad groups.\n", $page->totalNumEntries);
} else {
  print "No ad groups were found.\n";
}

Perl

# Create predicates.
my $campaign_predicate = Google::Ads::AdWords::v201609::Predicate->new({
    field    => "CampaignId",
    operator => "EQUALS",
    values   => [$draft_campaign_id]});

# Create selector.
my $paging = Google::Ads::AdWords::v201609::Paging->new({
  startIndex    => 0,
  numberResults => PAGE_SIZE
});
my $selector = Google::Ads::AdWords::v201609::Selector->new({
  fields     => ["Id"],
  predicates => [$campaign_predicate],
  paging     => $paging
});

my $ad_group_page = $client->AdGroupService()->get({
  serviceSelector => $selector
});
if ($ad_group_page->get_entries()) {
  printf(
    "Found %d of %d ad groups.\n",
    scalar(@{$ad_group_page->get_entries()}),
    $ad_group_page->get_totalNumEntries());
} else {
  printf("No ad groups found.\n");
}

Python

ad_group_service = client.GetService('AdGroupService', version='v201609')

selector = {
    'fields': ['Id'],
    'paging': {
        'startIndex': str(0),
        'numberResults': str(PAGE_SIZE)
    },
    'predicates': [{
        'field': 'CampaignId',
        'operator': 'IN',
        'values': [draft_campaign_id]
    }]
}

response = ad_group_service.get(selector)
draft_ad_groups = response['entries'] if 'entries' in response else []

Ruby

ad_group_srv = adwords.service(:AdGroupService, API_VERSION)

selector = {
  :fields => ['Id'],
  :predicates => [{
    :field => 'CampaignId',
    :operator => 'IN',
    :values => [draft_campaign_id]
  }],
  :paging => {
    :start_index => 0,
    :number_results => 100
  }
}

ad_group_page = ad_group_srv.get(selector)

unless ad_group_page[:entries].nil?
  puts "Found %d of %d ad groups." %
      [ad_group_page[:entries].size, ad_group_page[:total_num_entries]]
else
  puts "No ad groups found."
end

对广告系列草稿的广告政策检查和对常规广告系列一样。添加的广告草稿不会导致在广告投放之后才会发生的政策违规行为,但这种违规行为会在您使用该草稿进行试验时导致错误。

在您完成对广告系列草稿的更改后,您有两个选择:

  1. 将您在广告系列草稿中做出的更改直接推广到基本广告系列。
  2. 制作试验广告系列,与基本广告系列同时投放。

推广广告系列草稿

您可以使用草稿暂时存放真实广告系列的待更改项,甚至不需要创建试验。将所有更改都暂时存放在广告系列草稿中后,只需推广该草稿,即可将所有更改都应用到基本广告系列。要推广草稿,只需提交一个将草稿状态设置为 PROMOTING 的 mutate 操作。由于这涉及到将广告系列草稿中的所有更改都复制到基本广告系列中,因此草稿的推广是异步操作,并且不可撤消。设置为上述状态即开始这一异步过程。

您可以使用草稿的 draftIdbaseCampaignId 对其进行轮询,以监测状态字段。当状态不再是 PROMOTING 时,说明上述异步操作已结束。 PROMOTED 表示操作成功;PROMOTE_FAILED 表示发生错误。我们将在试验部分详细介绍轮询架构。

如果在尝试推广草稿时发生错误,您可以通过将 draftIdbaseCampaignId 提供给 DraftAsyncErrorService 来获取具体错误的更多详情。请参阅错误部分的示例。

广告系列草稿很容易识别,因为它们的 campaignTrialTypeDRAFT。您还可以使用 baseCampaignId 字段查找广告系列草稿所对应的基本广告系列 ID。对于常规广告系列(不是通过草稿或试验创建的广告系列),campaignTrialTypeBASEbaseCampaignId 字段中是该广告系列自己的 ID。

默认情况下,草稿实体不会包含在 CampaignService 和 AdGroupService 等其他服务的 get 结果中。要提取草稿实体,比如广告系列草稿或广告组草稿,您需要将谓词中的 campaignTrialType 明确指定为 DRAFT,或者按照已知草稿实体的 ID(例如 draftCampaignId)进行过滤。

试验

试验是一种实体,可帮助您管理与基本广告系列一起投放的试验广告系列,它会占用一部分流量和预算,以测试您在草稿中所做的设置。试验会产生真实的试验广告系列(又称为实验),它们就像常规广告系列一样投放广告。它与基本广告系列分开收集统计信息;但是,试验中的计数仍需遵循帐户限制,比如每次最多可使用的广告系列、广告组等的数量。

创建试验

要创建试验,您需要提供 draftIdbaseCampaignId(作为您要进行试验的草稿的唯一标识)、一个唯一名称,以及您想要分配给试验的流量百分比。创建试验时,将自动创建一个新关联的试验广告系列。

与草稿不同,试验由其 ID 唯一标识,因此在创建了试验后,您无需使用 baseCampaignId 来引用该试验。

Java

// Get the TrialService.
TrialServiceInterface trialService = adWordsServices.get(session, TrialServiceInterface.class);

Trial trial = new Trial();
trial.setDraftId(draftId);
trial.setBaseCampaignId(baseCampaignId);
trial.setName("Test Trial #" + System.currentTimeMillis());
trial.setTrafficSplitPercent(50);

TrialOperation trialOperation = new TrialOperation();
trialOperation.setOperator(Operator.ADD);
trialOperation.setOperand(trial);

long trialId = trialService.mutate(new TrialOperation[] {trialOperation}).getValue(0).getId();

VB

Dim newTrial As New Trial
newTrial.draftId = draftId
newTrial.baseCampaignId = baseCampaignId
newTrial.name = "Test Trial #" & ExampleUtilities.GetRandomString()
newTrial.trafficSplitPercent = 50

Dim trialOperation As New TrialOperation()
trialOperation.operator = [Operator].ADD
trialOperation.operand = newTrial

C#

Trial trial = new Trial() {
  draftId = draftId,
  baseCampaignId = baseCampaignId,
  name = "Test Trial #" + ExampleUtilities.GetRandomString(),
  trafficSplitPercent = 50
};

TrialOperation trialOperation = new TrialOperation() {
  @operator = Operator.ADD,
  operand = trial
};

PHP

// Get the TrialService, which loads the required classes.
$trialService = $user->GetService('TrialService', ADWORDS_VERSION);
$trialAsynErrorService =
    $user->GetService('TrialAsyncErrorService', ADWORDS_VERSION);

// Create a trial.
$trial = new Trial();
$trial->draftId = $draftId;
$trial->baseCampaignId = $baseCampaignId;
$trial->name = 'Test Trial #' . uniqid();
$trial->trafficSplitPercent = 50;

// Create an operation.
$operation = new TrialOperation();
$operation->operand = $trial;
$operation->operator = 'ADD';
$operations[] = $operation;

// Make the mutate request.
$result = $trialService->mutate($operations);
$trial = $result->value[0];

Perl

my $trial = Google::Ads::AdWords::v201609::Trial->new({
    draftId             => $draft_id,
    baseCampaignId      => $base_campaign_id,
    name                => sprintf("Test Trial #%s", uniqid()),
    trafficSplitPercent => 50,
});

# Create operation.
my $trial_operation = Google::Ads::AdWords::v201609::TrialOperation->new({
    operator => "ADD",
    operand  => $trial
});

# Add trial.
my $result =
  $client->TrialService()->mutate({operations => [$trial_operation]});

Python

trial_service = client.GetService('TrialService', version='v201609')
trial_async_error_service = client.GetService('TrialAsyncErrorService',
                                              version='v201609')

trial = {
    'draftId': draft_id,
    'baseCampaignId': base_campaign_id,
    'name': 'Test Trial #%d' % uuid.uuid4(),
    'trafficSplitPercent': 50
}

trial_operation = {'operator': 'ADD', 'operand': trial}
trial_id = trial_service.mutate([trial_operation])['value'][0]['id']

Ruby

trial_srv = adwords.service(:TrialService, API_VERSION)
trial_async_error_srv = adwords.service(:TrialAsyncErrorService, API_VERSION)

trial = {
  :draft_id => draft_id,
  :base_campaign_id => base_campaign_id,
  :name => 'Test Trial #%d' % (Time.new.to_f * 1000).to_i,
  :traffic_split_percent => 50
}
trial_operation = {:operator => 'ADD', :operand => trial}

trial_result = trial_srv.mutate([trial_operation])

trial_id = trial_result[:value].first[:id]

和创建草稿时一样,请记下试验的 trialCampaignId,以备在今后的操作中使用。

创建试验是一个异步操作(与创建草稿不同)。新试验的状态为 CREATING。您需要进行轮询,直到它变成其他某种状态,然后才能继续:

Java

Selector trialSelector =
    new SelectorBuilder()
        .fields(
            TrialField.Id,
            TrialField.Status,
            TrialField.BaseCampaignId,
            TrialField.TrialCampaignId)
        .equalsId(trialId)
        .build();

trial = null;
boolean isPending = true;
int pollAttempts = 0;
do {
  long sleepSeconds = (long) Math.scalb(30d, pollAttempts);
  System.out.printf("Sleeping for %d seconds.%n", sleepSeconds);
  Thread.sleep(sleepSeconds * 1000);
  trial = trialService.get(trialSelector).getEntries(0);

  System.out.printf("Trial ID %d has status '%s'.%n", trial.getId(), trial.getStatus());
  pollAttempts++;
  isPending = TrialStatus.CREATING.equals(trial.getStatus());
} while (isPending && pollAttempts < MAX_POLL_ATTEMPTS);

VB

' Since creating a trial is asynchronous, we have to poll it to wait
' for it to finish.
Dim trialSelector As New Selector()
trialSelector.fields = New string() {
    Trial.Fields.Id, Trial.Fields.Status, Trial.Fields.BaseCampaignId,
    Trial.Fields.TrialCampaignId
}
trialSelector.predicates = New Predicate() {
    Predicate.Equals(Trial.Fields.Id, trialId)
}
newTrial = Nothing
Dim isPending As Boolean = true
Dim pollAttempts As Integer = 0

Do
  Dim sleepMillis As Integer = CType(Math.Pow(2, pollAttempts) * _
      POLL_INTERVAL_SECONDS_BASE * 1000, Integer)
  Console.WriteLine("Sleeping {0} millis...", sleepMillis)
  Thread.Sleep(sleepMillis)

  newTrial = trialService.get(trialSelector).entries(0)

  Console.WriteLine("Trial ID {0} has status '{1}'.", newTrial.id, newTrial.status)
  pollAttempts = pollAttempts + 1
  isPending = (newTrial.status = TrialStatus.CREATING)
Loop while isPending AndAlso (pollAttempts <= MAX_RETRIES)

If newTrial.status = TrialStatus.ACTIVE Then
  ' The trial creation was successful.
  Console.WriteLine("Trial created with ID {0} and trial campaign ID {1}.",
      newTrial.id, newTrial.trialCampaignId)
Else If newTrial.status = TrialStatus.CREATION_FAILED Then
  ' The trial creation failed, and errors can be fetched from the
  ' TrialAsyncErrorService.
  Dim errorsSelector As New Selector()
  errorsSelector.fields = New string() {
      TrialAsyncError.Fields.TrialId, TrialAsyncError.Fields.AsyncError
  }
  errorsSelector.predicates = New Predicate() {
    Predicate.Equals(TrialAsyncError.Fields.TrialId, newTrial.id)
  }

  Dim trialAsyncErrorService As TrialAsyncErrorService = _
      CType(user.GetService(AdWordsService.v201609.TrialAsyncErrorService),
          TrialAsyncErrorService)

  Dim trialAsyncErrorPage As TrialAsyncErrorPage = trialAsyncErrorService.get(
      errorsSelector)
  If trialAsyncErrorPage.entries Is Nothing OrElse _
      trialAsyncErrorPage.entries.Length = 0 Then
    Console.WriteLine("Could not retrieve errors for trial {0}.", newTrial.id)
  Else
    Console.WriteLine("Could not create trial ID {0} for draft ID {1} due to the " & _
        "following errors:", trialId, draftId)
    Dim i As Integer = 1
    For Each err As TrialAsyncError In trialAsyncErrorPage.entries
      Dim asyncError As ApiError = err.asyncError
      Console.WriteLine("Error #{0}: errorType='{1}', errorString='{2}', trigger='{3}'," & _
        " fieldPath='{4}'", i, asyncError.ApiErrorType, asyncError.errorString, _
        asyncError.trigger, asyncError.fieldPath)
      i += 1
    Next
  End If
Else
  ' Most likely, the trial is still being created. You can continue
  ' polling, but we have limited the number of attempts in the
  ' example.
  Console.WriteLine("Timed out waiting to create trial from draft ID {0} with " +
      "base campaign ID {1}.", draftId, baseCampaignId)
End If

C#

// Since creating a trial is asynchronous, we have to poll it to wait
// for it to finish.
Selector trialSelector = new Selector() {
  fields = new string[] {
    Trial.Fields.Id, Trial.Fields.Status, Trial.Fields.BaseCampaignId,
    Trial.Fields.TrialCampaignId
  },
  predicates = new Predicate[] {
    Predicate.Equals(Trial.Fields.Id, trialId)
  }
};

trial = null;
bool isPending = true;
int pollAttempts = 0;

do {
  int sleepMillis = (int) Math.Pow(2, pollAttempts) *
      POLL_INTERVAL_SECONDS_BASE * 1000;
  Console.WriteLine("Sleeping {0} millis...", sleepMillis);
  Thread.Sleep(sleepMillis);

  trial = trialService.get(trialSelector).entries[0];

  Console.WriteLine("Trial ID {0} has status '{1}'.", trial.id, trial.status);
  pollAttempts++;
  isPending = (trial.status == TrialStatus.CREATING);
} while (isPending && pollAttempts <= MAX_RETRIES);

if (trial.status == TrialStatus.ACTIVE) {
  // The trial creation was successful.
  Console.WriteLine("Trial created with ID {0} and trial campaign ID {1}.",
      trial.id, trial.trialCampaignId);
} else if (trial.status == TrialStatus.CREATION_FAILED) {
  // The trial creation failed, and errors can be fetched from the
  // TrialAsyncErrorService.
  Selector errorsSelector = new Selector() {
    fields = new string[] {
      TrialAsyncError.Fields.TrialId, TrialAsyncError.Fields.AsyncError
    },
    predicates = new Predicate[] {
      Predicate.Equals(TrialAsyncError.Fields.TrialId, trial.id)
    }
  };

  TrialAsyncErrorService trialAsyncErrorService =
      (TrialAsyncErrorService) user.GetService(
          AdWordsService.v201609.TrialAsyncErrorService);

  TrialAsyncErrorPage trialAsyncErrorPage = trialAsyncErrorService.get(errorsSelector);
  if (trialAsyncErrorPage.entries == null || trialAsyncErrorPage.entries.Length == 0) {
    Console.WriteLine("Could not retrieve errors for trial {0}.", trial.id);
  } else {
    Console.WriteLine("Could not create trial ID {0} for draft ID {1} due to the " +
        "following errors:", trial.id, draftId);
    int i = 0;
    foreach (TrialAsyncError error in trialAsyncErrorPage.entries) {
      ApiError asyncError = error.asyncError;
      Console.WriteLine("Error #{0}: errorType='{1}', errorString='{2}', trigger='{3}'," +
        " fieldPath='{4}'", i++, asyncError.ApiErrorType, asyncError.errorString,
        asyncError.trigger, asyncError.fieldPath);
    }
  }
} else {
    // Most likely, the trial is still being created. You can continue
    // polling, but we have limited the number of attempts in the
    // example.
    Console.WriteLine("Timed out waiting to create trial from draft ID {0} with " +
        "base campaign ID {1}.", draftId, baseCampaignId);
}

PHP

$selector = new Selector();
$selector->fields =
    array('Id', 'Status', 'BaseCampaignId', 'TrialCampaignId');
$selector->predicates = new Predicate('Id', 'IN', array($trial->id));

// Since creating a trial is asynchronous, we have to poll it to wait for it
// to finish.
$pollAttempts = 0;
$isPending = true;
$trial = null;
do {
  $sleepSeconds = POLL_FREQUENCY_SECONDS * pow(2, $pollAttempts);
  printf("Sleeping %d seconds...\n", $sleepSeconds);
  sleep($sleepSeconds);

  $trial = $trialService->get($selector)->entries[0];
  printf("Trial ID %d has status '%s'.\n", $trial->id, $trial->status);

  $pollAttempts++;
  $isPending = ($trial->status === 'CREATING') ? true : false;
} while ($isPending && $pollAttempts <= MAX_POLL_ATTEMPTS);

Perl

my $trial_id = $result->get_value()->[0]->get_id()->get_value();

my $predicate = Google::Ads::AdWords::v201609::Predicate->new({
    field    => "Id",
    operator => "IN",
    values   => [$trial_id]});
my $paging = Google::Ads::AdWords::v201609::Paging->new({
    startIndex    => 0,
    numberResults => 1
});
my $selector = Google::Ads::AdWords::v201609::Selector->new({
    fields => ["Id", "Status", "BaseCampaignId", "TrialCampaignId"],
    predicates => [$predicate],
    paging     => $paging
});

# Since creating a trial is asynchronous, we have to poll it to wait for
# it to finish.
my $poll_attempts = 0;
my $is_pending    = 1;
my $end_time      = time + JOB_TIMEOUT_IN_MILLISECONDS;
do {
  # Check to see if the trial is still in the process of being created.
  my $result = $client->TrialService()->get({selector => $selector});
  $trial = $result->get_entries()->[0];
  my $waittime_in_milliseconds =
    JOB_BASE_WAITTIME_IN_MILLISECONDS * (2**$poll_attempts);
  if (((time + $waittime_in_milliseconds) < $end_time)
    and $trial->get_status() eq 'CREATING')
  {
    printf("Sleeping %d milliseconds...\n", $waittime_in_milliseconds);
    sleep($waittime_in_milliseconds / 1000);    # Convert to seconds.
    $poll_attempts++;
  }
} while (time < $end_time
  and $trial->get_status() eq 'CREATING');

Python

selector = {
    'fields': ['Id', 'Status', 'BaseCampaignId', 'TrialCampaignId'],
    'predicates': [{
        'field': 'Id',
        'operator': 'IN',
        'values': [trial_id]
    }]
}

# Since creating a trial is asynchronous, we have to poll it to wait for it to
# finish.
poll_attempts = 0
is_pending = True
trial = None

while is_pending and poll_attempts < MAX_POLL_ATTEMPTS:
  trial = trial_service.get(selector)['entries'][0]
  print 'Trial ID %d has status "%s"' % (trial['id'], trial['status'])
  poll_attempts += 1
  is_pending = trial['status'] == 'CREATING'

  if is_pending:
    sleep_seconds = 30 * (2 ** poll_attempts)
    print 'Sleeping for %d seconds.' % sleep_seconds
    time.sleep(sleep_seconds)

Ruby

selector = {
  :fields => ['Id', 'Status', 'BaseCampaignId', 'TrialCampaignId'],
  :predicates => [
    :field => 'Id', :operator => 'IN', :values => [trial_id]
  ]
}

poll_attempts = 0
is_pending = true
trial = nil
begin
  sleep_seconds = 30 * (2 ** poll_attempts)
  puts "Sleeping for %d seconds" % sleep_seconds
  sleep(sleep_seconds)

  trial = trial_srv.get(selector)[:entries].first

  puts "Trial ID %d has status '%s'" % [trial[:id], trial[:status]]

  poll_attempts += 1
  is_pending = (trial[:status] == 'CREATING')
end while is_pending and poll_attempts < MAX_POLL_ATTEMPTS

创建试验后,所关联的试验广告系列基本上就像常规广告系列一样投放。您可以在运行试验时对它进行修改,但以下这些由试验指定并且在试验广告系列中不可更改的字段除外(与在常规广告系列中不同):

  • status
  • name
  • startDate
  • endDate
  • budget

在创建试验时,如果未做出指定,则 startDateendDate 将默认为基本广告系列的日期。对试验的 startDateendDate 以及 name 的修改将应用到试验广告系列。对基本广告系列的 status 的修改将同时应用到试验广告系列。

试验操作

试验有两个分开进行的异步操作:创建和推广。如果在尝试创建或推广试验时发生错误,您可以通过向 TrialAsyncErrorService 提供试验的 ID 来获取具体错误的更多详情。请参阅错误部分的示例。

您随时可以通过将试验置于 HALTED 状态将其永久停止。您无法解除停止试验,使其再次与基本广告系列一起开始投放,但您可以推广、升级或归档已停止的试验。

试验广告系列与基本广告系列隐式共享预算。此预算不可与其他任何广告系列共享,并且必须明确标记为非共享预算,也就是将 isExplicitlyShared 设为 false。在整个试验期间,试验广告系列将按指定比例占用基本广告系列的预算。试验结束后(由于试验期满或被手动停止),其全部流量都将立即归于相应的基本广告系列。

试验广告系列很容易识别,它们的 campaignTrialTypeTRIAL。与草稿一样,通过它们的 baseCampaignId 即可知道试验复制自哪个基本广告系列。

推广、升级和归档

根据您对试验结果的满意程度,您有多种选择,这可以通过将试验设置为不同状态来实现。

如果您不满意某个试验所取得的效果,可以将它归档,只需将其设置为 ARCHIVED 状态即可。此操作会立即将试验广告系列标记为 REMOVED,并停止实验。

或者,如果您很满意某个试验所取得的效果,并且该试验仍处于 ACTIVE 状态,您可以采用以下两种方式之一永久实施试验广告系列。一种方式是,将试验广告系列中的所有修改应用到基本广告系列中,这个过程称为推广。另一种方式是,让现有的试验广告系列变得独立于试验之外而存在,即作为完整的广告系列来投放,并且所有在试验期间不可更改的字段均变得可以修改,这个过程称为升级

推广是一个异步过程,和推广草稿差不多。首先,将试验的状态设为 PROMOTING,然后进行轮询,直到状态变成 PROMOTEDPROMOTE_FAILED。您可以使用 TrialAsyncErrorService 查看异步错误。

升级是一个同步过程。您将试验设为 GRADUATED 后,该广告系列将立即变得可以修改和投放。在升级试验广告系列时,您还必须为该广告系列将要使用的新预算指定 budgetId。在升级后,该广告系列不能继续与基本广告系列共享预算。

报告

试验广告系列不会从基本广告系列复制先前的统计信息;它单独统计数据。在试验运行期间,基本广告系列和试验广告系列的统计信息是分开累计的;有各自的展示次数、点击次数等。这种情况在推广或升级时不会改变,统计信息仍在原处,绝不会被复制到另一个实体。

在推广后,基本广告系列将保留先前的所有统计信息,并包含复制到其中的所有新更改。试验广告系列中的统计信息在推广后仍保留在试验广告系列中。

在升级后,基本广告系列和试验广告系列都作为独立实体继续存在,各自保留其用于报告的统计信息。

对于这些实体,您可以使用所有常规报告,例如广告系列效果报告。广告系列上的 baseCampaignId 字段表示基本广告系列,您还可以使用 campaignTrialType 字段来区分常规广告系列和试验广告系列。

错误

草稿和试验都允许执行某些类型的异步操作(草稿的推广、试验的创建和推广)。在这些情况下,应该对相应服务进行指数回退的轮询,直到成功完成操作或发生错误。如果发生错误,实体的状态会指示错误,然后您可以通过相应的 AsyncErrorService 查看详情。

以下示例说明了当某个试验未能推广时,如何请求详细的错误信息:

Java

Selector errorsSelector =
    new SelectorBuilder()
        .fields(TrialAsyncErrorField.TrialId, TrialAsyncErrorField.AsyncError)
        .equals(TrialAsyncErrorField.TrialId, trial.getId().toString())
        .build();

TrialAsyncErrorServiceInterface trialAsyncErrorService =
    adWordsServices.get(session, TrialAsyncErrorServiceInterface.class);
TrialAsyncErrorPage trialAsyncErrorPage = trialAsyncErrorService.get(errorsSelector);
if (trialAsyncErrorPage.getEntries() == null
    || trialAsyncErrorPage.getEntries().length == 0) {
  System.out.printf(
      "Could not retrieve errors for trial ID %d for draft ID %d.%n", trial.getId(), draftId);
} else {
  System.out.printf(
      "Could not create trial ID %d for draft ID %d due to the following errors:%n",
      trial.getId(),
      draftId);
  int i = 0;
  for (TrialAsyncError error : trialAsyncErrorPage.getEntries()) {
    ApiError asyncError = error.getAsyncError();
    System.out.printf(
        "Error #%d: errorType='%s', errorString='%s', trigger='%s', fieldPath='%s'%n",
        i++,
        asyncError.getApiErrorType(),
        asyncError.getErrorString(),
        asyncError.getTrigger(),
        asyncError.getFieldPath());
  }

VB

Else If newTrial.status = TrialStatus.CREATION_FAILED Then
  ' The trial creation failed, and errors can be fetched from the
  ' TrialAsyncErrorService.
  Dim errorsSelector As New Selector()
  errorsSelector.fields = New string() {
      TrialAsyncError.Fields.TrialId, TrialAsyncError.Fields.AsyncError
  }
  errorsSelector.predicates = New Predicate() {
    Predicate.Equals(TrialAsyncError.Fields.TrialId, newTrial.id)
  }

  Dim trialAsyncErrorService As TrialAsyncErrorService = _
      CType(user.GetService(AdWordsService.v201609.TrialAsyncErrorService),
          TrialAsyncErrorService)

  Dim trialAsyncErrorPage As TrialAsyncErrorPage = trialAsyncErrorService.get(
      errorsSelector)
  If trialAsyncErrorPage.entries Is Nothing OrElse _
      trialAsyncErrorPage.entries.Length = 0 Then
    Console.WriteLine("Could not retrieve errors for trial {0}.", newTrial.id)
  Else
    Console.WriteLine("Could not create trial ID {0} for draft ID {1} due to the " & _
        "following errors:", trialId, draftId)
    Dim i As Integer = 1
    For Each err As TrialAsyncError In trialAsyncErrorPage.entries
      Dim asyncError As ApiError = err.asyncError
      Console.WriteLine("Error #{0}: errorType='{1}', errorString='{2}', trigger='{3}'," & _
        " fieldPath='{4}'", i, asyncError.ApiErrorType, asyncError.errorString, _
        asyncError.trigger, asyncError.fieldPath)
      i += 1
    Next
  End If

C#

} else if (trial.status == TrialStatus.CREATION_FAILED) {
  // The trial creation failed, and errors can be fetched from the
  // TrialAsyncErrorService.
  Selector errorsSelector = new Selector() {
    fields = new string[] {
      TrialAsyncError.Fields.TrialId, TrialAsyncError.Fields.AsyncError
    },
    predicates = new Predicate[] {
      Predicate.Equals(TrialAsyncError.Fields.TrialId, trial.id)
    }
  };

  TrialAsyncErrorService trialAsyncErrorService =
      (TrialAsyncErrorService) user.GetService(
          AdWordsService.v201609.TrialAsyncErrorService);

  TrialAsyncErrorPage trialAsyncErrorPage = trialAsyncErrorService.get(errorsSelector);
  if (trialAsyncErrorPage.entries == null || trialAsyncErrorPage.entries.Length == 0) {
    Console.WriteLine("Could not retrieve errors for trial {0}.", trial.id);
  } else {
    Console.WriteLine("Could not create trial ID {0} for draft ID {1} due to the " +
        "following errors:", trial.id, draftId);
    int i = 0;
    foreach (TrialAsyncError error in trialAsyncErrorPage.entries) {
      ApiError asyncError = error.asyncError;
      Console.WriteLine("Error #{0}: errorType='{1}', errorString='{2}', trigger='{3}'," +
        " fieldPath='{4}'", i++, asyncError.ApiErrorType, asyncError.errorString,
        asyncError.trigger, asyncError.fieldPath);
    }
  }

PHP

$selector = new Selector();
$selector->fields = array('TrialId', 'AsyncError');
$selector->predicates = new Predicate('TrialId', 'IN', array($trial->id));

$errors = $trialAsynErrorService->get($selector)->entries;

if (count($errors) === 0) {
  printf("Could not retrieve errors for the trial with ID %d\n",
      $trial->id);
} else {
  printf("Could not create trial due to the following errors:\n");
  $i = 0;
  foreach ($errors as $error) {
    printf("Error #%d: %s\n", $i++, $error->asyncError);
  }
}

Perl

my $error_selector = Google::Ads::AdWords::v201609::Selector->new({
    fields     => ["TrialId", "AsyncError"],
    predicates => [
      Google::Ads::AdWords::v201609::Predicate->new({
          field    => "TrialId",
          operator => "IN",
          values   => [$trial_id]})]});

my $errors =
  $client->TrialAsyncErrorService->get({selector => $error_selector})
  ->get_entries();
if (!$errors) {
  printf("Could not retrieve errors for trial %d", $trial->get_id());
} else {
  printf("Could not create trial due to the following errors:");
  my $index = 0;
  for my $error ($errors) {
    printf("Error %d: %s", $index, $error->get_asyncError()
    ->get_errorString());
    $index++;
  }
}

Python

selector = {
    'fields': ['TrialId', 'AsyncError'],
    'predicates': [{
        'field': 'TrialId',
        'operator': 'IN',
        'values': [trial['id']]
    }]
}

errors = trial_async_error_service.get(selector)['entries']

if not errors:
  print 'Could not retrieve errors for trial %d' % trial['id']
else:
  print 'Could not create trial due to the following errors:'
  for error in errors:
    print 'Error: %s' % error['asyncError']

Ruby

selector = {
  :fields => ['TrialId', 'AsyncError'],
  :predicates => [
    {:field => 'TrialId', :operator => 'IN', :values => [trial[:id]]}
  ]
}

errors = trial_async_error_srv.get(selector)[:entries]

if errors.nil?
  puts "Could not retrieve errors for trial %d" % trial[:id]
else
  puts "Could not create trial due to the following errors:"
  errors.each_with_index do |error, i|
    puts "Error #%d: %s" % [i, error[:async_error]]
  end
end

通常,异步操作即使在遇到错误后,也会继续实施更改,因此如果存在大量不合要求的修改,系统报出的错误数可能迅速增加。可能的原因包括重复的广告组名称、不合要求的出价策略或超出帐户限制等。

发送以下问题的反馈:

此网页
AdWords API
AdWords API
需要帮助?请访问我们的支持页面