スクリプトのパフォーマンスとプラットフォームのパフォーマンスを向上させるには、以下に概説するベスト プラクティスを確認して、それに従ってください。
セレクターの操作
フィルターを使用する
エンティティを自分でフィルター処理する代わりに、セレクターのフィルターを使用します。 セレクターを使用すると、ID と条件でフィルター処理できます。 たとえば、エンティティのパフォーマンス (平均 CPC が 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.
}
セレクターや不要な数の取得によるタイトなループを避ける
1 つのエンティティを取得する GET 要求でのループを避けます。 たとえば、キーワード (keyword) パフォーマンス レポートを実行し、レポート内のキーワードを更新したいとします。 レポートから行を取得してキーワード (keyword)を取得してから更新するのではなく、レポートの各行をループするときにキーワード (keyword) 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 キーワード (keyword) を使用します
多数のエンティティを取得し、ループで処理する 1 つのリストに読み込む場合、いくつかの欠点があります。
要求のサイズによっては、ループが開始されるまでにすべてのエンティティをフェッチするために n 個のバックエンド要求が必要になる場合があります。 それらをすべて処理しないと、未処理のエンティティを取得するために使用される時間とコンピューティング能力が無駄になります。 たとえば、10K のキーワードを取得し、2K のキーワードのみを処理した後にループが中断した場合、残りの 8K のキーワードを取得するのに使用される時間と計算能力が無駄になります。
リストを作成するには、すべてのエンティティを同時に保持するためにより多くのメモリが必要です。
これらの問題に対処するには、yield キーワード (keyword) を使用します。これにより、スクリプトはエンティティをオンデマンドでフェッチしたり、ある意味では必要なときにのみ "怠惰" フェッチしたりできます。 これは、スクリプトがその時点で必要とする以上の呼び出しを行っておらず、オブジェクトの大きなリストを渡していないことを意味します。
この例には、降伏キーワード (keyword)を使用したときの制御のフローを示すためのログ記録が含まれています。
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 キーワード (keyword) の使用の詳細については、「大規模なエンティティ セットを取得するときに yield キーワード (keyword) を使用する」を参照してください。
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();
}
}
}
}