Microsoft Advertising Scripts のベスト プラクティス

スクリプトのパフォーマンスとプラットフォームのパフォーマンスを向上させるには、次に示すベスト プラクティスを確認して従ってください。

セレクターの操作

フィルターを使用する

エンティティを自分でフィルター処理するのではなく、セレクターのフィルターを使用します。 セレクターを使用すると、ID と条件でフィルター処理できます。 たとえば、エンティティのパフォーマンス (平均クリック単価が 10 を超えるキャンペーンの返品)、その状態 (一時停止されているキャンペーン)、エンティティの親オブジェクトの名前などをフィルター処理できます。

フィルターを使用する利点:

  • セレクターから返されるエンティティの数を、必要なエンティティのみに制限 します

  • スクリプトをより高速に実行できます (返して処理するエンティティが少なくなります)

  • エンティティの読み取り制限にぶつかる可能性を減らします ( 「スクリプトの実行制限」を参照してください)。

正しい方法

    var adGroups = AdsApp.adGroups()
        .withCondition('Status = PAUSED')
        .get();

    while (adGroups.hasNext()) {
        var adGroup = adGroups.next();
        // Do something with paused ad group.
    }

間違った方法

    var adGroups = AdsApp.adGroups().get();

    while (adGroups.hasNext()) {
        var adGroup = adGroups.next();
        
        if (adGroup.isPaused() == true) {
            // Do something with paused ad group.
        }
    }

エンティティ階層を走査しない

エンティティの子エンティティまたはエンティティの親エンティティを取得する場合は、エンティティ階層を走査して取得しないでください。

子エンティティを取得するには、必要なレベルで子エンティティのコレクションを使用します。

正しい方法

    // Get all ads.
    var ads = AdsApp.ads().get();

    while (ads.hasNext()) {
        var ad = ads.next();
        // Do something with ad.
    }

または、特定のキャンペーンの広告が必要な場合:

    // Get all ads in the campaign, 'mycampaign'.
    var ads = AdsApp.ads()
        .withCondition("CampaignName = 'mycampaign'")
        .get();

    while (ads.hasNext()) {
        var ad = ads.next();
        // Do something with ad.
    }

または、キャンペーン オブジェクトがある場合にキャンペーンの広告を取得します。

    // Get all ads in the campaign.
    var ads = campaign.ads().get();

    while (ads.hasNext()) {
        var ad = ads.next();
        // Do something with ad.
    }

間違った方法

    var campaigns = AdsApp.campaigns().get();

    while (campaigns.hasNext()) {
        var adGroups = campaigns.next().adGroups().get();
        
        while (adGroups.hasNext()) {
            var ads = adGroups.next().ads().get();

            while (ads.hasNext()) {
                var ad = ads.next();
                // Do something with ad.
            }
        }
    }

エンティティの親を取得する場合も同じことが当てはまります。 階層を走査して親を取得する代わりに、子エンティティの親アクセサー メソッドを使用します。

正しい方法

    // Get all ads.
    var ads = AdsApp.ads()
        .withCondition('Clicks > 5')
        .forDateRange('LAST_7_DAYS')
        .get();

    while (ads.hasNext()) {
        var ad = ads.next();
        
        // Do something with campaign and adGroup.
        var adGroup = ad.adGroup();
        var campaign = ad.campaign();
    }

間違った方法

    var campaigns = AdsApp.campaigns().get();

    while (campaigns.hasNext()) {
        var campaign = campaigns.next();
        var adGroups = campaign.adGroups().get();
        
        while (adGroups.hasNext()) {
            var adGroup = adGroups.next();
            var ads = adGroup.ads().get();

            while (ads.hasNext()) {
                var ad = ads.next();
                
                if ('<some condition is met>') {
                    // Do something with campaign and adGroup.
                }
            }
        }
    }

可能な場合はエンティティ ID を使用する

ID を使用してエンティティをフィルター処理すると、最適なパフォーマンスが得られます。

これ

    var adGroups = AdsApp.adGroups()
        .withIds(["123456"])
        .get();

    while (adGroups.hasNext()) {
        var adGroup = adGroups.next();
        
        // Do something with adGroup.
    }

これより優れたパフォーマンスを提供します

    var adGroups = AdsApp.adGroups()
        .withCondition("Name = 'myadgroup'")
        .get();

    while (adGroups.hasNext()) {
        var adGroup = adGroups.next();
        
        // Do something with adGroup.
    }

セレクターと不要な数の取得によるタイトなループを避ける

単一のエンティティを取得する get 要求を含むループを回避します。 たとえば、キーワード パフォーマンス レポートを実行し、レポート内のキーワードを更新するとします。 レポートから行を取得し、キーワードを取得してから更新する代わりに、レポート内の各行をループするときにキーワード ID の一覧を作成する必要があります。 次に、ID の一覧をセレクターに渡して、1 つの get 要求内のすべてのキーワードを取得します。 その後、キーワードの一覧を反復子して更新できます。

正しい方法

    var report = AdsApp.report('<report query goes here>');

    var rows = report.rows();
    var idLists = []; // an array where each element contains an array of IDs.
    var idList = [];  // array of IDs that's limited to maxCount.
    var maxCount = 10000;

    while (rows.hasNext()) {
        var row = rows.next();

        if (idList.length < maxCount) {
            idList.push(row['id']);
        }
        else {
            idLists.push(idList);
            idList = [];
        }
    }

    for (idList of idLists) {
        var keywords = AdsApp.keywords()
            .withIds(idList)
            .get();

        while (keywords.hasNext()) {
            var keyword = keywords.next();
            // update the keyword        
        }
    }

間違った方法

    var report = AdsApp.report('<report query goes here>');

    var rows = report.rows();

    while (rows.hasNext()) {
        var row = rows.next();

        var keyword = AdsApp.keywords()
            .withIds([row['id']])
            .get()
            .next();

        // update the keyword        
    }

エンティティの getStats メソッドを呼び出す予定の場合にのみ、forDateRange メソッドを含めます

セレクターの forDateRange メソッドを呼び出すと、セレクターはエンティティのパフォーマンス データを取得します。 エンティティのパフォーマンス データの取得にはコストがかかるため、エンティティのメソッドを呼び出してデータを使用する予定がある getStats 場合にのみ取得します。

エンティティに対して指定する日付範囲は、そのエンティティからアクセスする親エンティティまたは子エンティティには適用されません。 たとえば、広告グループを取得し、その親キャンペーンを取得し、キャンペーンのパフォーマンス 指標にアクセスしようとすると、通話は失敗します。

次の例の呼び出しは campaignStats.getReturnOnAdSpend() 、日付範囲がキャンペーンではなく広告グループに適用されるため、失敗します。

    var myAdGroups = AdsApp.adGroups().
        .withCondition("CampaignName CONTAINS 'gen'")
        .forDateRange("LAST_7_DAYS")
        .get();

    while (myAdGroups.hasNext()) {
        var adGroup = myAdGroups.next();
        var campaign = adGroup.getCampaign();
        var campaignStats = campaign.getStats();
        var campaignROAS = campaignStats.getReturnOnAdSpend();
    }

これを機能させるには、キャンペーンのセレクターを作成する必要があります。

    var myAdGroups = AdsApp.adGroups().
        .withCondition("CampaignName CONTAINS 'gen'")
        .forDateRange("LAST_7_DAYS")
        .get();

    while (myAdGroups.hasNext()) {
        var adGroup = myAdGroups.next();
        var campaign = AdsApp.campaigns()
            .withIds([adGroup.getCampaign().getId()])
            .forDateRange("LAST_7_DAYS")
            .get()
            .next();
        var campaignStats = campaign.getStats();
        var campaignROAS = campaignStats.getReturnOnAdSpend();
    }

セレクターで条件として使用されるエンティティのプロパティを変更しないでください

反復子は、一連の項目全体ではなく、一度に 1 つの項目のみを読み込むことにより、メモリ負荷を軽減します。 このため、セレクターで条件として使用したプロパティを変更すると、予期しない動作が発生する可能性があります。

正しい方法

    var adGroups = []; 

    var iterator = AdsApp.adGroups()
        .withCondition('Status = ENABLED')
        .get();

    while (iterator.hasNext()) {
        adGroups.push(iterator.next());
    }

    for (var adGroup of adGroups) {
        adGroup.pause();
    }

間違った方法

    var adGroups = AdsApp.adGroups()
        .withCondition('Status = ENABLED')
        .get();

    while (adGroups.hasNext()) {
        var adGroup = adGroups.next();
        adGroup.pause();
    }

更新プログラムのバッチ処理

パフォーマンスを向上させるために、スクリプトはビルド要求をバッチ処理します。 ビルド要求の操作メソッドを呼び出すと、スクリプトはキューに登録されたビルド要求を直ちに処理し、パフォーマンスの向上を否定します。 複数のエンティティを作成する場合は、エンティティのビルドに使用するのと同じループで操作メソッドを実行しないでください。 一度に処理されるエンティティは 1 つだけであるため、パフォーマンスが低下します。 代わりに、操作の配列を作成し、ビルド ループの後に処理します。

正しい方法

    // An array to hold the operations, so you 
    // can process them after all the entities are queued.
    var operations = []; 

    // Create all the new entities.
    for (var i = 0; i < keywords.length; i++) {
        var keywordOperation = AdsApp.adGroups().get().next()
          .newKeywordBuilder()
          .withText(keywords[i])
          .build();
        operations.push(keywordOperation);
    }

    // Now call the operation method so the build requests
    // get processed in batches.
    for (var i = 0; i < operations.length; i++) {
        var newKeyword = operations[i].getResult();
    }

間違った方法

    for (var i = 0; i < keywords.length; i++) {
        var keywordOperation = AdsApp.adGroups().get().next()  // Get the first ad group
          .newKeywordBuilder()  // Add the keyword to the ad group
          .withText(keywords[i])
          .build();

        // Don't get results in the same loop that creates
        // the entity because Scripts then only processes one
        // entity at a time.
        var newKeyword = keywordOperation.getResult();
    }

エンティティを更新し、更新したのと同じプロパティを取得する場合も同じことが当てはまります。 次の操作を行わないでください

    var bidAmount = 1.2;

    while (keywords.hasNext()) {
        var keyword = keywords.next();

        keyword.bidding().setCpc(bidAmount);

        if (keyword.bidding().getCpc() != bidAmount) {
            Logger.log(`Failed to update bid amount for keyword, ${keyword.getText()} (${keyword.getId()})`);
        }
    }

大規模なエンティティ セットを取得する場合は、yield キーワードを使用します

多数のエンティティを取得し、ループで処理する単一のリストに読み込むには、いくつかの欠点があります。

  1. 要求のサイズによっては、ループが開始される前にすべてのエンティティをフェッチするために、 n 個の バックエンド要求が必要になる場合があります。 それらすべてを処理しない場合、未処理のエンティティを取得するために使用される時間とコンピューティング能力は無駄になります。 たとえば、10K キーワードを取得し、2K キーワードのみを処理した後にループが中断した場合、残りの 8K キーワードを取得するために使用される時間とコンピューティング能力は無駄になります。

  2. リストを作成するには、すべてのエンティティを同時に保持するために、より多くのメモリが必要です。

これらの問題に対処するには、 yield キーワードを使用します。これにより、スクリプトは必要に応じてエンティティをフェッチできます。つまり、ある意味では、必要なときにのみエンティティを "遅延" でフェッチできます。 つまり、スクリプトは現時点で必要以上に多くの呼び出しを行っず、オブジェクトの大規模なリストを渡していません。

この例には、 yield キーワードを使用する場合の制御フローを示すログ記録が含まれています。

function main() {
    const keywords = getKeywords();

    //@ts-ignore <-- suppresses iterator error
    for (const keyword of keywords) {
        Logger.log("in for loop\n\n");
    }
}

// Note that you must use the yield keyword in a generator function - see the
// '*' at the end of the function keyword.

function* getKeywords() {
    const keywords = AdsApp.keywords()
        .withCondition("Status = ENABLED")
        .withCondition("CombinedApprovalStatus = APPROVED")
        .withLimit(10)
        .get();

    Logger.log(`total keywords in account: ${keywords.totalNumEntities()} \n\n`);

    while (keywords.hasNext()) {
        Logger.log("before next()\n\n");
        yield keywords.next();
        Logger.log("after next\n\n");
    }
}

エンティティの制限を回避するための呼び出しパターン

アカウントに対してスクリプトが返すことができるエンティティの数には制限があります。 要求がこの制限を超える値を返す場合、スクリプトはメッセージでエラーをスローします。 エンティティが多すぎます。 次の例は、多数のエンティティを取得するときに使用する必要がある呼び出しパターンを示しています。 この例では、最初にアカウント レベルですべてのキーワードをフェッチしようとします。 失敗した場合は、通常、キャンペーン レベルのエンティティが少ないため、キャンペーンごとにキーワードをフェッチするための複数の呼び出しが試行されます。 通常、このパターンは、必要に応じて広告グループ レベルまで続行できます。

yield キーワードの使用については、「大規模なエンティティ セットを取得するときに yield キーワードを使用する」を参照してください。

function* getEntities() {
    const applyConditions = _ => _
        .withCondition('CampaignStatus = ENABLED')
        .withCondition('AdGroupStatus = ENABLED')
        .withCondition('Status = ENABLED')
        .withCondition("CombinedApprovalStatus = DISAPPROVED");

    try {
        // Get the account's keywords. 
        const keywords = applyConditions(AdsApp.keywords()).get();

        while (keywords.hasNext()) {
            yield keywords.next();
        }
    } catch (e) {
        if (!e.message.startsWith('There are too many entities')) {
            throw e;
        }

        // If there are too many keywords at the account level,
        // get keywords by campaigns under the account.
        const campaigns = AdsApp.campaigns().get();

        while (campaigns.hasNext()) {
            const campaign = campaigns.next();

            const keywords = applyConditions(campaign.keywords()).get();

            while (keywords.hasNext()) {
                yield keywords.next();
            }
        }
    }
}