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

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

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

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

Так выглядит процесс в общих чертах, однако сервисы DraftService и TrialService можно использовать и по-другому. Например, с помощью проекта можно подготовить изменения для основной кампании, не создавая пробную. Или, если вас устраивает эффективность пробной кампании, вы можете перенести ее атрибуты в основную либо выпустить ее в качестве полноценной кампании, которая будет проводиться параллельно с основной.

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

Объекты 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

$draftService = $adWordsServices->get($session, DraftService::class);

$operations = [];
// Create a draft.
$draft = new Draft();
$draft->setBaseCampaignId($baseCampaignId);
$draft->setDraftname('Test Draft #' . uniqid());

// Create a draft operation and add it to the operations list.
$operation = new DraftOperation();
$operation->setOperand($draft);
$operation->setOperator(Operator::ADD);
$operations[] = $operation;

// Create the draft on the server and print out some information for
// the created draft.
$result = $draftService->mutate($operations);
$draft = $result->getValue()[0];

Perl

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

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

Python

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

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 = $adWordsServices->get($session, CampaignCriterionService::class);

// Create a criterion.
$language = new Language();
$language->setId(1003); // Spanish
$campaignCriterion = new CampaignCriterion();
$campaignCriterion->setCampaignId($draft->getDraftCampaignId());
$campaignCriterion->setCriterion($language);

// Create a campaign criterion operation and add it to the operations list.
$operations = [];
$operation = new CampaignCriterionOperation();
$operation->setOperand($campaignCriterion);
$operation->setOperator(Operator::ADD);
$operations[] = $operation;

// Create a campaign criterion on the server.
$campaignCriterionService->mutate($operations);

Perl

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

my $operation =
  Google::Ads::AdWords::v201802::CampaignCriterionOperation->new({
    operator => "ADD",
    operand  => Google::Ads::AdWords::v201802::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='v201802')

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.v201802.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

$adGroupService =
    $adWordsServices->get($session, AdGroupService::class);

// Create a selector to select all ad groups for the specified draft
// campaign.
$selector = new Selector();
$selector->setFields(['Id']);
$selector->setPredicates(
    [
        new Predicate(
            'CampaignId',
            PredicateOperator::EQUALS,
            [$draftCampaignId]
        )
    ]
);
$selector->setPaging(new Paging(0, self::PAGE_LIMIT));

// Retrieve ad groups for the specified draft campaign.
$page = $adGroupService->get($selector);

// Print out some information for the ad groups.
if ($page->getTotalNumEntries() > 0) {
    printf("Found %d ad groups.\n", $page->getTotalNumEntries());
} else {
    print "No ad groups were found.\n";
}

Perl

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

# Create selector.
my $paging = Google::Ads::AdWords::v201802::Paging->new({
  startIndex    => 0,
  numberResults => PAGE_SIZE
});
my $selector = Google::Ads::AdWords::v201802::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='v201802')

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

$trialService = $adWordsServices->get($session, TrialService::class);
$trialAsynErrorService = $adWordsServices->get($session, TrialAsyncErrorService::class);

// Create a trial.
$trial = new Trial();
$trial->setDraftId($draftId);
$trial->setBaseCampaignId($baseCampaignId);
$trial->setName('Test Trial #' . uniqid());
$trial->setTrafficSplitPercent(50);

// Create a trial operation and add it to the operations list.
$operations = [];
$operation = new TrialOperation();
$operation->setOperand($trial);
$operation->setOperator(Operator::ADD);
$operations[] = $operation;

// Create the trial on the server.
$trial = $trialService->mutate($operations)->getValue()[0];

Perl

my $trial = Google::Ads::AdWords::v201802::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::v201802::TrialOperation->new({
    operator => "ADD",
    operand  => $trial
});

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

Python

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

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)
  ElseIf 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 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)
    }
  };

  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->setFields(
    ['Id', 'Status', 'BaseCampaignId', 'TrialCampaignId']
);
$selector->setPredicates(
    [new Predicate('Id', PredicateOperator::IN, [$trial->getId()])]
);

// 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 = self::POLL_FREQUENCY_SECONDS * pow(2, $pollAttempts);
    printf("Sleeping %d seconds...\n", $sleepSeconds);
    sleep($sleepSeconds);

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

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

Perl

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

my $predicate = Google::Ads::AdWords::v201802::Predicate->new({
    field    => "Id",
    operator => "IN",
    values   => [$trial_id]});
my $paging = Google::Ads::AdWords::v201802::Paging->new({
    startIndex    => 0,
    numberResults => 1
});
my $selector = Google::Ads::AdWords::v201802::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

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

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

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

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

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

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

Во время эксперимента базовая и пробная кампании делят между собой один бюджет и одни и те же аукционы в соотношении, которое определяется долей трафика, выделенной для эксперимента. Если одна из кампаний израсходует свой бюджет полностью, другая продолжит действовать до тех пор, пока ее доля бюджета не будет исчерпана или пока не закончится эксперимент. При этом ее объявления будут бороться за право показа только на предназначенных для нее аукционах. По окончании эксперимента базовая кампания снова получит 100% бюджета и сможет участвовать в любых аукционах.

Пробные кампании можно распознать по значению 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

ElseIf 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 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)
    }
  };

  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->setFields(['TrialId', 'AsyncError']);
$selector->setPredicates(
    [new Predicate('TrialId', PredicateOperator::IN, [$trial->getId()])]
);

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

if (count($errors) === 0) {
    printf(
        "Could not retrieve errors for the trial with ID %d\n",
        $trial->getId()
    );
} 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->getAsyncError());
    }
}

Perl

my $error_selector = Google::Ads::AdWords::v201802::Selector->new({
    fields     => ["TrialId", "AsyncError"],
    predicates => [
      Google::Ads::AdWords::v201802::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

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

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

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