知识库首页 广告 cpc.txt

cpc

本地来源:广告/ads-data-script/cpc.txt

/**
 * Global CPC cap script (Google Ads Scripts)
 *
 * What this script does:
 * 1) Keyword layer: normalize keyword max CPC to TARGET_CPC
 * 2) Ad group layer: normalize ad group max CPC to TARGET_CPC
 * 3) Campaign layer: monitor only. Report campaigns whose realized avg CPC
 *    is still above CAP_THRESHOLD in the chosen date range.
 *
 * Important:
 * - In Google Ads Scripts, campaign avg CPC is a performance metric, not a
 *   generic "campaign max CPC" knob that can be safely normalized the same way
 *   as keyword / ad group bids in this account structure.
 * - This script therefore writes on keyword + ad group, and reports on campaign.
 */

const CONFIG = {
  TARGET_CPC: 0.09,
  CAP_THRESHOLD: 0.13,

  DRY_RUN: false,
  ONLY_ENABLED: false,
  REQUIRE_CLICKS: false,
  CLICK_DATE_RANGE: 'LAST_30_DAYS',

  RUN_KEYWORD_CAP: true,
  RUN_ADGROUP_CAP: true,
  RUN_CAMPAIGN_MONITOR: true,

  KEYWORD_SELECTOR_LIMIT: 200000,
  ADGROUP_SELECTOR_LIMIT: 200000,
  CAMPAIGN_SELECTOR_LIMIT: 50000,

  KEYWORD_MAX_UPDATES: 20000,
  ADGROUP_MAX_UPDATES: 20000,

  CAMPAIGN_MONITOR_DATE_RANGE: 'LAST_7_DAYS',
  CAMPAIGN_MONITOR_TOP_N: 100,

  LOG_SAMPLE_LIMIT: 50,
  LOG_ERROR_LIMIT: 100,
};

function shouldNormalizeBid(oldCpc) {
  if (oldCpc === null || oldCpc === undefined || isNaN(oldCpc)) return true;
  return Math.abs(Number(oldCpc) - CONFIG.TARGET_CPC) > 1e-9;
}

function isEnabledEntity(entity) {
  return !CONFIG.ONLY_ENABLED || entity.isEnabled();
}

function isManualCpcCampaign(campaign) {
  const strategy = String(
    campaign && campaign.getBiddingStrategyType
      ? campaign.getBiddingStrategyType()
      : ''
  );
  return !strategy || strategy === 'MANUAL_CPC';
}

function safeMessage(error) {
  if (!error) return 'Unknown error';
  if (error.message) return String(error.message);
  return String(error);
}

function pushSample(samples, line) {
  if (samples.length < CONFIG.LOG_SAMPLE_LIMIT) {
    samples.push(line);
  }
}

function pushError(samples, line) {
  if (samples.length < CONFIG.LOG_ERROR_LIMIT) {
    samples.push(line);
  }
}

function formatCpc(value) {
  if (value === null || value === undefined || isNaN(value)) return 'null';
  return Number(value).toFixed(3);
}

function logLines(title, lines) {
  if (!lines.length) return;
  Logger.log(title);
  for (var i = 0; i < lines.length; i++) {
    Logger.log(lines[i]);
  }
}

function runKeywordCap() {
  const stats = {
    layer: 'keyword',
    scanned: 0,
    wouldUpdate: 0,
    updated: 0,
    skippedDisabled: 0,
    skippedNonManualStrategy: 0,
    skippedNoBid: 0,
    skippedNoRuleMatch: 0,
    errors: 0,
  };

  const samples = [];
  const errors = [];
  let selector = AdsApp.keywords();
  if (CONFIG.REQUIRE_CLICKS) {
    selector = selector
      .forDateRange(CONFIG.CLICK_DATE_RANGE)
      .withCondition('metrics.clicks > 0');
  }
  const it = selector.withLimit(CONFIG.KEYWORD_SELECTOR_LIMIT).get();

  while (it.hasNext()) {
    const keyword = it.next();
    stats.scanned++;

    const campaign = keyword.getCampaign();
    const adGroup = keyword.getAdGroup();

    if (!isEnabledEntity(campaign) || !isEnabledEntity(adGroup) || !isEnabledEntity(keyword)) {
      stats.skippedDisabled++;
      continue;
    }

    if (!isManualCpcCampaign(campaign)) {
      stats.skippedNonManualStrategy++;
      continue;
    }

    const oldCpc = keyword.bidding().getCpc();
    if (!shouldNormalizeBid(oldCpc)) {
      stats.skippedNoRuleMatch++;
      continue;
    }

    stats.wouldUpdate++;

    const descriptor =
      campaign.getName() +
      ' | ' +
      adGroup.getName() +
      ' | ' +
      keyword.getText() +
      ' | ' +
      formatCpc(oldCpc) +
      ' -> ' +
      formatCpc(CONFIG.TARGET_CPC);

    pushSample(samples, descriptor);

    if (CONFIG.DRY_RUN) continue;

    if (stats.updated >= CONFIG.KEYWORD_MAX_UPDATES) {
      Logger.log('Keyword layer reached KEYWORD_MAX_UPDATES, stop this run.');
      break;
    }

    try {
      keyword.bidding().setCpc(CONFIG.TARGET_CPC);
      stats.updated++;
    } catch (e) {
      stats.errors++;
      pushError(errors, descriptor + ' | ERROR: ' + safeMessage(e));
    }
  }

  Logger.log('=== Keyword cap summary ===');
  Logger.log(JSON.stringify(stats));
  logLines('--- keyword sample updates ---', samples);
  logLines('--- keyword errors ---', errors);

  return stats;
}

function runAdGroupCap() {
  const stats = {
    layer: 'adgroup',
    scanned: 0,
    wouldUpdate: 0,
    updated: 0,
    skippedDisabled: 0,
    skippedNonManualStrategy: 0,
    skippedNoBid: 0,
    skippedNoRuleMatch: 0,
    errors: 0,
  };

  const samples = [];
  const errors = [];
  let selector = AdsApp.adGroups();
  if (CONFIG.REQUIRE_CLICKS) {
    selector = selector
      .forDateRange(CONFIG.CLICK_DATE_RANGE)
      .withCondition('metrics.clicks > 0');
  }
  const it = selector.withLimit(CONFIG.ADGROUP_SELECTOR_LIMIT).get();

  while (it.hasNext()) {
    const adGroup = it.next();
    stats.scanned++;

    const campaign = adGroup.getCampaign();

    if (!isEnabledEntity(campaign) || !isEnabledEntity(adGroup)) {
      stats.skippedDisabled++;
      continue;
    }

    if (!isManualCpcCampaign(campaign)) {
      stats.skippedNonManualStrategy++;
      continue;
    }

    const oldCpc = adGroup.bidding().getCpc();
    if (!shouldNormalizeBid(oldCpc)) {
      stats.skippedNoRuleMatch++;
      continue;
    }

    stats.wouldUpdate++;

    const descriptor =
      campaign.getName() +
      ' | ' +
      adGroup.getName() +
      ' | ' +
      formatCpc(oldCpc) +
      ' -> ' +
      formatCpc(CONFIG.TARGET_CPC);

    pushSample(samples, descriptor);

    if (CONFIG.DRY_RUN) continue;

    if (stats.updated >= CONFIG.ADGROUP_MAX_UPDATES) {
      Logger.log('Ad group layer reached ADGROUP_MAX_UPDATES, stop this run.');
      break;
    }

    try {
      adGroup.bidding().setCpc(CONFIG.TARGET_CPC);
      stats.updated++;
    } catch (e) {
      stats.errors++;
      pushError(errors, descriptor + ' | ERROR: ' + safeMessage(e));
    }
  }

  Logger.log('=== Ad group cap summary ===');
  Logger.log(JSON.stringify(stats));
  logLines('--- ad group sample updates ---', samples);
  logLines('--- ad group errors ---', errors);

  return stats;
}

function runCampaignMonitor() {
  const stats = {
    layer: 'campaign-monitor',
    scanned: 0,
    withClicks: 0,
    aboveCap: 0,
  };

  const hotCampaigns = [];
  const it = AdsApp.campaigns()
    .withLimit(CONFIG.CAMPAIGN_SELECTOR_LIMIT)
    .get();

  while (it.hasNext()) {
    const campaign = it.next();
    stats.scanned++;

    if (!isEnabledEntity(campaign)) {
      continue;
    }

    const perf = campaign.getStatsFor(CONFIG.CAMPAIGN_MONITOR_DATE_RANGE);
    const clicks = perf.getClicks();
    if (!clicks) {
      continue;
    }
    stats.withClicks++;

    const avgCpc = perf.getAverageCpc();
    if (!avgCpc || avgCpc <= CONFIG.CAP_THRESHOLD + 1e-9) {
      continue;
    }

    stats.aboveCap++;
    hotCampaigns.push({
      name: campaign.getName(),
      clicks: clicks,
      avgCpc: avgCpc,
      cost: perf.getCost(),
      conversions: perf.getConversions(),
      strategy: String(
        campaign.getBiddingStrategyType ? campaign.getBiddingStrategyType() : ''
      ),
    });
  }

  hotCampaigns.sort(function (a, b) {
    if (b.cost !== a.cost) return b.cost - a.cost;
    return b.avgCpc - a.avgCpc;
  });

  Logger.log('=== Campaign monitor summary ===');
  Logger.log(
    JSON.stringify({
      stats: stats,
      dateRange: CONFIG.CAMPAIGN_MONITOR_DATE_RANGE,
      capThreshold: CONFIG.CAP_THRESHOLD,
    })
  );

  const limit = Math.min(CONFIG.CAMPAIGN_MONITOR_TOP_N, hotCampaigns.length);
  for (var i = 0; i < limit; i++) {
    const item = hotCampaigns[i];
    Logger.log(
      item.name +
        ' | clicks=' +
        item.clicks +
        ' | avgCpc=' +
        formatCpc(item.avgCpc) +
        ' | cost=' +
        formatCpc(item.cost) +
        ' | conv=' +
        item.conversions +
        ' | strategy=' +
        item.strategy
    );
  }

  return stats;
}

function main() {
  Logger.log('=== Global CPC cap run start ===');
  Logger.log(
    JSON.stringify({
      mode: 'normalize-to-target-cpc',
      targetCpc: CONFIG.TARGET_CPC,
      capThreshold: CONFIG.CAP_THRESHOLD,
      dryRun: CONFIG.DRY_RUN,
      onlyEnabled: CONFIG.ONLY_ENABLED,
      requireClicks: CONFIG.REQUIRE_CLICKS,
      clickDateRange: CONFIG.CLICK_DATE_RANGE,
      runKeywordCap: CONFIG.RUN_KEYWORD_CAP,
      runAdGroupCap: CONFIG.RUN_ADGROUP_CAP,
      runCampaignMonitor: CONFIG.RUN_CAMPAIGN_MONITOR,
      campaignMonitorDateRange: CONFIG.CAMPAIGN_MONITOR_DATE_RANGE,
    })
  );

  if (CONFIG.RUN_KEYWORD_CAP) {
    runKeywordCap();
  }

  if (CONFIG.RUN_ADGROUP_CAP) {
    runAdGroupCap();
  }

  if (CONFIG.RUN_CAMPAIGN_MONITOR) {
    runCampaignMonitor();
  }

  Logger.log('=== Global CPC cap run end ===');
}

本文档为站内渲染。原始文件本地路径:saas/source/ads/广告-ads-data-script-cpc-c4fbd7.txt(仅本地保留,不入库不部署)