使用GPT编写RSA的谷歌广告脚本

一个使用GPT编写RSA的谷歌广告脚本

这个基于GPT的谷歌RSA广告脚本可以帮助你利用GPT的API来使用最大数量的RSA资产,进而提升谷歌付费搜索广告展现机会。

有效管理PPC活动需要使用正确的工具组合。

长期以来,谷歌广告脚本一直是我的最爱之一,因为它们可以完全定制,具有合理的规模,并且在任何谷歌广告账户中都是免费的。

但现在有一个新的孩子-GPT。我们能否将生成性人工智能与谷歌广告脚本结合起来?

这是我要回答的问题,结果是我第一个使用GPT的谷歌广告脚本。

它确定了响应式搜索广告(RSA)的机会,并使用GPT来帮助生成额外的创意资产,以利用这个机会。

你可以在本文末尾抓取脚本代码,并在你自己的账户上试用。

为什么你应该使用RSA的最大资产数量

响应式搜索广告(RSA)是谷歌广告的一种广告格式,允许广告商为一个广告创建多个标题和描述。

然后,谷歌使用机器学习来测试不同的标题和描述组合,以确定哪些组合对不同的搜索查询和用户环境表现最佳。

这有助于提高广告的相关性和性能,使广告商能够接触到更多的受众。

我们发现,RSA所带来的展现是扩展文本广告的4倍,而具有更多标题变体的广告比具有较少变体的广告获得更多展现。

更多标题变体的广告比具有较少变体的广告获得更多展现

谷歌允许广告商为每个RSA提交15个版本的标题和四个版本的描述。

虽然这并不意味着你的资产的43,000种变化会收集到相同的展现,但向机器提供最大数量的资产总是一个好主意,这样它的算法就可以向每个感兴趣的用户展示你的广告。

但是,让我们面对现实吧,为你账户中的每一个广告组写15个伟大的标题和4个长的描述可能会变得很乏味。因此,许多广告商在他们的RSA中存在差距,这并不奇怪。

PPC管理工具可以很容易地发现和解决这个问题。但也有一些方法可以在不支付额外软件的情况下大规模地做到这一点。

谷歌广告脚本是对广告账户进行批量操作的最佳免费工具之一。

去年,作者和长期从事PPC的思想领袖、Twitter上#ppcchat的创始人Matt Umbro一起写了一个脚本,创建了一个资产丢失的RSA广告列表。

但那是在ChatGPT提高公众对生成性人工智能的认识之前。因此,我决定看看我是否能结合脚本和GPT的API来改善广告账户。

使用脚本

该脚本创建了一个电子表格,每一行和每一列都有一个RSA,用于每个标题和描述资产。

RSA电子表格

当RSA没有使用谷歌允许的最大数量的变化时,它会调用GPT API来建议额外的广告文本变化。

这些人工智能生成的建议然后被放置在电子表格中,以便被批量加载到谷歌,从而轻松地创建缺少的资产。

为了便于查看GPT生成的内容,这些单元格被自动填充为绿色。

GPT生成的谷歌广告内容

为了运行这个脚本,你需要从OpenAI的网站上获得一个API密钥,并将密钥添加到脚本第39行左右的位置:

var OPEN_AI_API_KEY = ''; // get your own API key at https://platform.openai.com/account/api-keys

你可以在预览模式下运行该脚本,并查看日志以获得新电子表格的URL。

查看日志以获得新电子表格的URL

每次脚本运行时,它都会使用OpenAI的API。这要花钱,所以不要把这个脚本放在自动任务计划上。

下载脚本

在这里获取一份代码副本

/******************************************
* RSA Report
* @version: 3.0
* @authors: Naman Jindal (Optmyzr), Frederick Vallaeys (Optmyzr)
* -------------------------------
* Install this script in your Google Ads account (not an MCC account)
* to generate a Google Sheet with a list of all your responsive search ads
* and their headlines and descriptions.
* For RSAs that are not using the maximum number of allowed variations,
* this script will suggest new variations for headlines and descriptions 
* using the OpenAI GPT API.
* The resulting sheet can be bulk uploaded back into Google Ads.
* --------------------------------
* For more PPC tools, visit www.optmyzr.com.
******************************************/

// If Blank script will create a new Google sheet everytime it runs.
var SS_URL = ''; 

// Name of the tab in the Google sheet.
var TAB_NAME = 'RSA';

// Flag to decide if the script checks only ads in active campaigns and active ad groups
var INCLUDE_PAUSED = false;

// only include ads with this many or fewer headlines on the output spreadsheet (defaults to 15)
var MAX_HEADLINES = 15;

// only include ads with this many or fewer descriptions on the output spreadsheet (defaults to 4)
var MAX_DESCRIPTIONS = 4;

// Multiple emails can be added sepearated by comma (,)
// Used for access to spreadsheet and for sending email
var EMAIL = '';

// Set to true if you want to recieve the report on Email.
var SEND_EMAIL = false;

var OPEN_AI_API_KEY = ''; // get your own API key at https://platform.openai.com/account/api-keys

var GPT_MODEL = 'gpt-3.5-turbo';

// Do not edit anything below this line
function main() {
  
  var output = [[
    'Account ID', 'Account Name', 'Campaign', 'Ad Group', 'Ad ID', '# Headlines', '# Descriptions', 'Ad Strength', 
    'Headline 1', 'Headline 2', 'Headline 3', 'Headline 4', 'Headline 5', 'Headline 6', 'Headline 7', 'Headline 8', 'Headline 9', 
    'Headline 10', 'Headline 11', 'Headline 12', 'Headline 13', 'Headline 14', 'Headline 15', 
    'Description Line 1', 'Description Line 2', 'Description Line 3', 'Description Line 4'
  ]];
  
  var columCount = output[0].length;
  var backgroupHeader = [];
  while(backgroupHeader.length < columCount) {
    backgroupHeader.push('#ffffff');
  }
  
  var backgrounds = [backgroupHeader];
  
  var accId = AdsApp.currentAccount().getCustomerId(),
      accName = AdsApp.currentAccount().getName();
  
  var query = [
    'SELECT campaign.name, ad_group.name, ad_group_ad.ad.id, ad_group_ad.ad_strength,',
    'ad_group_ad.ad.responsive_search_ad.headlines, ad_group_ad.ad.responsive_search_ad.descriptions',
    'FROM ad_group_ad WHERE ad_group_ad.ad.type = RESPONSIVE_SEARCH_AD AND metrics.impressions >= 0',
    INCLUDE_PAUSED ? '' : 'AND ad_group_ad.status = ENABLED AND campaign.status = ENABLED and ad_group.status = ENABLED',
    'AND segments.date DURING LAST_7_DAYS'
  ].join(' ');
  
  var rows = AdsApp.report(query).rows();
  while(rows.hasNext()) {
    var row = rows.next();
    
    var headlines = row['ad_group_ad.ad.responsive_search_ad.headlines'];
    var headlineCount = headlines.length;
    
    var descriptions = row['ad_group_ad.ad.responsive_search_ad.descriptions'];
    var descriptionCount = descriptions.length;
    
    if(headlineCount > MAX_HEADLINES || descriptionCount > MAX_DESCRIPTIONS) { continue; }
    
    var out = [
      accId, accName, row['campaign.name'], row['ad_group.name'], row['ad_group_ad.ad.id'],
      headlineCount, descriptionCount, row['ad_group_ad.ad_strength']
    ];
    
    var bgRow = ['#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff', '#ffffff'];
    
    var headlinesText = [];
    for(var z in headlines) {
      headlinesText.push(headlines[z].text);
      bgRow.push('#ffffff');
    }
    
    var diff = 15 - headlinesText.length;
    
    var autoHeadlines = [];
    if(diff > 0) {
      autoHeadlines = generateTextOpenAI('Find '+diff+' more ad headlines under 30 characters that are similar to these:\n', headlinesText);
    }
    
    if(autoHeadlines.length) {
      headlinesText = headlinesText.concat(autoHeadlines);
      for(var i = 0; i < autoHeadlines.length; i++) {
        bgRow.push('#d9ead3');
      }
    }
    
    while(headlinesText.length < 15) {
      headlinesText.push('');
      bgRow.push('#fffff')
    }
    
    while(headlinesText.length > 15) {
      headlinesText.pop();
      bgRow.pop();
    }
    
    var descriptionsText = [];
    for(var z in descriptions) {
      descriptionsText.push(descriptions[z].text);
      bgRow.push('#ffffff');
    }
    
    var diff = 4 - descriptions.length;
    var autoDescriptions = [];
    if(diff > 0) {
      autoDescriptions = generateTextOpenAI('Find '+diff+' more ad descriptions under 90 characters that are similar to these:\n', descriptionsText);
    }
    
    if(autoDescriptions.length) {
      descriptionsText = descriptionsText.concat(autoDescriptions);
      for(var i = 0; i < autoDescriptions.length; i++) {
        bgRow.push('#d9ead3');
      }
    }
    
    while(descriptionsText.length < 4) {
      descriptionsText.push('');
      bgRow.push('#ffffff');
    }
    
    while(descriptionsText.length > 4) {
      descriptionsText.pop();
      bgRow.pop();
    }
    
    out = out.concat(headlinesText).concat(descriptionsText);
    
    backgrounds.push(bgRow);
    output.push(out);
  }
  
  if(!SS_URL) {
    var ss = SpreadsheetApp.create(accName + ': RSA Report');
    SS_URL = ss.getUrl(); 
    
    if(EMAIL) { 
      ss.addEditors(EMAIL.split(','));
    }
  }
  
  Logger.log('Report URL: ' + SS_URL);
  var ss = SpreadsheetApp.openByUrl(SS_URL);
  var tab = ss.getSheetByName(TAB_NAME);
  if(!tab) {
    tab = ss.getSheetByName('Sheet1');
    if(!tab) {
     tab = ss.insertSheet(TAB_NAME);
    } else {
     tab.setName(TAB_NAME) 
    }
  }
  
  tab.clearContents();
  tab.setFrozenRows(1);
  tab.getRange(1,1,output.length,output[0].length).setValues(output).setBackgrounds(backgrounds).setFontFamily('Calibri');
  
  if(EMAIL && SEND_EMAIL) {
    MailApp.sendEmail(EMAIL, accName + ' RSA Report is ready', 'Report is available at below link:\n'+SS_URL);
  }
}

function getGoogleAdsFormattedDate(d, format){
  var date = new Date();
  date.setDate(date.getDate() - d);
  return Utilities.formatDate(date,AdsApp.currentAccount().getTimeZone(),format);
}

function generateTextOpenAI(question, texts) {
  
  //texts.pop();
  var prompt = question + texts.join('\n');
  
  var messages= [
    {"role": "user", "content": prompt}
  ];
  
  var payload = {
    "model": GPT_MODEL,
    "messages": messages
  };
  
  var httpOptions = {
    "method" : "POST",
    "muteHttpExceptions": true,
    "contentType": "application/json",
    "headers" : {
      "Authorization" : 'Bearer ' + OPEN_AI_API_KEY  
    },
    'payload': JSON.stringify(payload)
  };
  
  //Logger.log(JSON.stringify(payload));
  var response = JSON.parse(UrlFetchApp.fetch('https://api.openai.com/v1/chat/completions', httpOptions));
  var choices = response['choices'];
  
  var texts = [];
  if(choices && choices[0] && choices[0]['message']) {
    var output = choices[0]['message']['content'].split('\n');
    for(var z in output) {
      if(!output[z].trim()) { continue; }
      var parts = output[z].split('. ');
      if(parts.length > 1) {
        parts.shift();
      }
      texts.push(parts.join('. '));
    }
  } else {
    //Logger.log('No results found!')
    Logger.log(response);
  }
  
  return texts;
}

如果你是谷歌广告脚本的新手,你可以按照这里的说明进行首次设置。

GPT生成谷歌广告语和关键词的局限性

虽然GPT在写作方面很出色,但它在计数方面却出了名的糟糕。我问它为什么,它是这样解释的:

“作为一个人工智能语言模型,GPT(Generative Pre-trained Transformer)在数学方面本质上并不差。然而,它的表现可能不如专门的数学模型,因为它没有经过专门的数学任务训练。GPT主要是为自然语言处理而设计的,这涉及理解和生成人类语言。虽然它可以进行基本的数学运算,但它可能会在更复杂的计算中挣扎”。

对于我们的PPC用例,你必须仔细检查它建议的资产长度,以确保它们符合谷歌的字符限制。

当我要求GPT在每个建议的标题旁边添加字符数时,你可以看到它通常很接近,但会出错:

GPT生成标题字符数

经验告诉我,GPT在完成一项任务时倾向于遵循提示中发现的模式。

这意味着在处理现有的标题列表时,它更有可能写出适当长度的标题,就像你目前在你的账户中的那些标题一样。

因此,该脚本在完成只缺少几个元素的RSA时效果最好,而不是大部分或全部元素。

正如你在前面的截图中所看到的,它有时也倾向于给出编号的建议列表,而当你的每个标题都以’1)’、’2)’等开头时,这并不是一个好的广告。

我不会让GPT自动生成广告,因为我对让谷歌的一些建议进行自动化感到犹豫。

当我不小心打开了自动应用冗余关键词的建议时,它把我的品牌关键词Optmyzr从我的账户中移除。

把GPT的工作作为一个建议,帮助加快生成新的广告变体。

将谷歌广告与生成性人工智能相结合

GPT是互联网发明以来最令人兴奋的新技术之一。

它对问题的理解程度和它的反应的自信(尽管并不总是准确的)是令人震惊的。

我很高兴在我的PPC工具包中拥有另一个神奇的工具,以帮助我的广告胜过竞争对手。

我希望这个脚本能让你体会到,当你把谷歌广告和GPT这样的生成性人工智能结合起来时,会有什么可能。

评论留言