ベスト プラクティス

このページでは、Google 広告スクリプトを使用した開発に関するさまざまなベスト プラクティスについて説明します。

セレクタ

セレクタでフィルタをかける

可能な限り、フィルタを使って必要なエンティティだけをリクエストしてください。フィルタを適切に適用することには、次のようなメリットがあります。

  • よりシンプルで理解しやすいコードになります。
  • スクリプトの実行時間が大幅に短縮されます。

以下のコード スニペットを比較してください。

コーディングのアプローチ コード スニペット
セレクタを使用してフィルタする(推奨)
var keywords = AdsApp.keywords()
    .withCondition('Clicks > 10')
    .forDateRange('LAST_MONTH')
    .get();
while (keywords.hasNext()) {
  var keyword = keywords.next();
  // Do work here.
}
コードでフィルタする(推奨されない)
var keywords = AdsApp.keywords().get();

while (keywords.hasNext()) {
  var keyword = keywords.next();
  var stats = keyword.getStatsFor(
      'LAST_MONTH');
  if (stats.getClicks() > 10) {
    // Do work here.
  }
}

2 つ目の方法は、アカウント内のすべてのキーワードのリストを取得して、そのリストにフィルタを適用するだけなので、おすすめしません。

キャンペーンの階層構造の順次処理を避ける

特定のレベルでエンティティを取得する場合は、キャンペーン階層全体を走査するのではなく、そのレベルでコレクション メソッドを使用します。これにより、シンプルになるだけでなく、パフォーマンスも大幅に向上します。システムがすべてのキャンペーンと広告グループを不必要に読み込む必要がなくなります。

アカウントのすべての広告を取得する次のコード スニペットを比較します。

コーディングのアプローチ コード スニペット
適切な収集方法を使用する(推奨)

var ads = AdsApp.ads();

階層を走査する(非推奨)
var campaigns = AdsApp.campaigns().get();
while (campaigns.hasNext()) {
  var adGroups = campaigns.next().
      adGroups().get();
  while (adGroups.hasNext()) {
    var ads = adGroups.next().ads().get();
    // Do your work here.
  }
}

2 つ目の方法は、広告のみが必要な一方で、オブジェクトの階層全体(キャンペーン、広告グループ)を取得しようとするため、推奨されません。

特定の親アクセサ メソッドを使う

取得済みオブジェクトの親エンティティを取得する必要がある場合は、この場合、階層全体を取得するのではなく、指定されたアクセサ メソッドを使用する必要があります。

先月 50 回以上のクリックがあったテキスト広告を含む広告グループを取得する次のコード スニペットを比較してください。

コーディングのアプローチ コード スニペット
適切な親アクセサラ メソッドを使用する(推奨)
var ads = AdsApp.ads()
    .withCondition('Clicks > 50')
    .forDateRange('LAST_MONTH')
    .get();

while (ads.hasNext()) {
  var ad = ads.next();
  var adGroup = ad.getAdGroup();
  var campaign = ad.getCampaign();
  // Store (campaign, adGroup) to an array.
}
階層を走査する(推奨されない)
var campaigns = AdsApp.campaigns().get();
while (campaigns.hasNext()) {
  var adGroups = campaigns.next()
      .adGroups()
      .get();
  while (adGroups.hasNext()) {
    var ads = adGroups.ads()
       .withCondition('Clicks > 50')
       .forDateRange('LAST_MONTH')
       .get();
    if (ads.totalNumEntities() > 0) {
      // Store (campaign, adGroup) to an array.
    }
  }
}

2 つ目の方法は、アカウント内のキャンペーンと広告グループの階層全体を取得するため、広告セットに関連付けられているキャンペーンと広告グループのサブセットのみが必要である場合におすすめしません。最初のアプローチでは、関連する広告コレクションのみを取得し、適切なメソッドを使用して親オブジェクトにアクセスします。

特定の親フィルタを使う

特定のキャンペーンまたは広告グループ内のエンティティにアクセスするには、取得してから階層を移動するのではなく、セレクタで特定のフィルタを使用します。

指定したキャンペーンと広告グループ内のテキスト広告のリストを取得し、前月に 50 回以上クリックされた広告を取得する次のコード スニペットを比較します。

コーディングのアプローチ コード スニペット
適切な親レベルのフィルタを使用する(推奨)
var ads = AdsApp.ads()
    .withCondition('CampaignName = "Campaign 1"')
    .withCondition('AdGroupName = "AdGroup 1"')
    .withCondition('Clicks > 50')
    .forDateRange('LAST_MONTH')
    .get();

while (ads.hasNext()) {
  var ad = ads.next();
  var adGroup = ad.getAdGroup();
  var campaign = ad.getCampaign();
  // Store (campaign, adGroup, ad) to
  // an array.
}
階層を走査する(推奨されない)
var campaigns = AdsApp.campaigns()
    .withCondition('Name = "Campaign 1"')
    .get();

while (campaigns.hasNext()) {
  var adGroups = campaigns.next()
      .adGroups()
      .withCondition('Name = "AdGroup 1"')
      .get();
  while (adGroups.hasNext()) {
    var ads = adGroups.ads()
       .withCondition('Clicks > 50')
       .forDateRange('LAST_MONTH')
       .get();
    while (ads.hasNext()) {
      var ad = ads.next();
      // Store (campaign, adGroup, ad) to
      // an array.
    }
  }
}

2 番目の方法では、アカウント内のキャンペーンと広告グループの階層をすべて繰り返しますが、必要なのは広告のセットと、その親であるキャンペーンと広告グループであるため、おすすめしません。最初のアプローチでは、セレクタに親エンティティの特定のフィルタを適用することで、反復処理を広告リストに制限します。

可能な場合はフィルタに ID を使う

エンティティをフィルタする場合は、他のフィールドではなく ID でエンティティをフィルタすることをおすすめします。

キャンペーンを選択する以下のコード スニペットを考えてみます。

コーディングのアプローチ コード スニペット
ID でフィルタする(推奨)
var campaign = AdsApp.campaigns()
    .withIds([12345])
    .get()
    .next();
名前でフィルタする(あまり適切ではない)
var campaign = AdsApp.campaigns()
    .withCondition('Name="foo"')
    .get()
    .next();

ID ではないフィールドでフィルタしている後者のアプローチは、可能であれば避けてください。

可能な場合は常に親 ID でフィルタする

エンティティを選択する場合、可能であれば常に親 ID でフィルタしてください。これにより、結果のフィルタリング時にサーバーが取得するエンティティのリストが制限され、クエリの速度が向上します。

広告グループを ID で取得する次のコード スニペットを考えてみます。 親キャンペーン ID が既知であることを前提としています。

コーディングのアプローチ コード スニペット
キャンペーン ID と広告グループ ID でフィルタする(推奨)
var adGroup = AdsApp.adGroups()
    .withIds([12345])
    .withCondition('CampaignId="54678"')
    .get()
    .next();
広告グループ ID のみでフィルタする(最適ではない)
var adGroup = AdsApp.adGroups()
    .withIds([12345])
    .get()
    .next();

どちらのコード スニペットも同じ結果になりますが、コード スニペット 1 で親 ID (CampaignId="54678") を使用してフィルタを追加すると、結果をフィルタリングする際にサーバーが反復処理する必要があるエンティティのリストが制限されるため、コードの効率が向上します。

フィルタ条件が多すぎる場合はラベルを使う

フィルタ条件が多すぎる場合は、処理するエンティティのラベルを作成し、そのラベルを使用してエンティティをフィルタすることをおすすめします。

キャンペーンのリストを名前で取得する次のコード スニペットについて考えてみましょう。

コーディングのアプローチ コード スニペット
ラベルを使用する(推奨)
var label = AdsApp.labels()
    .withCondition('Name = "My Label"')
    .get()
    .next();
var campaigns = label.campaigns.get();
while (campaigns.hasNext()) {
  var campaign = campaigns.next();
  // Do more work
}
複雑なセレクタを作成する(非推奨)
var campaignNames = [‘foo’, ‘bar’, ‘baz’];

for (var i = 0; i < campaignNames.length; i++) {
  campaignNames[i] = '"' + campaignNames[i] + '"';
}

var campaigns = AdsApp.campaigns
    .withCondition('CampaignName in [' + campaignNames.join(',') + ']')
    .get();

while (campaigns.hasNext()) {
  var campaign = campaigns.next();
  // Do more work.
}

どちらのコード スニペットでもパフォーマンスは同程度ですが、2 つ目のアプローチでは、セレクタの条件数が増えるにつれてコードが複雑になる傾向があります。また、新しいエンティティを追加するようにスクリプトを編集するよりも、新しいエンティティにラベルを適用する方が簡単です。

IN 句に含める条件の数を制限する

スクリプトを実行する場合の一般的なユースケースは、エンティティのリストのレポートを実行することです。通常、デベロッパーは、IN 句を使用してエンティティ ID でフィルタする非常に長い AWQL クエリを作成することで、この処理を行います。このアプローチは、エンティティの数が限られている場合に適しています。ただし、クエリの長さが増加すると、次の 2 つの理由でスクリプトのパフォーマンスが低下します。

  • クエリが長くなると解析時間も長くなる。
  • IN 句に追加する ID は、評価する条件が増えるため、時間がかかります。

このような場合は、エンティティにラベルを適用してから、LabelId でフィルタリングすることをおすすめします。

コーディングのアプローチ コード スニペット
ラベルを適用して labelID でフィルタする(推奨)
// The label applied to the entity is "Report Entities"
var label = AdsApp.labels()
    .withCondition('LabelName contains "Report Entities"')
    .get()
    .next();

var report = AdsApp.report('SELECT AdGroupId, Id, Clicks, ' +
    'Impressions, Cost FROM KEYWORDS_PERFORMANCE_REPORT ' +
    'WHERE LabelId = "' + label.getId() + '"');
IN 句を使用して長いクエリを作成する(非推奨)
var report = AdsApp.report('SELECT AdGroupId, Id, Clicks, ' +
    'Impressions, Cost FROM KEYWORDS_PERFORMANCE_REPORT WHERE ' +
    'AdGroupId IN (123, 456) and Id in (123,345, 456…)');

アカウントの変更

一括で変更する

Google 広告エンティティに変更を加えても、Google 広告スクリプトでは変更がすぐに実行されるわけではありません。代わりに、複数の変更をバッチにまとめ、複数の変更を行う単一のリクエストを発行できるようにします。この方法では、スクリプトの実行速度が向上し、Google 広告サーバーへの負荷が軽減されます。ただし、Google 広告スクリプトがオペレーションのバッチを頻繁にフラッシュし、スクリプトの実行速度が低下するコードパターンもあります。

キーワードリストの入札単価を更新する以下のスクリプトを考えてみます。

コーディングのアプローチ コード スニペット
更新された要素を追跡する(推奨)
var keywords = AdsApp.keywords()
    .withCondition('Clicks > 50')
    .withCondition('CampaignName = "Campaign 1"')
    .withCondition('AdGroupName = "AdGroup 1"')
    .forDateRange('LAST_MONTH')
    .get();

var list = [];
while (keywords.hasNext()) {
  var keyword = keywords.next();
  keyword.bidding().setCpc(1.5);
  list.push(keyword);
}

for (var i = 0; i < list.length; i++) {
  var keyword = list[i];
  Logger.log('%s, %s', keyword.getText(),
      keyword.bidding().getCpc());
}
更新された要素をタイトなループで取得する(非推奨)
var keywords = AdsApp.keywords()
    .withCondition('Clicks > 50')
    .withCondition('CampaignName = "Campaign 1"')
    .withCondition('AdGroupName = "AdGroup 1"')
    .forDateRange('LAST_MONTH')
    .get();

while (keywords.hasNext()) {
  var keyword = keywords.next();
  keyword.bidding().setCpc(1.5);
  Logger.log('%s, %s', keyword.getText(),
      keyword.bidding().getCpc());
}

2 つ目のアプローチは、keyword.bidding().getCpc() の呼び出しにより Google 広告スクリプトが setCpc() オペレーションを強制的にフラッシュし、一度に 1 つのオペレーションしか実行されないため、おすすめしません。最初のアプローチは 2 つ目のアプローチと似ていますが、getCpc() 呼び出しが setCpc() が呼び出されるループとは別のループで行われるため、バッチ処理をサポートするという利点があります。

可能な場合はビルダーを使う

Google 広告スクリプトでは、新しいオブジェクトを作成するための 2 つの方法(ビルダーと作成メソッド)がサポートされています。ビルダーは、API 呼び出しから作成されたオブジェクトにアクセスできるため、作成メソッドよりも柔軟性があります。

以下のコード スニペットを考えてみます。

コーディングのアプローチ コード スニペット
ビルダーを使用する(推奨)
var operation = adGroup.newKeywordBuilder()
    .withText('shoes')
    .build();
var keyword = operation.getResult();
作成方法を使用する(非推奨)
adGroup.createKeyword('shoes');
var keyword = adGroup.keywords()
    .withCondition('KeywordText="shoes"')
    .get()
    .next();

2 つ目の方法は、キーワードの取得に追加の選択オペレーションが伴うため、推奨されません。また、作成メソッドも非推奨になりました。

ただし、ビルダーを誤って使用すると、Google 広告スクリプトがオペレーションをバッチ処理できなくなる可能性があります。

キーワードのリストを作成して、新しく作成されたキーワードの ID を出力する次のコード スニペットについて考えてみましょう。

コーディングのアプローチ コード スニペット
更新された要素を追跡する(推奨)
var keywords = [‘foo’, ‘bar’, ‘baz’];

var list = [];
for (var i = 0; i < keywords.length; i++) {
  var operation = adGroup.newKeywordBuilder()
      .withText(keywords[i])
      .build();
  list.push(operation);
}

for (var i = 0; i < list.length; i++) {
  var operation = list[i];
  var result = operation.getResult();
  Logger.log('%s %s', result.getId(),
      result.getText());
}
更新された要素をタイトなループで取得する(非推奨)
var keywords = [‘foo’, ‘bar’, ‘baz’];

for (var i = 0; i < keywords.length; i++) {
  var operation = adGroup.newKeywordBuilder()
      .withText(keywords[i])
      .build();
  var result = operation.getResult();
  Logger.log('%s %s', result.getId(),
      result.getText());
}

2 つ目の方法は、オペレーションを作成する同じループ内で operation.getResult() を呼び出すため、Google 広告スクリプトが一度に 1 つのオペレーションしか実行できないため、推奨されません。最初のアプローチは類似していますが、作成された場所とは異なるループで operation.getResult() を呼び出すため、バッチ処理が可能です。

更新が大規模な場合は一括アップロードを検討する

デベロッパーが一般的に行う作業は、レポートを実行し、現在のパフォーマンス値に基づいてエンティティのプロパティ(キーワードの入札単価など)を更新することです。多数のエンティティを更新する必要がある場合は、一括アップロードのほうがパフォーマンスが向上する傾向があります。たとえば、過去 1 か月の TopImpressionPercentage > 0.4 が大きいキーワードの MaxCpc を増やす次のスクリプトについて考えてみましょう。

コーディングのアプローチ コード スニペット
一括アップロードを使用する(推奨)

var report = AdsApp.report(
  'SELECT AdGroupId, Id, CpcBid FROM KEYWORDS_PERFORMANCE_REPORT ' +
  'WHERE TopImpressionPercentage > 0.4 DURING LAST_MONTH');

var upload = AdsApp.bulkUploads().newCsvUpload([
  report.getColumnHeader('AdGroupId').getBulkUploadColumnName(),
  report.getColumnHeader('Id').getBulkUploadColumnName(),
  report.getColumnHeader('CpcBid').getBulkUploadColumnName()]);
upload.forCampaignManagement();

var reportRows = report.rows();
while (reportRows.hasNext()) {
  var row = reportRows.next();
  row['CpcBid'] = row['CpcBid'] + 0.02;
  upload.append(row.formatForUpload());
}

upload.apply();
ID でキーワードを選択して更新する(最適ではない)
var reportRows = AdsApp.report('SELECT AdGroupId, Id, CpcBid FROM ' +
    'KEYWORDS_PERFORMANCE_REPORT WHERE TopImpressionPercentage > 0.4 ' +
    ' DURING LAST_MONTH')
    .rows();

var map = {
};

while (reportRows.hasNext()) {
  var row = reportRows.next();
  var adGroupId = row['AdGroupId'];
  var id = row['Id'];

  if (map[adGroupId] == null) {
    map[adGroupId] = [];
  }
  map[adGroupId].push([adGroupId, id]);
}

for (var key in map) {
  var keywords = AdsApp.keywords()
      .withCondition('AdGroupId="' + key + '"')
      .withIds(map[key])
      .get();

  while (keywords.hasNext()) {
    var keyword = keywords.next();
    keyword.bidding().setCpc(keyword.bidding().getCpc() + 0.02);
  }
}

2 つ目のアプローチはパフォーマンスが非常に優れていますが、この場合は次の理由から 1 つ目のアプローチが推奨されます。

  • Google 広告スクリプトでは、1 回の実行で取得または更新できるオブジェクトの数に上限があり、2 つ目の方法の選択と更新のオペレーションはその上限にカウントされます。

  • 一括アップロードでは、更新できるエンティティの数と全体的な実行時間の両方に上限があります。

複数の一括アップロードをキャンペーン別にグループ化する

一括アップロードを作成する際は、親キャンペーンごとにオペレーションをグループ化することをおすすめします。これにより、効率が向上し、競合する変更や同時実行エラーが発生する可能性が低くなります。

2 つの一括アップロード タスクを並列で実行する場合を考えてみます。1 つは広告グループ内の広告を一時停止し、もう 1 つはキーワードの入札単価を調整します。オペレーションは関連性がなくても、同じ広告グループ(または同じキャンペーンの 2 つの異なる広告グループ)のエンティティに適用される場合があります。その場合、親エンティティ(共有の広告グループまたはキャンペーン)がロックされ、一括アップロードのタスクが互いにブロックされることになります。

Google 広告スクリプトでは、1 つの一括アップロード タスク内で実行を最適化できるため、最も簡単な方法は、アカウントごとに一度に 1 つの一括アップロード タスクのみを実行することです。アカウントごとに複数の一括アップロードを実行する場合は、パフォーマンスを最適化するために、一括アップロードが相互に排他的なキャンペーン リスト(およびその子エンティティ)で実行されるようにしてください。

レポート

レポートを使って統計情報を取得する

大量のエンティティとその統計情報を取得する必要がある場合は、標準の AdsApp メソッドではなくレポートの使用をおすすめします。レポートを使用することをおすすめします。その理由は次のとおりです。

  • 大規模なクエリで良好なパフォーマンスを得られる。
  • 一般的な取得数の上限に達することがない。

先月 50 回以上のクリックを獲得したすべてのキーワードのクリック数、インプレッション数、費用、テキストを取得する次のコード スニペットを比較します。

コーディングのアプローチ コード スニペット
レポートを使用する(推奨)
  report = AdsApp.search(
      'SELECT ' +
      '   ad_group_criterion.keyword.text, ' +
      '   metrics.clicks, ' +
      '   metrics.cost_micros, ' +
      '   metrics.impressions ' +
      'FROM ' +
      '   keyword_view ' +
      'WHERE ' +
      '   segments.date DURING LAST_MONTH ' +
      '   AND metrics.clicks > 50');
  while (report.hasNext()) {
    var row = report.next();
    Logger.log('Keyword: %s Impressions: %s ' +
        'Clicks: %s Cost: %s',
        row.adGroupCriterion.keyword.text,
        row.metrics.impressions,
        row.metrics.clicks,
        row.metrics.cost);
  }
AdsApp イテレータを使用する(非推奨)
var keywords = AdsApp.keywords()
    .withCondition('metrics.clicks > 50')
    .forDateRange('LAST_MONTH')
    .get();
while (keywords.hasNext()) {
  var keyword = keywords.next();
  var stats = keyword.getStatsFor('LAST_MONTH');
  Logger.log('Keyword: %s Impressions: %s ' +
      'Clicks: %s Cost: %s',
      keyword.getText(),
      stats.getImpressions(),
      stats.getClicks(),
      stats.getCost());
}

2 番目の方法は、キーワードを反復処理してエンティティを 1 つずつ取得するため、おすすめしません。この場合、すべてのデータを 1 回の呼び出しで取得し、必要に応じてストリーミングするため、レポートのパフォーマンスが向上します。さらに、2 番目の方法で取得したキーワードは、get() 呼び出しで取得されるエンティティ数のスクリプトの割り当てにカウントされます。

レポートではなく検索を使用する

report メソッドは古いインフラストラクチャ用に構築されており、GAQL を使用している場合でも結果がフラット形式で出力されます。つまり、クエリの結果を変換して古いスタイルに合わせる必要があります。これはすべてのフィールドでサポートされているわけではなく、各呼び出しにオーバーヘッドが発生します。

代わりに検索を使用して、新しい Google Ads API レポートのすべての機能を活用することをおすすめします。

AWQL よりも GAQL を優先する

AWQL はレポートクエリと withCondition 呼び出しでは引き続きサポートされますが、実際の AWQL との完全な互換性がない変換レイヤを介して実行されます。クエリを完全に制御するには、GAQL を使用していることを確認してください。

変換する既存の AWQL クエリがある場合は、クエリ移行ツールを使用できます。

必要以上に行を選択しない

レポート(およびセレクタ)の実行速度は、反復処理を行うかどうかに関係なく、レポートによって返される行の合計数に基づきます。つまり、ユースケースに合わせて、常に特定のフィルタを使用して結果セットを可能な限り最小限に抑える必要があります。

たとえば、特定の範囲外の入札単価の広告グループを探す場合、すべての広告グループを取得して、関心のない広告グループを無視するよりも、下限しきい値を下回る入札単価と上限しきい値を超える入札単価の 2 つのクエリを別々に実行するほうが速くなります。

コーディングのアプローチ コード スニペット
2 つのクエリを使用する(推奨)
var adGroups = []
var report = AdsApp.search(
    'SELECT ad_group.name, ad_group.cpc_bid_micros' +
    ' FROM ad_group WHERE ad_group.cpc_bid_micros < 1000000');

while (report.hasNext()) {
  var row = report.next();
  adGroups.push(row.adGroup);
}
var report = AdsApp.search(
    'SELECT ad_group.name, ad_group.cpc_bid_micros' +
    ' FROM ad_group WHERE ad_group.cpc_bid_micros > 2000000');

while (report.hasNext()) {
  var row = report.next();
  adGroups.push(row.adGroup);
}
汎用クエリから絞り込む(推奨されない)
var adGroups = []
var report = AdsApp.search(
    'SELECT ad_group.name, ad_group.cpc_bid_micros' +
    ' FROM ad_group');

while (report.hasNext()) {
  var row = report.next();
  var cpcBidMicros = row.adGroup.cpcBidMicros;
  if (cpcBidMicros < 1000000 || cpcBidMicros > 2000000) {
    adGroups.push(row.adGroup);
  }
}

アド マネージャー(MCC)のスクリプト

可能な限りシリアル実行ではなく executeInParallel を使う

MCC アカウントのスクリプトを作成する場合は、可能であれば、シリアル実行ではなく executeInParallel() を使用してください。executeInParallel() を使用すると、スクリプトの処理時間が長くなり(最大 1 時間)、処理されるアカウントごとに最大 30 分(シリアル実行の場合は合計 30 分)の時間が確保されます。詳しくは、上限に関するページをご覧ください。

スプレッドシート

スプレッドシートの更新には一括操作を使う

スプレッドシートを更新するときは、一度に 1 つのセルを更新するメソッドではなく、一括操作メソッド(getRange() など)を使用するようにしてください。

スプレッドシートにフラクタル パターンを生成する次のコード スニペットについて考えてみましょう。

コーディングのアプローチ コード スニペット
1 回の呼び出しでセルの範囲を更新する(推奨)
var colors = new Array(100);
for (var y = 0; y < 100; y++) {
  xcoord = xmin;
  colors[y] = new Array(100);
  for (var x = 0; x < 100; x++) {
    colors[y][x] = getColor_(xcoord, ycoord);
    xcoord += xincrement;
  }
  ycoord -= yincrement;
}
sheet.getRange(1, 1, 100, 100).setBackgroundColors(colors);
一度に 1 つのセルを更新する(非推奨)
var cell = sheet.getRange('a1');
for (var y = 0; y < 100; y++) {
  xcoord = xmin;
  for (var x = 0; x < 100; x++) {
    var c = getColor_(xcoord, ycoord);
    cell.offset(y, x).setBackgroundColor(c);
    xcoord += xincrement;
  }
  ycoord -= yincrement;
  SpreadsheetApp.flush();
}

Google スプレッドシートは値をキャッシュに保存することで 2 つ目のコード スニペットを最適化しようとしますが、API 呼び出しの数が多いため、最初のスニペットと比較してパフォーマンスが低下します。