Проекты кампаний и эксперименты

Можно ли получать больше конверсий, если выбрать для объявлений другую целевую страницу? Возросло ли бы число кликов, если немного изменить рекламный текст? Каждый рекламодатель наверняка задавал себе подобные вопросы. Настраивать тест, чтобы выделить нужный тип трафика, – долгое и трудоемкое занятие. Однако проекты кампаний и эксперименты могут избавить вас от всех хлопот, связанных с тестированием.

Настраивать эксперименты с помощью служб DraftService и TrialService очень просто. Вот что для этого нужно сделать:

  1. Создайте проект на основе существующей кампании. Проект будет ее "зеркалом", но с тем отличием, что объявления показываться не будут.
  2. Измените проект кампании в соответствии с целями эксперимента.
  3. Создайте на основе проекта пробную кампанию. Трафик будет разделен между пробной и основной кампаниями. Другое название пробной кампании – эксперимент.
  4. Сравните статистику по пробной и основной кампаниям, чтобы узнать, какая из них эффективнее.

Так выглядит процесс в общих чертах, однако службы DraftService и TrialService можно использовать и по-другому. Например, с помощью проекта можно подготовить изменения для основной кампании, не создавая пробную. Or, if you do use a trial and like the performance, you have the option of promoting its attributes back to the base campaign or graduating it into its own full–fledged campaign alongside the base campaign.

На этой схеме показан процесс использования проектов кампаний и экспериментов:

Объекты Draft

Объект Draft обеспечивает связь между проектом и основной кампанией. Чтобы его создать, используйте службу DraftService. Вам понадобится предоставить идентификатор (ID) существующей кампании, которая будет служить основой для проекта. DraftService автоматически создаст новый проект кампании и свяжет его с объектом Draft. Объект Draft сам по себе не является проектом кампании, а лишь связывает его с основной кампанией. Проект кампании фактически существует в виде объекта Campaign. Идентификатор campaignId для проекта доступен как draftCampaignId объекта Draft. Проект кампании можно изменять так же, как обычную кампанию: редактировать ее критерии, группы объявлений, ставки и объявления. Однако объявления из проекта кампании не показываются.

Чтобы существующую кампанию можно было использовать в качестве основы для проекта, она должна относиться к типу "Только поисковая сеть" или "Поисковая сеть и оптимизированная контекстно-медийная сеть" и иметь необщий бюджет (поле isExplicitlyShared объекта Budget должно иметь значение false). Хотя в экспериментах доступны почти все те же самые функции, что и в кампаниях, есть некоторые исключения.

Создание объекта Draft

Чтобы создать объект Draft, задайте значение baseCampaignId и укажите название проекта кампании. Оно должно быть уникальным, т. е. в аккаунте не должно быть других проектов с таким же именем. Вот пример кода:

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]

Объект Draft будет включать такие поля, как draftCampaignId, draftId и baseCampaignId. Все они очень важны: draftCampaignId используется для изменения проекта кампании и входящих в него групп объявлений, критериев и объявлений, а draftId и baseCampaignId служат ссылкой на проект при создании пробной кампании или продвижении проекта.

Настройка проекта кампании

Поле draftCampaignId может использоваться как идентификатор фактической кампании со службами, которым требуется такой идентификатор (CampaignService, AdGroupService, CampaignCriterionService и т. д.). Например, чтобы добавить новый критерий языка, просто используйте значение draftCampaignId из объекта Draft в качестве 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. Создать пробную кампанию, которая будет проводиться параллельно с основной.

Продвижение проекта кампании

Проекты можно использовать для подготовки изменений в реальных кампаниях. Когда все будет готово, просто перенесите изменения из проекта в основную кампанию. Для этого передайте операцию mutate, изменяющую статус объекта Draft на PROMOTING. Поскольку при этом все изменения из проекта копируются обратно в основную кампанию, процесс выполняется асинхронно и является необратимым. Назначение статуса запускает этот асинхронный процесс.

Для проверки статуса используйте идентификаторы draftId и baseCampaignId объекта Draft. Если статус изменился с PROMOTING на другой, значит операция завершена. Значение PROMOTED означает успешное выполнение, тогда как PROMOTE_FAILED свидетельствует об ошибке. Подробнее о схемах проверки рассказывается в разделе Объекты Trial.

Если произошла ошибка, вы можете получить о ней подробную информацию, передав идентификаторы draftId и baseCampaignId в службу DraftAsyncErrorService. См. пример в разделе Ошибки.

Проект кампании можно распознать по значению DRAFT в поле campaignTrialType. Кроме того, вы можете найти в проекте идентификатор основной кампании, используя поле baseCampaignId. Для обычных кампаний (созданных не на основе проекта или пробной кампании) поле campaignTrialType будет иметь значение BASE, а в поле baseCampaignId будет указан идентификатор кампании.

По умолчанию проекты не включаются в результаты операции get из других служб, таких как CampaignService или AdGroupService. Чтобы получить проект кампании или группы объявлений, нужно либо явно указать значение DRAFT для campaignTrialType в предикатах, либо выполнить фильтрацию по идентификатору известного проекта, например draftCampaignId.

Объекты Trial

Объект Trial позволяет создать пробную кампанию, которая будет проводиться параллельно с основной. На пробную кампанию отводится часть трафика и бюджета в целях тестирования изменений из проекта. Добавление объекта Trial приводит к созданию пробной кампании (эксперимента), объявления из которой показываются как обычно. Статистика по пробной кампании собирается отдельно. Пробные кампании учитываются при проверке соответствия ограничениям на максимальное число объектов (кампаний, групп объявлений и т. д.) в аккаунте.

Создание объекта Trial

Чтобы создать объект Trial, укажите идентификаторы draftId и baseCampaignId, однозначно идентифицирующие проект, на котором основана эта пробная кампания, а также уникальное название и процент трафика, который вы хотите на нее выделить. Будет автоматически создана новая пробная кампания.

Пробные кампании, в отличие от проектов, однозначно идентифицируются по идентификатору. Поэтому, создав объект Trial, вам не нужно будет использовать 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 – вы будете использовать этот идентификатор в будущих операциях.

Создание объекта Trial, в отличие от Draft, является асинхронной операцией. У нового объекта Trial будет статус 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

Когда вы создадите объект Trial, будет запущена соответствующая пробная кампания. Она будет работать практически так же, как и обычная. Вы сможете вносить в нее изменения, за исключением следующих полей, которые управляются объектом Trial и неизменяемы в пробной кампании (в отличие от обычной):

  • status
  • name
  • startDate
  • endDate
  • budget

Если при создании объекта Trial вы не укажете значения в полях startDate и endDate, по умолчанию будут использоваться даты начала и окончания из основной кампании. Изменения в полях startDate, endDate и name объекта Trial будут распространяться на пробную кампанию, как и изменения в поле status основной кампании.

Операции с объектом Trial

С объектами Trial можно выполнять две асинхронные операции: создание и продвижение. Если произошла ошибка, вы можете получить о ней более подробные сведения, передав идентификатор объекта Trial в службу TrialAsyncErrorService. См. пример в разделе Ошибки.

Вы также можете заблокировать объект Trial, переведя его в статус HALTED. Повторно активировать заблокированный объект Trial, чтобы пробная кампания снова начала проводиться наряду с основной, нельзя, однако его можно продвинуть, выпустить или архивировать.

Пробные кампании всегда имеют общий бюджет с основными. Этот бюджет нельзя использовать больше ни в каких других кампаниях, и его нужно явным образом отметить как необщий, указав значение false в поле isExplicitlyShared. На пробную кампанию в течение всего срока ее проведения будет выделяться определенный процент бюджета основной кампании. Как только пробная кампания завершится (наступит дата ее окончания или вы остановите ее вручную), весь трафик вернется в основную кампанию.

Пробные кампании можно распознать по значению TRIAL в поле campaignTrialType. Как и в случае с проектами, baseCampaignId будет указывать на основную кампанию, с которой была скопирована эта пробная кампания.

Продвижение, выпуск и архивирование

В зависимости от того, насколько вы довольны эффективностью пробной кампании, можно перевести ее в один из трех статусов.

Если результаты пробной кампании вас не устроили, ее можно архивировать, установив статус ARCHIVED. Кампания будет отмечена как удаленная (REMOVED), и эксперимент прекратится.

Если вы остались довольны результатами пробной кампании и она все ещё активна (ACTIVE), у вас есть два варианта: применить все изменения к основной кампании (это называется продвижением) или преобразовать ее в независимую кампанию (в таком случае все неизменяемые поля вновь станут редактируемыми, и это называется выпуском).

Продвижение пробной кампании – асинхронный процесс. Чтобы его запустить, установите статус PROMOTING и проверяйте его до тех пор, пока он не изменится на PROMOTED или PROMOTE_FAILED. Выполнить проверку на ошибки можно с помощью службы TrialAsyncErrorService.

Выпуск является синхронным процессом. Как только вы установите статус GRADUATED, кампания будет сразу же готова к работе. При выпуске пробной кампании вам также понадобится указать для нее новый бюджет в поле budgetId (он уже не может быть общим с основной кампанией).

Отчеты

Статистика из основной кампании не копируется в пробную – сбор данных начинается с нуля. В течение всего срока проведения пробной кампании статистика для нее (показы, клики и т. д.) ведется отдельно, в том числе при ее продвижении или выпуске, и никогда не копируется в другой объект.

После продвижения в основную кампанию копируются изменения из пробной, а все накопленные данные сохраняются. Статистика пробной кампании остается в ней.

После выпуска основная и пробная кампании продолжают быть отдельными объектами. В каждой из них сохраняется собственная статистика.

Для анализа результатов этих кампаний можно использовать все те же самые отчеты, что и обычно (например, отчет по эффективности кампаний). Поле baseCampaignId представляет основную кампанию, а поле campaignTrialType позволяет определить, обычная это кампания или пробная.

Ошибки

Объекты Draft и Trial позволяют выполнять определенные действия (для Draft – продвижение, для Trial – создание и продвижение), которые являются асинхронными. В этих случаях необходимо опрашивать службу с экспоненциальным увеличением паузы, пока операция не будет выполнена успешно или пока не будет возвращена ошибка. Если произошла ошибка, это будет отражено в статусе объекта и вы сможете получить о ней более подробные сведения с помощью службы 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

Обычно асинхронные операции продолжают выполняться даже в случае ошибки, поэтому число ошибок может стремительно расти при большом количестве несовместимых изменений. К возможным причинам относятся повторяющиеся названия групп объявлений, несовместимые стратегии назначения ставок и превышение ограничений в аккаунте.

Оставить отзыв о...

Текущей странице
Нужна помощь? Обратитесь в службу поддержки.