第 13 章

Google Ads 脚本与自动化

自动化是规模化管理的核心能力。本章脚本需经测试后用于生产环境。

★ 与 Premier Partner 的关系

规模化运营需要自动化。Premier Partner 代理需要管理大量账户,手工操作不可持续。

13.1 入门:30 分钟部署你的第一个脚本#

进入 Google Ads → 工具 → 批量操作 → 脚本 → + 新建脚本。编辑器支持 JavaScript ES5。以下是一个最简单的脚本——每天邮件报告:

function main() {
  var report = AdsApp.report(
    "SELECT campaign.name, metrics.impressions, metrics.clicks, metrics.cost_micros, metrics.conversions " +
    "FROM campaign WHERE segments.date DURING YESTERDAY");
  var rows = report.rows();
  var output = "Campaign\tImpressions\tClicks\tCost\tConversions\n";
  while(rows.hasNext()) {
    var row = rows.next();
    output += row["campaign.name"] + "\t" +
      row["metrics.impressions"] + "\t" +
      row["metrics.clicks"] + "\t" +
      (row["metrics.cost_micros"] / 1000000).toFixed(2) + "\t" +
      row["metrics.conversions"] + "\n";
  }
  MailApp.sendEmail("your@email.com", "Daily Report", output);
}

设置执行频率:每天 → 保存 → 预览 → 运行。

13.2 5 个直接可用的脚本#

脚本 1:暂停 7 天无转化的关键词

function main() {
  var campaignIterator = AdsApp.campaigns().get();
  while(campaignIterator.hasNext()) {
    var campaign = campaignIterator.next();
    var keywordIterator = campaign.keywords()
      .withCondition("ad_group.status = ENABLED")
      .withCondition("keyword.status = ENABLED")
      .get();
    while(keywordIterator.hasNext()) {
      var keyword = keywordIterator.next();
      var stats = keyword.getStatsFor("LAST_7_DAYS");
      // getCost() 返回账户币种金额(非 micros)
      if(stats.getConversions() == 0 && stats.getCost() > 50) {
        keyword.pause();
        Logger.log("Paused: " + keyword.getText() + " - Cost: $" + stats.getCost().toFixed(2));
      }
    }
  }
}
单位说明

Google Ads Scripts 中 Stats.getCost()Budget.getAmount() 直接返回账户币种金额(如 USD 10.50)。不要除以 1,000,000——那是 Google Ads API(GAQL)的 micros 单位。脚本与 API 的单位不同。

脚本 2:建议新搜索词(安全模式:写入 Sheet 供人工审批)

function main() {
  var sheet = SpreadsheetApp.openById("YOUR_SHEET_ID").getActiveSheet();
  sheet.appendRow(["Date", "Search Term", "Clicks", "Conversions", "Cost"]);

  var report = AdsApp.report(
    "SELECT search_term.view_text, metrics.clicks, metrics.conversions, metrics.cost_micros " +
    "FROM search_term_view WHERE segments.date DURING LAST_30_DAYS " +
    "AND metrics.conversions > 0 AND metrics.clicks > 10 " +
    "AND search_term.view_text NOT LIKE '%.%'");
  var rows = report.rows();
  while(rows.hasNext()) {
    var row = rows.next();
    var term = row["search_term.view_text"];
    // 检查是否已存在(全局去重)
    var exists = AdsApp.keywords()
      .withCondition("keyword.text = '" + term.replace(/'/g, "\\'") + "'")
      .get().hasNext();
    if(!exists) {
      sheet.appendRow([
        new Date(),
        term,
        row["metrics.clicks"],
        row["metrics.conversions"],
        (row["metrics.cost_micros"] / 1000000).toFixed(2)
      ]);
    }
  }
}
安全提醒

自动将搜索词添加为关键词可能带来无关流量和预算风险。务必写入 Google Sheets 经人工审核后再添加。本例中 report 使用 cost_micros(API 单位),而 Scripts 的对象方法如 getCost() 直接返回币种金额——注意区分。

脚本 3:预算超限自动告警(Slack)

var SLACK_WEBHOOK = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL";
var BUDGET_THRESHOLD = 0.8; // 告警阈值:已用预算的 80%
function main() {
  var campaigns = AdsApp.campaigns().withCondition("campaign.status = ENABLED").get();
  while(campaigns.hasNext()) {
    var c = campaigns.next();
    var budget = c.getBudget();
    var stats = c.getStatsFor("LAST_7_DAYS");
    // Scripts 中 getCost() / getAmount() 直接返回币种金额
    var spend = stats.getCost();
    var dailyBudget = budget.getAmount();
    var totalBudget = dailyBudget * 7;
    if(totalBudget > 0) {
      var spendRate = spend / totalBudget;
      if(spendRate > BUDGET_THRESHOLD) {
        var msg = "⚠ Campaign \"" + c.getName() + "\" spent " + (spendRate*100).toFixed(0) +
                  "% of weekly budget ($" + spend.toFixed(2) + " / $" + totalBudget.toFixed(2) + ")";
        UrlFetchApp.fetch(SLACK_WEBHOOK, {method:"POST", payload:JSON.stringify({text:msg}), contentType:"application/json"});
      }
    }
  }
}

脚本 4:质量分监控→Sheets

function main() {
  var sheet = SpreadsheetApp.openById("YOUR_SHEET_ID").getActiveSheet();
  sheet.appendRow(["Date", "Campaign", "Keyword", "Quality Score", "Impressions", "Clicks"]);
  var report = AdsApp.report(
    "SELECT campaign.name, keyword.text, metrics.quality_score, " +
    "metrics.impressions, metrics.clicks " +
    "FROM keyword WHERE metrics.quality_score < 5 AND metrics.impressions > 100 " +
    "AND segments.date DURING LAST_7_DAYS");
  var rows = report.rows();
  while(rows.hasNext()) {
    var row = rows.next();
    sheet.appendRow([new Date(),
      row["campaign.name"], row["keyword.text"],
      row["metrics.quality_score"],
      row["metrics.impressions"], row["metrics.clicks"]]);
  }
}

脚本 5:MCC 跨账户预算汇总

function main() {
  var sheet = SpreadsheetApp.openById("YOUR_SHEET_ID").getActiveSheet();
  sheet.appendRow(["Account Name", "Customer ID", "Cost", "Conversions", "Impressions", "Clicks"]);
  var accounts = AdsManagerApp.accounts().get();
  while(accounts.hasNext()) {
    var account = accounts.next();
    AdsManagerApp.select(account);
    var stats = AdsApp.currentAccount().getStatsFor("LAST_7_DAYS");
    // Scripts 中 getCost() 直接返回币种金额(非 micros)
    sheet.appendRow([
      account.getName(),
      account.getCustomerId(),
      stats.getCost().toFixed(2),
      stats.getConversions(),
      stats.getImpressions(),
      stats.getClicks()
    ]);
  }
}

13.3 常用 API 端点速查#

功能API 端点GAQL 查询示例
数据查询GoogleAdsService.SearchStreamSELECT campaign.name, metrics.impressions FROM campaign
广告系列操作CampaignService.MutateCampaignsoperations: [{update: {resource_name, status: PAUSED}}]
离线转化上传ConversionUploadService.UploadClickConversionsconversions: [{gclid, conversionValue, conversionDateTime}]
报表导出GoogleAdsService.Search配合 pagination 分页导出大量数据

13.4 脚本与 API 工程化治理#

生产环境使用自动化必须遵循以下原则:

代码部署规范

  • Preview 模式:所有脚本第一次运行时必须使用 Preview 模式,确认输出符合预期后再切换为正式运行
  • 白名单策略:涉及"暂停/启用/修改预算/新增对象"的操作,必须先用 Logger.log 输出结果,人工确认后再放开执行
  • 幂等设计:脚本多次运行应产生相同结果(或只执行一次、第二次无副作用)
  • 日志记录:每个脚本在 Logger.log 中输出关键操作的时间和影响范围
  • 异常告警:脚本应包含 try-catch 块,出错时通过 MailApp.sendEmail 或 Slack 告警

API 工程化

  • Developer Token:生产环境申请 standard token(而非 test token),注意配额限制
  • OAuth 2.0:管理已归集到自有 MCC 的账户时可使用 Service Account;替其他用户提供授权接入的 SaaS 应使用 multi-user authentication。两种方式都需要 developer token
  • 分页与重试:大数据量查询必须分页,遇到 quota error 使用指数退避重试
  • Partial Failure:批量操作开启 partial failure 模式,避免单条失败导致全部回滚
  • 版本管理:API 每年更新版本,提前 6 个月关注废弃公告

上线审批与回滚

阶段要求交付物
开发只读查询或 Logger.log,不执行 mutate影响对象样例、阈值说明
Preview用测试账户和小范围白名单验证运行日志、误报清单
审批负责人确认影响范围、预算上限、回滚方式审批记录、版本号
生产先告警后执行,逐步扩大账户范围变更日志、失败告警
回滚禁用定时任务,按日志恢复原状态回滚记录、复盘结论

密钥不得写入脚本正文或共享表格。使用受限权限的密钥存储,并记录轮换日期。每次 API 请求保留 request ID,便于定位失败调用。

Google Ads Editor

  • 批量编辑的首选工具,支持离线操作和变更草稿
  • 适用于:批量添加/修改关键词、广告文案、出价调整
  • 不适用于:基于实时数据的决策(需使用脚本或 API)
  • 变更前导出原账户为备份文件,变更后可回滚

Automated Rules

  • 适用场景:定时暂停、预算调整、标签管理、邮件告警
  • 推荐用于简单条件规则(如"CPA > $50 时暂停关键词")
  • 复杂逻辑(跨账户/多条件组合/自定义计算)应使用脚本
自动化边界

默认原则:先告警、后执行。涉及预算变更、广告暂停、关键词新增的操作必须先进入人工审批流程。只有数据监控和报告类的自动化可以全自动运行。

✦ ✦ ✦

本章要点

  • Google Ads Scripts 基于 JavaScript,免费,在后台运行
  • 5 个可直接部署的脚本覆盖大部分日常需求
  • API 用于更复杂的集成需求
  • 自动化监控 + 人工决策是最佳组合
✓ 行动清单
  • 部署"暂停 7 天无转化关键词"脚本
  • 部署"质量分监控"脚本写入 Google Sheets
  • 创建 MCC 级别脚本管理所有账户
  • 为每个自动化任务补齐 Preview、审批、日志、告警和回滚记录