キャンペーンの下書きとウェブテスト

「広告のランディング ページを変更したらコンバージョンが増えるのでは ないか」、「広告文を変えたら、サイトへのトラフィックが増えるだろうか?」などと、考えたことは ありませんか。このような特定の可変要素を振り分けるテストは準備作業が面倒になりがちですが、 キャンペーンの下書きとウェブテストを使うことで、データのコピーや設定などの 繁雑な作業をすべて自動的に処理することができます。

DraftServiceTrialService を使用すると、 新しいキャンペーン ウェブテストを短時間で簡単に設定できます。ウェブテストを設定するための 手順は次のとおりです。

  1. 既存のベース キャンペーンから Draft(下書き) を作成します。下書きとは、広告を配信しない、既存のキャンペーンの コピーです。
  2. ウェブテストの目的に応じて、下書き用キャンペーンに変更を加えます。
  3. 下書きから Trial(テスト) を作成します。Trial を使って、下書き用キャンペーンのコピーとして開始された 新しいテスト キャンペーンにアクセスし、テスト キャンペーンとベース キャンペーン間でトラフィックを分割します。テスト キャンペーンは、 ウェブテストとも呼ばれます。
  4. テスト キャンペーンとベース キャンペーンの統計情報を比較し、どちらの キャンペーンが目的に適しているか調べます。

これは最も一般的な手順ですが、DraftService と TrialService は柔軟性が高く、 さまざまな方法で利用することができます。たとえば、下書きを使って ベース キャンペーンへの変更をステージングした後、その変更をベース キャンペーンに 組み込むことができます。その場合、テストを使用する必要はありません。また、テスト キャンペーンを使用して、 その掲載結果に満足した場合は、そのテストの属性をベース キャンペーンに反映したり、 ベース キャンペーンから独立した別の通常のキャンペーンにしたり できます。

次のフローチャートは、キャンペーンの下書きとウェブテストを利用するときの ワークフローを示しています。

下書き

Draft(下書き)とは、下書き用キャンペーンとベース キャンペーンの関連付けを保持する オブジェクトです。Draft は、ベースとなる既存のキャンペーンの ID を DraftService に入力することで作成されます。DraftService が自動的に 新しい下書き用キャンペーンを作成し、それに Draft を関連付けます。Draft は 下書き用キャンペーンをベース キャンペーンに関連付けるだけの要素であり、 Draft オブジェクト自体が下書き用キャンペーンを表すわけではありません。下書き用キャンペーンは、実際には Campaign オブジェクトとして作成され、下書き用 キャンペーンの campaignId は Draft の draftCampaignId から取得されます。実際の キャンペーンと同様に、下書き用キャンペーンについても条件、広告グループ、入札単価、 広告などを変更することができます。ただし、下書き用キャンペーンでは広告が配信されません。

下書きのベースとなる既存のキャンペーンは、一定の要件を満たす 必要があります。検索キャンペーンまたは検索ネットワーク(ディスプレイ ネットワーク対応)キャンペーンであることと、 共有されない予算が割り当てられている(キャンペーンの予算の isExplicitlyShared が false である)ことが必須です。ウェブテストではほとんどの キャンペーン機能がサポートされますが、 いくつか例外があります

Draft の作成

Draft を作成するには、baseCampaignId を設定し、Draft の 名前を付けます。アカウント内の他の Draft と異なる名前を付けてください。 次のサンプルコードは 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]

Draft には draftCampaignIddraftIdbaseCampaignId などの詳細パラメータが含まれますが、そのすべてが重要です。draftCampaignId は 下書き用キャンペーンおよびその広告グループ、条件、広告を変更するときに使用します。draftIdbaseCampaignId はテストを作成したり、Draft をプロモーションするときに、 Draft から参照されます。

下書き用キャンペーンのカスタマイズ

draftCampaignId フィールドは実際のキャンペーン ID として使用でき、 そのような ID が必要になるサービス(CampaignService、AdGroupService、CampaignCriterionService など)で 利用できます。たとえばキャンペーンに新しい言語条件を追加するには、Draft から 取得した draftCampaignIdcampaignId として使用するだけです。

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. ベース キャンペーンとは別に独立して実行される テスト キャンペーンを作成する

下書き用キャンペーンのプロモーション

テストを作成せず、実際のキャンペーンに加える変更を検討するための ステージング領域としてのみ、Draft を利用することができます。すべての変更を 下書き用キャンペーンでステージングしたら、Draft をプロモーションするだけで、 その変更がベース キャンペーンに適用されます。Draft をプロモーションするには、 Draft のステータスを PROMOTING に設定する変換操作を実行します。このとき、 下書き用キャンペーンの変更がすべてベース キャンペーンにコピーされるため、プロモーションは 非同期的に処理され、元に戻すことができません。このステータスを設定した時点で非同期処理が 開始されます。

Draft の draftIdbaseCampaignId を使って Draft をポーリングすることで、 ステータス フィールドをモニタリングできます。ステータスが PROMOTING 以外に変わったときに、処理が完了します。 PROMOTED は成功したことを示し、PROMOTE_FAILED はエラーが発生したことを示します。 ポーリングの手法については、Trial で詳しく説明します。

Draft をプロモーションするときにエラーが発生した場合、draftIdbaseCampaignIdDraftAsyncErrorService に入力することで エラーの詳細情報を得ることができます。 詳しくは、エラー セクションのサンプルをご覧ください。

下書き用キャンペーンは、campaignTrialTypeDRAFT であるため識別できます。また、 baseCampaignId フィールドを使って、下書き用キャンペーンのベース キャンペーン ID を 参照することもできます。通常のキャンペーン(下書きやテストを使って作成されていない キャンペーン)の場合、campaignTrialTypeBASE となり、baseCampaignId フィールドは そのキャンペーン独自の ID になります。

既定では、CampaignService や AdGroupService などの他のサービスからの get 結果に下書きエンティティは含まれません。下書き用キャンペーンや 下書き広告グループなどの下書きエンティティを取得するには、predicate で campaignTrialType に明示的に DRAFT に指定するか、draftCampaignId などの 既知の下書きエンティティの ID にフィルタを適用する必要があります。

Trial

Trial は、ベース キャンペーンとは別に実行されるテスト キャンペーンを管理する ためのエンティティで、共有されたトラフィックと予算を使って、下書きで設定した内容を テストできます。Trial は、通常のキャンペーンと同じように広告を配信する、リアルな テスト キャンペーン(ウェブテストとも呼ばれます)を生成します。Trial はベース キャンペーンから独立して 統計情報を収集しますが、キャンペーンや広告グループなどと同じように、 アカウントで一度に利用できる機能数の上限の計算対象に含まれます。

Trial の作成

Trial を作成するには、Trial の基となる Draft を一意に 識別する draftIdbaseCampaignId、一意の名前、そして Trial に割り当てるトラフィックの割合を指定します。Trial を作成すると、 新しく関連付けるテスト キャンペーンが自動的に作成されます。

Trial は Draft とは異なり、ID で一意に識別できます。そのため、 Trial を作成した後で 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]

Draft を作成する場合と同様に、以後の操作に備えて Trial の trialCampaignId を メモしてください。

Draft の作成とは異なり、Trial の作成は非同期操作です。新しい Trial のステータスは CREATING になります。次の手順に進む前に、 Trial がそれ以外のステータスになるまでポーリングする必要があります。

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 を作成するときに指定しない限り、startDateendDate はデフォルトで ベース キャンペーンと同じ日時になります。Trial の startDateendDatename を 変更すると、それがテスト キャンペーンに反映されます。ベース キャンペーンの status を 変更した場合は、それもテスト キャンペーンに反映されます。

Trial の動作

Trial には作成とプロモーションという 2 種類の動作があり、互いに非同期的に実行されます。Trial の 作成中またはプロモーション中にエラーが発生した場合、TrialAsyncErrorService に Trial の ID を入力することで、発生したエラーの詳細情報を得ることが できます。 詳しくは、エラー セクションのサンプルをご覧ください。

また、Trial を HALTED ステータスにすることで、直ちに停止する こともできます。Trial の停止を解除し、ベース キャンペーンと平行してテスト キャンペーンを 再開することはできませんが、停止した Trial のプロモーション、グラデュエーション、アーカイブ は可能です。

テスト キャンペーンは常に暗黙的にベース キャンペーンと予算を共有します。 この予算は他のキャンペーンと共有できず、個別に isExplicitlyShared を false に設定して非共有として設定する必要があります。テスト キャンペーンは、 テスト期間中、ベース キャンペーンの予算を一定の割合で 利用します。テストが終了すると(テスト期間が終了するか 手動で停止されると)、トラフィックの 100% がベース キャンペーンに 戻ります。

テスト キャンペーンは campaignTrialTypeTRIAL であることで 識別できます。下書きと同様に、baseCampaignId によってテストのコピー元の ベース キャンペーンがわかります。

プロモーション、グラデュエーション、アーカイブ

テスト結果を確認した後の選択肢はいくつかあり、目的に応じて Trial をいずれかのステータスに 設定します。

テストの結果に満足できなかった場合は、ARCHIVED ステータスにすることで アーカイブできます。直ちにテスト キャンペーンが REMOVED に設定され、 ウェブテストが停止されます。

テストの結果に満足し、テストが ACTIVE のままになっている場合は、 2 つの方法を使ってテスト キャンペーンをさらに長期間実施することが できます。1 つは、すべての変更をベース キャンペーンに反映する方法です。これは プロモーションと呼ばれます。一方、既存のテスト キャンペーンを Trial から 独立させて通常のキャンペーンと同様に機能できるようにすることで、 通常はテスト中に変更できないフィールドを変更できるようにすることが できます。この操作をグラデュエーションと呼びます。

Draft のプロモーションと同じく、このプロモーションも非同期的な処理です。開始するには、 Trial を PROMOTING ステータスに設定し、PROMOTED または PROMOTE_FAILED になるまでポーリングを 実行します。TrialAsyncErrorService を使って非同期エラーをチェックすることが できます。

グラデュエーションは同期的な処理です。Trial を GRADUATED に設定すると、 直ちにキャンペーンの変更と実行が可能になります。テスト キャンペーンを グラデュエーションする場合、そのキャンペーンで使用される新しい予算の budgetId も 指定する必要があります。 グラデュエーションの後でベース キャンペーンの予算を引き続き共有することは できません。

レポート

テスト キャンペーンでは、ベース キャンペーンの以前の統計情報はコピーされず、 まったく新しい状態で開始されます。テストの実施中、ベース キャンペーンと テスト キャンペーンの統計情報は別々に収集され、表示回数やクリック数など も別々に記録されます。プロモーションやグラデュエーションを経ても この動作は変わらず、統計情報はそのまま保持され、他のエンティティに コピーされることはありません。

プロモーションを実行すると、新しい変更がベース キャンペーンにコピーされますが、 ベース キャンペーンの過去の統計情報はすべて維持されます。テスト キャンペーンの 統計情報は、プロモーション後もテスト キャンペーンで保持されます。

グラデュエーションを実行すると、ベース キャンペーンとテスト キャンペーンは 別々のエンティティとして存続し、それぞれ独自の統計情報がレポート用に保持されます。

このようなエンティティに対して、キャンペーンの掲載結果レポートなどの一般的なレポートを すべて使用できます。キャンペーンの baseCampaignId フィールドはベース キャンペーンを表し、 campaignTrialType フィールドを使って通常のキャンペーンとテスト キャンペーンを 判別することができます。

エラー

Draft と Trial では、特定の操作(Draft ではプロモーション、 Trial では作成とプロモーション)を非同期的に実行できます。そのとき、 処理が正常に完了するかエラーが発生するまで、そのサービスを ポーリングする必要があります(一般的には待機します)。エラーが発生した場合、 エンティティのステータスによってわかるので、適切な AsyncErrorService を調べて 詳細を確認できます。

たとえば、Trial のプロモーションが失敗した場合、次のようなコードを使って 詳細なエラーをリクエストできます。

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

通常は、エラーが発生しても非同期操作によって変更が適用され 続けます。このため、不適切な変更が大量に加えられると、エラーログが急速に ふくれあがる場合が あります。原因となるのは、広告グループの名前が重複していたり、 入札戦略が不適切であったり、アカウントの制限を超過していたりすることなどです。

フィードバックを送信...

ご不明な点がありましたら、Google のサポートページをご覧ください。