Automação Google Ads

Automação Google Ads: Gerencie múltiplas contas com o Google Drive.

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads.

 

1. Adicione scripts para múltiplas contas com o Google Drive – Por Russell Savage. Utilizando uma planilha do Google é possível você automatizar o trabalho de atualizar todos os scripts de suas contas.

Uma das desvantagens de usar o AdWords Scripts é que você precisa fazer login em cada conta e configurar o script. Para a maioria das pessoas, isso não é um problema nas primeiras vezes. Mas quando você começa a ver o valor de alguns desses scripts, pode haver um conjunto deles que você deseja colocar em todas as suas contas. Configurá-los é bom, até encontrar um bug no seu código e ter que entrar e atualizar todas as 20 cópias do script em cada conta.

Bem, neste post, criei uma maneira simples de manter uma única cópia do seu script e carregá-lo em qualquer número de contas do Google AdWords. Então, se você tiver alguma alteração, poderá atualizar uma única versão do script e todas as contas começarão a usar o novo código instantaneamente.

O código para isso consiste em duas partes. O primeiro snippet de código é o código genérico que você precisa colocar em cada uma das suas contas. Esse código faz referência a uma única planilha do Google (aqui está uma amostra para você copiar: //goo.gl/y6hPfy ) que é usada para saber quais scripts ela deve executar. A planilha tem apenas três colunas: uma descrição que é usada apenas para registro, o local do script no Google Drive e o nome do objeto no script. Não se preocupe com isso agora, vou descrevê-lo melhor na próxima seção. Finalmente, carrega o arquivo de script e executa a função principal.

/************************************
* Generic Script Runner
* Version 1.0
* Created By: Russ Savage
* FreeAdWordsScripts.com
***********************************/
function main() {
//See //goo.gl/KvINmD for an example spreadsheet.
var scriptConfigId = ‘Your Spreadsheet Id Goes Here’;
var sheet = SpreadsheetApp.openById(scriptConfigId).getActiveSheet();
var data = sheet.getRange(‘A:C’).getValues();
for(var i in data) {
if(i == 0) { continue; }
var [description, location, classname] = data[i];
if(!location) { continue; }
Logger.log(‘Running “‘+description+'” from location: ‘+location);
var scriptFile = getFile(location);
var scriptText = scriptFile.getBlob().getDataAsString();
eval(scriptText);
var script = eval(‘new ‘+classname+'();’);
script.main();
}
}

//This function gets the file from GDrive
function getFile(loc) {
var locArray = loc.split(‘/’);
var folder = getFolder(loc);
if(folder.getFilesByName(locArray[locArray.length-1]).hasNext()) {
return folder.getFilesByName(locArray[locArray.length-1]).next();
} else {
return null;
}
}

//This function finds the folder for the file and creates folders if needed
function getFolder(folderPath) {
var folder = DriveApp.getRootFolder();
if(folderPath) {
var pathArray = folderPath.split(‘/’);
for(var i in pathArray) {
if(i == pathArray.length – 1) { break; }
var folderName = pathArray[i];
if(folder.getFoldersByName(folderName).hasNext()) {
folder = folder.getFoldersByName(folderName).next();
}
}
}
return folder;
}

Agora que temos um código genérico que lê a descrição, o local e o nome do objeto de uma planilha e executa o código, precisamos fazer algumas pequenas modificações em alguns dos nossos scripts existentes para que ele funcione.

Um dos meus scripts favoritos é sobre como encontrar anomalias na sua conta . Para que o script seja executado a partir de outro script, precisamos convertê-lo em um objeto com uma única função pública. Essa mesma técnica deve funcionar em quase todos os scripts do FreeAdWordsScripts.com .

Primeiro, coloque todo o script em uma chamada de função e dê a ele qualquer nome que desejar.

function Anomalies() {
// Copy and Paste the code from:
// //goo.gl/IT1UcV
};

Em seguida, você precisa atualizar a função principal para ser um método público, para que possamos chamá-la de nosso script genérico.

this.main = function() {
// Don’t make any changes to the body of the main method
}

Uma versão completa do código atualizado pode ser encontrada aqui: Encontre anomalias na versão do objeto da sua conta .

Agora você pode salvar este novo script em algum lugar em seu GDrive e atualizar o local e o nome do objeto (anomalias neste caso) em sua planilha de configuração.

Agora você deveria estar bem para ir. Você pode adicionar quantos scripts desejar à planilha de configuração, mas lembre-se de que o limite de 30 minutos ainda se aplica.

2. Criando “deep links” para sua conta – Por Russell Savage. Este script cria links diretamente para a entidade que está com problemas, assim, você consegue acessar rapidamente a raiz do problema sem ter que acessar todas as páginas.

Acontece que existem dois números mágicos que você precisa para que isso funcione. Quando você acessar sua conta, no URL, você verá __u = e __c =. De acordo com esta postagem do blog , esses valores são ‘effectiveUserId’ e ‘customerId’ respectivamente. Infelizmente, não há como acessar esses valores ao usar scripts, portanto, você precisará copiá-los manualmente no script abaixo.

Depois disso, você pode incluir a função em todos os seus scripts e fazer um link direto para o conteúdo do seu coração. Não é a coisa mais bonita do mundo, mas é auto-contida, por isso deve ser fácil de copiar para o fundo de seus scripts.

// Link to the Keyword Tab of the AdGroup
Logger.log(getUrl(someAdGroupEntity,’Keywords’));
// Link to the Ads Tab of the AdGroup
Logger.log(getUrl(someAdGroupEntity,’Ads’));
// Link to Location Settings Tab of the Campaign
Logger.log(getUrl(comeCampaignEntity,’Settings:Locations’));

 

/***********************************
* Build Deep Link Urls for Entities
* Version 1.0
* Created By: Russ Savage
* FreeAdWordsScripts.com
***********************************/
function getUrl(entity,tab) {
var customerId = ‘__c from the url’;
var effectiveUserId = ‘__u from the url’;
var decodedTab = getTab(tab);

var base = ‘//adwords.google.com/cm/CampaignMgmt?’;
var url = base+’__c=’+customerId+’&__u=’+effectiveUserId+’#’;

if(typeof entity[‘getBudget’] !== ‘undefined’) {
//A Campaign
return url+’c.’+entity.getId()+’.’+decodedTab+’&app=cm’;
}
if(typeof entity[‘createKeyword’] !== ‘undefined’) {
//An AdGroup
return url+’a.’+entity.getId()+’_’+entity.getCampaign().getId()+’.’+decodedTab+’&app=cm’;
}
if(typeof entity[‘getMatchType’] !== ‘undefined’) {
//A Keyword
return url+’a.’+entity.getAdGroup().getId()+’_’+entity.getCampaign().getId()+’.key&app=cm’;
}
if(typeof entity[‘getHeadline’] !== ‘undefined’) {
//An Ad
return url+’a.’+entity.getAdGroup().getId()+’_’+entity.getCampaign().getId()+’.create&app=cm’;
}
return url+’r.ONLINE.di&app=cm’;

function getTab(tab) {
var mapping = {
‘Ad groups’:’ag’,’Settings:All settings’:’st_sum’,
‘Settings:Locations’:’st_loc’,’Settings:Ad schedule’:’st_as’,
‘Settings:Devices’:’st_p’,’Ads’:’create’,
‘Keywords’:’key’,’Audiences’:’au’,’Ad extensions’:’ae’,
‘Auto targets’:’at’,’Dimensions’ : ‘di’
};
if(mapping[tab]) { return mapping[tab]; }
return ‘key’; //default to keyword tab
}
}

 

3. Converta feeds RSS em JSON – Por Russel Savage. Utilize o Feed API do Google em seu script para converter feed em RSS para JSON. Assim você consegue puxar informações e trabalhá-las com mais facilidade.

/******************************************
* Use Google Feed API to convert RSS to json
* Version 1.0
* Created By: Russ Savage
* FreeAdWordsScripts.com
******************************************/
// Usage: var jsonData = convertRssToJson(‘//www.cpsc.gov/en/Newsroom/CPSC-RSS-Feed/Recalls-RSS/’);
function convertRssToJson(rssUrl) {
var FEED_API_URL = “//ajax.googleapis.com/ajax/services/feed/load?v=1.0&q=”
var url = FEED_API_URL+encodeURIComponent(rssUrl);
var resp = UrlFetchApp.fetch(url);
if(resp.getResponseCode() == 200) {
return JSON.parse(resp.getContentText());
} else {
throw “An error occured while trying to parse: “+rssUrl;
}
}

4. Scripts no fuso horário correto – Por Nathan Byloff. Os scripts do Google são executados no horário da região em que o servidor se encontra, portanto, caso o servidor esteja localizado em outra região de fuso horário, ele será executado em um horário diferente da sua conta. Esse script corrige este problema ajustando ao horário de sua conta.

A seguir, dois métodos de exemplo que usei para garantir que todas as datas foram definidas no mesmo fuso horário. Este primeiro apenas retorna o tempo atual correto da conta (não o tempo do servidor no Google).

/**
* Make sure when getting a date object, we’re basing it off the account time, not the data center server time
*/
function getAccountCurrentDateTime() {
return new Date(Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), “MMM dd,yyyy HH:mm:ss”));
}

Ou use um método como esse para garantir que um objeto de data com o qual você trabalha corresponda ao horário correto da conta.

/**
* The time will convert to whatever server/data center the script is running on. Make sure the time is set to the AW account time
* @param {Date} date
*/
function setProperTimeZone(date) {
return new Date(Utilities.formatDate(date, AdWordsApp.currentAccount().getTimeZone(), “MMM dd,yyyy HH:mm:ss”));
}

5. Utilize o recurso de autocompletar do Google – Por Russell Savage. Utilize esse script para aumentar ou negativar as palavras-chave de sua conta utilizando as sugestões de autocompletar do próprio Google.

/**********************************************************************************************************************
* Brand Keyword Analysis Google
* Leverages the Google Autocomplete feature to find potential keyword opportunities and negative keywowrds
* Version 1.0
* Created By: Derek Martin
* DerekMartinLA.com or MixedMarketingArtist.com
**********************************************************************************************************************/

var hashMapResults = {};
var numOfKeywords = 0;
var doWork = false;
var keywordsToQuery = new Array();
var keywordsToQueryIndex = 0;
var queryflag = false;

var brandKeywordList = [];
var targetKeyword = “serious mass”; // this is the keyword that you want to know about
var emailAddress =”[email protected]”; // this is where the final report will be sent

function main() {
// Get the brand name from the account
var accountName = AdWordsApp.currentAccount().getName().split(‘-‘);

var clientName = accountName[0];

info(‘Now starting Google Search Autocomplete Analysis..’);

buildKeywordList(targetKeyword);

// Sort array
brandKeywordList.sort();

/* // remove any keywords that don’t include the brand term
for (i = 0; i < brandKeywordList.length; i++) {
if ((brandKeywordList[i].indexOf(accountName)) == -1) {

brandKeywordList.splice(i,1);

}
} */

// let user know that search has completed
info(‘Google Autocomplete Search has completed, expect an email with search term results momentarily’);
info(brandKeywordList.length + ‘ searches were found. ‘);

var fileUrl = createSpreadsheet(brandKeywordList);

info(‘ Or you can find it here:’ + fileUrl);
sendAnEmail(clientName, brandKeywordList.toString(), fileUrl);
}

function buildKeywordList(keyword) {

// get the first set of keywords related to the term and add to list
brandKeywordList = queryKeyword(keyword);

// iterate through alphabet and build keyword list for initial keyword
info(‘now checking for the variation [‘ + keyword + ‘] + [a-z][0-9] …’);
info(‘variation: ‘ + keyword);

for(var j = 0; j < 26; j++) {
var chr = String.fromCharCode(97 + j);

keywordVariation = keyword + ‘ ‘+ chr;

var alphaList = {};
alphaList = queryKeyword(keywordVariation);

for (var x = 0; x < alphaList.length; x++) {

if (x !== 0) {
info(alphaList[x]);
brandKeywordList.push(alphaList[x]);

}
}
}

for(var n = 0; n <= 9; n++) {

keywordVariation = keyword + ‘ ‘+ n;

var numberList = {};
numberList = queryKeyword(keywordVariation);

for (var y = 0; y < numberList.length; y++) {

if (y !== 0) {
info(numberList[y]);
brandKeywordList.push(numberList[y]);

}
}
}

// Split keyword up if possible and look for different variations
var keywordPieces = _.str.words(keyword);

if (keywordPieces.length > 1) {
info(keywordPieces);
// iterate through alphabet and build keyword list for the variation: [keywordPiece1] + [a-z][0-9] + [keywordPiece2]

warn(‘now checking for the variation [keywordPiece1] + [a-z][0-9] + [keywordPiece2]…’);
warn(‘variation: ‘ + keywordPieces[0] + ‘ ‘+ keywordPieces[1] + keywordPieces[2]);

for(var j = 0; j < 26; j++) {
var chr = String.fromCharCode(97 + j);

keywordVariation = keywordPieces[0] + ‘ ‘+ chr + ‘ ‘ + keywordPieces[1]+ ‘ ‘ + keywordPieces[2];

var alphaList = {};
alphaList = queryKeyword(keywordVariation);

for (var x = 0; x < alphaList.length; x++) {

if (x !== 0) {
info(alphaList[x]);
brandKeywordList.push(alphaList[x]);

}
}
}

for(var n = 0; n <= 9; n++) {

keywordVariation = keywordPieces[0] + ‘ ‘+ n + ‘ ‘ + keywordPieces[1] +’ ‘+ keywordPieces[2];

var numberList = {};
numberList = queryKeyword(keywordVariation);

for (var y = 0; y < numberList.length; y++) {

if (y !== 0) {
info(numberList[y]);
brandKeywordList.push(numberList[y]);

}
}
}

Utilities.sleep(2000);

/* CHECK FOR THE VARIATION [keywordPiece1] + [a-z][0-9] + [keywordPiece0] */
warn(‘now checking for the variation [keywordPiece2] + [a-z][0-9] + [keywordPiece1]…’);
warn(‘variation: ‘ + keywordPieces[1] + ‘ ‘+ keywordPieces[0]);

for(var j = 0; j < 26; j++) {
var chr = String.fromCharCode(97 + j);

keywordVariation = keywordPieces[1] + ‘ ‘ + keywordPieces[2] + ‘ ‘+ chr + ‘ ‘ + keywordPieces[0];

var alphaList = {};
alphaList = queryKeyword(keywordVariation);

for (var x = 0; x < alphaList.length; x++) {

if (x !== 0) {
info(alphaList[x]);
brandKeywordList.push(alphaList[x]);

}
}
}

for(var n = 0; n <= 9; n++) {

keywordVariation = keywordPieces[1] + ‘ ‘ + keywordPieces[2] + ‘ ‘+ n + ‘ ‘ + keywordPieces[0];

var numberList = {};
numberList = queryKeyword(keywordVariation);

for (var y = 0; y < numberList.length; y++) {

if (y !== 0) {
info(numberList[y]);
brandKeywordList.push(numberList[y]);

}
}
}

Utilities.sleep(2000);
/* last variation: [a-z][0-9] [keyword1] [keyword2] */
/* CHECK FOR THE VARIATION [keywordPiece1] + [a-z][0-9] + [keywordPiece0] */
info(‘now checking for the variation [a-z][0-9] + [keywordPiece1] + [keywordPiece2]…’);
// warn(‘variation: ‘ + keywordPieces[0] + ‘ ‘+ keywordPieces[1]);

for(var j = 0; j < 26; j++) {
var chr = String.fromCharCode(97 + j);

keywordVariation = chr + ‘ ‘ + keywordPieces[0] + ‘ ‘ + keywordPieces[1] + keywordPieces[2];

var alphaList = {};
alphaList = queryKeyword(keywordVariation);

for (var x = 0; x < alphaList.length; x++) {

if (x !== 0) {
info(alphaList[x]);
brandKeywordList.push(alphaList[x]);

}
}
}

for(var n = 0; n <= 9; n++) {

keywordVariation = n + ‘ ‘ + keywordPieces[0] + ‘ ‘ + keywordPieces[1] + keywordPieces[2];

var numberList = {};
numberList = queryKeyword(keywordVariation);

for (var y = 0; y < numberList.length; y++) {

if (y !== 0) {
info(numberList[y]);
brandKeywordList.push(numberList[y]);

}
}
}

}
}

function createSpreadsheet(results) {
var newSS = SpreadsheetApp.create(‘searchtermreport’, results.length, 26);

var sheet = newSS.getActiveSheet();

var columnNames = [“Campaign Name”, “AdGroup”, “Keyword”, “Match Type”];

var headersRange = sheet.getRange(1, 1, 1, columnNames.length);

for (i = 0; i < results.length; i++) {

headersRange.setValues([columnNames]);

var resultKw;
resultKw = results[i].toString();

sheet.appendRow([“Your Campaign”, “Your AdGroup”, resultKw,’Phrase’]);

// Sets the first column to a width which fits the text
sheet.setColumnWidth(1, 300);

}

return newSS.getUrl();

}

function sendAnEmail (results, fileUrl) {

var data = Utilities.parseCsv(results, ‘\t’);
var today = new Date();

var filename = ‘search-results’ + today;

// Send an email with Search list attachment
var blob = Utilities.newBlob(results, ‘text/html’, ”);

MailApp.sendEmail(emailAddress, ‘Google Autocomplete Results ‘, ‘You can find the results at the following URL:’ + fileUrl, {
name: ‘Google Autocomplete Search Results’
});

}
/* Utility Functions */

function warn(msg) {
Logger.log(‘WARNING: ‘+msg);
}

function info(msg) {
Logger.log(msg);
}

function queryKeyword(keyword)
{
var querykeyword = encodeURIComponent(keyword);

var queryresult = ”;
queryflag = true;

Utilities.sleep(1000);
var response = UrlFetchApp.fetch(“//www.google.com/s?gs_rn=18&gs_ri=psy-ab&cp=7&gs_id=d7&xhr=t&q=” + querykeyword);

var retval = response.getContentText();

var test = _.str.stripTags(retval);

var retList = ScrapePage(retval, ‘[“‘, ‘”,’);

queryflag = false;

return retList;
}
// });
// }

function ScrapePage(page, left, right)
{
var i = 0;
var retVal = new Array();
var firstIndex = page.indexOf(left);
while (firstIndex != -1)
{
firstIndex += left.length;
var secondIndex = page.indexOf(right, firstIndex);
if (secondIndex != -1)
{
var val = page.substring(firstIndex, secondIndex);
val = val.replace(“\\u003cb\\u003e”, “”);
val = val.replace(“\\u003c\\/b\\u003e”, “”);
val = val.replace(“\\u003c\\/b\\u003e”, “”);
val = val.replace(“\\u003cb\\u003e”, “”);
val = val.replace(“\\u003c\\/b\\u003e”, “”);
val = val.replace(“\\u003cb\\u003e”, “”);
val = val.replace(“\\u003cb\\u003e”, “”);
val = val.replace(“\\u003c\\/b\\u003e”, “”);
val = val.replace(“\\u0026amp;”, “&”);
val = val.replace(“\\u003cb\\u003e”, “”);
val = val.replace(“\\u0026”, “”);
val = val.replace(“\\u0026#39;”, “‘”);
val = val.replace(“#39;”, “‘”);
val = val.replace(“\\u003c\\/b\\u003e”, “”);
val = val.replace(“\\u2013”, “2013”);
retVal[i] = val;
i++;
firstIndex = page.indexOf(left, secondIndex);
}
else
{
return retVal;
}
}
return retVal;
}

!function(e,t){“use strict”;var n=t.prototype.trim;var r=t.prototype.trimRight;var i=t.prototype.trimLeft;var s=function(e){return e*1||0};var o=function(e,t){if(t<1)return””;var n=””;while(t>0){if(t&1)n+=e;t>>=1,e+=e}return n};var u=[].slice;var a=function(e){if(e==null)return”\\s”;else if(e.source)return e.source;else return”[“+p.escapeRegExp(e)+”]”};var f={lt:”<“,gt:”>”,quot:'”‘,apos:”‘”,amp:”&”};var l={};for(var c in f){l[f[c]]=c}var h=function(){function e(e){return Object.prototype.toString.call(e).slice(8,-1).toLowerCase()}var n=o;var r=function(){if(!r.cache.hasOwnProperty(arguments[0])){r.cache[arguments[0]]=r.parse(arguments[0])}return r.format.call(null,r.cache[arguments[0]],arguments)};r.format=function(r,i){var s=1,o=r.length,u=””,a,f=[],l,c,p,d,v,m;for(l=0;l<o;l++){u=e(r[l]);if(u===”string”){f.push(r[l])}else if(u===”array”){p=r[l];if(p[2]){a=i[s];for(c=0;c<p[2].length;c++){if(!a.hasOwnProperty(p[2][c])){throw new Error(h(‘[_.sprintf] property “%s” does not exist’,p[2][c]))}a=a[p[2][c]]}}else if(p[1]){a=i[p[1]]}else{a=i[s++]}if(/[^s]/.test(p[8])&&e(a)!=”number”){throw new Error(h(“[_.sprintf] expecting number but found %s”,e(a)))}switch(p[8]){case”b”:a=a.toString(2);break;case”c”:a=t.fromCharCode(a);break;case”d”:a=parseInt(a,10);break;case”e”:a=p[7]?a.toExponential(p[7]):a.toExponential();break;case”f”:a=p[7]?parseFloat(a).toFixed(p[7]):parseFloat(a);break;case”o”:a=a.toString(8);break;case”s”:a=(a=t(a))&&p[7]?a.substring(0,p[7]):a;break;case”u”:a=Math.abs(a);break;case”x”:a=a.toString(16);break;case”X”:a=a.toString(16).toUpperCase();break}a=/[def]/.test(p[8])&&p[3]&&a>=0?”+”+a:a;v=p[4]?p[4]==”0″?”0″:p[4].charAt(1):” “;m=p[6]-t(a).length;d=p[6]?n(v,m):””;f.push(p[5]?a+d:d+a)}}return f.join(“”)};r.cache={};r.parse=function(e){var t=e,n=[],r=[],i=0;while(t){if((n=/^[^\x25]+/.exec(t))!==null){r.push(n[0])}else if((n=/^\x25{2}/.exec(t))!==null){r.push(“%”)}else if((n=/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(t))!==null){if(n[2]){i|=1;var s=[],o=n[2],u=[];if((u=/^([a-z_][a-z_\d]*)/i.exec(o))!==null){s.push(u[1]);while((o=o.substring(u[0].length))!==””){if((u=/^\.([a-z_][a-z_\d]*)/i.exec(o))!==null){s.push(u[1])}else if((u=/^\[(\d+)\]/.exec(o))!==null){s.push(u[1])}else{throw new Error(“[_.sprintf] huh?”)}}}else{throw new Error(“[_.sprintf] huh?”)}n[2]=s}else{i|=2}if(i===3){throw new Error(“[_.sprintf] mixing positional and named placeholders is not (yet) supported”)}r.push(n)}else{throw new Error(“[_.sprintf] huh?”)}t=t.substring(n[0].length)}return r};return r}();var p={VERSION:”2.3.0″,isBlank:function(e){if(e==null)e=””;return/^\s*$/.test(e)},stripTags:function(e){if(e==null)return””;return t(e).replace(/<\/?[^>]+>/g,””)},capitalize:function(e){e=e==null?””:t(e);return e.charAt(0).toUpperCase()+e.slice(1)},chop:function(e,n){if(e==null)return[];e=t(e);n=~~n;return n>0?e.match(new RegExp(“.{1,”+n+”}”,”g”)):[e]},clean:function(e){return p.strip(e).replace(/\s+/g,” “)},count:function(e,n){if(e==null||n==null)return 0;return t(e).split(n).length-1},chars:function(e){if(e==null)return[];return t(e).split(“”)},swapCase:function(e){if(e==null)return””;return t(e).replace(/\S/g,function(e){return e===e.toUpperCase()?e.toLowerCase():e.toUpperCase()})},escapeHTML:function(e){if(e==null)return””;return t(e).replace(/[&<>”‘]/g,function(e){return”&”+l[e]+”;”})},unescapeHTML:function(e){if(e==null)return””;return t(e).replace(/\&([^;]+);/g,function(e,n){var r;if(n in f){return f[n]}else if(r=n.match(/^#x([\da-fA-F]+)$/)){return t.fromCharCode(parseInt(r[1],16))}else if(r=n.match(/^#(\d+)$/)){return t.fromCharCode(~~r[1])}else{return e}})},escapeRegExp:function(e){if(e==null)return””;return t(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,”\\$1″)},splice:function(e,t,n,r){var i=p.chars(e);i.splice(~~t,~~n,r);return i.join(“”)},insert:function(e,t,n){return p.splice(e,t,0,n)},include:function(e,n){if(n===””)return true;if(e==null)return false;return t(e).indexOf(n)!==-1},join:function(){var e=u.call(arguments),t=e.shift();if(t==null)t=””;return e.join(t)},lines:function(e){if(e==null)return[];return t(e).split(“\n”)},reverse:function(e){return p.chars(e).reverse().join(“”)},startsWith:function(e,n){if(n===””)return true;if(e==null||n==null)return false;e=t(e);n=t(n);return e.length>=n.length&&e.slice(0,n.length)===n},endsWith:function(e,n){if(n===””)return true;if(e==null||n==null)return false;e=t(e);n=t(n);return e.length>=n.length&&e.slice(e.length-n.length)===n},succ:function(e){if(e==null)return””;e=t(e);return e.slice(0,-1)+t.fromCharCode(e.charCodeAt(e.length-1)+1)},titleize:function(e){if(e==null)return””;return t(e).replace(/(?:^|\s)\S/g,function(e){return e.toUpperCase()})},camelize:function(e){return p.trim(e).replace(/[-_\s]+(.)?/g,function(e,t){return t.toUpperCase()})},underscored:function(e){return p.trim(e).replace(/([a-z\d])([A-Z]+)/g,”$1_$2″).replace(/[-\s]+/g,”_”).toLowerCase()},dasherize:function(e){return p.trim(e).replace(/([A-Z])/g,”-$1″).replace(/[-_\s]+/g,”-“).toLowerCase()},classify:function(e){return p.titleize(t(e).replace(/_/g,” “)).replace(/\s/g,””)},humanize:function(e){return p.capitalize(p.underscored(e).replace(/_id$/,””).replace(/_/g,” “))},trim:function(e,r){if(e==null)return””;if(!r&&n)return n.call(e);r=a(r);return t(e).replace(new RegExp(“^”+r+”+|”+r+”+$”,”g”),””)},ltrim:function(e,n){if(e==null)return””;if(!n&&i)return i.call(e);n=a(n);return t(e).replace(new RegExp(“^”+n+”+”),””)},rtrim:function(e,n){if(e==null)return””;if(!n&&r)return r.call(e);n=a(n);return t(e).replace(new RegExp(n+”+$”),””)},truncate:function(e,n,r){if(e==null)return””;e=t(e);r=r||”…”;n=~~n;return e.length>n?e.slice(0,n)+r:e},prune:function(e,n,r){if(e==null)return””;e=t(e);n=~~n;r=r!=null?t(r):”…”;if(e.length<=n)return e;var i=function(e){return e.toUpperCase()!==e.toLowerCase()?”A”:” “},s=e.slice(0,n+1).replace(/.(?=\W*\w*$)/g,i);if(s.slice(s.length-2).match(/\w\w/))s=s.replace(/\s*\S+$/,””);else s=p.rtrim(s.slice(0,s.length-1));return(s+r).length>e.length?e:e.slice(0,s.length)+r},words:function(e,t){if(p.isBlank(e))return[];return p.trim(e,t).split(t||/\s+/)},pad:function(e,n,r,i){e=e==null?””:t(e);n=~~n;var s=0;if(!r)r=” “;else if(r.length>1)r=r.charAt(0);switch(i){case”right”:s=n-e.length;return e+o(r,s);case”both”:s=n-e.length;return o(r,Math.ceil(s/2))+e+o(r,Math.floor(s/2));default:s=n-e.length;return o(r,s)+e}},lpad:function(e,t,n){return p.pad(e,t,n)},rpad:function(e,t,n){return p.pad(e,t,n,”right”)},lrpad:function(e,t,n){return p.pad(e,t,n,”both”)},sprintf:h,vsprintf:function(e,t){t.unshift(e);return h.apply(null,t)},toNumber:function(e,n){if(e==null||e==””)return 0;e=t(e);var r=s(s(e).toFixed(~~n));return r===0&&!e.match(/^0+$/)?Number.NaN:r},numberFormat:function(e,t,n,r){if(isNaN(e)||e==null)return””;e=e.toFixed(~~t);r=r||”,”;var i=e.split(“.”),s=i[0],o=i[1]?(n||”.”)+i[1]:””;return s.replace(/(\d)(?=(?:\d{3})+$)/g,”$1″+r)+o},strRight:function(e,n){if(e==null)return””;e=t(e);n=n!=null?t(n):n;var r=!n?-1:e.indexOf(n);return~r?e.slice(r+n.length,e.length):e},strRightBack:function(e,n){if(e==null)return””;e=t(e);n=n!=null?t(n):n;var r=!n?-1:e.lastIndexOf(n);return~r?e.slice(r+n.length,e.length):e},strLeft:function(e,n){if(e==null)return””;e=t(e);n=n!=null?t(n):n;var r=!n?-1:e.indexOf(n);return~r?e.slice(0,r):e},strLeftBack:function(e,t){if(e==null)return””;e+=””;t=t!=null?””+t:t;var n=e.lastIndexOf(t);return~n?e.slice(0,n):e},toSentence:function(e,t,n,r){t=t||”, “;n=n||” and “;var i=e.slice(),s=i.pop();if(e.length>2&&r)n=p.rtrim(t)+n;return i.length?i.join(t)+n+s:s},toSentenceSerial:function(){var e=u.call(arguments);e[3]=true;return p.toSentence.apply(p,e)},slugify:function(e){if(e==null)return””;var n=”ąàáäâãåæćęèéëêìíïîłńòóöôõøùúüûñçżź”,r=”aaaaaaaaceeeeeiiiilnoooooouuuunczz”,i=new RegExp(a(n),”g”);e=t(e).toLowerCase().replace(i,function(e){var t=n.indexOf(e);return r.charAt(t)||”-“});return p.dasherize(e.replace(/[^\w\s-]/g,””))},surround:function(e,t){return[t,e,t].join(“”)},quote:function(e){return p.surround(e,'”‘)},exports:function(){var e={};for(var t in this){if(!this.hasOwnProperty(t)||t.match(/^(?:include|contains|reverse)$/))continue;e[t]=this[t]}return e},repeat:function(e,n,r){if(e==null)return””;n=~~n;if(r==null)return o(t(e),n);for(var i=[];n>0;i[–n]=e){}return i.join(r)},levenshtein:function(e,n){if(e==null&&n==null)return 0;if(e==null)return t(n).length;if(n==null)return t(e).length;e=t(e);n=t(n);var r=[],i,s;for(var o=0;o<=n.length;o++)for(var u=0;u<=e.length;u++){if(o&&u)if(e.charAt(u-1)===n.charAt(o-1))s=i;else s=Math.min(r[u],r[u-1],i)+1;else s=o+u;i=r[u];r[u]=s}return r.pop()}};p.strip=p.trim;p.lstrip=p.ltrim;p.rstrip=p.rtrim;p.center=p.lrpad;p.rjust=p.lpad;p.ljust=p.rpad;p.contains=p.include;p.q=p.quote;if(typeof exports!==”undefined”){if(typeof module!==”undefined”&&module.exports){module.exports=p}exports._s=p}else if(typeof define===”function”&&define.amd){define(“underscore.string”,[],function(){return p})}else{e._=e._||{};e._.string=e._.str=p}}(this,String)

 

6. Junte múltiplas campanhas em apenas uma – Por Russell Savage. Caso você utilize diversas campanhas com segmentações diferentes e queira juntar todas em uma esse script pode auxiliá-lo. Ele pausa as campanhas e copia-as para uma única campanha escolhida.

//———————————–
// Merge Multiple Campaigns Together
// Created By: Russ Savage
// FreeAdWordsScripts.com
//———————————–
function main() {
var DESTINATION_CAMPAIGN_NAME = “dest_camp_name”;
var ORIGIN_CAMPAIGN_NAMES = [“to_merge_camp_name_1”,”to_merge_camp_name_2″/*,…*/];
var DEFAULT_KW_BID = 0.01; //used in case we can’t get the origin kw bid

//build a list of adgroups in the original
var dest_adgroups = [];
var ag_iter = AdWordsApp.adGroups()
.withCondition(“CampaignName = ‘”+DESTINATION_CAMPAIGN_NAME+”‘”)
.get();

while(ag_iter.hasNext()) {
dest_adgroups.push(ag_iter.next());
}

var dest_camp;
if(dest_adgroups.length > 0) {
dest_camp = dest_adgroups[0].getCampaign();
}

for(var i in ORIGIN_CAMPAIGN_NAMES) {
var camp_name = ORIGIN_CAMPAIGN_NAMES[i];
var kw_iter = AdWordsApp.keywords()
.withCondition(“CampaignName = ‘”+camp_name+”‘”)
.get();
while(kw_iter.hasNext()) {
var kw = kw_iter.next();
var dest_adgroup = _find_adgroup(dest_adgroups,kw.getAdGroup());
if(!dest_adgroup) {
dest_adgroup = dest_camp.newAdGroupBuilder()
.withName(kw.getAdGroup().getName())
.withStatus((kw.getAdGroup().isPaused()) ? “PAUSED” : “ENABLED”)
.withKeywordMaxCpc(kw.getAdGroup().getKeywordMaxCpc())
.create();
dest_adgroups.push(dest_adgroup);
//now we move all the ads over
var ad_iter = kw.getAdGroup().ads().get();
while(ad_iter.hasNext()) {
var ad = ad_iter.next();
dest_adgroup.createTextAd(
ad.getHeadline(),
ad.getDescription1(),
ad.getDescription2(),
ad.getDisplayUrl(),
ad.getDestinationUrl(),
{ isMobilePreferred : ad.isMobilePreferred() }
);
ad.pause();
}
}
var max_cpc = kw.getMaxCpc() || DEFAULT_KW_BID;
var dest_url = kw.getDestinationUrl() || “”;
var kw_text = kw.getText();
dest_adgroup.createKeyword(kw_text,max_cpc,dest_url);

kw.pause();
}
}

function _find_adgroup(ag_list,ag) {
for(var i in ag_list) {
if(ag_list[i].getName() == ag.getName()) {
return ag_list[i];
}
}
return null;
}
}

Automação Google Ads: Campanhas de Display e Shopping

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para “Trabalhar com Campanhas de Display e Shopping”.

 

1. Campanhas de Shopping – Por Google Ads. Gerencie suas campanhas do Google Shopping sem a necessidade de utilizar a plataforma do Google Ads. Esse grupo de scripts permitem criar anúncios de produtos, atualizar lances, etc.

Recuperar todas as campanhas de compras

function getAllShoppingCampaigns() {
var retval = [];
var campaignIterator = AdWordsApp.shoppingCampaigns().get();
while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();

// Optional: Comment out if you don’t need to print details.
Logger.log(‘Campaign Name: %s’, campaign.getName());

retval.push(campaign);
}
return retval;
}

 

Recuperar uma campanha de compras pelo nome dela

function getShoppingCampaignByName(campaignName) {
  var campaignIterator = AdWordsApp.shoppingCampaigns()
      .withCondition("CampaignName = '" + campaignName + "'")
      .get();
  while (campaignIterator.hasNext()) {
    var campaign = campaignIterator.next();
    Logger.log('Campaign Name: %s', campaign.getName());
  }
}


Recuperar um grupo de anúncios de compras pelo nome dele

function getShoppingAdGroup() {
var campaignName = ‘INSERT_CAMPAIGN_NAME_HERE’;
var adGroupName = ‘INSERT_ADGROUP_NAME_HERE’;

var adGroupIterator = AdWordsApp.shoppingAdGroups()
.withCondition(“CampaignName = ‘” + campaignName +
“‘ and AdGroupName = ‘” + adGroupName + “‘”)
.get();
while (adGroupIterator.hasNext()) {
var adGroup = adGroupIterator.next();
Logger.log(
‘AdGroup Name: %s, CPC = %s, Mobile Bid ‘ + ‘Modifier = %s’,
adGroup.getName(),
adGroup.bidding().getCpc(),
adGroup.devices().getMobileBidModifier()
);
}
}

 

Crie um grupo de anúncios do Shopping

function createShoppingAdGroup() {
var campaignName = ‘INSERT_CAMPAIGN_NAME_HERE’;

var shoppingCampaign = AdWordsApp.shoppingCampaigns()
.withCondition(“CampaignName = ‘” + campaignName + “‘”)
.get()
.next();
var adGroupOperation = shoppingCampaign.newAdGroupBuilder().build();
var adGroup = adGroupOperation.getResult();
Logger.log(adGroup);
}

 

Criar uma hierarquia de grupo de produtos de compras

function createTree() {
var campaignName = ‘INSERT_CAMPAIGN_NAME_HERE’;
var adGroupName = ‘INSERT_ADGROUP_NAME_HERE’;

var shoppingAdGroup = AdWordsApp.shoppingAdGroups()
.withCondition(“CampaignName = ‘” + campaignName +
“‘ and AdGroupName = ‘” + adGroupName + “‘”)
.get()
.next();

var root = shoppingAdGroup.rootProductGroup();

// The structure created is
// – root
// – cardcow brand
// – New
// – Refurbished
// – Other conditions
// – Other brands

// Add a brand product group for “cardcow” under root product group.
var brandNode = root.newChild()
.brandBuilder()
.withName(‘cardcow’)
.withBid(1.2)
.build()
.getResult();

// Add new conditions for New and Refurbished cardcow brand items.
var newItems = brandNode.newChild()
.conditionBuilder()
.withCondition(‘NEW’)
.build()
.getResult();

var refurbishedItems = brandNode.newChild()
.conditionBuilder()
.withCondition(‘REFURBISHED’)
.withBid(0.9)
.build()
.getResult();
}

 

Atravessa a hierarquia do grupo de produtos

function walkProductPartitionTree() {
var campaignName = ‘INSERT_CAMPAIGN_NAME_HERE’;
var adGroupName = ‘INSERT_ADGROUP_NAME_HERE’;

var shoppingAdGroup = AdWordsApp.shoppingAdGroups()
.withCondition(“CampaignName = ‘” + campaignName +
“‘ and AdGroupName = ‘” + adGroupName + “‘”)
.get()
.next();
var root = shoppingAdGroup.rootProductGroup();
walkHierarchy(root, 0);
}

function walkHierarchy(productGroup, level) {
var description = ”;

if (productGroup.isOtherCase()) {
description = ‘Other’;
} else if (productGroup.getDimension() == ‘CATEGORY’) {
// Shows how to process a product group differently based on its type.
description = productGroup.asCategory().getName();
} else {
description = productGroup.getValue();
}

var padding = new Array(level + 1).join(‘-‘);

// Note: Child product groups may not have a max cpc if it has been excluded.
Logger.log(
‘%s %s, %s, %s, %s, %s’,
padding,
description,
productGroup.getDimension(),
productGroup.getMaxCpc(),
productGroup.isOtherCase(),
productGroup.getId().toFixed()
);
var childProductGroups = productGroup.children().get();
while (childProductGroups.hasNext()) {
var childProductGroup = childProductGroups.next();
walkHierarchy(childProductGroup, level + 1);
}
}

 

Obtém o grupo de produtos “Tudo mais”

function getEverythingElseProductGroup() {
var campaignName = ‘INSERT_CAMPAIGN_NAME_HERE’;
var adGroupName = ‘INSERT_ADGROUP_NAME_HERE’;

var shoppingAdGroup = AdWordsApp.shoppingAdGroups()
.withCondition(“CampaignName = ‘” + campaignName +
“‘ and AdGroupName = ‘” + adGroupName + “‘”)
.get()
.next();

var rootProductGroup = shoppingAdGroup.rootProductGroup();
var childProductGroups = rootProductGroup.children().get();
while (childProductGroups.hasNext()) {
var childProductGroup = childProductGroups.next();
if (childProductGroup.isOtherCase()) {
// Note: Child product groups may not have a max cpc if it has been
// excluded.
Logger.log(
‘”Everything else” product group found. Type of the product ‘ +
‘group is %s and bid is %s.’,
childProductGroup.getDimension(),
childProductGroup.getMaxCpc());
return;
}
}
Logger.log(‘”Everything else” product group not found under root ‘ +
‘product group.’);
}

 

Atualizar lances para grupos de produtos

function updateProductGroupBid() {
var productGroups = AdWordsApp.productGroups()
.withCondition(‘Clicks > 5’)
.withCondition(‘Ctr > 0.01’)
.forDateRange(‘LAST_MONTH’)
.get();
while (productGroups.hasNext()) {
var productGroup = productGroups.next();
productGroup.setMaxCpc(productGroup.getMaxCpc() + 0.01);
}
}

 

Recuperar anúncios de produtos

function getProductAds() {
var adGroupName = ‘INSERT_ADGROUP_NAME_HERE’;

var shoppingAdGroup = AdWordsApp.shoppingAdGroups()
.withCondition(“AdGroupName = ‘” + adGroupName + “‘”)
.get()
.next();

var productAds = shoppingAdGroup.ads().get();
while (productAds.hasNext()) {
var productAd = productAds.next();
Logger.log(
“Ad with ID = %s was found.”,
productAd.getId().toFixed(0));
}
}

 

Crie anúncios de produtos

function createProductAd() {
var adGroupName = ‘INSERT_ADGROUP_NAME_HERE’;

var shoppingAdGroup = AdWordsApp.shoppingAdGroups()
.withCondition(“AdGroupName = ‘” + adGroupName + “‘”)
.get()
.next();

var adOperation = shoppingAdGroup.newAdBuilder()
.withMobilePreferred(true)
.build();
var productAd = adOperation.getResult();
Logger.log(
“Ad with ID = %s was created.”,
productAd.getId().toFixed(0));

}

 

2. Conteúdo do Shopping – Por Google Ads. Assim como o script de Campanhas de Shopping, é possível inserir produtos, extrair informações da conta do merchant, listar todos os produtos e mais, sem a necessidade da plataforma do Google Ads.

Inserir um produto

function insertProduct() {
var merchantId = ‘INSERT_MERCHANT_ID_HERE’;

// Create a product resource. See
// //developers.google.com/shopping-content/v2/reference/v2/products
// for the full list of fields supported by product resource.
var productResource = {
‘offerId’: ‘book123’,
‘title’: ‘A Tale of Two Cities’,
‘description’: ‘A classic novel about the French Revolution’,
‘link’: ‘//my-book-shop.com/tale-of-two-cities.html’,
‘imageLink’: ‘//my-book-shop.com/tale-of-two-cities.jpg’,
‘contentLanguage’: ‘en’,
‘targetCountry’: ‘US’,
‘channel’: ‘online’,
‘availability’: ‘in stock’,
‘condition’: ‘new’,
‘googleProductCategory’: ‘Media > Books’,
‘productType’: ‘Media > Books’,
‘gtin’: ‘9780007350896’,
‘price’: {
‘value’: ‘2.50’,
‘currency’: ‘USD’
},
‘shipping’: [{
‘country’: ‘US’,
‘service’: ‘Standard shipping’,
‘price’: {
‘value’: ‘0.99’,
‘currency’: ‘USD’
}
}],
‘shippingWeight’: {
‘value’: ‘2’,
‘unit’: ‘pounds’
}
};

ShoppingContent.Products.insert(productResource, merchantId);
}

 

Listar todos os produtos

function listProducts() {
var merchantId = ‘INSERT_MERCHANT_ID_HERE’;

// List all the products for a given merchant.
var products = ShoppingContent.Products.list(merchantId);
if (products.resources) {
for (var i = 0; i < products.resources.length; i++) {
Logger.log(products.resources[i]);
}
}
}

 

Inserir produtos usando a API custombatch

function custombatch() {
var merchantId = ‘INSERT_MERCHANT_ID_HERE’;

// Create your product resources. See
// //developers.google.com/shopping-content/v2/reference/v2/products
// for the full list of fields supported by product resource. See the
// insertProduct() snippet for a code example that shows how to construct
// a product resource.
var productResource1 = {
// FILL THIS OUT.
};

var productResource2 = {
// FILL THIS OUT.
};

var productResource3 = {
// FILL THIS OUT.
};

var custombatchResource = {
‘entries’: [
{
‘batchId’: 1,
‘merchantId’: merchantId,
‘method’: ‘insert’,
‘productId’: ‘book124’,
‘product’: productResource1
},
{
‘batchId’: 2,
‘merchantId’: merchantId,
‘method’: ‘insert’,
‘productId’: ‘book125’,
‘product’: productResource2
},
{
‘batchId’: 3,
‘merchantId’: merchantId,
‘method’: ‘insert’,
‘productId’: ‘book126’,
‘product’: productResource3
},

]
};
var response = ShoppingContent.Products.custombatch(custombatchResource);
Logger.log(response);
}

 

Obter informações da conta do comerciante

function getAccountInfo() {
var merchantId = ‘INSERT_MERCHANT_ID_HERE’;
var accountId = ‘INSERT_ACCOUNT_ID_HERE’;

// See //developers.google.com/shopping-content/v2/reference/v2/accounts
// for the list of fields supported by Account type.
var accounts = ShoppingContent.Accounts.get(merchantId, accountId);
Logger.log(accounts);

// See //developers.google.com/shopping-content/v2/reference/v2/accountstatuses
// for the list of account status fields supported by Shopping Content API.
var accountstatuses = ShoppingContent.Accountstatuses.get(merchantId,
accountId);
Logger.log(accountstatuses);

// See //developers.google.com/shopping-content/v2/reference/v2/accountshipping
// for various Account shipping settings fields supported by Shopping
// Content API..
var accountshipping = ShoppingContent.Accountshipping.get(merchantId,
accountId);
Logger.log(accountshipping);

// See //developers.google.com/shopping-content/v2/reference/v2/accounttax
// for various Account tax fields supported by Shopping Content API.
var accounttax = ShoppingContent.Accounttax.get(merchantId, accountId);
Logger.log(accounttax);
}

3. Rede de Display – Por Google Ads. Esse script permite gerenciar alguns recursos da rede de display sem que você utilize a plataforma.

Adicionar um canal a um grupo de anúncios existente

function addPlacementToAdGroup() {
var adGroup = AdWordsApp.adGroups()
.withCondition(“Name = ‘INSERT_ADGROUP_NAME_HERE'”)
.withCondition(‘CampaignName = “INSERT_CAMPAIGN_NAME_HERE”‘)
.get()
.next();

// Other display criteria can be built in a similar manner using the
// corresponding builder method in the AdWordsApp.Display,
// AdWordsApp.CampaignDisplay or AdWordsApp.AdGroupDisplay class.
var placementOperation = adGroup.display()
.newPlacementBuilder()
.withUrl(‘//www.site.com’) // required
.withCpc(0.50) // optional
.build();
var placement = placementOperation.getResult();
Logger.log(‘Placement with id = %s and url = %s was created.’,
placement.getId(), placement.getUrl());
}

 

Recuperar todos os tópicos em um grupo de anúncios existente

function getAllTopics() {
var adGroup = AdWordsApp.adGroups()
.withCondition(‘Name = “INSERT_ADGROUP_NAME_HERE”‘)
.withCondition(‘CampaignName = “INSERT_CAMPAIGN_NAME_HERE”‘)
.get()
.next();

// Other display criteria can be retrieved in a similar manner using
// the corresponding selector methods in the AdWordsApp.Display,
// AdWordsApp.CampaignDisplay or AdWordsApp.AdGroupDisplay class.
var topicIterator = AdWordsApp.display()
.topics()
.withCondition(‘Impressions > 100’)
.forDateRange(‘LAST_MONTH’)
.orderBy(‘Clicks DESC’)
.get();

while (topicIterator.hasNext()) {
var topic = topicIterator.next();

// The list of all topic IDs can be found on
// //developers.google.com/adwords/api/docs/appendix/verticals
Logger.log(‘Topic with criterion id = %s and topic id = %s was ‘ +
‘found.’, topic.getId().toFixed(0),
topic.getTopicId().toFixed(0));
}
}

 

Obter estatísticas de todos os públicos-alvo de um grupo de anúncios existente

function getAudienceStats() {
var adGroup = AdWordsApp.adGroups()
.withCondition(‘Name = “INSERT_ADGROUP_NAME_HERE”‘)
.withCondition(‘CampaignName = “INSERT_CAMPAIGN_NAME_HERE”‘)
.get()
.next();

// Other display criteria can be retrieved in a similar manner using
// the corresponding selector methods in the AdWordsApp.Display,
// AdWordsApp.CampaignDisplay or AdWordsApp.AdGroupDisplay class.
var audienceIterator = adGroup.display()
.audiences()
.get();

Logger.log(‘ID, Audience ID, Clicks, Impressions, Cost’);

while (audienceIterator.hasNext()) {
var audience = audienceIterator.next();
var stats = audience.getStatsFor(‘LAST_MONTH’);

// User List IDs (List IDs) are available on the details page of
// a User List (found under the Audiences section of the Shared
// Library)
Logger.log(‘%s, %s, %s, %s, %s’, audience.getId().toFixed(0),
audience.getAudienceId(), stats.getClicks(),
stats.getImpressions(), stats.getCost());
}
}

4. Encontre oportunidades nos canais na Rede de Display – Por Derek Martin. O script destaca canais que estão com a performance baixa com base em algumas condições. A rede de display gera uma grande quantidade de canais, com esse script é possível economizar tempo já que ele faz essa análise automaticamente.

// This script reviews your GDN placements for the following conditions:
// 1) Placements that are converting at less than $40
// 2) Placements that have cost more than $50 but haven’t converted
// 3) Placements that have more than 5K impressions and less than .10 CTR

function main() {

var body = “<h2>Google Display Network Alert</h2>”;
body += “<h3>Placements that are converting at less than $40:</h3> ” ;
body += “<ul>”;

var list = runLowCostAndConvertingReport();

for (i=0; i < list.length; i++) {
body += “<li><strong>” + list[i].placement + “</strong> – ” + list[i].adgroup + ‘ – $’ + list2[i].cost + “</li>”;

}
body += “</ul>”;

body += “<h3>Placements that have cost more than $50 but haven’t converted:</h3> ” ;
body += “<ul>”;

var list2 = runHighCostNoConversionsReport();

for (i=0; i < list2.length; i++) {
body += “<li><strong>” + list2[i].placement + “</strong> – ” + list2[i].adgroup + ‘ – $’ + list2[i].cost + “</li>”;

}
body += “</ul>”;

body += “<h3>Placements that have more than 5K impressions and less than .10 CTR:</h3> ” ;
body += “<ul>”;

var list3 = runLowCtrAndHighImpressionsReport();

for (i=0; i < list3.length; i++) {
body += “<li><strong>” + list3[i].placement + “</strong> – ” + list3[i].adgroup +” – ” + parseFloat(list3[i].clicks/list3[i].impressions).toFixed(4) + “% – ” + list3[i].clicks + ” clicks – ” + list3[i].impressions + ‘ impressions ‘ + “</li>”;

}
body += “</ul>”;

MailApp.sendEmail(‘[email protected]’,’Display Network Alerts ‘, body,{htmlBody: body});
}

function runLowCostAndConvertingReport()
{
list = [];

// Any placement detail (individual page) that has converted 2 or more times with CPA below $40
var report = AdWordsApp.report(
‘SELECT Url, CampaignName, AdGroupName, Clicks, Impressions, Conversions, Cost ‘ +
‘FROM URL_PERFORMANCE_REPORT ‘ +
‘WHERE Cost < 40000000 ‘ +
‘AND Conversions >= 2 ‘ +
‘DURING LAST_30_DAYS’);

var rows = report.rows();

while (rows.hasNext()) {

var row = rows.next();

var anonymous = row[‘Url’].match(/anonymous\.google/g);
if (anonymous == null) {
var placement = row[‘Url’];

var campaign = row[‘CampaignName’];
var adgroup = row[‘AdGroupName’];
var clicks = row[‘Clicks’];
var impressions = row[‘Impressions’];
var conversions = row[‘Conversions’];
var cost = row[‘Cost’];

var placementDetail = new placementObject(placement, campaign, adgroup, clicks, impressions, conversions, cost);

list.push(placementDetail);
}
}
return list;
}
function runLowCtrAndHighImpressionsReport()
{
list = [];

// Any placement detail (individual page) that has converted 2 or more times with CPA below $40
var report = AdWordsApp.report(
‘SELECT Url, CampaignName, AdGroupName, Clicks, Impressions, Conversions, Cost ‘ +
‘FROM URL_PERFORMANCE_REPORT ‘ +
‘WHERE Impressions > 5000 ‘ +
‘AND Ctr < 0.1 ‘ +
‘DURING LAST_30_DAYS’);

var rows = report.rows();

while (rows.hasNext()) {

var row = rows.next();

var anonymous = row[‘Url’].match(/anonymous\.google/g);
if (anonymous == null) {
var placement = row[‘Url’];

var campaign = row[‘CampaignName’];
var adgroup = row[‘AdGroupName’];
var clicks = row[‘Clicks’];
var impressions = row[‘Impressions’];
var conversions = row[‘Conversions’];
var cost = row[‘Cost’];

var placementDetail = new placementObject(placement, campaign, adgroup, clicks, impressions, conversions, cost);

list.push(placementDetail);
}
}
return list;
}

function runHighCostNoConversionsReport()
{
list = [];

// Any placement detail (individual page) that has converted 2 or more times with CPA below $40
var report = AdWordsApp.report(
‘SELECT Url, CampaignName, AdGroupName, Clicks, Impressions, Conversions, Cost ‘ +
‘FROM URL_PERFORMANCE_REPORT ‘ +
‘WHERE Cost > 50000000 ‘ +
‘AND Conversions = 0 ‘ +
‘DURING LAST_30_DAYS’);

var rows = report.rows();

while (rows.hasNext()) {

var row = rows.next();

var anonymous = row[‘Url’].match(/anonymous\.google/g);
if (anonymous == null) {
var placement = row[‘Url’];

var campaign = row[‘CampaignName’];
var adgroup = row[‘AdGroupName’];
var clicks = row[‘Clicks’];
var impressions = row[‘Impressions’];
var conversions = row[‘Conversions’];
var cost = row[‘Cost’];

var placementDetail = new placementObject(placement, campaign, adgroup, clicks, impressions, conversions, cost);

list.push(placementDetail);
}
}
return list;
}

function placementObject(placement, campaign, adgroup, clicks, impressions, conversions, cost) {
this.placement = placement;
this.campaign = campaign;
this.adgroup = adgroup;
this.clicks = clicks;
this.impressions = impressions;
this.conversions = conversions;
this.cost = cost;

}

// Helpers
function warn(msg) {
Logger.log(‘WARNING: ‘+msg);
}

function info(msg) {
Logger.log(msg);
}

 

5. Identifique mudanças bruscas nos produtos do Google Shopping – Por Derek Martin. Receba um e-mail com um alerta de mudanças nas suas campanhas do Google Shopping. Ele analisa o custo e o CPC médio de seus produtos.

/***************************************************************************************
* AdWords Account Management — Review Google Shopping Products for sharp changes in
* Product level Cost and Product level Avg CPC (200%+ or -200%)
* Will e-mail any offending products for review.
* Version 1.1
* Created By: Derek Martin
* DerekMartinLA.com
****************************************************************************************/
var EMAIL_ADDRESS = “[email protected]

function main() {
var clientName = AdWordsApp.currentAccount().getName().split(“-“)[0];
var offendingProducts = []; // will hold list of product items that need review
var products = [];

products = runShoppingReport();
offendingProducts = analyzeShoppingResults(products);

if (offendingProducts.length > 0) {
var file = createSpreadsheet(clientName,offendingProducts);
sendAnEmail(clientName[0], offendingProducts.toString(), file);
} // end of if statement

} // end of main function

function runShoppingReport() {

// var reportFormat = _.str.quote(camp.getName());
var listOfProducts = [];

var report = AdWordsApp.report(
‘SELECT Date, Brand, OfferId, AverageCpc, Cost ‘ +
‘FROM SHOPPING_PERFORMANCE_REPORT ‘ +
‘DURING LAST_7_DAYS’);

var rows = report.rows();

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

var brand = row[‘Brand’];
var offerId= row[‘OfferId’];
var date = row[‘Date’];
var averageCpc = row[‘AverageCpc’];
var cost = row[‘Cost’];

var productResult = new productData(brand, offerId, date, averageCpc, cost);

listOfProducts.push(productResult);

} // end of report run

return listOfProducts;

}

function productData(brand, offerId, date, averageCpc, totalCost) {
this.brand = brand;
this.offerId = offerId;
this.date = date;
this.averageCpc = averageCpc;
this.totalCost = totalCost;
} // end of productData

function analyzeShoppingResults (products) {
var listOfProducts = products;
var listOfResults = [];

listOfProducts = _.uniq(listOfProducts);
listOfProducts.sort(); // sort list to keep things clean

var i = 0;
for each (offerId in listOfProducts) {

var currentOffer = _.where(listOfProducts, {offerId: listOfProducts[i].offerId});
if (currentOffer.length > 1) { // check if there are multiple dates at play, this way we can calculate min and max-

var oldestAvgCpc = parseFloat(currentOffer[0].averageCpc);
var oldestCost = parseFloat(currentOffer[0].totalCost);
var newestAvgCpc = parseFloat(currentOffer[currentOffer.length – 1].averageCpc);
var newestCost = parseFloat(currentOffer[currentOffer.length – 1].totalCost);

var cpcChange = parseFloat((newestAvgCpc – oldestAvgCpc) / oldestAvgCpc).toFixed(2);
var costChange = parseFloat((newestCost – oldestCost) / oldestCost).toFixed(2);

var offerResult = new productResult(listOfProducts[i].brand, listOfProducts[i].offerId, oldestAvgCpc, newestAvgCpc, cpcChange, oldestCost, newestCost, costChange);

listOfResults.push(offerResult);
} // end of length if statement
i++;
} // end of for each

listOfResults = _.uniq(listOfResults);

listOfResults.sort();

var uniqueList = _.uniq(listOfResults, function(item, key, productId) {
return item.productId;
} );

uniqueList.sort();

var offendingProducts = _.filter (uniqueList, function(product) {
return product.cpcDelta <= -2 || product.cpcDelta >=2 || product.costDelta >= 2 || product.costDelta <= -2 ;
});

return offendingProducts;

} // end of analyzeShoppingResults

function productResult (brand, id, oldCpc, newCpc, cpcDelta, oldCost, newCost, costDelta) {
this.brand = brand;
this.productId = id;
this.oldCpc = oldCpc;
this.newCpc = newCpc
this.cpcDelta = cpcDelta;
this.oldCost = oldCost;
this.newCost = newCost;
this.costDelta = costDelta;

} // end of productResult

function createSpreadsheet(client,results) {

var productResults = results;
var clientName = client;
var spreadsheetName = clientName + ‘-shoppingreport’;

var newSS = SpreadsheetApp.create(spreadsheetName, results.length, 26);

var sheet = newSS.getActiveSheet();

var columnNames = [“ProductId”, “Brand”, “Old AvgCpc”, “New AvgCpc”, “AvgCpc Delta”, “Old Daily Cost”, “New Daily Cost”, “Cost Delta”];

var headersRange = sheet.getRange(1, 1, 1, columnNames.length);

for (i = 0; i < productResults.length; i++) {

headersRange.setValues([columnNames]);

var product;
product = productResults[i].productId;
var productBrand = productResults[i].brand;
var oldAvgCpc = productResults[i].oldCpc;
var newAvgCpc = productResults[i].newCpc;
var cpcDelta = productResults[i].cpcDelta;
var oldCost = productResults[i].oldCost;
var newCost = productResults[i].newCost
var costDelta = productResults[i].costDelta;

sheet.appendRow([product, productBrand, oldAvgCpc, newAvgCpc, cpcDelta, oldCost, newCost, costDelta]);

// Sets the first column to a width which fits the text
sheet.setColumnWidth(1, 300);

var range = sheet.getRange(sheet.getMaxColumns(), sheet.getMaxRows());

range.setFontFamily(“Helvetica”);
range.setFontSize(30);

}

return newSS.getUrl();

}

function sendAnEmail (clientName, results, fileUrl) {

var data = Utilities.parseCsv(results, ‘\t’);
var today = new Date();
today = today.getMonth() + today.getDate() + today.getFullYear();

var filename = clientName + ‘search-results’ + today;

// Send an email with Search list attachment
var blob = Utilities.newBlob(results, ‘text/html’, ”);

MailApp.sendEmail(EMAIL_ADDRESS, clientName + ‘ – Google Shopping Alert Results ‘, ‘There are Google Shopping Products that need your attention. You can find the results at the following URL:\n\n’ + fileUrl, {
name: ‘Google Shopping Alert’
});

} // end of sendAnEmail function
/* HELPER FUNCTIONS */

function warn(msg) {
Logger.log(‘WARNING: ‘+msg);
}

function info(msg) {
Logger.log(msg);
}

/* UNDERSCORE LIBRARIES */

// Underscore.js 1.6.0
// //underscorejs.org
// (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Underscore may be freely distributed under the MIT license.
(function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,w=Object.keys,_=i.bind,j=function(n){return n instanceof j?n:this instanceof j?void(this._wrapped=n):new j(n)};”undefined”!=typeof exports?(“undefined”!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION=”1.6.0″;var A=j.each=j.forEach=function(n,t,e){if(null==n)return n;if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a=j.keys(n),u=0,i=a.length;i>u;u++)if(t.call(e,n[a[u]],a[u],n)===r)return;return n};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var O=”Reduce of empty array with no initial value”;j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[–i]:–i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},j.find=j.detect=function(n,t,r){var e;return k(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var k=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:k(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,j.property(t))},j.where=function(n,t){return j.filter(n,j.matches(t))},j.findWhere=function(n,t){return j.find(n,j.matches(t))},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);var e=-1/0,u=-1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;o>u&&(e=n,u=o)}),e},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);var e=1/0,u=1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;u>o&&(e=n,u=o)}),e},j.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=j.random(r++),e[r-1]=e[t],e[t]=n}),e},j.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=j.values(n)),n[j.random(n.length-1)]):j.shuffle(n).slice(0,Math.max(0,t))};var E=function(n){return null==n?j.identity:j.isFunction(n)?n:j.property(n)};j.sortBy=function(n,t,r){return t=E(t),j.pluck(j.map(n,function(n,e,u){return{value:n,index:e,criteria:t.call(r,n,e,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),”value”)};var F=function(n){return function(t,r,e){var u={};return r=E(r),A(t,function(i,a){var o=r.call(e,i,a,t);n(u,o,i)}),u}};j.groupBy=F(function(n,t,r){j.has(n,t)?n[t].push(r):n[t]=[r]}),j.indexBy=F(function(n,t,r){n[t]=r}),j.countBy=F(function(n,t){j.has(n,t)?n[t]++:n[t]=1}),j.sortedIndex=function(n,t,r,e){r=E(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])<u?i=o+1:a=o}return i},j.toArray=function(n){return n?j.isArray(n)?o.call(n):n.length===+n.length?j.map(n,j.identity):j.values(n):[]},j.size=function(n){return null==n?0:n.length===+n.length?n.length:j.keys(n).length},j.first=j.head=j.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:0>t?[]:o.call(n,0,t)},j.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},j.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},j.rest=j.tail=j.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},j.compact=function(n){return j.filter(n,j.identity)};var M=function(n,t,r){return t&&j.every(n,j.isArray)?c.apply(r,n):(A(n,function(n){j.isArray(n)||j.isArguments(n)?t?a.apply(r,n):M(n,t,r):r.push(n)}),r)};j.flatten=function(n,t){return M(n,t,[])},j.without=function(n){return j.difference(n,o.call(arguments,1))},j.partition=function(n,t){var r=[],e=[];return A(n,function(n){(t(n)?r:e).push(n)}),[r,e]},j.uniq=j.unique=function(n,t,r,e){j.isFunction(t)&&(e=r,r=t,t=!1);var u=r?j.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:j.contains(a,r))||(a.push(r),i.push(n[e]))}),i},j.union=function(){return j.uniq(j.flatten(arguments,!0))},j.intersection=function(n){var t=o.call(arguments,1);return j.filter(j.uniq(n),function(n){return j.every(t,function(t){return j.contains(t,n)})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,”length”).concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,””+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if(“number”!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u–;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var R=function(){};j.bind=function(n,t){var r,e;if(_&&n.bind===_)return _.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));R.prototype=n.prototype;var u=new R;R.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===j&&(e[u]=arguments[r++]);for(;r<arguments.length;)e.push(arguments[r++]);return n.apply(this,e)}},j.bindAll=function(n){var t=o.call(arguments,1);if(0===t.length)throw new Error(“bindAll must be passed function names”);return A(t,function(t){n[t]=j.bind(n[t],n)}),n},j.memoize=function(n,t){var r={};return t||(t=j.identity),function(){var e=t.apply(this,arguments);return j.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},j.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},j.defer=function(n){return j.delay.apply(j,[n,1].concat(o.call(arguments,1)))},j.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var c=function(){o=r.leading===!1?0:j.now(),a=null,i=n.apply(e,u),e=u=null};return function(){var l=j.now();o||r.leading!==!1||(o=l);var f=t-(l-o);return e=this,u=arguments,0>=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u),e=u=null):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u,i,a,o,c=function(){var l=j.now()-a;t>l?e=setTimeout(c,t-l):(e=null,r||(o=n.apply(i,u),i=u=null))};return function(){i=this,u=arguments,a=j.now();var l=r&&!e;return e||(e=setTimeout(c,t)),l&&(o=n.apply(i,u),i=u=null),o}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return j.partial(t,n)},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r–)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return–n<1?t.apply(this,arguments):void 0}},j.keys=function(n){if(!j.isObject(n))return[];if(w)return w(n);var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},j.pairs=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},j.invert=function(n){for(var t={},r=j.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case”[object String]”:return n==String(t);case”[object Number]”:return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case”[object Date]”:case”[object Boolean]”:return+n==+t;case”[object RegExp]”:return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if(“object”!=typeof n||”object”!=typeof t)return!1;for(var i=r.length;i–;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o)&&”constructor”in n&&”constructor”in t)return!1;r.push(n),e.push(t);var c=0,f=!0;if(“[object Array]”==u){if(c=n.length,f=c==t.length)for(;c–&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c–)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return”[object Array]”==l.call(n)},j.isObject=function(n){return n===Object(n)},A([“Arguments”,”Function”,”String”,”Number”,”Date”,”RegExp”],function(n){j[“is”+n]=function(t){return l.call(t)==”[object “+n+”]”}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,”callee”))}),”function”!=typeof/./&&(j.isFunction=function(n){return”function”==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||”[object Boolean]”==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.constant=function(n){return function(){return n}},j.property=function(n){return function(t){return t[n]}},j.matches=function(n){return function(t){if(t===n)return!0;for(var r in n)if(n[r]!==t[r])return!1;return!0}},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},j.now=Date.now||function(){return(new Date).getTime()};var T={escape:{“&”:”&amp;”,”<“:”&lt;”,”>”:”&gt;”,'”‘:”&quot;”,”‘”:”&#x27;”}};T.unescape=j.invert(T.escape);var I={escape:new RegExp(“[“+j.keys(T.escape).join(“”)+”]”,”g”),unescape:new RegExp(“(“+j.keys(T.unescape).join(“|”)+”)”,”g”)};j.each([“escape”,”unescape”],function(n){j[n]=function(t){return null==t?””:(“”+t).replace(I[n],function(t){return T[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+””;return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={“‘”:”‘”,”\\”:”\\”,”\r”:”r”,”\n”:”n”,” “:”t”,”\u2028″:”u2028″,”\u2029″:”u2029″},D=/\\|’|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join(“|”)+”|$”,”g”),i=0,a=”__p+='”;n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return”\\”+B[n]}),r&&(a+=”‘+\n((__t=(“+r+”))==null?”:_.escape(__t))+\n'”),e&&(a+=”‘+\n((__t=(“+e+”))==null?”:__t)+\n'”),u&&(a+=”‘;\n”+u+”\n__p+='”),i=o+t.length,t}),a+=”‘;\n”,r.variable||(a=”with(obj||{}){\n”+a+”}\n”),a=”var __t,__p=”,__j=Array.prototype.join,”+”print=function(){__p+=__j.call(arguments,”);};\n”+a+”return __p;\n”;try{e=new Function(r.variable||”obj”,”_”,a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source=”function(“+(r.variable||”obj”)+”){\n”+a+”}”,c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A([“pop”,”push”,”reverse”,”shift”,”sort”,”splice”,”unshift”],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),”shift”!=n&&”splice”!=n||0!==r.length||delete r[0],z.call(this,r)}}),A([“concat”,”join”,”slice”],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}}),”function”==typeof define&&define.amd&&define(“underscore”,[],function(){return j})}).call(this);
//# sourceMappingURL=underscore-min.map

// Underscore.string
!function(e,t){“use strict”;var n=t.prototype.trim,r=t.prototype.trimRight,i=t.prototype.trimLeft,s=function(e){return e*1||0},o=function(e,t){if(t<1)return””;var n=””;while(t>0)t&1&&(n+=e),t>>=1,e+=e;return n},u=[].slice,a=function(e){return e==null?”\\s”:e.source?e.source:”[“+p.escapeRegExp(e)+”]”},f={lt:”<“,gt:”>”,quot:'”‘,apos:”‘”,amp:”&”},l={};for(var c in f)l[f[c]]=c;var h=function(){function e(e){return Object.prototype.toString.call(e).slice(8,-1).toLowerCase()}var n=o,r=function(){return r.cache.hasOwnProperty(arguments[0])||(r.cache[arguments[0]]=r.parse(arguments[0])),r.format.call(null,r.cache[arguments[0]],arguments)};return r.format=function(r,i){var s=1,o=r.length,u=””,a,f=[],l,c,p,d,v,m;for(l=0;l<o;l++){u=e(r[l]);if(u===”string”)f.push(r[l]);else if(u===”array”){p=r[l];if(p[2]){a=i[s];for(c=0;c<p[2].length;c++){if(!a.hasOwnProperty(p[2][c]))throw new Error(h(‘[_.sprintf] property “%s” does not exist’,p[2][c]));a=a[p[2][c]]}}else p[1]?a=i[p[1]]:a=i[s++];if(/[^s]/.test(p[8])&&e(a)!=”number”)throw new Error(h(“[_.sprintf] expecting number but found %s”,e(a)));switch(p[8]){case”b”:a=a.toString(2);break;case”c”:a=t.fromCharCode(a);break;case”d”:a=parseInt(a,10);break;case”e”:a=p[7]?a.toExponential(p[7]):a.toExponential();break;case”f”:a=p[7]?parseFloat(a).toFixed(p[7]):parseFloat(a);break;case”o”:a=a.toString(8);break;case”s”:a=(a=t(a))&&p[7]?a.substring(0,p[7]):a;break;case”u”:a=Math.abs(a);break;case”x”:a=a.toString(16);break;case”X”:a=a.toString(16).toUpperCase()}a=/[def]/.test(p[8])&&p[3]&&a>=0?”+”+a:a,v=p[4]?p[4]==”0″?”0″:p[4].charAt(1):” “,m=p[6]-t(a).length,d=p[6]?n(v,m):””,f.push(p[5]?a+d:d+a)}}return f.join(“”)},r.cache={},r.parse=function(e){var t=e,n=[],r=[],i=0;while(t){if((n=/^[^\x25]+/.exec(t))!==null)r.push(n[0]);else if((n=/^\x25{2}/.exec(t))!==null)r.push(“%”);else{if((n=/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(t))===null)throw new Error(“[_.sprintf] huh?”);if(n[2]){i|=1;var s=[],o=n[2],u=[];if((u=/^([a-z_][a-z_\d]*)/i.exec(o))===null)throw new Error(“[_.sprintf] huh?”);s.push(u[1]);while((o=o.substring(u[0].length))!==””)if((u=/^\.([a-z_][a-z_\d]*)/i.exec(o))!==null)s.push(u[1]);else{if((u=/^\[(\d+)\]/.exec(o))===null)throw new Error(“[_.sprintf] huh?”);s.push(u[1])}n[2]=s}else i|=2;if(i===3)throw new Error(“[_.sprintf] mixing positional and named placeholders is not (yet) supported”);r.push(n)}t=t.substring(n[0].length)}return r},r}(),p={VERSION:”2.3.0″,isBlank:function(e){return e==null&&(e=””),/^\s*$/.test(e)},stripTags:function(e){return e==null?””:t(e).replace(/<\/?[^>]+>/g,””)},capitalize:function(e){return e=e==null?””:t(e),e.charAt(0).toUpperCase()+e.slice(1)},chop:function(e,n){return e==null?[]:(e=t(e),n=~~n,n>0?e.match(new RegExp(“.{1,”+n+”}”,”g”)):[e])},clean:function(e){return p.strip(e).replace(/\s+/g,” “)},count:function(e,n){return e==null||n==null?0:t(e).split(n).length-1},chars:function(e){return e==null?[]:t(e).split(“”)},swapCase:function(e){return e==null?””:t(e).replace(/\S/g,function(e){return e===e.toUpperCase()?e.toLowerCase():e.toUpperCase()})},escapeHTML:function(e){return e==null?””:t(e).replace(/[&<>”‘]/g,function(e){return”&”+l[e]+”;”})},unescapeHTML:function(e){return e==null?””:t(e).replace(/\&([^;]+);/g,function(e,n){var r;return n in f?f[n]:(r=n.match(/^#x([\da-fA-F]+)$/))?t.fromCharCode(parseInt(r[1],16)):(r=n.match(/^#(\d+)$/))?t.fromCharCode(~~r[1]):e})},escapeRegExp:function(e){return e==null?””:t(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,”\\$1″)},splice:function(e,t,n,r){var i=p.chars(e);return i.splice(~~t,~~n,r),i.join(“”)},insert:function(e,t,n){return p.splice(e,t,0,n)},include:function(e,n){return n===””?!0:e==null?!1:t(e).indexOf(n)!==-1},join:function(){var e=u.call(arguments),t=e.shift();return t==null&&(t=””),e.join(t)},lines:function(e){return e==null?[]:t(e).split(“\n”)},reverse:function(e){return p.chars(e).reverse().join(“”)},startsWith:function(e,n){return n===””?!0:e==null||n==null?!1:(e=t(e),n=t(n),e.length>=n.length&&e.slice(0,n.length)===n)},endsWith:function(e,n){return n===””?!0:e==null||n==null?!1:(e=t(e),n=t(n),e.length>=n.length&&e.slice(e.length-n.length)===n)},succ:function(e){return e==null?””:(e=t(e),e.slice(0,-1)+t.fromCharCode(e.charCodeAt(e.length-1)+1))},titleize:function(e){return e==null?””:t(e).replace(/(?:^|\s)\S/g,function(e){return e.toUpperCase()})},camelize:function(e){return p.trim(e).replace(/[-_\s]+(.)?/g,function(e,t){return t.toUpperCase()})},underscored:function(e){return p.trim(e).replace(/([a-z\d])([A-Z]+)/g,”$1_$2″).replace(/[-\s]+/g,”_”).toLowerCase()},dasherize:function(e){return p.trim(e).replace(/([A-Z])/g,”-$1″).replace(/[-_\s]+/g,”-“).toLowerCase()},classify:function(e){return p.titleize(t(e).replace(/_/g,” “)).replace(/\s/g,””)},humanize:function(e){return p.capitalize(p.underscored(e).replace(/_id$/,””).replace(/_/g,” “))},trim:function(e,r){return e==null?””:!r&&n?n.call(e):(r=a(r),t(e).replace(new RegExp(“^”+r+”+|”+r+”+$”,”g”),””))},ltrim:function(e,n){return e==null?””:!n&&i?i.call(e):(n=a(n),t(e).replace(new RegExp(“^”+n+”+”),””))},rtrim:function(e,n){return e==null?””:!n&&r?r.call(e):(n=a(n),t(e).replace(new RegExp(n+”+$”),””))},truncate:function(e,n,r){return e==null?””:(e=t(e),r=r||”…”,n=~~n,e.length>n?e.slice(0,n)+r:e)},prune:function(e,n,r){if(e==null)return””;e=t(e),n=~~n,r=r!=null?t(r):”…”;if(e.length<=n)return e;var i=function(e){return e.toUpperCase()!==e.toLowerCase()?”A”:” “},s=e.slice(0,n+1).replace(/.(?=\W*\w*$)/g,i);return s.slice(s.length-2).match(/\w\w/)?s=s.replace(/\s*\S+$/,””):s=p.rtrim(s.slice(0,s.length-1)),(s+r).length>e.length?e:e.slice(0,s.length)+r},words:function(e,t){return p.isBlank(e)?[]:p.trim(e,t).split(t||/\s+/)},pad:function(e,n,r,i){e=e==null?””:t(e),n=~~n;var s=0;r?r.length>1&&(r=r.charAt(0)):r=” “;switch(i){case”right”:return s=n-e.length,e+o(r,s);case”both”:return s=n-e.length,o(r,Math.ceil(s/2))+e+o(r,Math.floor(s/2));default:return s=n-e.length,o(r,s)+e}},lpad:function(e,t,n){return p.pad(e,t,n)},rpad:function(e,t,n){return p.pad(e,t,n,”right”)},lrpad:function(e,t,n){return p.pad(e,t,n,”both”)},sprintf:h,vsprintf:function(e,t){return t.unshift(e),h.apply(null,t)},toNumber:function(e,n){if(e==null||e==””)return 0;e=t(e);var r=s(s(e).toFixed(~~n));return r===0&&!e.match(/^0+$/)?Number.NaN:r},numberFormat:function(e,t,n,r){if(isNaN(e)||e==null)return””;e=e.toFixed(~~t),r=r||”,”;var i=e.split(“.”),s=i[0],o=i[1]?(n||”.”)+i[1]:””;return s.replace(/(\d)(?=(?:\d{3})+$)/g,”$1″+r)+o},strRight:function(e,n){if(e==null)return””;e=t(e),n=n!=null?t(n):n;var r=n?e.indexOf(n):-1;return~r?e.slice(r+n.length,e.length):e},strRightBack:function(e,n){if(e==null)return””;e=t(e),n=n!=null?t(n):n;var r=n?e.lastIndexOf(n):-1;return~r?e.slice(r+n.length,e.length):e},strLeft:function(e,n){if(e==null)return””;e=t(e),n=n!=null?t(n):n;var r=n?e.indexOf(n):-1;return~r?e.slice(0,r):e},strLeftBack:function(e,t){if(e==null)return””;e+=””,t=t!=null?””+t:t;var n=e.lastIndexOf(t);return~n?e.slice(0,n):e},toSentence:function(e,t,n,r){t=t||”, “,n=n||” and “;var i=e.slice(),s=i.pop();return e.length>2&&r&&(n=p.rtrim(t)+n),i.length?i.join(t)+n+s:s},toSentenceSerial:function(){var e=u.call(arguments);return e[3]=!0,p.toSentence.apply(p,e)},slugify:function(e){if(e==null)return””;var n=”ąàáäâãåæćęèéëêìíïîłńòóöôõøùúüûñçżź”,r=”aaaaaaaaceeeeeiiiilnoooooouuuunczz”,i=new RegExp(a(n),”g”);return e=t(e).toLowerCase().replace(i,function(e){var t=n.indexOf(e);return r.charAt(t)||”-“}),p.dasherize(e.replace(/[^\w\s-]/g,””))},surround:function(e,t){return[t,e,t].join(“”)},quote:function(e){return p.surround(e,'”‘)},exports:function(){var e={};for(var t in this){if(!this.hasOwnProperty(t)||t.match(/^(?:include|contains|reverse)$/))continue;e[t]=this[t]}return e},repeat:function(e,n,r){if(e==null)return””;n=~~n;if(r==null)return o(t(e),n);for(var i=[];n>0;i[–n]=e);return i.join(r)},levenshtein:function(e,n){if(e==null&&n==null)return 0;if(e==null)return t(n).length;if(n==null)return t(e).length;e=t(e),n=t(n);var r=[],i,s;for(var o=0;o<=n.length;o++)for(var u=0;u<=e.length;u++)o&&u?e.charAt(u-1)===n.charAt(o-1)?s=i:s=Math.min(r[u],r[u-1],i)+1:s=o+u,i=r[u],r[u]=s;return r.pop()}};p.strip=p.trim,p.lstrip=p.ltrim,p.rstrip=p.rtrim,p.center=p.lrpad,p.rjust=p.lpad,p.ljust=p.rpad,p.contains=p.include,p.q=p.quote,typeof exports!=”undefined”?(typeof module!=”undefined”&&module.exports&&(module.exports=p),exports._s=p):typeof define==”function”&&define.amd?define(“underscore.string”,[],function(){return p}):(e._=e._||{},e._.string=e._.str=p)}(this,String);

 

6. Criador de grupos de anúncios do Shopping em massa – Por Google Ads. Crie grupos de anúncios e produtos em massa com esse script do Google. Para utilizá-lo é necessário que você utilize uma planilha do Google.

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Bulk Shopping AdGroup Creator
*
* @overview The Bulk Shopping AdGroup Creator provides a way to bulk create ad
* groups and product groups in existing Shopping Campaigns. See
* //developers.google.com/adwords/scripts/docs/solutions/bulk-shopping-ad-group-creator
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.0.2
*
* @changelog
* – version 1.0.2
* – Removed use of ProductAdBuilder.withPromotionLine as it is deprecated.
* – version 1.0.1
* – Added validation for external spreadsheet setup.
* – version 1.0
* – Released initial version.
*/

/**
* SPREADSHEET_URL: URL for spreadsheet to read
* SHEET_NAME: Name of sheet in spreadsheet to read
*/
var SPREADSHEET_URL = ‘YOUR_SPREADSHEET_URL’;
var SHEET_NAME = ‘YOUR_SHEET_NAME’;

/**
* Column header specification
* These are the actual text the script looks for in the spreadsheet header.
*/
var CAMPAIGN_COLUMN = ‘Campaign’;
var AD_GROUP_COLUMN = ‘Ad Group’;
var MAX_CPC_COLUMN = ‘Max CPC’;

var PRODUCT_GROUP_COLUMN = ‘Product Group’;
var AD_GROUP_STATUS_COLUMN = ‘AdGroup Status’;

var REQUIRED_COLUMN_NAMES = {};
REQUIRED_COLUMN_NAMES[CAMPAIGN_COLUMN] = true;
REQUIRED_COLUMN_NAMES[AD_GROUP_COLUMN] = true;
REQUIRED_COLUMN_NAMES[MAX_CPC_COLUMN] = true;
REQUIRED_COLUMN_NAMES[PRODUCT_GROUP_COLUMN] = true;
REQUIRED_COLUMN_NAMES[AD_GROUP_STATUS_COLUMN] = true;
/** End of column header specification */

/**
* Reads campaign and bid data from spreadsheet and writes it to AdWords.
*/
function main() {
Logger.log(‘Using spreadsheet – %s.’, SPREADSHEET_URL);
var sheet = validateAndGetSpreadsheet(SPREADSHEET_URL, SHEET_NAME);
parseSheetAndConstructProductGroups(sheet);

Logger.log(‘Parsed spreadsheet and completed shopping campaign ‘ +
‘construction.’);
}

//Stores results of each row, along with formatting details
var resultsByRow = [‘Result’, ‘Notes’];
var resultColors = [‘Black’, ‘Black’];

/**
* Validates header of sheet to make sure that header matches expected format.
* Throws exception if problems are found. Saves the column number of each
* expected column to global variable.
*
* @param {!Array.<string>} headerRow A list of column header names.
* @return {!Object.<number>} A mapping from column name to column index.
* @throws If required column is missing.
*/
function validateHeader(headerRow) {
var result = {};

var missingColumns = Object.keys(REQUIRED_COLUMN_NAMES);

// Grab the column # for each expected input column.
for (var columnIndex = 0; columnIndex < headerRow.length; columnIndex++) {
var columnName = headerRow[columnIndex];
if (columnName in REQUIRED_COLUMN_NAMES) {
result[columnName] = columnIndex;
var index = missingColumns.indexOf(columnName);
if (index >= 0) {
missingColumns.splice(index, 1);
}
}
}

if (missingColumns.length > 0) {
throw ‘Bid sheet data format doesn\’t match expected format. ‘ +
‘Missing columns: ‘ + missingColumns.join();
}
return result;
}

/**
* Converts a spreadsheet row into map representation.
*
* @param {!Array.<!Object>} row The spreadsheet row.
* @param {!Object.<number>} headers Mapping from column name to column index.
* @return {?Object} The row in object form, or null for a parsing error.
*/
function parseRow(row, headers) {
var parsedRow = {};

for (var header in headers) {
var colNum = headers[header];
var val = row[colNum].toString().trim();
if (!val) {
continue;
}
// AWEditor will add double quotes (“) around string if it contains
// commas or double quotes, so we need to strip those out.
if (val.charAt(0) == ‘”‘ && val.charAt(val.length – 1) == ‘”‘) {
val = val.substring(1, val.length – 1);
}
// AWEditor escapes double quotes (“) with another double quote (“”).
val = val.replace(/””/g, ‘”‘);
if (header == PRODUCT_GROUP_COLUMN) {
var productGroups = [];
var parsedProductGroups =
parseAWEditorFormat(val);
if (parsedProductGroups.error) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] = parsedProductGroups.error;
resultColors[0] = ‘Red’;
return null;
}

for (var x = 0; x < parsedProductGroups.length; x++) {
// Product group type and value indices are level-1
// (e.g. for L1, x=0).
productGroups[x] = {
type: parsedProductGroups[x][0],
value: parsedProductGroups[x][1]
};
}
parsedRow[header] = productGroups;
} else {
parsedRow[header] = val;
}
}

// Ignore rows that don’t have any useful information.
var testRow = [];
for (var k in parsedRow) {
// Remove campaign, ad group columns and test if the rest is empty.
if (k == CAMPAIGN_COLUMN || k == AD_GROUP_COLUMN) {
continue;
}
testRow.push(parsedRow[k]);
}
if (testRow.toString().replace(/[,]+/g, ”) == ”) {
resultsByRow[0] = ‘SKIPPED’;
resultsByRow[1] = ‘Superfluous row’;
return null;
}
return parsedRow;
}

/**
* Parses spreadsheet and constructs ad groups and product groups in AdWords.
*
* @param {!Sheet} sheet The sheet to parse.
*/
function parseSheetAndConstructProductGroups(sheet) {
var headerRow = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];

var headers = validateHeader(headerRow);

var values = sheet.getRange(2, 1, sheet.getLastRow() – 1,
sheet.getLastColumn()).getValues();

var campaigns = {};

var outputColumn = sheet.getLastColumn() + 1;
var resultHeaderRange = sheet.getRange(1, outputColumn, 1, 2);
resultHeaderRange.setValues([resultsByRow]);
resultHeaderRange.setFontColors([resultColors]);
resultHeaderRange.setFontWeights([[‘Bold’, ‘Bold’]]);

// Iterate through rows.
for (var r = 0; r < values.length; r++) {
resultsByRow = [”, ”];
resultColors = [‘Black’, ‘Black’];
var row = values[r];

var parsedRow = parseRow(row, headers);

if (parsedRow) {
var bid = parsedRow[MAX_CPC_COLUMN];
if (bid) {
// For European locale, decimal points are commas.
bid = bid.toString().toLowerCase().replace(/,/g, ‘.’);
if (bid != ‘excluded’ && (isNaN(parseFloat(bid)) || !isFinite(bid))) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] = ‘Invalid bid’;
}
}
var campaignName = parsedRow[CAMPAIGN_COLUMN];
campaigns[campaignName] =
fetchCampaignIfNecessary(campaigns, campaignName);
if (campaigns[campaignName]) {
var adGroupName = parsedRow[AD_GROUP_COLUMN];
campaigns[campaignName].createdAdGroups[adGroupName] =
buildAdGroupIfNecessary(parsedRow, campaigns[campaignName],
adGroupName, bid);
if (campaigns[campaignName].createdAdGroups[adGroupName]) {
if (!campaigns[campaignName].createdAdGroups[adGroupName].skip) {
buildProductGroups(parsedRow,
campaigns[campaignName].createdAdGroups[adGroupName], bid);
} else {
resultsByRow[0] = ‘SKIPPED’;
resultsByRow[1] =
‘Ad group already exists with product groups’;
}
}
}

if (!resultsByRow[0]) {
resultsByRow[0] = ‘SUCCESS’;
}
}
switch (resultsByRow[0]) {
case ‘ERROR’:
resultColors[0] = ‘Red’;
break;
case ‘SUCCESS’:
resultColors[0] = ‘Green’;
break;
case ‘WARNING’:
resultColors[0] = ‘Yellow’;
}
var resultRange = sheet.getRange(r + 2, outputColumn, 1, 2);
resultRange.setValues([resultsByRow]);
resultRange.setFontColors([resultColors]);
resultRange.setFontWeights([[‘Bold’, ‘Normal’]]);
}
}

/**
* Fetches campaign from AdWords if it hasn’t already been done.
*
* @param {!Object.<ShoppingCampaign>} campaigns A cache of campaigns.
* @param {string} campaignName The name of the campaign to fetch.
* @return {?ShoppingCampaign} The campaign, or null if not found.
*/
function fetchCampaignIfNecessary(campaigns, campaignName) {
//Find the campaign
if (!campaignName) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] = ‘Missing campaign name’;
return null;
}
var campaign = campaigns[campaignName];
if (!campaign) {
campaign = findCampaignByName(campaignName);
campaign.createdAdGroups = {};
if (!campaign) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] = ‘Could not find campaign’;
}
}
return campaign;
}

/**
* Builds ad group if necessary, otherwise returns existing ad group.
*
* @param {!Array.<Object>} row A spreadsheet row.
* @param {!ShoppingCampaign} campaign
* @param {string} adGroupName The ad group to build or fetch.
* @param {number} bid
* @return {?ShoppingAdGroup} The ad group of null if there is an error.
*/
function buildAdGroupIfNecessary(row, campaign, adGroupName, bid) {
if (!adGroupName) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] = ‘Missing ad group name’;
return null;
}
// See if we already fetched/created the ad group.
var adGroup = campaign.createdAdGroups[adGroupName];
if (!adGroup) {
// Only use the bid on this line for the ad group default bid if there are
// no product groups specified for it. Ad group default bid must be
// specified.
if (row[PRODUCT_GROUP_COLUMN]) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] = ‘Ad Group is missing a default bid.’;
return null;
}
// Set default status to enabled.
var status = ‘ENABLED’;
// If ad group status is specified, make sure it’s “active”, “enabled”, or
// “paused”, and set status. Ad group status must be set on the first row
// that the ad group appears in.
if (row[AD_GROUP_STATUS_COLUMN]) {
status = row[AD_GROUP_STATUS_COLUMN].toUpperCase();
if (status == ‘ACTIVE’) {
status = ‘ENABLED’;
}
}
adGroup = createAdGroup(adGroupName, status, bid, campaign);
if (adGroup) {
adGroup.rootProductGroup = adGroup.rootProductGroup();
adGroup.rootProductGroup.childMap = {};
}
}
return adGroup;
}

/**
* Builds product groups from row.
*
* @param {!Array.<Object>} row A spreadsheet row.
* @param {!ShoppingAdGroup} adGroup The ad group to operate on.
* @param {number} bid The product group bid.
*/
function buildProductGroups(row, adGroup, bid) {
if (!row[PRODUCT_GROUP_COLUMN]) {
return;
}
// Iterate through product groups in row.
var maxLevel = -1;
var productGroupsToAdd = row[PRODUCT_GROUP_COLUMN];
var productGroup = adGroup.rootProductGroup;
for (var i = 0; i < productGroupsToAdd.length; i++) {
var type =
productGroupsToAdd[i].type.toString().toLowerCase()
.replace(/[ ]+/g, ”);
var val = productGroupsToAdd[i].value.toString().trim();
if (type) {
//For each Group level n, row must contain values for 1…n-1
if (i – maxLevel > 1) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] = ‘Every level of the product ‘ +
‘group type must have all higher ‘ +
‘level values. L’ + i + ‘ is filled but missing L’ +
(maxLevel + 1);
return;
}
maxLevel = i;

// Each row must have matching # of bidding attribute type and value.
if (!val) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] =
‘Every product group type must have an associated value. L’ +
i + ‘ has a type but no value’;
return;
}

// Build product groups.
if (!productGroup.childMap[val.toLowerCase()]) {
if (val == ‘*’) {
if (Object.keys(productGroup.childMap).length == 0) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] =
‘”Everything else” product group must come after all others’;
return;
} else {
var child = productGroup.childMap[val];
if (!child) {
var children = productGroup.children().get();
while (children.hasNext()) {
child = children.next();
if (child.isOtherCase()) {
break;
}
}
child.childMap = {};
}
productGroup.childMap[val] = child;
productGroup = child;
productGroup.setMaxCpc(adGroup.getMaxCpc());
//Only assign the bid to the lowest level product group
//on that row
if (i + 1 == productGroupsToAdd.length) {
if (bid != ‘excluded’) {
productGroup.setMaxCpc(bid);
} else {
productGroup.exclude();
}
}
}
} else {
var productGroupBuilder = productGroup.newChild();
// Verify that bidding attribute type is valid, construct
// productGroupBuilder.
switch (type) {
case ‘producttype’:
val = val.toLowerCase();
productGroupBuilder = productGroupBuilder.productTypeBuilder()
.withValue(val);
break;
case ‘brand’:
val = val.toLowerCase();
productGroupBuilder = productGroupBuilder.brandBuilder()
.withName(val);
break;
case ‘category’:
productGroupBuilder = productGroupBuilder.categoryBuilder()
.withName(val);
break;
case ‘condition’:
val = val.toUpperCase();
productGroupBuilder = productGroupBuilder.conditionBuilder()
.withCondition(val);
break;
case ‘itemid’:
val = val.toLowerCase();
productGroupBuilder = productGroupBuilder.itemIdBuilder()
.withValue(val);
break;
default:
if (type.match(/^custom((\\s)?(label|attribute))?/)) {
val = val.toLowerCase();
//make sure there’s a number at the end that’s between 0-4
if (type.match(/[0-4]$/)) {
productGroupBuilder =
productGroupBuilder.customLabelBuilder()
.withType(‘CUSTOM_LABEL_’ +
type.substring(type.length – 1))
.withValue(val);
} else {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] =
‘Invalid custom attribute type: ‘ +
productGroupsToAdd[i].type;
return;
}
} else {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] =
‘Invalid bidding attribute type: ‘ +
productGroupsToAdd[i].type;
return;
}
}

var productGroupOp = productGroupBuilder.build();

if (!productGroupOp.isSuccessful()) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] = ‘Error creating product group ‘ +
‘level ‘ + (i + 1) + ‘: ‘ + productGroupOp.getErrors();
return;
}

var result = productGroupOp.getResult();
// Only assign the bid to the lowest level product group on that row.
if (i + 1 == productGroupsToAdd.length) {
if (bid == ‘excluded’) {
result.exclude();
} else if (bid) {
result.setMaxCpc(bid);
}
}

result.childMap = {};
productGroup.childMap[val.toLowerCase()] = result;

// Set current product group to the newly created product group.
productGroup = result;
}
} else {
// Set current product group to the last read product group.
productGroup = productGroup.childMap[val.toLowerCase()];
}
}
}
}

/**
* Parses AdWords Editor product group format
* (e.g. * / Condition=’New’ / Custom label 2=’furniture’ /
* Product type=’bar carts’).
*
* @param {string} productGroupPath The product group path.
* @return {!Array.<!Array.<string>>} A list of product group component name/
* value pairs.
*/
function parseAWEditorFormat(productGroupPath) {
// Ignore * / case which is the root product group.
if (productGroupPath == ‘* /’ || !productGroupPath) {
return [];
}

var regexVals = productGroupPath.match(new RegExp(/'(.*?)’/g));

if (regexVals) {
for (var i = 0; i < regexVals.length; i++) {
productGroupPath = productGroupPath.replace(regexVals[i], ‘$’ + i);
}
}

var result = [];
var productGroup = productGroupPath.split(‘/’);

// AW Editor format starts with ‘* /’ so we ignore first one.
for (var x = 1; x < productGroup.length; x++) {
if (!productGroup[x]) {
continue;
}
// AW Editor format looks like: Brand=’nike’.
var pair = productGroup[x].trim().split(‘=’);
if (pair.length != 2) {
return {error: ‘Product group string malformed. Should have 1 “=”, ‘ +
‘but has ‘ + (pair.length – 1)};
}
var val = pair[1];
if (val.charAt(0) != ‘$’ && val.charAt(0) != ‘*’) {
return {error: ‘Product group string malformed. Please ensure you are ‘ +
‘using AdWords Editor format’};
}
// ‘*’ value doesn’t have single quotes around it.
if (val != ‘*’) {
var values = pair[1].split(‘$’);
// Skip 0 because it’s always blank. String always starts with $.
for (var i = 1; i < values.length; i++) {
val = val.replace(‘$’ + values[i], regexVals[values[i]]);
}
val = val.substring(1, val.length – 1).replace(/”/g, ‘\”);
}

result.push([pair[0], val]);
}
return result;
}

/**
* Fetches campaign from AdWords by name.
*
* @param {string} name The campaign name.
* @return {?ShoppingCampaign} The found campaign, or null if not found.
*/
function findCampaignByName(name) {
var campaignName = name.replace(/’/g, ‘\\\”);
var shoppingCampaignSelector = AdWordsApp
.shoppingCampaigns()
.withCondition(‘Name = \” + campaignName + ‘\”);

var campaign = null;

var shoppingCampaignIterator = shoppingCampaignSelector.get();
if (shoppingCampaignIterator.hasNext()) {
campaign = shoppingCampaignIterator.next();
}

return campaign;
}

/**
* Fetches ad group from AdWords given ad group name and campaign.
*
* @param {string} agName The name of the ad group.
* @param {!ShoppingCampaign} campaign The campaign within which to search.
* @return {?ShoppingAdGroup} The ad group or null if not found.
*/
function findAdGroupByName(agName, campaign) {
var adGroupName = agName.replace(/’/g, ‘\\\”);
var adGroupSelector = campaign
.adGroups()
.withCondition(‘Name = \” + adGroupName + ‘\”);

var adGroup = null;

var adGroupIterator = adGroupSelector.get();
if (adGroupIterator.hasNext()) {
adGroup = adGroupIterator.next();
}

return adGroup;
}

/**
* Creates ad group in AdWords if it doesn’t already exist, along with ad group
* ad.
*
* @param {string} name The name of the ad group.
* @param {string} status The desired status of the ad group.
* @param {number} defaultBid The max CPC for the ad group.
* @param {!ShoppingCampaign} campaign The campaign to create the ad group for.
* @return {?ShoppingAdGroup} The created ad group or null if there is an error.
*/
function createAdGroup(name, status, defaultBid, campaign) {
// See if ad group exists. If so, fetch it.
var adGroup = findAdGroupByName(name, campaign);
if (adGroup != null) {
if (adGroup.rootProductGroup()) {
// If root product group exists and not delete, then skip ad group.
adGroup.skip = true;
} else {
adGroup.createRootProductGroup();
}
return adGroup;
}

// Build ad group.
var adGroupOp = campaign.newAdGroupBuilder()
.withName(name)
.withStatus(status)
.withMaxCpc(defaultBid)
.build();

// Check for errors.
if (!adGroupOp.isSuccessful()) {
resultsByRow[0] = ‘ERROR’;
resultsByRow[1] = ‘Error creating ad group: ‘ +
adGroupOp.getErrors();
return null;
}

var adGroupResult = adGroupOp.getResult();

adGroupResult.createRootProductGroup();

Logger.log(‘Successfully created ad group [‘ + adGroupResult.getName() + ‘]’);

// Build ad group ad.
var adGroupAdOp = adGroupResult.newAdBuilder().build();

// Check for errors.
if (!adGroupAdOp.isSuccessful()) {
resultsByRow[0] = ‘WARNING’;
resultsByRow[1] = ‘Error creating ad group ad: ‘ +
adGroupAdOp.getErrors();
}

return adGroupResult;
}

/**
* DO NOT EDIT ANYTHING BELOW THIS LINE.
* Please modify your spreadsheet URL and email addresses at the top of the file
* only.
*/

/**
* Validates the provided spreadsheet URL and email address
* to make sure that they’re set up properly. Throws a descriptive error message
* if validation fails.
*
* @param {string} spreadsheeturl The URL of the spreadsheet to open.
* @param {string} sheetname The name of the sheet within the spreadsheet that
* should be fetched.
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
* @throws {Error} If the spreadsheet URL or email hasn’t been set
*/
function validateAndGetSpreadsheet(spreadsheeturl, sheetname) {
if (spreadsheeturl == ‘YOUR_SPREADSHEET_URL’) {
throw new Error(‘Please specify a valid Spreadsheet URL. You can find’ +
‘ a link to a template in the associated guide for this script.’);
}
if (sheetname == ‘YOUR_SHEET_NAME’) {
throw new Error(‘Please specify the name of the sheet you want to use on’ +
‘ your spreadsheet.’);
}
var spreadsheet = SpreadsheetApp.openByUrl(spreadsheeturl);
var sheet = spreadsheet.getSheetByName(sheetname);
return sheet;
}

 

Como Configurar

Automação Google Ads: Palavras-chave

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para “Trabalhar com Palavras-chave”.

 

1. Palavras-chave “exatas” – Por Brainlabs. O script faz com que suas palavras-chave sejam exatamente o que estão sendo digitadas, eliminando as variações aproximadas, como erros de digitação, plural, etc.

/**
*
* Adds as campaign or AdGroup negatives search queries which have triggered exact keywords
* Version: 1.1
* Updated: 2015-10-26
* Authors: Visar Shabi & Daniel Gilbert
* brainlabsdigital.com
*
**/
function main() {

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//Options

//Choose whether to add your negative exact keywords at campaign or AdGroup level.
//Set variable as “true” to add or as “false” to not add.
var AddAdGroupNegative = true; // true or false
var AddCampaignNegative = true; // true of false

//Parameters for filtering by campaign name and AdGroup name. The filtering is case insensitive.
//Leave blank, i.e. “”, if you want this script to run over all campaigns and AdGroups.
var campaignNameContains = “”;
var adGroupNameContains = “”;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

var campaigns = {};
var adGroups = {};

var exactKeywords = [];

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//Pull a list of all exact match keywords in the account

var report = AdWordsApp.report(
“SELECT AdGroupId, Id ” +
“FROM KEYWORDS_PERFORMANCE_REPORT ” +
“WHERE Impressions > 0 AND KeywordMatchType = EXACT ” +
“DURING LAST_7_DAYS”);

var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
var keywordId = row[‘Id’];
var adGroupId = row[‘AdGroupId’];
exactKeywords.push(adGroupId + “#” + keywordId);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//Pull a list of all exact (close variant) search queries

var report = AdWordsApp.report(
“SELECT Query, AdGroupId, CampaignId, KeywordId, KeywordTextMatchingQuery, Impressions, QueryMatchTypeWithVariant ” +
“FROM SEARCH_QUERY_PERFORMANCE_REPORT ” +
“WHERE CampaignName CONTAINS_IGNORE_CASE ‘” + campaignNameContains + “‘ ” +
“AND AdGroupName CONTAINS_IGNORE_CASE ‘” + adGroupNameContains + “‘ ” +
“DURING LAST_7_DAYS”);

var rows = report.rows();
while (rows.hasNext()) {
var row = rows.next();
var adGroupId = parseInt(row[‘AdGroupId’]);
var campaignId = parseInt(row[‘CampaignId’]);
var keywordId = parseInt(row[‘KeywordId’]);
var searchQuery = row[‘Query’].toLowerCase();
var keyword = row[‘KeywordTextMatchingQuery’].toLowerCase();
var matchType = row[‘QueryMatchTypeWithVariant’].toLowerCase();
if(keyword !== searchQuery && matchType.indexOf(“exact (close variant)”) !== -1){

if(!campaigns.hasOwnProperty(campaignId)){
campaigns[campaignId] = [[], []];
}

campaigns[campaignId][0].push(searchQuery);
campaigns[campaignId][1].push(adGroupId + “#” + keywordId);

if(!adGroups.hasOwnProperty(adGroupId)){
adGroups[adGroupId] = [[], []];
}

adGroups[adGroupId][0].push(searchQuery);
adGroups[adGroupId][1].push(adGroupId + “#” + keywordId);
}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//Parse data correctly

var adGroupIds = [];
var campaignIds = [];
var adGroupNegatives = [];
var campaignNegatives = [];

for(var x in campaigns){
campaignIds.push(parseInt(x));
campaignNegatives.push([]);
for(var y = 0; y < campaigns[x][0].length; y++){
var keywordId = campaigns[x][1][y];
var keywordText = campaigns[x][0][y];
if(exactKeywords.indexOf(keywordId) !== -1){
campaignNegatives[campaignIds.indexOf(parseInt(x))].push(keywordText);
}
}
}

for(var x in adGroups){
adGroupIds.push(parseInt(x));
adGroupNegatives.push([]);
for(var y = 0; y < adGroups[x][0].length; y++){
var keywordId = adGroups[x][1][y];
var keywordText = adGroups[x][0][y];
if(exactKeywords.indexOf(keywordId) !== -1){
adGroupNegatives[adGroupIds.indexOf(parseInt(x))].push(keywordText);
}
}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//Create the new negative exact keywords

var campaignResults = {};
var adGroupResults = {};

if(AddCampaignNegative){
var campaignIterator = AdWordsApp.campaigns()
.withIds(campaignIds)
.get();
while(campaignIterator.hasNext()){
var campaign = campaignIterator.next();
var campaignId = campaign.getId();
var campaignName = campaign.getName();
var campaignIndex = campaignIds.indexOf(campaignId);
for(var i = 0; i < campaignNegatives[campaignIndex].length; i++){
campaign.createNegativeKeyword(“[” + campaignNegatives[campaignIndex][i] + “]”)
if(!campaignResults.hasOwnProperty(campaignName)){
campaignResults[campaignName] = [];
}
campaignResults[campaignName].push(campaignNegatives[campaignIndex][i]);
}
}
}

if(AddAdGroupNegative){
var adGroupIterator = AdWordsApp.adGroups()
.withIds(adGroupIds)
.get();
while(adGroupIterator.hasNext()){
var adGroup = adGroupIterator.next();
var adGroupId = adGroup.getId();
var adGroupName = adGroup.getName();
var adGroupIndex = adGroupIds.indexOf(adGroupId);
for(var i = 0; i < adGroupNegatives[adGroupIndex].length; i++){
adGroup.createNegativeKeyword(“[” + adGroupNegatives[adGroupIndex][i] + “]”);
if(!adGroupResults.hasOwnProperty(adGroupName)){
adGroupResults[adGroupName] = [];
}
adGroupResults[adGroupName].push(adGroupNegatives[adGroupIndex][i]);
}
}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//Format the results

var resultsString = “The following negative keywords have been added to the following campaigns:”;

for(var x in campaignResults){
resultsString += “\n\n” + x + “:\n” + campaignResults[x].join(“\n”);
}

resultsString += “\n\n\n\nThe following negative keywords have been added to the following AdGroups:”;

for(var x in adGroupResults){
resultsString += “\n\n” + x + “:\n” + adGroupResults[x].join(“\n”);
}

Logger.log(resultsString);

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//

}

 

2. Oportunidades de Termos de Pesquisa – Por Daniel Gilbert. Utilize esse scripts para gerar insights dos termos que são buscados e que geraram boas conversões ou apenas custos. Ele quebra as palavras nos termos de pesquisa e organiza por custo e conversão, assim você poderá ter uma ideia de que tipo palavras estão gerando conversão ou apenas custos. Excelente script para buscar termos de cauda longa e analisar seu desempenho.

/**
*
* Search Query Mining Tool
*
* This script calculates the contribution of each word found in the search query report
* and outputs a report into a Google Doc spreadsheet.
*
* Version: 1.0
* Google Apps Script maintained on brainlabsdigital.com
*
**/

function main() {

//////////////////////////////////////////////////////////////////////////////
// Options

var startDate = “2015-04-01”;
var endDate = “2015-04-30”;
// The start and end date of the date range for your search query data
// Format is yyyy-mm-dd

var currencySymbol = “£”;
// The currency symbol used for formatting. For example “£”, “$” or “€”.

var campaignNameContains = “”;
// Use this if you only want to look at some campaigns
// such as campaigns with names containing ‘Brand’ or ‘Shopping’.
// Leave as “” if not wanted.

var spreadsheetUrl = “//docs.google.com/YOUR-SPREADSHEET-URL-HERE”;
// The URL of the Google Doc the results will be put into.

//////////////////////////////////////////////////////////////////////////////

// Thresholds

var impressionThreshold = 10;
var clickThreshold = 0;
var costThreshold = 0;
var conversionThreshold = 0;
// Words will be ignored if their statistics are lower than any of these thresholds

//////////////////////////////////////////////////////////////////////////////
// Find the negative keywords

var negativesByGroup = [];
var negativesByCampaign = [];
var sharedSetData = [];
var sharedSetNames = [];
var sharedSetCampaigns = [];
var dateRange = startDate.replace(/-/g, “”) + “,” + endDate.replace(/-/g, “”);
var activeCampaignIds = [];

// Gather ad group level negative keywords

var keywordReport = AdWordsApp.report(
“SELECT CampaignId, AdGroupId, KeywordText, KeywordMatchType ” +
“FROM KEYWORDS_PERFORMANCE_REPORT ” +
“WHERE CampaignStatus = ENABLED AND AdGroupStatus = ENABLED AND Status = ENABLED AND IsNegative = TRUE ” +
“AND CampaignName CONTAINS_IGNORE_CASE ‘” + campaignNameContains + “‘ ” +
“DURING ” + dateRange);

var keywordRows = keywordReport.rows();
while (keywordRows.hasNext()) {
var keywordRow = keywordRows.next();

if (negativesByGroup[keywordRow[“AdGroupId”]] == undefined) {
negativesByGroup[keywordRow[“AdGroupId”]] =
[[keywordRow[“KeywordText”].toLowerCase(),keywordRow[“KeywordMatchType”].toLowerCase()]];
} else {

negativesByGroup[keywordRow[“AdGroupId”]].push([keywordRow[“KeywordText”].toLowerCase(),keywordRow[“KeywordMatchType”].toLowerCase()]);
}

if (activeCampaignIds.indexOf(keywordRow[“CampaignId”]) < 0) {
activeCampaignIds.push(keywordRow[“CampaignId”]);
}
}//end while

// Gather campaign level negative keywords

var campaignNegReport = AdWordsApp.report(
“SELECT CampaignId, KeywordText, KeywordMatchType ” +
“FROM CAMPAIGN_NEGATIVE_KEYWORDS_PERFORMANCE_REPORT ” +
“WHERE IsNegative = TRUE ” +
“AND CampaignId IN [” + activeCampaignIds.join(“,”) + “]”
);
var campaignNegativeRows = campaignNegReport.rows();
while (campaignNegativeRows.hasNext()) {
var campaignNegativeRow = campaignNegativeRows.next();

if (negativesByCampaign[campaignNegativeRow[“CampaignId”]] == undefined) {
negativesByCampaign[campaignNegativeRow[“CampaignId”]] = [[campaignNegativeRow[“KeywordText”].toLowerCase(),campaignNegativeRow[“KeywordMatchType”].toLowerCase()]];
} else {

negativesByCampaign[campaignNegativeRow[“CampaignId”]].push([campaignNegativeRow[“KeywordText”].toLowerCase(),campaignNegativeRow[“KeywordMatchType”].toLowerCase()]);
}
}//end while

// Find which campaigns use shared negative keyword sets

var campaignSharedReport = AdWordsApp.report(
“SELECT CampaignName, CampaignId, SharedSetName, SharedSetType, Status ” +
“FROM CAMPAIGN_SHARED_SET_REPORT ” +
“WHERE SharedSetType = NEGATIVE_KEYWORDS ” +
“AND CampaignName CONTAINS_IGNORE_CASE ‘” + campaignNameContains + “‘”);
var campaignSharedRows = campaignSharedReport.rows();
while (campaignSharedRows.hasNext()) {
var campaignSharedRow = campaignSharedRows.next();

if (sharedSetCampaigns[campaignSharedRow[“SharedSetName”]] == undefined) {
sharedSetCampaigns[campaignSharedRow[“SharedSetName”]] = [campaignSharedRow[“CampaignId”]];
} else {

sharedSetCampaigns[campaignSharedRow[“SharedSetName”]].push(campaignSharedRow[“CampaignId”]);
}
}//end while

// Map the shared sets’ IDs (used in the criteria report below)
// to their names (used in the campaign report above)

var sharedSetReport = AdWordsApp.report(
“SELECT Name, SharedSetId, MemberCount, ReferenceCount, Type ” +
“FROM SHARED_SET_REPORT ” +
“WHERE ReferenceCount > 0 AND Type = NEGATIVE_KEYWORDS “);
var sharedSetRows = sharedSetReport.rows();
while (sharedSetRows.hasNext()) {
var sharedSetRow = sharedSetRows.next();
sharedSetNames[sharedSetRow[“SharedSetId”]] = sharedSetRow[“Name”];
}//end while

// Collect the negative keyword text from the sets,
// and record it as a campaign level negative in the campaigns that use the set

var sharedSetReport = AdWordsApp.report(
“SELECT SharedSetId, KeywordMatchType, KeywordText ” +
“FROM SHARED_SET_CRITERIA_REPORT “);
var sharedSetRows = sharedSetReport.rows();
while (sharedSetRows.hasNext()) {
var sharedSetRow = sharedSetRows.next();
var setName = sharedSetNames[sharedSetRow[“SharedSetId”]];
if (sharedSetCampaigns[setName] !== undefined) {
for (var i=0; i<sharedSetCampaigns[setName].length; i++) {
var campaignId = sharedSetCampaigns[setName][i];
if (negativesByCampaign[campaignId] == undefined) {
negativesByCampaign[campaignId] =
[[sharedSetRow[“KeywordText”].toLowerCase(),sharedSetRow[“KeywordMatchType”].toLowerCase()]];
} else {

negativesByCampaign[campaignId].push([sharedSetRow[“KeywordText”].toLowerCase(),sharedSetRow[“KeywordMatchType”].toLowerCase()]);
}
}
}
}//end while

Logger.log(“Finished negative keyword lists.”);

//////////////////////////////////////////////////////////////////////////////
// Defines the statistics to download or calculate, and their formatting

var statColumns = [“Clicks”, “Impressions”, “Cost”, “ConvertedClicks”, “ConversionValue”];
var calculatedStats = [[“CTR”,”Clicks”,”Impressions”],
[“CPC”,”Cost”,”Clicks”],
[“Conv. Rate”,”ConvertedClicks”,”Clicks”],
[“Cost / conv.”,”Cost”,”ConvertedClicks”],
[“Conv. value/cost”,”ConversionValue”,”Cost”]]
var currencyFormat = currencySymbol + “#,##0.00”;
var formatting = [“#,##0”, “#,##0”, currencyFormat, “#,##0″, currencyFormat,”0.00%”,currencyFormat,”0.00%”,currencyFormat,”0.00%”];

//////////////////////////////////////////////////////////////////////////////
// Go through the search query report, remove searches already excluded by negatives
// record the performance of each word in each remaining query

var queryReport = AdWordsApp.report(
“SELECT CampaignName, CampaignId, AdGroupId, AdGroupName, Query, ” + statColumns.join(“, “) + ” ” +
“FROM SEARCH_QUERY_PERFORMANCE_REPORT ” +
“WHERE CampaignStatus = ENABLED AND AdGroupStatus = ENABLED ” +
“AND CampaignName CONTAINS_IGNORE_CASE ‘” + campaignNameContains + “‘ ” +
“DURING ” + dateRange);

var campaignSearchWords = [];
var totalSearchWords = [];
var totalSearchWordsKeys = [];
var numberOfWords = [];

var queryRows = queryReport.rows();
while (queryRows.hasNext()) {
var queryRow = queryRows.next();
var searchIsExcluded = false;

// Checks if the query is excluded by an ad group level negative

if (negativesByGroup[queryRow[“AdGroupId”]] !== undefined) {
for (var i=0; i<negativesByGroup[queryRow[“AdGroupId”]].length; i++) {
if ( (negativesByGroup[queryRow[“AdGroupId”]][i][1] == “exact” &&
queryRow[“Query”] == negativesByGroup[queryRow[“AdGroupId”]][i][0]) ||
(negativesByGroup[queryRow[“AdGroupId”]][i][1] != “exact” &&
(” “+queryRow[“Query”]+” “).indexOf(” “+negativesByGroup[queryRow[“AdGroupId”]][i][0]+” “) > -1 )){
searchIsExcluded = true;
break;
}
}
}

// Checks if the query is excluded by a campaign level negative

if (!searchIsExcluded && negativesByCampaign[queryRow[“CampaignId”]] !== undefined) {
for (var i=0; i<negativesByCampaign[queryRow[“CampaignId”]].length; i++) {
if ( (negativesByCampaign[queryRow[“CampaignId”]][i][1] == “exact” &&
queryRow[“Query”] == negativesByCampaign[queryRow[“CampaignId”]][i][0]) ||
(negativesByCampaign[queryRow[“CampaignId”]][i][1]!= “exact” &&
(” “+queryRow[“Query”]+” “).indexOf(” “+negativesByCampaign[queryRow[“CampaignId”]][i][0]+” “) > -1 )){
searchIsExcluded = true;
break;
}
}
}

if (searchIsExcluded) {continue;}
// if the search is already excluded by the current negatives,
// we ignore it and go on to the next query

var currentWords = queryRow[“Query”].split(” “);
var doneWords = [];

if (campaignSearchWords[queryRow[“CampaignName”]] == undefined) {
campaignSearchWords[queryRow[“CampaignName”]] = [];
}

var wordLength = currentWords.length;
if (wordLength > 6) {
wordLength = “7+”;
}
if (numberOfWords[wordLength] == undefined) {
numberOfWords[wordLength] = [];
}
for (var i=0; i<statColumns.length; i++) {
if (numberOfWords[wordLength][statColumns[i]] > 0) {
numberOfWords[wordLength][statColumns[i]] += parseFloat(queryRow[statColumns[i]].replace(/,/g, “”));
} else {
numberOfWords[wordLength][statColumns[i]] = parseFloat(queryRow[statColumns[i]].replace(/,/g, “”));
}
}

// Splits the query into words and records the stats for each

for (var w=0;w<currentWords.length;w++) {
if (doneWords.indexOf(currentWords[w]) < 0) { //if this word hasn’t been in the query yet

if (campaignSearchWords[queryRow[“CampaignName”]][currentWords[w]] == undefined) {
campaignSearchWords[queryRow[“CampaignName”]][currentWords[w]] = [];
}
if (totalSearchWords[currentWords[w]] == undefined) {
totalSearchWords[currentWords[w]] = [];
totalSearchWordsKeys.push(currentWords[w]);
}

for (var i=0; i<statColumns.length; i++) {
var stat = parseFloat(queryRow[statColumns[i]].replace(/,/g, “”));
if (campaignSearchWords[queryRow[“CampaignName”]][currentWords[w]][statColumns[i]] > 0) {
campaignSearchWords[queryRow[“CampaignName”]][currentWords[w]][statColumns[i]] += stat;
} else {
campaignSearchWords[queryRow[“CampaignName”]][currentWords[w]][statColumns[i]] = stat;
}
if (totalSearchWords[currentWords[w]][statColumns[i]] > 0) {
totalSearchWords[currentWords[w]][statColumns[i]] += stat;
} else {
totalSearchWords[currentWords[w]][statColumns[i]] = stat;
}
}

doneWords.push(currentWords[w]);
}//end if
}//end for
}//end while

Logger.log(“Finished analysing queries.”);

//////////////////////////////////////////////////////////////////////////////
// Output the data into the spreadsheet

var campaignSearchWordsOutput = [];
var campaignSearchWordsFormat = [];
var totalSearchWordsOutput = [];
var totalSearchWordsFormat = [];
var wordLengthOutput = [];
var wordLengthFormat = [];

// Add headers

var calcStatNames = [];
for (var s=0; s<calculatedStats.length; s++) {
calcStatNames.push(calculatedStats[s][0]);
}
var statNames = statColumns.concat(calcStatNames);
campaignSearchWordsOutput.push([“Campaign”,”Word”].concat(statNames));
totalSearchWordsOutput.push([“Word”].concat(statNames));
wordLengthOutput.push([“Word count”].concat(statNames));

// Output the campaign level stats

for (var campaign in campaignSearchWords) {
for (var word in campaignSearchWords[campaign]) {

if (campaignSearchWords[campaign][word][“Impressions”] < impressionThreshold) {continue;}
if (campaignSearchWords[campaign][word][“Clicks”] < clickThreshold) {continue;}
if (campaignSearchWords[campaign][word][“Cost”] < costThreshold) {continue;}
if (campaignSearchWords[campaign][word][“ConvertedClicks”] < conversionThreshold) {continue;}

// skips words under the thresholds

var printline = [campaign, word];

for (var s=0; s<statColumns.length; s++) {
printline.push(campaignSearchWords[campaign][word][statColumns[s]]);
}

for (var s=0; s<calculatedStats.length; s++) {
var multiplier = calculatedStats[s][1];
var divisor = calculatedStats[s][2];
if (campaignSearchWords[campaign][word][divisor] > 0) {
printline.push(campaignSearchWords[campaign][word][multiplier] / campaignSearchWords[campaign][word][divisor]);
} else {
printline.push(“-“);
}
}

campaignSearchWordsOutput.push(printline);
campaignSearchWordsFormat.push(formatting);
}
} // end for

totalSearchWordsKeys.sort(function(a,b) {return totalSearchWords[b][“Cost”] – totalSearchWords[a][“Cost”];});

for (var i = 0; i<totalSearchWordsKeys.length; i++) {
var word = totalSearchWordsKeys[i];

if (totalSearchWords[word][“Impressions”] < impressionThreshold) {continue;}
if (totalSearchWords[word][“Clicks”] < clickThreshold) {continue;}
if (totalSearchWords[word][“Cost”] < costThreshold) {continue;}
if (totalSearchWords[word][“ConvertedClicks”] < conversionThreshold) {continue;}

// skips words under the thresholds

var printline = [word];

for (var s=0; s<statColumns.length; s++) {
printline.push(totalSearchWords[word][statColumns[s]]);
}

for (var s=0; s<calculatedStats.length; s++) {
var multiplier = calculatedStats[s][1];
var divisor = calculatedStats[s][2];
if (totalSearchWords[word][divisor] > 0) {
printline.push(totalSearchWords[word][multiplier] / totalSearchWords[word][divisor]);
} else {
printline.push(“-“);
}
}

totalSearchWordsOutput.push(printline);
totalSearchWordsFormat.push(formatting);
} // end for

for (var i = 1; i<8; i++) {
if (i < 7) {
var wordLength = i;
} else {
var wordLength = “7+”;
}

var printline = [wordLength];

if (numberOfWords[wordLength] == undefined) {
printline.push([0,0,0,0,”-“,”-“,”-“,”-“]);
} else {
for (var s=0; s<statColumns.length; s++) {
printline.push(numberOfWords[wordLength][statColumns[s]]);
}

for (var s=0; s<calculatedStats.length; s++) {
var multiplier = calculatedStats[s][1];
var divisor = calculatedStats[s][2];
if (numberOfWords[wordLength][divisor] > 0) {
printline.push(numberOfWords[wordLength][multiplier] / numberOfWords[wordLength][divisor]);
} else {
printline.push(“-“);
}
}
}

wordLengthOutput.push(printline);
wordLengthFormat.push(formatting);
} // end for

// Finds available names for the new sheets

var campaignWordName = “Campaign Word Analysis”;
var totalWordName = “Total Word Analysis”;
var wordCountName = “Word Count Analysis”;
var campaignWordSheet = SpreadsheetApp.openByUrl(spreadsheetUrl).getSheetByName(campaignWordName);
var totalWordSheet = SpreadsheetApp.openByUrl(spreadsheetUrl).getSheetByName(totalWordName);
var wordCountSheet = SpreadsheetApp.openByUrl(spreadsheetUrl).getSheetByName(wordCountName);
var i = 1;
while (campaignWordSheet != null || wordCountSheet != null || totalWordSheet != null) {
campaignWordName = “Campaign Word Analysis ” + i;
totalWordName = “Total Word Analysis ” + i;
wordCountName = “Word Count Analysis ” + i;
campaignWordSheet = SpreadsheetApp.openByUrl(spreadsheetUrl).getSheetByName(campaignWordName);
totalWordSheet = SpreadsheetApp.openByUrl(spreadsheetUrl).getSheetByName(totalWordName);
wordCountSheet = SpreadsheetApp.openByUrl(spreadsheetUrl).getSheetByName(wordCountName);
i++;
}
campaignWordSheet = SpreadsheetApp.openByUrl(spreadsheetUrl).insertSheet(campaignWordName);
totalWordSheet = SpreadsheetApp.openByUrl(spreadsheetUrl).insertSheet(totalWordName);
wordCountSheet = SpreadsheetApp.openByUrl(spreadsheetUrl).insertSheet(wordCountName);

campaignWordSheet.getRange(“R1C1”).setValue(“Analysis of Words in Search Query Report, By Campaign”);
wordCountSheet.getRange(“R1C1”).setValue(“Analysis of Search Query Performance by Words Count”);

if (campaignNameContains == “”) {
totalWordSheet.getRange(“R1C1”).setValue(“Analysis of Words in Search Query Report, By Account”);
} else {
totalWordSheet.getRange(“R1C1”).setValue(“Analysis of Words in Search Query Report, Over All Campaigns Containing ‘” + campaignNameContains + “‘”);
}

campaignWordSheet.getRange(“R2C1:R” + (campaignSearchWordsOutput.length+1) + “C” + campaignSearchWordsOutput[0].length).setValues(campaignSearchWordsOutput);
campaignWordSheet.getRange(“R3C3:R” + (campaignSearchWordsOutput.length+1) + “C” + (formatting.length+2)).setNumberFormats(campaignSearchWordsFormat);
totalWordSheet.getRange(“R2C1:R” + (totalSearchWordsOutput.length+1) + “C” + totalSearchWordsOutput[0].length).setValues(totalSearchWordsOutput);
totalWordSheet.getRange(“R3C2:R” + (totalSearchWordsOutput.length+1) + “C” + (formatting.length+1)).setNumberFormats(totalSearchWordsFormat);
wordCountSheet.getRange(“R2C1:R” + (wordLengthOutput.length+1) + “C” + wordLengthOutput[0].length).setValues(wordLengthOutput);
wordCountSheet.getRange(“R3C2:R” + (wordLengthOutput.length+1) + “C” + (formatting.length+1)).setNumberFormats(wordLengthFormat);

Logger.log(“Finished writing to spreadsheet.”);
}

 

3. Analise seus Termos de Pesquisa – Por Derek Martin. Revise seus termos de pesquisa e faça ajustes pela planilha do Google.

/**********************************************************************************************
* AdWords Account Management — Review Search Queries & Post Adjustments via Google Docs.
* Version 1.0
* Created By: Derek Martin
* DerekMartinLA.com & MixedMarketingArtist.com
*********************************************************************************************/

var GOOGLE_DOC_URL = “put your url here”;
var START_DATE = ‘20150401’;
var END_DATE = ‘20150415’;

function main() {
var results = runQueryReport();
modifySpreadSheet(results);
}

// check a query for whether the keyword exists in the account
// returns true or false

function keywordExists(keyword) {
var kw = keyword;

if (kw != null) {
kwIter = AdWordsApp.keywords().withCondition(“Text = \'”+kw+”\'”).withCondition(“Status = ENABLED”).get();

var exists = kwIter.totalNumEntities() > 0 ? true : false;
return exists;
}
}

function runQueryReport() {

var listOfQueries = [];

var report = AdWordsApp.report(
‘SELECT Query, CampaignName, AdGroupName, ConversionsManyPerClick, ConversionValue, Cost, AverageCpc, Clicks, Impressions, Ctr, ConversionRateManyPerClick ‘ +
‘FROM SEARCH_QUERY_PERFORMANCE_REPORT ‘ +
‘WHERE CampaignName does_not_contain Shopping ‘ +
‘DURING ‘ + START_DATE + ‘,’ + END_DATE +’ ‘);

var rows = report.rows();

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

var query = row[‘Query’];
var campaign= row[‘CampaignName’];
var adgroup = row[‘AdGroupName’];
var conversions = row[‘ConversionsManyPerClick’];
var conversionValue = parseFloat(row[‘ConversionValue’]).toPrecision(2);
var cost = parseFloat(row[‘Cost’]).toPrecision(2);
var roas = conversionValue / cost;
var averageCpc = row[‘AverageCpc’];
var clicks = row[‘Clicks’];
var impressions = row[‘Impressions’];
var ctr = row[‘Ctr’];
var conversionRate = row[‘ConversionRateManyPerClick’];

if (query.length < 20) {
var keyword_exists = keywordExists(query);
} else {
var keyword_exists = false;
}

var queryResult = new queryData(query, campaign, adgroup, conversions, conversionValue,cost, roas,averageCpc, clicks, impressions, ctr, conversionRate, keyword_exists);

listOfQueries.push(queryResult);

} // end of report run

return listOfQueries;

}

function queryData(query, campaign, adgroup, conversions, conversionValue, cost, roas, averageCpc, clicks, impressions, ctr, conversionRate, exists ) {
this.query = query;
this.campaign = campaign;
this.adgroup = adgroup;
this.conversions = conversions;
this.conversionValue = conversionValue;
this.cost = cost;
this.roas = roas;
this.averageCpc = averageCpc;
this.clicks = clicks;
this.impressions = impressions;
this.ctr = ctr;
this.conversionRate = conversionRate;
this.exists = exists;
} // end of productData

function modifySpreadSheet(results) {

var queryResults = results;

var querySS = SpreadsheetApp.openByUrl(GOOGLE_DOC_URL);

var sheet = querySS.getActiveSheet();

var columnNames = [“Query”, “In Account”, “Campaign”, “Ad Group”, “Conversions”, “Conversion Value”, “Cost”, “ROAS”, “Average CPC”,”Clicks”, “Impressions”, “Ctr”, “ConversionRate”];

var headersRange = sheet.getRange(1, 1, 1, columnNames.length);

headersRange.setFontWeight(“bold”);
headersRange.setFontSize(12);
headersRange.setBorder(false, false, true, false, false, false);

for (i = 0; i < queryResults.length; i++) {
headersRange.setValues([columnNames]);

var query = queryResults[i].query;
var exists = (queryResults[i].exists == true) ? “Added” : “Not Added”;
var campaign = queryResults[i].campaign;
var adgroup = queryResults[i].adgroup;
var conversions = queryResults[i].conversions;
var conversionValue = queryResults[i].conversionValue;
var cost = queryResults[i].cost;
var roas = (isNaN(queryResults[i].roas)) ? 0.00 : queryResults[i].roas;
var averageCpc = queryResults[i].averageCpc;
var clicks = queryResults[i].clicks;
var impressions = queryResults[i].impressions;
var ctr = queryResults[i].ctr;
var conversionRate = queryResults[i].conversionRate;

sheet.appendRow([query, exists, campaign, adgroup, conversions, conversionValue, cost, roas, averageCpc, clicks, impressions, ctr, conversionRate]);
}

sheet.getRange(“A2:M”).setFontSize(12);
sheet.getRange(“E:E”).setNumberFormat(“0”);
sheet.getRange(“F:G”).setNumberFormat(“$0.00”);
sheet.getRange(“H:H”).setNumberFormat(“0.00”);
sheet.getRange(“I:I”).setNumberFormat(“$0.00”);

sheet.getRange(“J:K”).setNumberFormat(“0.00”);
sheet.getRange(“A2:M”)
.sort({column: 5, ascending: false});
sheet.getRange(“A:D”).setVerticalAlignment(“middle”);
sheet.getRange(“A:D”).setHorizontalAlignment(“center”);
}

// Helper functions
function warn(msg) {
Logger.log(‘WARNING: ‘+msg);
}

function info(msg) {
Logger.log(msg);
}

 

4. Lista de palavras-chave negativas – Por Google Ads. Com esses 2 scripts você pode criar uma nova lista de palavras-chave negativa e apagar os termos que estão sendo compartilhados das outras listas de palavras-chave.

Construa uma nova lista de palavras-chave negativas e adicione-a a uma campanha

function addNegativeKeywordListToCampaign() {
var NEGATIVE_KEYWORD_LIST_NAME = ‘INSERT_LIST_NAME_HERE’;
var CAMPAIGN_NAME = ‘INSERT_CAMPAIGN_NAME_HERE’;

var negativeKeywordListOperator =
AdWordsApp.newNegativeKeywordListBuilder()
.withName(NEGATIVE_KEYWORD_LIST_NAME)
.build();

if (negativeKeywordListOperator.isSuccessful()) {
var negativeKeywordList = negativeKeywordListOperation.getResult();
negativeKeywordList.addKeywords(
‘broad match keyword’,
‘”phrase match keyword”‘,
‘[exact match keyword]’
);

var campaign = AdWordsApp.campaigns()
.withCondition(‘Name = “‘ + CAMPAIGN_NAME + ‘”‘)
.get();
campaign.addNegativeKeywordList(negativeKeywordList);
} else {
Logger.log(‘Could not add Negative Keyword List.’);
}
}

Remover todas as palavras-chave negativas compartilhadas em uma lista de palavras-chave negativas

function removeAllNegativeKeywordsFromList() {
var NEGATIVE_KEYWORD_LIST_NAME = ‘INSERT_LIST_NAME_HERE’;

var negativeKeywordListIterator =
AdWordsApp.negativeKeywordLists()
.withCondition(‘Name = “‘ + NEGATIVE_KEYWORD_LIST_NAME + ‘”‘)
.get();

if (negativeKeywordListIterator.totalNumEntities() == 1) {
var negativeKeywordList = negativeKeywordListIterator.next();
var sharedNegativeKeywordIterator =
negativeKeywordList.negativeKeywords().get();

var sharedNegativeKeywords = [];

while (sharedNegativeKeywordIterator.hasNext()) {
sharedNegativeKeywords.push(sharedNegativeKeywordIterator.next());
}

for (var i = 0; i < sharedNegativeKeywords.length; i++) {
sharedNegativeKeywords[i].remove();
}
}
}

 

5. Palavras-chave negativas – Por Google Ads. Adicione e exporte palavras-chave de suas campanhas e grupos de anúncios com esse script do Google Ads. Utilize-o caso não queira utilizar a plataforma do Google Ads.

Adicione palavras-chave negativas a uma campanha

function addNegativeKeywordToCampaign() {
var campaignIterator = AdWordsApp.campaigns()
.withCondition(‘Name = “INSERT_CAMPAIGN_NAME_HERE”‘)
.get();
if (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
campaign.createNegativeKeyword(‘[Budget hotels]’);
}
}

 

Obter palavras-chave negativas em uma campanha

function getNegativeKeywordForCampaign() {
var campaignIterator = AdWordsApp.campaigns()
.withCondition(‘Name = “INSERT_CAMPAIGN_NAME_HERE”‘)
.get();
if (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
var negativeKeywordIterator = campaign.negativeKeywords().get();
while (negativeKeywordIterator.hasNext()) {
var negativeKeyword = negativeKeywordIterator.next();
Logger.log(‘Text: ‘ + negativeKeyword.getText() + ‘, MatchType: ‘ +
negativeKeyword.getMatchType());
}
}
}

 

Adicione uma palavra-chave negativa a um grupo de anúncios

function addNegativeKeywordToAdGroup() {
// If you have multiple ad groups with the same name, this snippet will
// pick an arbitrary matching ad group each time. In such cases, just
// filter on the campaign name as well:
//
// AdWordsApp.adGroups()
// .withCondition(‘Name = “INSERT_ADGROUP_NAME_HERE”‘)
// .withCondition(‘CampaignName = “INSERT_CAMPAIGN_NAME_HERE”‘)
var adGroupIterator = AdWordsApp.adGroups()
.withCondition(‘Name = “INSERT_ADGROUP_NAME_HERE”‘)
.get();
if (adGroupIterator.hasNext()) {
var adGroup = adGroupIterator.next();
adGroup.createNegativeKeyword(‘[Budget hotels]’);
}
}

 

Obter palavras-chave negativas em um grupo de anúncios

function getNegativeKeywordForAdGroup() {
// If you have multiple ad groups with the same name, this snippet will
// pick an arbitrary matching ad group each time. In such cases, just
// filter on the campaign name as well:
//
// AdWordsApp.adGroups()
// .withCondition(‘Name = “INSERT_ADGROUP_NAME_HERE”‘)
// .withCondition(‘CampaignName = “INSERT_CAMPAIGN_NAME_HERE”‘)
var adGroupIterator = AdWordsApp.adGroups()
.withCondition(‘Name = “INSERT_ADGROUP_NAME_HERE”‘)
.get();
if (adGroupIterator.hasNext()) {
var adGroup = adGroupIterator.next();
var negativeKeywordIterator = adGroup.negativeKeywords().get();
while (negativeKeywordIterator.hasNext()) {
var negativeKeyword = negativeKeywordIterator.next();
Logger.log(‘Text: ‘ + negativeKeyword.getText() + ‘, MatchType: ‘ +
negativeKeyword.getMatchType());
}
}
}

 

6. Lista mestre negativa – Por Google Ads. Esse script é ideal caso você tenha uma conta de administrador e tenha várias outras contas sob sua gerência, assim, com você consegue expandir termos comuns que não são interessantes para buscas.

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Master Negative List Script for AdWords manager accounts
*
* @overview The Master Negative List script for AdWords manager accounts
* applies negative keywords and placements from a spreadsheet to multiple
* campaigns in your account using shared keyword and placement lists. The
* script can process multiple AdWords accounts in parallel. See
* //developers.google.com/adwords/scripts/docs/solutions/mccapp-master-negative-list
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.0.2
*
* @changelog
* – version 1.0.2
* – Added validation for external spreadsheet setup.
* – version 1.0.1
* – Improvements to time zone handling.
* – version 1.0
* – Released initial version.
*/

/**
* The URL of the tracking spreadsheet. This should be a copy of
* //goo.gl/i4q728
*/
var SPREADSHEET_URL = ‘INSERT_SPREADSHEET_URL_HERE’;

/**
* Keep track of the spreadsheet names for various criteria types, as well as
* the criteria type being processed.
*/
var CriteriaType = {
KEYWORDS: ‘Keywords’,
PLACEMENTS: ‘Placements’
};

/**
* The code to execute when running the script.
*/
function main() {
var config = readConfig();

var accountSelector = MccApp.accounts();
if (config.customerids.length > 0) {
accountSelector.withIds(config.customerids);
}
accountSelector.executeInParallel(‘processAccounts’, ‘postProcess’);
}

/**
* Process an account when processing multiple accounts under an AdWords manager
* account in parallel.
*
* @return {string} A JSON string that summarizes the number of keywords and
* placements synced, and the number of campaigns processed.
*/
function processAccounts() {
return JSON.stringify(syncMasterLists());
}

/**
* Callback method after processing accounts, when processing multiple accounts
* under an AdWords manager account in parallel.
*
* @param {Array.<MccApp.ExecutionResult>} results The execution results from
* the accounts that were processed by this script.
*/
function postProcess(results) {
var config = readConfig();
var emailParams = {
// Number of placements that were synced.
PlacementCount: 0,
// Number of keywords that were synced.
KeywordCount: 0,
// Summary of customers who were synced.
Customers: {
// How many customers were synced?
Success: 0,
// How many customers failed to sync?
Failure: 0,
// Details of each account processed. Contains 3 properties:
// CustomerId, CampaignCount, Status.
Details: []
}
};

for (var i = 0; i < results.length; i++) {
var customerResult = {
// The customer ID that was processed.
CustomerId: results[i].getCustomerId(),
// Number of campaigns that were synced.
CampaignCount: 0,
// Status of processing this account – OK / ERROR / TIMEOUT
Status: results[i].getStatus()
};

if (results[i].getStatus() == ‘OK’) {
var retval = JSON.parse(results[i].getReturnValue());
customerResult.CampaignCount = retval.CampaignCount;
if (emailParams.Customers.Success == 0) {
emailParams.KeywordCount = retval.KeywordCount;
emailParams.PlacementCount = retval.PlacementCount;
}
emailParams.Customers.Success++;
} else {
emailParams.Customers.Failure++;
}
emailParams.Customers.Details.push(customerResult);
}

var spreadsheet = validateAndGetSpreadsheet(SPREADSHEET_URL);

// Make sure the spreadsheet is using the account’s timezone.
spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone());
spreadsheet.getRangeByName(‘LastRun’).setValue(new Date());
spreadsheet.getRangeByName(‘CustomerId’).setValue(
AdWordsApp.currentAccount().getCustomerId());

sendEmail(config, emailParams);
}

/**
* Sends a summary email about the changes that this script made.
*
* @param {Object} config The configuration object.
* @param {Object} emailParams Contains details required to create the email
* body.
*/
function sendEmail(config, emailParams) {
var html = [];

html.push(‘<html>’,
‘<head></head>’,
‘<body>’,
“<table style=’font-family:Arial,Helvetica; ” +
‘border-collapse:collapse;font-size:10pt; ‘ +
“color:#444444; border: solid 1px #dddddd;’ ” +
“width=’600′ cellpadding=20>”,
‘<tr>’,
‘<td>’,
‘<p>Hello,</p>’,
‘<p>The Master Negative List script synced a total ‘ +
‘of <b>’ + emailParams.KeywordCount + ‘</b> ‘ +
‘keywords and <b>’ + emailParams.PlacementCount +
‘</b> placements. <b>’ +
(emailParams.Customers.Success +
emailParams.Customers.Failure) +
‘</b> accounts were processed, of which <b>’ +
emailParams.Customers.Success + ‘</b> ‘ +
‘succeeded, and <b>’ +
emailParams.Customers.Failure + ‘</b> failed. ‘ +
‘See the table below’ +
‘ for details.</p>’,
“<table border=’1′ width=’100%’ ” +
“style=’border-collapse: collapse; ” +
“border: solid 1px #dddddd;font-size:10pt;’>”,
‘<tr>’,
‘<th>CustomerId</th>’,
‘<th>Synced Campaigns</th>’,
‘<th>Status</th>’,
‘</tr>’
);

for (var i = 0; i < emailParams.Customers.Details.length; i++) {
var detail = emailParams.Customers.Details[i];
html.push(‘<tr>’,
‘<td>’ + detail.CustomerId + ‘</td>’,
‘<td>’ + detail.CampaignCount + ‘</td>’,
‘<td>’ + detail.Status + ‘</td>’,
‘</tr>’
);
}

html.push(‘</table>’,
‘<p>Cheers<br />AdWords Scripts Team</p>’,
‘</td>’,
‘</tr>’,
‘</table>’,
‘</body>’,
‘</html>’
);

if (config.email != ”) {
MailApp.sendEmail({
to: config.email,
subject: ‘Master Negative List Script’,
htmlBody: html.join(‘\n’)
});
}
}

/**
* Synchronizes the negative criteria list in an account with the master list
* in the user spreadsheet.
*
* @return {Object} A summary of the number of keywords and placements synced,
* and the number of campaigns to which these lists apply.
*/
function syncMasterLists() {
var config = readConfig();
var syncedCampaignCount = 0;

var keywordListDetails = syncCriteriaInNegativeList(config,
CriteriaType.KEYWORDS);
syncedCampaignCount = syncCampaignList(config, keywordListDetails.SharedList,
CriteriaType.KEYWORDS);

var placementListDetails = syncCriteriaInNegativeList(config,
CriteriaType.PLACEMENTS);
syncedCampaignCount = syncCampaignList(config,
placementListDetails.SharedList, CriteriaType.PLACEMENTS);

return {
‘CampaignCount’: syncedCampaignCount,
‘PlacementCount’: placementListDetails.CriteriaCount,
‘KeywordCount’: keywordListDetails.CriteriaCount
};
}

/**
* Synchronizes the list of campaigns covered by a negative list against the
* desired list of campaigns to be covered by the master list.
*
* @param {Object} config The configuration object.
* @param {AdWordsApp.NegativeKeywordList|AdWordsApp.ExcludedPlacementList}
* sharedList The shared negative criterion list to be synced against the
* master list.
* @param {String} criteriaType The criteria type for the shared negative list.
*
* @return {Number} The number of campaigns synced.
*/
function syncCampaignList(config, sharedList, criteriaType) {
var campaignIds = getLabelledCampaigns(config.label);
var totalCampaigns = Object.keys(campaignIds).length;

var listedCampaigns = sharedList.campaigns().get();

var campaignsToRemove = [];

while (listedCampaigns.hasNext()) {
var listedCampaign = listedCampaigns.next();
if (listedCampaign.getId() in campaignIds) {
delete campaignIds[listedCampaign.getId()];
} else {
campaignsToRemove.push(listedCampaign);
}
}

// Anything left over in campaignIds starts a new list.
var campaignsToAdd = AdWordsApp.campaigns().withIds(
Object.keys(campaignIds)).get();
while (campaignsToAdd.hasNext()) {
var campaignToAdd = campaignsToAdd.next();

if (criteriaType == CriteriaType.KEYWORDS) {
campaignToAdd.addNegativeKeywordList(sharedList);
} else if (criteriaType == CriteriaType.PLACEMENTS) {
campaignToAdd.addExcludedPlacementList(sharedList);
}
}

for (var i = 0; i < campaignsToRemove.length; i++) {
if (criteriaType == CriteriaType.KEYWORDS) {
campaignsToRemove[i].removeNegativeKeywordList(sharedList);
} else if (criteriaType == CriteriaType.PLACEMENTS) {
campaignsToRemove[i].removeExcludedPlacementList(sharedList);
}
}

return totalCampaigns;
}

/**
* Gets a list of campaigns having a particular label.
*
* @param {String} labelText The label text.
*
* @return {Array.<Number>} An array of campaign IDs having the specified
* label.
*/
function getLabelledCampaigns(labelText) {
var campaignIds = {};

if (labelText != ”) {
var label = getLabel(labelText);
var campaigns = label.campaigns().withCondition(
‘Status in [ENABLED, PAUSED]’).get();
} else {
var campaigns = AdWordsApp.campaigns().withCondition(
‘Status in [ENABLED, PAUSED]’).get();
}

while (campaigns.hasNext()) {
var campaign = campaigns.next();
campaignIds[campaign.getId()] = 1;
}
return campaignIds;
}

/**
* Gets a label with the specified label text.
*
* @param {String} labelText The label text.
*
* @return {AdWordsApp.Label} The label text.
*/
function getLabel(labelText) {
var labels = AdWordsApp.labels().withCondition(
“Name='” + labelText + “‘”).get();
if (labels.totalNumEntities() == 0) {
var message = Utilities.formatString(‘Label named %s is missing in your ‘ +
‘account. Make sure the label exists in the account, and is applied ‘ +
‘to campaigns and adgroups you wish to process.’, labelText);
throw (message);
}

return labels.next();
}

/**
* Synchronizes the criteria in a shared negative criteria list with the user
* spreadsheet.
*
* @param {Object} config The configuration object.
* @param {String} criteriaType The criteria type for the shared negative list.
*
* @return {Object} A summary of the synced negative list, and the number of
* criteria that were synced.
*/
function syncCriteriaInNegativeList(config, criteriaType) {
var criteriaFromSheet = loadCriteria(criteriaType);
var totalCriteriaCount = Object.keys(criteriaFromSheet).length;

var sharedList = null;
var listName = config.listname[criteriaType];

sharedList = createNegativeListIfRequired(listName, criteriaType);

var negativeCriteria = null;

try {
if (criteriaType == CriteriaType.KEYWORDS) {
negativeCriteria = sharedList.negativeKeywords().get();
} else if (criteriaType == CriteriaType.PLACEMENTS) {
negativeCriteria = sharedList.excludedPlacements().get();
}
} catch (e) {
Logger.log(‘Failed to retrieve shared list. Error says ‘ + e);
if (AdWordsApp.getExecutionInfo().isPreview()) {
var message = Utilities.formatString(‘The script cannot create the ‘ +
‘negative %s list in preview mode. Either run the script without ‘ +
‘preview, or create a negative %s list with name “%s” manually ‘ +
‘before previewing the script.’, criteriaType, criteriaType,
listName);
Logger.log(message);
}
throw e;
}

var criteriaToDelete = [];

while (negativeCriteria.hasNext()) {
var negativeCriterion = negativeCriteria.next();
var key = null;

if (criteriaType == CriteriaType.KEYWORDS) {
key = negativeCriterion.getText();
} else if (criteriaType == CriteriaType.PLACEMENTS) {
key = negativeCriterion.getUrl();
}

if (key in criteriaFromSheet) {
// Nothing to do with this criteria. Remove it from loaded list.
delete criteriaFromSheet[key];
} else {
// This criterion is not in the sync list. Mark for deletion.
criteriaToDelete.push(negativeCriterion);
}
}

// Whatever left in the sync list are new items.
if (criteriaType == CriteriaType.KEYWORDS) {
sharedList.addNegativeKeywords(Object.keys(criteriaFromSheet));
} else if (criteriaType == CriteriaType.PLACEMENTS) {
sharedList.addExcludedPlacements(Object.keys(criteriaFromSheet));
}

for (var i = 0; i < criteriaToDelete.length; i++) {
criteriaToDelete[i].remove();
}

return {
‘SharedList’: sharedList,
‘CriteriaCount’: totalCriteriaCount,
‘Type’: criteriaType
};
}

/**
* Creates a shared negative criteria list if required.
*
* @param {string} listName The name of shared negative criteria list.
* @param {String} listType The criteria type for the shared negative list.
*
* @return {AdWordsApp.NegativeKeywordList|AdWordsApp.ExcludedPlacementList} An
* existing shared negative criterion list if it already exists in the
* account, or the newly created list if one didn’t exist earlier.
*/
function createNegativeListIfRequired(listName, listType) {
var negativeListSelector = null;
if (listType == CriteriaType.KEYWORDS) {
negativeListSelector = AdWordsApp.negativeKeywordLists();
} else if (listType == CriteriaType.PLACEMENTS) {
negativeListSelector = AdWordsApp.excludedPlacementLists();
}
var negativeListIterator = negativeListSelector.withCondition(
“Name = ‘” + listName + “‘”).get();

if (negativeListIterator.totalNumEntities() == 0) {
var builder = null;

if (listType == CriteriaType.KEYWORDS) {
builder = AdWordsApp.newNegativeKeywordListBuilder();
} else if (listType == CriteriaType.PLACEMENTS) {

builder = AdWordsApp.newExcludedPlacementListBuilder();
}

var negativeListOperation = builder.withName(listName).build();
return negativeListOperation.getResult();
} else {
return negativeListIterator.next();
}
}

/**
* Loads a list of criteria from the user spreadsheet.
*
* @param {string} sheetName The name of shared negative criteria list.
*
* @return {Object} A map of the list of criteria loaded from the spreadsheet.
*/
function loadCriteria(sheetName) {
var spreadsheet = validateAndGetSpreadsheet(SPREADSHEET_URL);
var sheet = spreadsheet.getSheetByName(sheetName);
var values = sheet.getRange(‘B4:B’).getValues();

var retval = {};
for (var i = 0; i < values.length; i++) {
var keyword = values[i][0].toString().trim();
if (keyword != ”) {
retval[keyword] = 1;
}
}
return retval;
}

/**
* Loads a configuration object from the spreadsheet.
*
* @return {Object} A configuration object.
*/
function readConfig() {
var spreadsheet = validateAndGetSpreadsheet(SPREADSHEET_URL);
var values = spreadsheet.getRangeByName(‘ConfigurationValues’).getValues();

var config = {
‘label’: values[0][0],
‘listname’: {
},
‘email’: values[3][0],
‘customerids’: extractCustomerIds(values[4][0])
};
config.listname[CriteriaType.KEYWORDS] = values[1][0];
config.listname[CriteriaType.PLACEMENTS] = values[2][0];
return config;
}

/**
* Extracts customerIds from a comma separated list.
*
* @param {string} data the input.
* @return {Array.<number>} A list of customer IDs.
*/
function extractCustomerIds(data) {
var retval = [];

var splits = data.split(‘,’);

for (var i = 0; i < splits.length; i++) {
var split = splits[i];
split = split.trim().replace(/-/g, ”).replace(/^\s+|\s+$/g, ”);
if (split) {
if (isNaN(split)) {
Logger.log(‘Invalid customer ID found in spreadsheet: ‘ + split);
} else {
var customerId = parseInt(split).toFixed(0);
retval.push(customerId);
}
}
}
return retval;
}

/**
* DO NOT EDIT ANYTHING BELOW THIS LINE.
* Please modify your spreadsheet URL at the top of the file only.
*/

/**
* Validates the provided spreadsheet URL and email address
* to make sure that they’re set up properly. Throws a descriptive error message
* if validation fails.
*
* @param {string} spreadsheeturl The URL of the spreadsheet to open.
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
* @throws {Error} If the spreadsheet URL or email hasn’t been set
*/
function validateAndGetSpreadsheet(spreadsheeturl) {
if (spreadsheeturl == ‘INSERT_SPREADSHEET_URL_HERE’) {
throw new Error(‘Please specify a valid Spreadsheet URL. You can find’ +
‘ a link to a template in the associated guide for this script.’);
}
var spreadsheet = SpreadsheetApp.openByUrl(spreadsheeturl);
return spreadsheet;
}

 

Como Configurar

 

7. Palavras-chave – Por Google Ads. Os scripts de palavras-chave do Google Ads permitem que você adicione/pause palavras-chave à um grupo de anúncios. Além disso, é possível que você extraia todas as palavras-chave de um grupo de anúncios e seus dados também.

Adicione uma palavra-chave a um grupo de anúncios existente

function addKeyword() {
// If you have multiple adGroups with the same name, this snippet will
// pick an arbitrary matching ad group each time. In such cases, just
// filter on the campaign name as well:
//
// AdWordsApp.adGroups()
// .withCondition(‘Name = “INSERT_ADGROUP_NAME_HERE”‘)
// .withCondition(‘CampaignName = “INSERT_CAMPAIGN_NAME_HERE”‘)
var adGroupIterator = AdWordsApp.adGroups()
.withCondition(‘Name = “INSERT_ADGROUP_NAME_HERE”‘)
.get();
if (adGroupIterator.hasNext()) {
var adGroup = adGroupIterator.next();

adGroup.newKeywordBuilder()
.withText(‘Hello world’)
.withCpc(1.25) // Optional
.withFinalUrl(‘//www.example.com’) // Optional
.build();

// KeywordBuilder has a number of other options. For more details see
// //developers.google.com/adwords/scripts/docs/reference/adwordsapp/adwordsapp_keywordbuilder
}
}

 

Pausar uma palavra-chave existente em um grupo de anúncios

function pauseKeywordInAdGroup() {
var adGroupIterator = AdWordsApp.adGroups()
.withCondition(‘Name = “INSERT_ADGROUP_NAME_HERE”‘)
.get();
if (adGroupIterator.hasNext()) {
var adGroup = adGroupIterator.next();
var keywordIterator = adGroup.keywords()
.withCondition(‘Text=”INSERT_KEYWORDS_HERE”‘).get();
while (keywordIterator.hasNext()) {
var keyword = keywordIterator.next();
keyword.pause();
}
}
}

 

Obter todas as palavras-chave em um grupo de anúncios

function getKeywordsInAdGroup() {
var keywordIterator = AdWordsApp.keywords()
.withCondition(‘AdGroupName = “INSERT_ADGROUP_NAME_HERE”‘)
.get();
if (keywordIterator.hasNext()) {
while (keywordIterator.hasNext()) {
var keyword = keywordIterator.next();
Logger.log(formatKeyword(keyword));
}
}
}

function formatKeyword(keyword) {
return ‘Text : ‘ + keyword.getText() + ‘\n’ +
‘Match type : ‘ + keyword.getMatchType() + ‘\n’ +
‘CPC : ‘ + keyword.bidding().getCpc() + ‘\n’ +
‘Final URL : ‘ + keyword.urls().getFinalUrl() + ‘\n’ +
‘Approval Status : ‘ + keyword.getApprovalStatus() + ‘\n’ +
‘Enabled : ‘ + keyword.isEnabled() + ‘\n’;
}

 

Obter estatísticas de todas as palavras-chave em um grupo de anúncios

function getKeywordStats() {
var adGroupIterator = AdWordsApp.adGroups()
.withCondition(‘Name = “INSERT_ADGROUP_NAME_HERE”‘)
.get();
if (adGroupIterator.hasNext()) {
var adGroup = adGroupIterator.next();
var keywordIterator = adGroup.keywords().get();
while (keywordIterator.hasNext()) {
var keyword = keywordIterator.next();
// You can also request reports for pre-defined date ranges. See
// //developers.google.com/adwords/api/docs/guides/awql,
// DateRangeLiteral section for possible values.
var stats = keyword.getStatsFor(‘LAST_MONTH’);
Logger.log(adGroup.getName() + ‘, ‘ + keyword.getText() + ‘, ‘ +
stats.getClicks() + ‘, ‘ + stats.getImpressions());
}
}
}

Automação Google Ads: Texto do Anúncio

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para “Trabalhar com Textos dos anúncios”.

 

1. Corrija os erros de letras maiúsculas em seus anúncios – por Russell Savage. Caso seus anúncios contenham muitas letras maiúsculas, o Google pode não aprová-los. Esse script substitui quaisquer sequências de 3 letras maiúsculas, criando um anúncio novo e pausando o antigo.

//———————————–
// Fix Ads with EXCESSIVE CAPITALIZATION
// Created By: Russ Savage
// FreeAdWordsScripts.com
//———————————–
function main() {
var find_caps = /[A-Z]{3,}/g;
var SEP = ‘[email protected]~~’; // this needs to be something you would never put in your ads.
var ad_iter = AdWordsApp.ads().withCondition(“ApprovalStatus = ‘DISAPPROVED'”).get();

while(ad_iter.hasNext()) {
var ad = ad_iter.next();
var old_ad_cnt = get_ad_count(ad.getAdGroup());
var old_ad_str = [ad.getHeadline(),ad.getDescription1(),ad.getDescription2(),ad.getDisplayUrl()].join(SEP);
var new_ad_str = old_ad_str;
Logger.log(“Before:”+old_ad_str);
var m = “”;
while((m = find_caps.exec(new_ad_str)) != null) {
new_ad_str = replace_all(new_ad_str,m[0],init_cap(m[0]),false);
}
Logger.log(“After:”+new_ad_str);
if(old_ad_str != new_ad_str) {
var [new_headline,new_desc1,new_desc2,new_disp_url] = new_ad_str.split(SEP);
ad.getAdGroup().createTextAd(new_headline, new_desc1, new_desc2, new_disp_url, ad.getDestinationUrl());
var new_ad_cnt = get_ad_count(ad.getAdGroup());
if(new_ad_cnt == (old_ad_cnt+1)) {
ad.remove();
}
} else {
Logger.log(“Skipping because no changes were made.”);
}
}

function init_cap(s) {
return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
}

// This function was adapted from: //dumpsite.com/forum/index.php?topic=4.msg8#msg8
function replace_all(original,str1, str2, ignore) {
return original.replace(new RegExp(str1.replace(/([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g,”\\$&”),(ignore?”gi”:”g”)),(typeof(str2)==”string”)?str2.replace(/\$/g,”$$$$”):str2);
}

function get_ad_count(ad_group) {
var ad_iter = ad_group.ads().get();
var new_ad_cnt = 0;
while(ad_iter.hasNext()) {
ad_iter.next();
new_ad_cnt++;
}
return new_ad_cnt;
}
}

 

2. Automação de testes de anúncios – Por Russell Savage. O Google costuma analisar em 1 dia anúncios para aprovação, porém nem sempre este é o caso. Esse script detecta quando há uma alteração no anúncio e grava em uma planilha do Google com a data em que ele começou a rodar.

/************************************
* Ad Creative Test Automation Script
* Version: 1.3
* Changelog v1.3 – Data is written to the spreadsheet a little faster
* Changelog v1.2 – Added additional threshold options
* Changelog v1.1 – Fixed issue with dates in email
* Created By: Russ Savage
* FreeAdWordsScripts.com
************************************/
// You can use any of the same values here as you can for METRIC below
var THRESHOLD = { metric : ‘Clicks’, value : 100 };
var TO = [‘[email protected]’];

//Try any of these values for METRIC:
//AverageCpc, AverageCpm, AveragePageviews, AveragePosition,
//AverageTimeOnSite, BounceRate, Clicks, ConversionRate,
//Conversions, Cost, Ctr, Impressions
var METRIC = ‘Ctr’;
var ASC_OR_DESC = ‘ASC’; //ASC – pause ad with lowest value, DESC – pause ad with highest value

function main() {
//Start by finding what has changed in the account
var ad_map = buildCurrentAdMap();
var prev_ad_map = readMapFromSpreadsheet();
prev_ad_map = updatePreviousAdMap(prev_ad_map,ad_map);
writeMapToSpreadsheet(prev_ad_map);

//Now run through the adgroups to find creative tests
var ag_iter = AdWordsApp.adGroups().get();
var paused_ads = [];
while(ag_iter.hasNext()) {
var ag = ag_iter.next();
if(!prev_ad_map[ag.getId()]) { continue; }

//Here is the date range for the test metrics
var last_touched_str = _getDateString(prev_ad_map[ag.getId()].last_touched,’yyyyMMdd’);
var get_today_str = _getDateString(new Date(),’yyyyMMdd’);

var ag_stats = ag.getStatsFor(last_touched_str, get_today_str);
if(ag_stats[‘get’+THRESHOLD.metric]() >= THRESHOLD.value) {
var ad_iter = ag.ads().withCondition(‘Status = ENABLED’)
.forDateRange(last_touched_str, get_today_str)
.orderBy(METRIC+” “+ASC_OR_DESC).get();
var ad = ad_iter.next();
var metric = ad.getStatsFor(last_touched_str, get_today_str)[‘get’+METRIC]();
ad.pause();
paused_ads.push({a : ad, m : metric});
}
}
sendEmailForPausedAds(paused_ads);
}

// A function to send an email with an attached report of ads it has paused
function sendEmailForPausedAds(ads) {
if(ads.length == 0) { return; } //No ads paused, no email
var email_body = ‘”‘ + [‘CampaignName’,’AdGroupName’,’Headline’,’Desc1′,’Desc2′,’DisplayUrl’,METRIC].join(‘”,”‘) + ‘”\n’;
for(var i in ads) {
var ad = ads[i].a;
var metric = ads[i].m;
email_body += ‘”‘ + [ad.getCampaign().getName(),
ad.getAdGroup().getName(),
ad.getHeadline(),
ad.getDescription1(),
ad.getDescription2(),
ad.getDisplayUrl(),
metric
].join(‘”,”‘) +
‘”\n’;
}
var date_str = _getDateString(new Date(),’yyyy-MM-dd’);
var options = { attachments: [Utilities.newBlob(email_body, ‘text/csv’, “FinishedTests_”+date_str+’.csv’)] };
var subject = ‘Finished Tests – ‘ + date_str;
for(var i in TO) {
MailApp.sendEmail(TO[i], subject, ‘See attached.’, options);
}
}

//Given two lists of ads, this checks to make sure they are the same.
function sameAds(ads1,ads2) {
for(var i in ads1) {
if(ads1[i] != ads2[i]) { return false; }
}
return true;
}

//This reads the stored data from the spreadsheet
function readMapFromSpreadsheet() {
var ad_map = {};
var sheet = SpreadsheetApp.openById(findSpreadsheetId()).getActiveSheet();
var data = sheet.getRange(‘A:C’).getValues();
for(var i in data) {
if(data[i][0] == ”) { break; }
var [ag_id,last_touched,ad_ids] = data[i];
ad_map[ag_id] = { ad_ids : (”+ad_ids).split(‘,’), last_touched : new Date(last_touched) };
}
return ad_map;
}

//This will search for a label containing the spreadsheet id
//If one isn’t found, it will create a new one and the label as well
function findSpreadsheetId() {
var spreadsheet_id = “”;
var label_iter = AdWordsApp.labels().withCondition(“Name STARTS_WITH ‘history_script:'”).get();
if(label_iter.hasNext()) {
var label = label_iter.next();
return label.getName().split(‘:’)[1];
} else {
var sheet = SpreadsheetApp.create(‘AdGroups History’);
var sheet_id = sheet.getId();
AdWordsApp.createLabel(‘history_script:’+sheet_id, ‘stores sheet id for adgroup changes script.’);
return sheet_id;
}
}

//This will store the data from the account into a spreadsheet
function writeMapToSpreadsheet(ad_map) {
var toWrite = [];
for(var ag_id in ad_map) {
var ad_ids = ad_map[ag_id].ad_ids;
var last_touched = ad_map[ag_id].last_touched;
toWrite.push([ag_id,last_touched,ad_ids.join(‘,’)]);
}
writeToSpreadsheet(toWrite);
}

// Write the keyword data to the spreadsheet
function writeToSpreadsheet(toWrite) {
var sheet = SpreadsheetApp.openById(findSpreadsheetId()).getActiveSheet();
sheet.clear();

var numRows = sheet.getMaxRows();
if(numRows < toWrite.length) {
sheet.insertRows(1,toWrite.length-numRows);
}
var range = sheet.getRange(1,1,toWrite.length,toWrite[0].length);
range.setValues(toWrite);
}

//This builds a map of the ads in the account so that it is easy to compare
function buildCurrentAdMap() {
var ad_map = {}; // { ag_id : { ad_ids : [ ad_id, … ], last_touched : date } }
var ad_iter = AdWordsApp.ads().withCondition(‘Status = ENABLED’).get();
while(ad_iter.hasNext()) {
var ad = ad_iter.next();
var ag_id = ad.getAdGroup().getId();
if(ad_map[ag_id]) {
ad_map[ag_id].ad_ids.push(ad.getId());
ad_map[ag_id].ad_ids.sort();
} else {
ad_map[ag_id] = { ad_ids : [ad.getId()], last_touched : new Date() };
}
}
return ad_map;
}

//This takes the old ad map and the current ad map and returns an
//updated map with all changes.
function updatePreviousAdMap(prev_ad_map,ad_map) {
for(var ag_id in ad_map) {
var current_ads = ad_map[ag_id].ad_ids;
var previous_ads = (prev_ad_map[ag_id]) ? prev_ad_map[ag_id].ad_ids : [];
if(!sameAds(current_ads,previous_ads)) {
prev_ad_map[ag_id] = ad_map[ag_id];
}
}
return prev_ad_map;
}

//Helper function to format the date
function _getDateString(date,format) {
return Utilities.formatDate(date,AdWordsApp.currentAccount().getTimeZone(),format);
}

 

3. Gerencie seus criativos com Excel – Por Russell Savage. Integre o Google Ads com o Drive e baixe todas as métricas automaticamente no seu computador. Não só isso, mas também adicione palavras-chave, gerencie anúncios, grupo de anúncios, extensões etc. Para utilizar esse recurso é necessário ter o Google Drive em seu computador.

/******************************************
* Manage AdWords Ads Using Excel
* Version 1.0
* Author: Russ Savage
* FreeAdWordsScripts.com
****************************************/
var FOLDER_PATH = ‘AdWordsData’; //The path where the file will be stored on GDrive
var FILE_NAME = ‘creatives.csv’; //The name of the file on GDrive

var INCLUDE_STATS = true; // Set to false to remove stats from the file
var DATE_RANGE = ‘LAST_30_DAYS’; //The date range for the stats

var INCLUDE_PAUSED_ADS = true; //Set to false to only report on active ads
var DELETE_ORIGINAL_ADS = true; //When set to false, the original ads will be paused instead of deleted

function main() {
var file = getFile(FILE_NAME,FOLDER_PATH);
if(!file) {
file = createNewFile(FILE_NAME,FOLDER_PATH,buildCreativesFile());
return;
}

var fileContent = file.getBlob().getDataAsString();
var creatives = {};
if(fileContent) {
creatives = parseFileContent(fileContent);
}

var creativeIter = getAdIterator();
while(creativeIter.hasNext()) {
var creative = creativeIter.next();
var adId = creative.getId();
if(creatives[adId]) {
//ok we found the ad.
//Checking status
var isEnabledFile = (creatives[adId][‘Status’] === ‘Enabled’);
if(creative.isEnabled() != isEnabledFile) {
if(isEnabledFile) {
creative.enable();
} else {
creative.pause();
}
}

if(hadAdChanged(creative,creatives[adId])) {
if(DELETE_ORIGINAL_ADS) {
creative.remove();
} else {
creative.pause();
}
createNewAd(creative.getAdGroup(),creatives[creative.getId()]);
}
}
}
file.setContent(buildCreativesFile());
}

//Helper function to create a new ad
function createNewAd(ag,newAd) {
var optArgs = {
isMobilePreferred: (newAd[‘Device’] === ‘Mobile’) ? true : false
};
ag.createTextAd(newAd[‘Headline’],newAd[‘Desc1’],newAd[‘Desc2’],newAd[‘DisplayUrl’],newAd[‘DestinationUrl’],optArgs);
}

//This checks to see if the ad has been changed
function hadAdChanged(ad,oldAd) {
var newAdText = [ad.getHeadline(),
ad.getDescription1(),
ad.getDescription2(),
ad.getDisplayUrl(),
ad.getDestinationUrl(),
(ad.isMobilePreferred()) ? ‘Mobile’ : ‘Desktop’].join(‘~~!~~’);
var oldAdText = [oldAd[‘Headline’],
oldAd[‘Desc1’],
oldAd[‘Desc2’],
oldAd[‘DisplayUrl’],
oldAd[‘DestinationUrl’],
oldAd[‘Device’]].join(‘~~!~~’);
Logger.log(newAdText);
Logger.log(oldAdText);
return (newAdText !== oldAdText);
}

//This builds the creatives file from all the ads in the account.
function buildCreativesFile() {
var report = getReportColumns();
var creativeIter = getAdIterator();
while(creativeIter.hasNext()) {
var creative = creativeIter.next();
report += getReportRow(creative);
}
return report;
}

//This returns the ad iterator based on options.
function getAdIterator() {
var adSelector = AdWordsApp.ads().withCondition(“Type = ‘TEXT_AD'”);
if(!INCLUDE_PAUSED_ADS) {
adSelector = adSelector.withCondition(‘CampaignStatus = ENABLED’)
.withCondition(‘AdGroupStatus = ENABLED’)
.withCondition(‘Status = ENABLED’);
}
return adSelector.get();
}

//This returns a CSV fow for the report.
function getReportRow(ad) {
var retVal = [
ad.getId(),
ad.getCampaign().getName(),(ad.getCampaign().isPaused()) ? ‘Paused’ : ‘Enabled’,
ad.getAdGroup().getName(),(ad.getAdGroup().isPaused()) ? ‘Paused’ : ‘Enabled’,
ad.getHeadline(),
ad.getDescription1(),
ad.getDescription2(),
ad.getDisplayUrl(),
ad.getDestinationUrl(),
(ad.isPaused()) ? ‘Paused’ : ‘Enabled’,
(ad.isMobilePreferred()) ? ‘Mobile’ : ‘Desktop’,
];
if(INCLUDE_STATS) {
var stats = ad.getStatsFor(DATE_RANGE);
retVal = retVal.concat([
stats.getImpressions(),
stats.getClicks(),
stats.getCtr(),
stats.getCost(),
stats.getAverageCpc(),
stats.getConversions(),
stats.getConversionRate(),
stats.getAveragePageviews(),
stats.getAveragePosition(),
stats.getAverageTimeOnSite(),
stats.getBounceRate()
]);
}
return ‘”‘ + retVal.join(‘”,”‘) + ‘”\n’;
}

//This returns the column headings used for the report.
function getReportColumns() {
var columnHeadings = [
‘AdId’,
‘CampaignName’,’CampaignStatus’,
‘AdGroupName’,’AdGroupStatus’,
‘Headline’,
‘Desc1’,
‘Desc2’,
‘DisplayUrl’,
‘DestinationUrl’,
‘Status’,
‘Device’];
if(INCLUDE_STATS) {
columnHeadings = columnHeadings.concat([
‘Impressions’,
‘Clicks’,
‘Ctr’,
‘Cost’,
‘Cpc’,
‘Conversions’,
‘ConversionRate’,
‘AveragePageviews’,
‘AvgPosition’,
‘AvgTimeOnSite’,
‘BounceRate’
]);
}
return ‘”‘ + columnHeadings.join(‘”,”‘) + ‘”\n’;
}

//This function parses the creatives file into an object for processing
function parseFileContent(fileContent) {
var headers = [];
var idHash = {};
var data = Utilities.parseCsv(fileContent);
for(var i in data) {
var cells = data[i]
if(cells.length == 1) { continue; } //skip any empty rows
if(i == 0) {
headers = cells;
} else {
var rowMap = {};
for(var x in headers) {
headers[x] = headers[x];
cells[x] = cells[x];
rowMap[headers[x]] = cells[x];
}
idHash[rowMap[‘AdId’]] = rowMap;
}
}
return idHash;
}

//This function gets the file from GDrive
function getFile(fileName,folderPath) {
var folder = getFolder(folderPath);
if(folder.getFilesByName(fileName).hasNext()) {
return folder.getFilesByName(fileName).next();
} else {
return null;
}
}

//This function creates a new file on GDrive
function createNewFile(fileName,folderPath,content) {
if(!fileName) { throw ‘createNewFile: Missing filename.’; }
var folder = getFolder(folderPath);

return folder.createFile(fileName, content);
}

//This function finds the folder for the file and creates folders if needed
function getFolder(folderPath) {
var folder = DriveApp.getRootFolder();
if(folderPath) {
var pathArray = folderPath.split(‘/’);
for(var i in pathArray) {
var folderName = pathArray[i];
if(folder.getFoldersByName(folderName).hasNext()) {
folder = folder.getFoldersByName(folderName).next();
} else {
folder = folder.createFolder(folderName);
}
}
}
return folder;
}

 

4. Copie anúncios existentes com uma nova URL – Por Derek Martin. Economize tempo ao realizar testes para uma nova página de destino, o script irá copiar os anúncios da sua conta com a URL desejada.

function main() {
// change the CampaignName condition to whatever suits you
var adIter = AdWordsApp.ads().withCondition(“CampaignName contains WP”).withCondition(“Status = ENABLED”).get();

while(adIter.hasNext()) {
var ad = adIter.next();

var headline = ad.getHeadline();
var d1 = ad.getDescription1()
var d2 = ad.getDescription2();
var displayUrl = ad.getDisplayUrl();
var dest = “//your-url-here.com/”;

var camp = ad.getCampaign();
var adgroup = ad.getAdGroup();

var newAd = adgroup.newTextAdBuilder()
.withHeadline(headline)
.withDescription1(d1)
.withDescription2(d2)
.withDisplayUrl(displayUrl)
.withDestinationUrl(dest)
.build();

}
}

 

 

5. Sincronize seu estoque com anúncios do Google Ads – Por Nathan Byloff. Esse script é exclusivo para sites que utilizam o WooCommerce. Caso o item de seu e-commerce esteja esgotado, o script pausa os anúncios impedindo que seu orçamento seja gastado.

var CAMPAIGN_NAME = “Your Campaign Name”;
var userKey = “Your API Key”;
var userSecret = “Your API Secret”;
function main() {
var campaign = getCampaign();
var url = ‘//www.yourstore.com/wc-api/v2/products?consumer_key=’ + userKey + ‘&consumer_secret=’ + userSecret;
var response = UrlFetchApp.fetch(url);
response = response.getContentText();
var data = JSON.parse(response);
for(var i=0; i < data.products.length; i++) {
var product = data.products[i];
if (product.in_stock == true) {
Logger.log(‘enable ad: ‘ + product.sku);
toggleAd(campaign, product.sku, true);
} else {
Logger.log(‘disable ad: ‘ + product.sku);
toggleAd(campaign, product.sku, false);
}
}
}

function toggleAd(campaign, sku, status) {
var adsIterator = campaign.ads().withCondition(‘LabelNames CONTAINS_ANY [“‘ + sku + ‘”]’).get();
if (adsIterator.hasNext()) {
//Find all ad groups and store them in an array
while (adsIterator.hasNext()) {
var ad = adsIterator.next();
if (status) {
if (!ad.isEnabled()) {
ad.enable();
}
} else if (!status) {
//disable ad
if (ad.isEnabled()) {
ad.pause();
}
}
}
}
}

/**
* Get a specified campaign based on a global var we’ve set
*/
function getCampaign() {
return AdWordsApp.campaigns().withCondition(“Name = ‘” + CAMPAIGN_NAME + “‘”).get().next();
}

 

6. Atualizador de contagens regressivas – Por Derek Martin. O script procura por anúncios que estejam com a contagem regressiva expirada e substitui por novos anúncios com datas atualizadas.

/***************************************************************************************
* AdWords Countdown Ad Updater — Find stale countdown ads and replace them with
* Ads that are updated with new dates.
* Version 1.0
* Created By: Derek Martin
* DerekMartinLA.com or MixedMarketingArtist.com
****************************************************************************************/

var DESCRIPTION2_TEXT = “Sale Ends In”

function main() {
// Set your campaign criteria here

var campIter = AdWordsApp.campaigns().withCondition(“Status=ENABLED”).withCondition(“Name does_not_contain Remarketing”).get();

while (campIter.hasNext()) {
var camp = campIter.next();

var agIter = camp.adGroups().withCondition(“Status = ENABLED”).get();

while (agIter.hasNext()) {
var ag = agIter.next();

var adsIter = ag.ads().withCondition(“Status = ENABLED”).get();

while (adsIter.hasNext()) {
ad = adsIter.next();

var headline,d1,d2,displayUrl, destUrl;
headline = ad.getHeadline();
d1 = ad.getDescription1();
d2 = ad.getDescription2();
displayUrl = ad.getDisplayUrl();
destUrl = ad.getDestinationUrl();

var regex = /\{=COUNTDOWN\(\”(\d{4})\/(\d{1,2})\/(\d{1,2})/

if (d2.match(regex)) {
match = d2.match(regex);
info(match);
var the_date = new Date(match[1], match[2]-1,match[3])
var today = new Date;
info(today);
if (daysBetween(today, the_date) < 0) {
ad.pause();
var newDate = new Date();

newDate.setDate(newDate.getDate() + 5); // add 5 days
var formattedDate = Utilities.formatDate(newDate, “PST”, “yyyy/MM/dd”);
var newDest2 = DESCRIPTION2_TEXT + ” {\=COUNTDOWN(\”” + formattedDate +” 23:59:00\”)}.”

status = ag.newTextAdBuilder().withHeadline(headline).withDescription1(d1).withDescription2(newDest2).withDisplayUrl(displayUrl).withDestinationUrl(destUrl).build();
}
}
}

}
}
}

function daysBetween( date1, date2 ) {
//Get 1 day in milliseconds
var one_day=1000*60*60*24;

// Convert both dates to milliseconds
var date1_ms = date1.getTime();
var date2_ms = date2.getTime();

// Calculate the difference in milliseconds
var difference_ms = date2_ms – date1_ms;

// Convert back to days and return
return Math.round(difference_ms/one_day);
}

/* HELPER FUNCTIONS */

function warn(msg) {
Logger.log(‘WARNING: ‘+msg);
}

function info(msg) {
Logger.log(msg);
}

 

7. Automatizador de criativos com estatísticas – Por Russell Savage. O script notifica por e-mail quando seus anúncios de teste adquirem certa significância estatística para você tomar uma ação. É um ótimo script caso não queira tomar decisões apenas pelas métricas do Google Ads.

/*********************************************
* Automated Creative Testing With Statistical Significance
* Version 2.1
* Changelog v2.1
* – Fixed INVALID_PREDICATE_ENUM_VALUE
* Changelog v2.0
* – Fixed bug in setting the correct date
* – Script now uses a minimum visitors threshold
* per Ad instead of AdGroup
* – Added the ability to add the start date as a label to AdGroups
* – Added ability to check mobile and desktop ads separately
* Changelog v1.1.1 – Fixed bug with getDisplayUrl
* Changelog v1.1
* – Added ability to only run on some campaigns
* – Fixed bug in info logging
* Russ Savage
* FreeAdWordsScripts.com
**********************************************/
var EXTRA_LOGS = true;
var TO = [‘[email protected]’];
var CONFIDENCE_LEVEL = 95; // 90%, 95%, or 99% are most common

//If you only want to run on some campaigns, apply a label to them
//and put the name of the label here. Leave blank to run on all campaigns.
var CAMPAIGN_LABEL = ”;

//These two metrics are the components that make up the metric you
//want to compare. For example, this measures CTR = Clicks/Impressions
//Other examples might be:
// Cost Per Conv = Cost/Conversions
// Conversion Rate = Conversions/Clicks
// Cost Per Click = Cost/Clicks
var VISITORS_METRIC = ‘Impressions’;
var CONVERSIONS_METRIC = ‘Clicks’;
//This is the number of impressions the Ad needs to have in order
//to start measuring the results of a test.
var VISITORS_THRESHOLD = 100;

//Setting this to true to enable the script to check mobile ads
//against other mobile ads only. Enabling this will start new tests
//in all your AdGroups so only enable this after you have completed
//a testing cycle.
var ENABLE_MOBILE_AD_TESTING = false;

//Set this on the first run which should be the approximate last time
//you started a new creative test. After the first run, this setting
//will be ignored.
var OVERRIDE_LAST_TOUCHED_DATE = ‘Jan 1, 2014’;

var LOSER_LABEL = ‘Loser ‘+CONFIDENCE_LEVEL+’% Confidence’;
var CHAMPION_LABEL = ‘Current Champion’;

// Set this to true and the script will apply a label to
// each AdGroup to let you know the date the test started
// This helps you validate the results of the script.
var APPLY_TEST_START_DATE_LABELS = true;

//These come from the url when you are logged into AdWords
//Set these if you want your emails to link directly to the AdGroup
var __c = ”;
var __u = ”;

function main() {
createLabelIfNeeded(LOSER_LABEL,”#FF00FF”); //Set the colors of the labels here
createLabelIfNeeded(CHAMPION_LABEL,”#0000FF”); //Set the colors of the labels here

//Let’s find all the AdGroups that have new tests starting
var currentAdMap = getCurrentAdsSnapshot();
var previousAdMap = getPreviousAdsSnapshot();
if(previousAdMap) {
currentAdMap = updateCurrentAdMap(currentAdMap,previousAdMap);
}
storeAdsSnapshot(currentAdMap);
previousAdMap = null;

//Now run through the AdGroups to find tests
var agSelector = AdWordsApp.adGroups()
.withCondition(‘CampaignStatus = ENABLED’)
.withCondition(‘AdGroupStatus = ENABLED’)
.withCondition(‘Status = ENABLED’);
if(CAMPAIGN_LABEL !== ”) {
var campNames = getCampaignNames();
agSelector = agSelector.withCondition(“CampaignName IN [‘”+campNames.join(“‘,'”)+”‘]”);
}
var agIter = agSelector.get();
var todayDate = getDateString(new Date(),’yyyyMMdd’);
var touchedAdGroups = [];
var finishedEarly = false;
while(agIter.hasNext()) {
var ag = agIter.next();

var numLoops = (ENABLE_MOBILE_AD_TESTING) ? 2 : 1;
for(var loopNum = 0; loopNum < numLoops; loopNum++) {
var isMobile = (loopNum == 1);
var rowKey;
if(isMobile) {
info(‘Checking Mobile Ads in AdGroup: “‘+ag.getName()+'”‘);
rowKey = [ag.getCampaign().getId(),ag.getId(),’Mobile’].join(‘-‘);
} else {
info(‘Checking Ads in AdGroup: “‘+ag.getName()+'”‘);
rowKey = [ag.getCampaign().getId(),ag.getId()].join(‘-‘);
}

if(!currentAdMap[rowKey]) { //This shouldn’t happen
warn(‘Could not find AdGroup: ‘+ag.getName()+’ in current ad map.’);
continue;
}

if(APPLY_TEST_START_DATE_LABELS) {
var dateLabel;
if(isMobile) {
dateLabel = ‘Mobile Tests Started: ‘+getDateString(currentAdMap[rowKey].lastTouched,’yyyy-MM-dd’);
} else {
dateLabel = ‘Tests Started: ‘+getDateString(currentAdMap[rowKey].lastTouched,’yyyy-MM-dd’);
}

createLabelIfNeeded(dateLabel,”#8A2BE2”);
//remove old start date
var labelIter = ag.labels().withCondition(“Name STARTS_WITH ‘”+dateLabel.split(‘:’)[0]+”‘”)
.withCondition(“Name != ‘”+dateLabel+”‘”).get();
while(labelIter.hasNext()) {
var label = labelIter.next();
ag.removeLabel(label.getName());
if(!label.adGroups().get().hasNext()) {
//if there are no more entities with that label, delete it.
label.remove();
}
}
applyLabel(ag,dateLabel);
}

//Here is the date range for the test metrics
var lastTouchedDate = getDateString(currentAdMap[rowKey].lastTouched,’yyyyMMdd’);
info(‘Last Touched Date: ‘+lastTouchedDate+’ Todays Date: ‘+ todayDate);
if(lastTouchedDate === todayDate) {
//Special case where the AdGroup was updated today which means a new test has started.
//Remove the old labels, but keep the champion as the control for the next test
info(‘New test is starting in AdGroup: ‘+ag.getName());
removeLoserLabelsFromAds(ag,isMobile);
continue;
}

//Is there a previous winner? if so we should use it as the control.
var controlAd = checkForPreviousWinner(ag,isMobile);

//Here we order by the Visitors metric and use that as a control if we don’t have one
var adSelector = ag.ads().withCondition(‘Status = ENABLED’).withCondition(‘AdType = TEXT_AD’);
if(!AdWordsApp.getExecutionInfo().isPreview()) {
adSelector = adSelector.withCondition(“LabelNames CONTAINS_NONE [‘”+[LOSER_LABEL,CHAMPION_LABEL].join(“‘,'”)+”‘]”);
}
var adIter = adSelector.forDateRange(lastTouchedDate, todayDate)
.orderBy(VISITORS_METRIC+” DESC”)
.get();
if( (controlAd == null && adIter.totalNumEntities() < 2) ||
(controlAd != null && adIter.totalNumEntities() < 1) )
{
info(‘AdGroup did not have enough eligible Ads. Had: ‘+adIter.totalNumEntities()+’, Needed at least 2′);
continue;
}

if(!controlAd) {
info(‘No control set for AdGroup. Setting one.’);
while(adIter.hasNext()) {
var ad = adIter.next();
if(shouldSkip(isMobile,ad)) { continue; }
controlAd = ad;
break;
}
if(!controlAd) {
continue;
}
applyLabel(controlAd,CHAMPION_LABEL);
}

while(adIter.hasNext()) {
var testAd = adIter.next();
if(shouldSkip(isMobile,testAd)) { continue; }
//The Test object does all the heavy lifting for us.
var test = new Test(controlAd,testAd,
CONFIDENCE_LEVEL,
lastTouchedDate,todayDate,
VISITORS_METRIC,CONVERSIONS_METRIC);
info(‘Control – Visitors: ‘+test.getControlVisitors()+’ Conversions: ‘+test.getControlConversions());
info(‘Test – Visitors: ‘+test.getTestVisitors()+’ Conversions: ‘+test.getTestConversions());
info(‘P-Value: ‘+test.getPValue());

if(test.getControlVisitors() < VISITORS_THRESHOLD ||
test.getTestVisitors() < VISITORS_THRESHOLD)
{
info(‘Not enough visitors in the control or test ad. Skipping.’);
continue;
}

//Check for significance
if(test.isSignificant()) {
var loser = test.getLoser();
removeLabel(loser,CHAMPION_LABEL); //Champion has been dethroned
applyLabel(loser,LOSER_LABEL);

//The winner is the new control. Could be the same as the old one.
controlAd = test.getWinner();
applyLabel(controlAd,CHAMPION_LABEL);

//We store some metrics for a nice email later
if(!ag[‘touchCount’]) {
ag[‘touchCount’] = 0;
touchedAdGroups.push(ag);
}
ag[‘touchCount’]++;
}
}

//Let’s bail if we run out of time so we can send the emails.
if((!AdWordsApp.getExecutionInfo().isPreview() && AdWordsApp.getExecutionInfo().getRemainingTime() < 60) ||
( AdWordsApp.getExecutionInfo().isPreview() && AdWordsApp.getExecutionInfo().getRemainingTime() < 10) )
{
finishedEarly = true;
break;
}
}
}
if(touchedAdGroups.length > 0) {
sendMailForTouchedAdGroups(touchedAdGroups,finishedEarly);
}
beacon();
}

// A helper function to return the list of campaign ids with a label for filtering
function getCampaignNames() {
var campNames = [];
var labelIter = AdWordsApp.labels().withCondition(“Name = ‘”+CAMPAIGN_LABEL+”‘”).get();
if(labelIter.hasNext()) {
var label = labelIter.next();
var campIter = label.campaigns().get();
while(campIter.hasNext()) {
campNames.push(campIter.next().getName());
}
}
return campNames;
}

function applyLabel(entity,label) {
if(!AdWordsApp.getExecutionInfo().isPreview()) {
entity.applyLabel(label);
} else {
var adText = (entity.getEntityType() === ‘Ad’) ? [entity.getHeadline(),entity.getDescription1(),
entity.getDescription2(),entity.getDisplayUrl()].join(‘ ‘)
: entity.getName();
Logger.log(‘PREVIEW: Would have applied label: ‘+label+’ to Entity: ‘+ adText);
}
}

function removeLabel(ad,label) {
if(!AdWordsApp.getExecutionInfo().isPreview()) {
ad.removeLabel(label);
} else {
var adText = [ad.getHeadline(),ad.getDescription1(),ad.getDescription2(),ad.getDisplayUrl()].join(‘ ‘);
Logger.log(‘PREVIEW: Would have removed label: ‘+label+’ from Ad: ‘+ adText);
}
}

// This function checks if the AdGroup has an Ad with a Champion Label
// If so, the new test should use that as the control.
function checkForPreviousWinner(ag,isMobile) {
var adSelector = ag.ads().withCondition(‘Status = ENABLED’)
.withCondition(‘AdType = TEXT_AD’);
if(!AdWordsApp.getExecutionInfo().isPreview()) {
adSelector = adSelector.withCondition(“LabelNames CONTAINS_ANY [‘”+CHAMPION_LABEL+”‘]”);
}
var adIter = adSelector.get();
while(adIter.hasNext()) {
var ad = adIter.next();
if(shouldSkip(isMobile,ad)) { continue; }
info(‘Found a previous winner. Using it as the control.’);
return ad;
}
return null;
}

function shouldSkip(isMobile,ad) {
if(isMobile) {
if(!ad.isMobilePreferred()) {
return true;
}
} else {
if(ad.isMobilePreferred()) {
return true;
}
}
return false;
}

// This function sends the email to the people in the TO array.
// If the script finishes early, it adds a notice to the email.
function sendMailForTouchedAdGroups(ags,finishedEarly) {
var htmlBody = ‘<html><head></head><body>’;
if(finishedEarly) {
htmlBody += ‘The script was not able to check all AdGroups. ‘ +
‘It will check additional AdGroups on the next run.<br / >’ ;
}
htmlBody += ‘The following AdGroups have one or more creative tests that have finished.’ ;
htmlBody += buildHtmlTable(ags);
htmlBody += ‘<p><small>Generated by <a href=”//www.freeadwordsscripts.com”>FreeAdWordsScripts.com</a></small></p>’ ;
htmlBody += ‘</body></html>’;
var options = {
htmlBody : htmlBody,
};
var subject = ags.length + ‘ Creative Test(s) Completed – ‘ +
Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), ‘yyyy-MM-dd’);
for(var i in TO) {
MailApp.sendEmail(TO[i], subject, ags.length+’ AdGroup(s) have creative tests that have finished.’, options);
}
}

// This function uses my HTMLTable object to build the styled html table for the email.
function buildHtmlTable(ags) {
var table = new HTMLTable();
//CSS from: //coding.smashingmagazine.com/2008/08/13/top-10-css-table-designs/
//Inlined using: //inlinestyler.torchboxapps.com/
table.setTableStyle([‘font-family: “Lucida Sans Unicode”,”Lucida Grande”,Sans-Serif;’,
‘font-size: 12px;’,
‘background: #fff;’,
‘margin: 45px;’,
‘width: 480px;’,
‘border-collapse: collapse;’,
‘text-align: left’].join(”));
table.setHeaderStyle([‘font-size: 14px;’,
‘font-weight: normal;’,
‘color: #039;’,
‘padding: 10px 8px;’,
‘border-bottom: 2px solid #6678b1’].join(”));
table.setCellStyle([‘border-bottom: 1px solid #ccc;’,
‘padding: 4px 6px’].join(”));
table.addHeaderColumn(‘#’);
table.addHeaderColumn(‘Campaign Name’);
table.addHeaderColumn(‘AdGroup Name’);
table.addHeaderColumn(‘Tests Completed’);
for(var i in ags) {
table.newRow();
table.addCell(table.getRowCount());
var campName = ags[i].getCampaign().getName();
var name = ags[i].getName();
var touchCount = ags[i][‘touchCount’];
var campLink, agLink;
if(__c !== ” && __u !== ”) { // You should really set these.
campLink = getUrl(ags[i].getCampaign(),’Ad groups’);
agLink = getUrl(ags[i],’Ads’);
table.addCell(a(campLink,campName));
table.addCell(a(agLink,name));
} else {
table.addCell(campName);
table.addCell(name);
}
table.addCell(touchCount,’text-align: right’);
}
return table.toString();
}

// Just a helper to build the html for a link.
function a(link,val) {
return ‘<a href=”‘+link+'”>’+val+'</a>’;
}

// This function finds all the previous losers and removes their label.
// It is used when the script detects a change in the AdGroup and needs to
// start a new test.
function removeLoserLabelsFromAds(ag,isMobile) {
var adSelector = ag.ads().withCondition(‘Status = ENABLED’);
if(!AdWordsApp.getExecutionInfo().isPreview()) {
adSelector = adSelector.withCondition(“LabelNames CONTAINS_ANY [‘”+LOSER_LABEL+”‘]”);
}
var adIter = adSelector.get();
while(adIter.hasNext()) {
var ad = adIter.next();
if(shouldSkip(isMobile,ad)) { continue; }
removeLabel(ad,LOSER_LABEL);
}
}

// A helper function to create a new label if it doesn’t exist in the account.
function createLabelIfNeeded(name,color) {
if(!AdWordsApp.labels().withCondition(“Name = ‘”+name+”‘”).get().hasNext()) {
info(‘Creating label: “‘+name+'”‘);
AdWordsApp.createLabel(name,””,color);
} else {
info(‘Label: “‘+name+'” already exists.’);
}
}

// This function compares the previous and current Ad maps and
// updates the current map with the date that the AdGroup was last touched.
// If OVERRIDE_LAST_TOUCHED_DATE is set and there is no previous data for the
// AdGroup, it uses that as the last touched date.
function updateCurrentAdMap(current,previous) {
info(‘Updating the current Ads map using historical snapshot.’);
for(var rowKey in current) {
var currentAds = current[rowKey].adIds;
var previousAds = (previous[rowKey]) ? previous[rowKey].adIds : [];
if(currentAds.join(‘-‘) === previousAds.join(‘-‘)) {
current[rowKey].lastTouched = previous[rowKey].lastTouched;
}
if(previousAds.length === 0 && OVERRIDE_LAST_TOUCHED_DATE !== ”) {
current[rowKey].lastTouched = new Date(OVERRIDE_LAST_TOUCHED_DATE);
}
//if we make it here without going into the above if statements
//then the adgroup has changed and we should keep the new date
}
info(‘Finished updating the current Ad map.’);
return current;
}

// This stores the Ad map snapshot to a file so it can be used for the next run.
// The data is stored as a JSON string for easy reading later.
function storeAdsSnapshot(data) {
info(‘Storing the Ads snapshot to Google Drive.’);
var fileName = getSnapshotFilename();
var file = DriveApp.getFilesByName(fileName).next();
file.setContent(Utilities.jsonStringify(data));
info(‘Finished.’);
}

// This reads the JSON formatted previous snapshot from a file on GDrive
// If the file doesn’t exist, it creates a new one and returns an empty map.
function getPreviousAdsSnapshot() {
info(‘Loading the previous Ads snapshot from Google Drive.’);
var fileName = getSnapshotFilename();
var fileIter = DriveApp.getFilesByName(fileName);
if(fileIter.hasNext()) {
return Utilities.jsonParse(fileIter.next().getBlob().getDataAsString());
} else {
DriveApp.createFile(fileName, ”);
return {};
}
}

// A helper function to build the filename for the snapshot.
function getSnapshotFilename() {
var accountId = AdWordsApp.currentAccount().getCustomerId();
return (accountId + ‘ Ad Testing Script Snapshot.json’);
}

// This function pulls the Ad Performance Report which is the fastest
// way to build a snapshot of the current ads in the account.
// This only pulls in active text ads.
function getCurrentAdsSnapshot() {
info(‘Running Ad Performance Report to get current Ads snapshot.’);
var OPTIONS = { includeZeroImpressions : true };
var cols = [‘CampaignId’,’AdGroupId’,’Id’,’DevicePreference’,’Impressions’];
var report = ‘AD_PERFORMANCE_REPORT’;
var query = [‘select’,cols.join(‘,’),’from’,report,
‘where AdType = TEXT_AD’,
‘and AdNetworkType1 = SEARCH’,
‘and CampaignStatus = ENABLED’,
‘and AdGroupStatus = ENABLED’,
‘and Status = ENABLED’,
‘during’,’TODAY’].join(‘ ‘);
var results = {}; // { campId-agId : row, … }
var reportIter = AdWordsApp.report(query, OPTIONS).rows();
while(reportIter.hasNext()) {
var row = reportIter.next();
var rowKey = [row.CampaignId,row.AdGroupId].join(‘-‘);
if(ENABLE_MOBILE_AD_TESTING && row.DevicePreference == 30001) {
rowKey += ‘-Mobile’;
}
if(!results[rowKey]) {
results[rowKey] = { adIds : [], lastTouched : new Date() };
}
results[rowKey].adIds.push(row.Id);
}
for(var i in results) {
results[i].adIds.sort();
}
info(‘Finished building the current Ad map.’);
return results;
}

//Helper function to format the date
function getDateString(date,format) {
return Utilities.formatDate(new Date(date),AdWordsApp.currentAccount().getTimeZone(),format);
}

// Function to build out the urls for deeplinking into the AdWords account.
// For this to work, you need to have __c and __u filled in.
// Taken from: //www.freeadwordsscripts.com/2013/11/building-entity-deep-links-with-adwords.html
function getUrl(entity,tab) {
var customerId = __c;
var effectiveUserId = __u;
var decodedTab = getTab(tab);

var base = ‘//adwords.google.com/cm/CampaignMgmt?’;
var url = base+’__c=’+customerId+’&__u=’+effectiveUserId+’#’;

if(typeof entity[‘getEntityType’] === ‘undefined’) {
return url+’r.ONLINE.di&app=cm’;
}

var type = entity.getEntityType()
if(type === ‘Campaign’) {
return url+’c.’+entity.getId()+’.’+decodedTab+’&app=cm’;
}
if(type === ‘AdGroup’) {
return url+’a.’+entity.getId()+’_’+entity.getCampaign().getId()+’.’+decodedTab+’&app=cm’;
}
if(type === ‘Keyword’) {
return url+’a.’+entity.getAdGroup().getId()+’_’+entity.getCampaign().getId()+’.key&app=cm’;
}
if(type === ‘Ad’) {
return url+’a.’+entity.getAdGroup().getId()+’_’+entity.getCampaign().getId()+’.create&app=cm’;
}
return url+’r.ONLINE.di&app=cm’;

function getTab(tab) {
var mapping = {
‘Ad groups’:’ag’,’Settings:All settings’:’st_sum’,
‘Settings:Locations’:’st_loc’,’Settings:Ad schedule’:’st_as’,
‘Settings:Devices’:’st_p’,’Ads’:’create’,
‘Keywords’:’key’,’Audiences’:’au’,’Ad extensions’:’ae’,
‘Auto targets’:’at’,’Dimensions’ : ‘di’
};
if(mapping[tab]) { return mapping[tab]; }
return ‘key’; //default to keyword tab
}
}

// Helper function to print info logs
function info(msg) {
if(EXTRA_LOGS) {
Logger.log(‘INFO: ‘+msg);
}
}

// Helper function to print more serious warnings
function warn(msg) {
Logger.log(‘WARNING: ‘+msg);
}

/********************************
* Track Script Runs in Google Analytics
* Created By: Russ Savage
* FreeAdWordsScripts.com
********************************/
function beacon() {
var TAG_ID = ‘UA-40187672-2’;
var CAMPAIGN_SOURCE = ‘adwords’;
var CAMPAIGN_MEDIUM = ‘scripts’;
var CAMPAIGN_NAME = ‘AdTestingScriptV2_1’;
var HOSTNAME = ‘www.freeadwordsscripts.com’;
var PAGE = ‘/Ad_Testing_Script_v2_1’;
if(AdWordsApp.getExecutionInfo().isPreview()) {
PAGE += ‘/preview’;
}
var DOMAIN_LINK = ‘//’+HOSTNAME+PAGE;

//Pulled from: //stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
var uuid = ‘xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx’.replace(/[xy]/g,
function(c) {var r = Math.random()*16|0,v=c==’x’?r:r&0x3|0x8;return v.toString(16);});

var url = ‘//www.google-analytics.com/collect?’;
var payload = {
‘v’:1,’tid’:TAG_ID,’cid’:uuid,
‘t’:’pageview’,’cs’:CAMPAIGN_SOURCE,’cm’:CAMPAIGN_MEDIUM,’cn’:CAMPAIGN_NAME,
‘dl’:DOMAIN_LINK
};
var qs = ”;
for(var key in payload) {
qs += key + ‘=’ + encodeURIComponent(payload[key]) + ‘&’;
}
url += qs.substring(0,qs.length-1);
UrlFetchApp.fetch(url);
}

/*********************************************
* Test: A class for runnning A/B Tests for Ads
* Version 1.0
* Based on VisualWebsiteOptimizer logic: //goo.gl/jiImn
* Russ Savage
* FreeAdWordsScripts.com
**********************************************/
// A description of the parameters:
// control – the control Ad, test – the test Ad
// startDate, endDate – the start and end dates for the test
// visitorMetric, conversionMetric – the components of the metric to use for the test
function Test(control,test,desiredConf,startDate,endDate,visitorMetric,conversionMetric) {
this.desiredConfidence = desiredConf/100;
this.verMetric = visitorMetric;
this.conMetric = conversionMetric;
this.startDate = startDate;
this.endDate = endDate;
this.winner;

this.controlAd = control;
this.controlStats = (this.controlAd[‘stats’]) ? this.controlAd[‘stats’] : this.controlAd.getStatsFor(this.startDate, this.endDate);
this.controlAd[‘stats’] = this.controlStats;
this.controlVisitors = this.controlStats[‘get’+this.verMetric]();
this.controlConversions = this.controlStats[‘get’+this.conMetric]();
this.controlCR = getConversionRate(this.controlVisitors,this.controlConversions);

this.testAd = test;
this.testStats = (this.testAd[‘stats’]) ? this.testAd[‘stats’] : this.testAd.getStatsFor(this.startDate, this.endDate);
this.testAd[‘stats’] = this.testStats;
this.testVisitors = this.testStats[‘get’+this.verMetric]();
this.testConversions = this.testStats[‘get’+this.conMetric]();
this.testCR = getConversionRate(this.testVisitors,this.testConversions);

this.pValue;

this.getControlVisitors = function() { return this.controlVisitors; }
this.getControlConversions = function() { return this.controlConversions; }
this.getTestVisitors = function() { return this.testVisitors; }
this.getTestConversions = function() { return this.testConversions; }

// Returns the P-Value for the two Ads
this.getPValue = function() {
if(!this.pValue) {
this.pValue = calculatePValue(this);
}
return this.pValue;
};

// Determines if the test has hit significance
this.isSignificant = function() {
var pValue = this.getPValue();
if(pValue && pValue !== ‘N/A’ && (pValue >= this.desiredConfidence || pValue <= (1 – this.desiredConfidence))) {
return true;
}
return false;
}

// Returns the winning Ad
this.getWinner = function() {
if(this.decideWinner() === ‘control’) {
return this.controlAd;
}
if(this.decideWinner() === ‘challenger’) {
return this.testAd;
}
return null;
};

// Returns the losing Ad
this.getLoser = function() {
if(this.decideWinner() === ‘control’) {
return this.testAd;
}
if(this.decideWinner() === ‘challenger’) {
return this.controlAd;
}
return null;
};

// Determines if the control or the challenger won
this.decideWinner = function () {
if(this.winner) {
return this.winner;
}
if(this.isSignificant()) {
if(this.controlCR >= this.testCR) {
this.winner = ‘control’;
} else {
this.winner = ‘challenger’;
}
} else {
this.winner = ‘no winner’;
}
return this.winner;
}

// This function returns the confidence level for the test
function calculatePValue(instance) {
var control = {
visitors: instance.controlVisitors,
conversions: instance.controlConversions,
cr: instance.controlCR
};
var challenger = {
visitors: instance.testVisitors,
conversions: instance.testConversions,
cr: instance.testCR
};
var z = getZScore(control,challenger);
if(z == -1) { return ‘N/A’; }
var norm = normSDist(z);
return norm;
}

// A helper function to make rounding a little easier
function round(value) {
var decimals = Math.pow(10,5);
return Math.round(value*decimals)/decimals;
}

// Return the conversion rate for the test
function getConversionRate(visitors,conversions) {
if(visitors == 0) {
return -1;
}
return conversions/visitors;
}

function getStandardError(cr,visitors) {
if(visitors == 0) {
throw ‘Visitors cannot be 0.’;
}
return Math.sqrt((cr*(1-cr)/visitors));
}

function getZScore(c,t) {
try {
if(!c[‘se’]) { c[‘se’] = getStandardError(c.cr,c.visitors); }
if(!t[‘se’]) { t[‘se’] = getStandardError(t.cr,t.visitors); }
} catch(e) {
Logger.log(e);
return -1;
}

if((Math.sqrt(Math.pow(c.se,2)+Math.pow(t.se,2))) == 0) {
Logger.log(‘WARNING: Somehow the denominator in the Z-Score calulator was 0.’);
return -1;
}
return ((c.cr-t.cr)/Math.sqrt(Math.pow(c.se,2)+Math.pow(t.se,2)));
}

//From: //www.codeproject.com/Articles/408214/Excel-Function-NORMSDIST-z
function normSDist(z) {
var sign = 1.0;
if (z < 0) { sign = -1; }
return round(0.5 * (1.0 + sign * erf(Math.abs(z)/Math.sqrt(2))));
}

// From: //picomath.org/javascript/erf.js.html
function erf(x) {
// constants
var a1 = 0.254829592;
var a2 = -0.284496736;
var a3 = 1.421413741;
var a4 = -1.453152027;
var a5 = 1.061405429;
var p = 0.3275911;

// Save the sign of x
var sign = 1;
if (x < 0) {
sign = -1;
}
x = Math.abs(x);

// A&S formula 7.1.26
var t = 1.0/(1.0 + p*x);
var y = 1.0 – (((((a5*t + a4)*t) + a3)*t + a2)*t + a1)*t*Math.exp(-x*x);

return sign*y;
}
}

/*********************************************
* HTMLTable: A class for building HTML Tables
* Version 1.0
* Russ Savage
* FreeAdWordsScripts.com
**********************************************/
function HTMLTable() {
this.headers = [];
this.columnStyle = {};
this.body = [];
this.currentRow = 0;
this.tableStyle;
this.headerStyle;
this.cellStyle;

this.addHeaderColumn = function(text) {
this.headers.push(text);
};

this.addCell = function(text,style) {
if(!this.body[this.currentRow]) {
this.body[this.currentRow] = [];
}
this.body[this.currentRow].push({ val:text, style:(style) ? style : ” });
};

this.newRow = function() {
if(this.body != []) {
this.currentRow++;
}
};

this.getRowCount = function() {
return this.currentRow;
};

this.setTableStyle = function(css) {
this.tableStyle = css;
};

this.setHeaderStyle = function(css) {
this.headerStyle = css;
};

this.setCellStyle = function(css) {
this.cellStyle = css;
if(css[css.length-1] !== ‘;’) {
this.cellStyle += ‘;’;
}
};

this.toString = function() {
var retVal = ‘<table ‘;
if(this.tableStyle) {
retVal += ‘style=”‘+this.tableStyle+'”‘;
}
retVal += ‘>’+_getTableHead(this)+_getTableBody(this)+'</table>’;
return retVal;
};

function _getTableHead(instance) {
var headerRow = ”;
for(var i in instance.headers) {
headerRow += _th(instance,instance.headers[i]);
}
return ‘<thead><tr>’+headerRow+'</tr></thead>’;
};

function _getTableBody(instance) {
var retVal = ‘<tbody>’;
for(var r in instance.body) {
var rowHtml = ‘<tr>’;
for(var c in instance.body[r]) {
rowHtml += _td(instance,instance.body[r][c]);
}
rowHtml += ‘</tr>’;
retVal += rowHtml;
}
retVal += ‘</tbody>’;
return retVal;
};

function _th(instance,val) {
var retVal = ‘<th scope=”col” ‘;
if(instance.headerStyle) {
retVal += ‘style=”‘+instance.headerStyle+'”‘;
}
retVal += ‘>’+val+'</th>’;
return retVal;
};

function _td(instance,cell) {
var retVal = ‘<td ‘;
if(instance.cellStyle || cell.style) {
retVal += ‘style=”‘;
if(instance.cellStyle) {
retVal += instance.cellStyle;
}
if(cell.style) {
retVal += cell.style;
}
retVal += ‘”‘;
}
retVal += ‘>’+cell.val+'</td>’;
return retVal;
};
}

Automação Google Ads: Acompanhamento

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para “Trabalhar com acompanhamento de anúncios”.

 

1. Acompanhamento do Índice de Qualidade – Por Martin Roettgerding. Acompanhe o Índice de Qualidade da sua conta com esse script. Além disso, o script oferece um dashboard com a média do Índice de Qualidade histórica de sua conta. Excelente para acompanhar o progresso de suas alterações nos anúncios, páginas de destino, por exemplo.

Apresentando o Quality Score Tracker v3.0
postado em 11 de março de 2016
É maior, é melhor e oficial: o novo Rastreador de Índice de Qualidade está finalmente disponível!

O Quality Score Tracker é um script gratuito do Google Ads que controla os Índices de qualidade de uma conta. A versão anterior ainda é incrivelmente popular, mas já tem três anos. É por isso que criamos um novo que é muito mais rápido e tem novos recursos extravagantes.

 

 

A nova versão é diferente das antigas em vários aspectos. Um dos principais objetivos era escrever um roteiro que fosse o mais fácil de usar possível – ainda mais fácil do que os antigos. O novo Rastreador de Índice de qualidade pode ser usado imediatamente, sem qualquer configuração adicional.

Apesar de ser fácil de usar o novo script é muito complexo e lida com muitas coisas sem incomodar o usuário.

Veja o que faz:

Acompanhamento do Índice de qualidade

O Quality Score Tracker salva os Índices de qualidade de suas palavras-chave em uma planilha. Ao contrário das versões anteriores, você não precisa escolher quais – ele rastreia todas elas. Isso permite que você volte e pesquise o histórico de qualquer uma de suas palavras-chave posteriormente.

Painel de controle

Embora o rastreamento de milhares de Índices de qualidade individuais possa ser útil posteriormente, o script também fornece um painel para mostrar onde você está atualmente.

 

 

O painel é fornecido em uma planilha do Google também. Essa planilha também fornece dados resumidos sobre seus Índices de qualidade.

Google Drive

Todos os arquivos (pontuações de qualidade do painel e da palavra-chave rastreada) são armazenados em uma pasta no seu Google Drive. Você pode acessá-los por meio do navegador ou usar todos os recursos do Google Drive, como sincronizar a pasta com o dispositivo ou compartilhar pastas com outras pessoas.

Há um limite de quantas células uma planilha do Google pode conter (dois milhões). Se você atingir esse limite durante o rastreamento de Índices de qualidade, o script criará planilhas adicionais. Os arquivos da planilha são nomeados e numerados para que você possa juntar tudo mais tarde, se necessário.

 

 

Como tudo está nos arquivos da planilha do Google, o espaço ocupado não conta para o limite de armazenamento do Google Drive.

Customizável

O script fornece várias configurações que você pode alterar (mas não é necessário). Por exemplo, você pode adicionar outros gráficos ao painel, como um Índice de qualidade ponderado por clique.

FAQ integrado

O painel vem com um FAQ que é atualizado toda vez que o script é executado.

Mais para vir!

O script já é bastante complexo, mas tenho algumas ideias para novos recursos que desejo adicionar no futuro (provavelmente uma versão da MCC será a próxima). Quando uma nova versão está disponível, um lembrete sutil é adicionado ao painel.

 

 

Configuração rápida

Como o script funciona imediatamente, é fácil configurá-lo.

  1. Obtenha o código (veja abaixo) e cole-o na sua conta.
  2. Agende o script para ser executado diariamente. Recomendação: 01:00
  3. Execute o script pela primeira vez. Verifique os registros para encontrar o URL do seu painel ou encontre tudo em seu Google Drive .

Configuração Detalhada

No Google AdWords, navegue até ” Operações em massa” e depois ” Scripts” . Clique em + Script . Cole o script no final deste post. Nomeie-o como “Quality Score Tracker v3.0”. Clique em Salvar e, em seguida, em Autorizar agora .

Volte para os Scripts e encontre o Rastreador de Índice de Qualidade no topo da lista. Clique em + Criar agendamento . Como frequência, selecione “Diariamente”, juntamente com um horário de sua conveniência. Recomendação: 01:00 Clique em Salvar .

Clique em Executar e , em seguida, role para baixo para ver o script sendo executado em Logs . Com grandes contas, isso pode demorar um pouco, caso contrário, isso pode ser feito em 15 segundos. Clique em Logs no lado direito. Na próxima tela, clique no botão Logs no topo. Agora, os URLs do painel e da pasta do Google Drive são exibidos. Como alternativa, você pode encontrar tudo em seu Google Drive .

 

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Quality Score Tracker v3.0.1
* Written by Martin Roettgerding.
* © 2016 Martin Roettgerding, Bloofusion Germany GmbH.
* www.ppc-epiphany.com/qstracker/latest
*/
function main(){
/*
* The following preferences can be changed to customize this script.
* Most of options can be set by using 1 for yes or 0 for no.
* You don’t have to change anything here. The script will do fine with the defaults.
*/
var config = {
/*
* Which of the following charts should be displayed on the dashboard?
* The “per QS” charts are column charts. They show the current state compared to a previous one (see next option).
* “Average” and “weighted” charts are line charts, showing changes over time.
*/
“chartsToDisplay” : {
“Keywords per QS” : 0,
“Average QS” : 0,
“Keywords with Impressions per QS” : 1,
“Average QS for Keywords with Impressions” : 0,
“Impressions per QS” : 0,
“Impression weighted QS” : 1,
“Clicks per QS” : 0,
“Click weighted QS” : 0,
“Conversions per QS” : 0,
“Conversion weighted QS” : 0,
“Conversion value per QS” : 0,
“Conversion value weighted QS” : 0,
},
/*
* Column charts can show a former date for comparison. Set the number of steps you want to go back for this.
* Note that the date you’re comparing this to will depend on how often you’ve run the script in the past.
* Example: If the setting is 30 and you ran the script daily, your comparison will be with the values from 30 days before. If you ran it hourly, it will be with values from 30 hours before.
* If you haven’t run the script often enough, the comparison will go as far back as possible.
* Put 0 to disable the comparison.
*/
“chartsCompareStepsBack” : 30,
/*
* When stats are taken into account (like impressions per QS, or impression weighted QS), this timeframe is used.
* Note that this affects the values to be tracked and stored. Past values that are already stored won’t be affected.
* Use one of the following: TODAY, YESTERDAY, LAST_7_DAYS, THIS_WEEK_SUN_TODAY, THIS_WEEK_MON_TODAY, LAST_WEEK, LAST_14_DAYS, LAST_30_DAYS, LAST_BUSINESS_WEEK, LAST_WEEK_SUN_SAT, THIS_MONTH
*/
“statsTimeframe” : “LAST_30_DAYS”,
/*
* Whether to only look at stats from Google (e.g. for impression weighted QS).
* Recommended. Quality Score itself only reflects data from Google, so weighting should only take Google into account and leave out search partners.
* Note that this affects the values to be tracked and stored. Past values that are already stored won’t be affected.
*/
“googleOnly” : 1,
/*
* Whether to only track active keywords. This means that the keyword, the adgroup, and the campaign have to be enabled.
* Recommended. Otherwise inactive keywords with meaningless Quality Scores might skew your data.
*/
“activeKeywordsOnly” : 1,
/*
* Set to 1 if you want your dates (in charts, table headers, and file names) to contain hours and minutes as well.
* Do this if you want to run the script hourly.
*/
“useHours” : 0,
/*
* Use this option to not keep track of individual keywords’ Quality Scores and only save data to the dashboard file.
* This makes sense if you have more than 400,000 keywords. Note that you don’t have to change this: The script will notice on its own and log a message otherwise.
*/
“skipIndividualKeywords” : 0,
/*
* The name of the file where dashboard and summarized data are stored.
*/
“summaryFileName” : “Dashboard + Summary”,
/*
* The base folder for all Quality Score Tracker files.
*/
“baseFolder” : “Quality Score Tracker/”,
/*
* Whether to add a client folder in the base folder (resulting in a folder like “Quality Score Tracker/CLIENT_NAME (123-456-7890)/”)
* The folder’s name is not important, as long as the Adwords client id remains in it.
* This is useful if you want to track multiple accounts with this script.
*/
“useClientFolder” : 1,
}

trackQS(config);
}

function trackQS(config){
var version = “3.0”;

if(config[‘useHours’]) var dateString = Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), “yyyy-MM-dd HH:mm”);
else var dateString = Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), “yyyy-MM-dd”);

var folder = getOrCreateFolder(config[‘baseFolder’]);
if(config[‘useClientFolder’]) folder = getOrCreateClientFolder(folder);

// Find the latest report file in the folder.
var maxFileNumber = 0;
var reportFile;
var summaryFile;
var fileIterator = folder.getFiles();
while(fileIterator.hasNext()){
var file = fileIterator.next();
var matches = new RegExp(‘ #([0-9]+) ‘).exec(file.getName());
if(matches && parseInt(matches[1]) > maxFileNumber){
maxFileNumber = parseInt(matches[1]);
reportFile = file;
}else if(file.getName() == config[‘summaryFileName’]) summaryFile = file;
}

// No report file found? Add a new one.
if(maxFileNumber == 0){
reportFile = addReportFile(folder, “QS Report #1 (” + dateString + “)”);
maxFileNumber = 1;
}
// No summary file found? Add a new one.
if(!summaryFile) summaryFile = addSummaryFile(folder, config[‘summaryFileName’]);

Logger.log(“All files are stored at”);
Logger.log(folder.getUrl());
Logger.log(“The dashboard is here:”);
Logger.log(summaryFile.getUrl());

var spreadsheet = SpreadsheetApp.open(reportFile);
var sheet = spreadsheet.getActiveSheet();
var lastRowNumber = sheet.getLastRow();
var sheetLastColumn = sheet.getLastColumn();
var idColumnValues = sheet.getRange(1, 4, lastRowNumber, 1).getValues();
var summarySpreadsheet = SpreadsheetApp.open(summaryFile);
updateInfo(summarySpreadsheet, version);
var sheetCharts = summarySpreadsheet.getSheetByName(“Dashboard”);

summarySpreadsheet.setActiveSheet(sheetCharts);
summarySpreadsheet.moveActiveSheet(1);

// Track an event in Google Analytics.
trackInAnalytics(version);

// Remember the line number for every keyword.
var lineNumbers = {};
for(var i = 1; i < lastRowNumber; i++){
lineNumbers[idColumnValues[i][0]] = i;
}

// qsValues represents the new column that will go right next to the others.
var qsValues = new Array(lastRowNumber);
qsValues[0] = [ dateString ];
// Initialize everything with an empty string.
var qsValuesLength = qsValues.length;
for(var i = 1; i < qsValuesLength; i++) qsValues[i] = [“”];

// In case new keywords are found, they’ll be added as new rows below the rest (campaign, adgroup, keyword, id string).
var newRows = [];

// All aggregated data goes in this variable.
var qsStats = {
“Keywords” : {},
“Keywords with impressions” : {},
“Impressions” : {},
“Clicks” : {},
“Conversions” : {},
“Conversion value” : {}
};
// Initialize the arrays so that everything can be added up later. Index 0 is for totals, 1-10 for Quality Scores.
for(var key in qsStats){
for(var i = 0; i <= 10; i++){
qsStats[key][i] = 0;
}
}

// Get the data from AdWords.
var awql = “SELECT Id, Criteria, KeywordMatchType, CampaignId, CampaignName, AdGroupId, AdGroupName, QualityScore, Impressions, Clicks, Conversions, ConversionValue FROM KEYWORDS_PERFORMANCE_REPORT WHERE Id NOT_IN [3000000, 3000006] AND Status = ‘ENABLED’ AND AdGroupStatus = ‘ENABLED’ AND CampaignStatus = ‘ENABLED'”;
if(config[‘googleOnly’]) awql += ” AND AdNetworkType2 = ‘SEARCH'”;
if(config[‘activeKeywordsOnly’]) awql += ” AND CampaignStatus = ‘ENABLED’ AND AdGroupStatus = ‘ENABLED’ AND Status = ‘ENABLED'”;
awql += ” DURING ” + config[‘statsTimeframe’];
var report = AdWordsApp.report(awql);
var reportRows = report.rows();

// Go through the report and count Quality Scores.
while(reportRows.hasNext()){
var row = reportRows.next();
// Save the aggregated data.
qsStats[‘Keywords’][row[‘QualityScore’]]++;
if(row[‘Impressions’] > 0) qsStats[‘Keywords with impressions’][row[‘QualityScore’]]++;
qsStats[‘Impressions’][row[‘QualityScore’]] += parseInt(row[‘Impressions’]);
qsStats[‘Clicks’][row[‘QualityScore’]] += parseInt(row[‘Clicks’]);
qsStats[‘Conversions’][row[‘QualityScore’]] += parseInt(row[‘Conversions’]);
qsStats[‘Conversion value’][row[‘QualityScore’]] += parseInt(row[‘ConversionValue’]);

// Save the individual keyword’s Quality Score.
if(!config[‘skipIndividualKeywords’]){
var id = row[‘CampaignId’]+”_”+row[‘AdGroupId’]+”_”+row[‘Id’];
// Check if there is already a line for this keyword
if(lineNumbers[id]) var line_number = lineNumbers[id];
else{
// There is no line for this keyword yet. Create a new one and add the line headers.
line_number = qsValues.length;
if(row[‘KeywordMatchType’] == “Exact”) var keyword = ‘[‘ + row[‘Criteria’] + ‘]’;
else if(row[‘KeywordMatchType’] == “Phrase”) var keyword = ‘”‘ + row[‘Criteria’] + ‘”‘;
else var keyword = row[‘Criteria’];

newRows.push([row[‘CampaignName’], row[‘AdGroupName’], keyword, id]);
}

qsValues[line_number] = [row[‘QualityScore’]];
}
}

// Check if everything fits.
if(!config[‘skipIndividualKeywords’]){
// A spreadsheet can hold up to 2 million cells. Calculate if the new data will fit in with the rest.
// With four rows needed for every keyword, plus one for every tracking run, this won’t fit if there are more than 400,000 rows (header + 399,999 keywords).
if(qsValues.length >= 400000){
Logger.log(“There are too many keywords to be tracked (” + qsValues.length + “). This tool can only track up to 399,999 keywords.”);
Logger.log(“A summary will be logged, but individual keyword quality scores cannot be stored.”);
skipIndividualKeywords = true;
}else if((qsValues.length + 1) * (sheet.getLastColumn() + 1) > 2000000){
// This spreadsheet is full, a new one is needed.
// Add new file.
maxFileNumber++;
reportFile = addReportFile(folder, “QS Report #” + maxFileNumber + ” (” + dateString + “)”);
var newSpreadsheet = SpreadsheetApp.open(reportFile);
var newSheet = newSpreadsheet.getActiveSheet();
// Copy the first columns from the old sheet to the new one.
newSheet.getRange(1, 1, lastRowNumber, 4).setValues(sheet.getRange(1, 1, lastRowNumber, 4).getValues());
// From now on, work with the new sheet and spreadsheet.
spreadsheet = newSpreadsheet;
sheet = newSheet;
sheetLastColumn = 4;
}
}

// Store the keyword data in the spreadsheet.
if(!config[‘skipIndividualKeywords’]){
// If there are new rows, add their line headers beneath the others.
if(newRows.length > 0){
sheet.insertRowsAfter(lastRowNumber, newRows.length).getRange(lastRowNumber + 1, 1, newRows.length, 4).setValues(newRows);
sheet.autoResizeColumn(1).autoResizeColumn(2).autoResizeColumn(3);
}
// Add a new column with the tracked data.
sheet.insertColumnAfter(sheetLastColumn);
sheet.getRange(1, sheetLastColumn + 1, qsValues.length, 1).setValues(qsValues);
sheet.autoResizeColumn(sheetLastColumn + 1);

// Change file name to reflect the new date.
// Find out which dates are currently noted in the file’s name.
var matches = /\((.*?)( – (.*))?\)/.exec(reportFile.getName());
if(matches && matches[1]){
if(matches[2]){
// There’s a start date and an end date.
var startDate = matches[1];
var endDate = matches[3];
if(endDate != dateString){
var newFileName = reportFile.getName().replace(endDate, dateString);
reportFile.setName(newFileName);
}
}else{
// There’s just a start date.
var startDate = matches[1];
if(startDate != dateString){
var newFileName = reportFile.getName().replace(startDate, startDate + ” – ” + dateString);
reportFile.setName(newFileName);
}
}
}else{
Logger.log(“Could not recognize dates in file name ” + reportFile.getName() +”. File name remains unchanged.”);
}
}

// Now take care of the summary file.

// Get the total numbers.
for(var key in qsStats){
for(var i = 1; i <= 10; i++){
qsStats[key][0] += qsStats[key][i];
}
}

// Prepare a new column for the Percentages data sheet.
var newValues = [];
var newValuesNumberFormats = [];

for(var key in qsStats){
newValues.push([dateString]);
newValuesNumberFormats.push([“@[email protected]”]);
for(var i = 1; i <= 10; i++){
if(qsStats[key][0]) newValues.push([qsStats[key][i] / qsStats[key][0]]); else newValues.push([0]);
newValuesNumberFormats.push([“0.00%”]);
}
newValues.push([qsStats[key][0]]);
newValuesNumberFormats.push([“0.##”]);
}

var sheetPercentages = summarySpreadsheet.getSheetByName(“Percentages”);
var sheetAverages = summarySpreadsheet.getSheetByName(“Averages”);

var lastCol = sheetPercentages.getLastColumn() + 1;
var lastRow = sheetAverages.getLastRow() + 1;

// Add the data to the Percentages sheet.
sheetPercentages.insertColumnAfter(lastCol – 1);
sheetPercentages.getRange(1, lastCol, 72, 1).setNumberFormats(newValuesNumberFormats).setValues(newValues);
sheetPercentages.autoResizeColumn(lastCol);

// Add a new row with formulas to the Averages sheet.
sheetAverages.appendRow([“”]);
sheetAverages.getRange(lastRow, 1, 1, 1).setValue(dateString);
sheetAverages.getRange(lastRow, 2, 1, 6).setFormulasR1C1([[
“=SUMPRODUCT(Percentages!R2C1:R11C1; Percentages!R2C” + lastCol + “:R11C” + lastCol + “)”,
“=SUMPRODUCT(Percentages!R14C1:R23C1; Percentages!R14C” + lastCol + “:R23C” + lastCol + “)”,
“=SUMPRODUCT(Percentages!R26C1:R35C1; Percentages!R26C” + lastCol + “:R35C” + lastCol + “)”,
“=SUMPRODUCT(Percentages!R38C1:R47C1; Percentages!R38C” + lastCol + “:R47C” + lastCol + “)”,
“=SUMPRODUCT(Percentages!R50C1:R59C1; Percentages!R50C” + lastCol + “:R59C” + lastCol + “)”,
“=SUMPRODUCT(Percentages!R62C1:R71C1; Percentages!R62C” + lastCol + “:R71C” + lastCol + “)”
]]);

// The properties for the charts. This is not meant to be reconfigured.
var chartsProperties = {
“Keywords per QS” : {
“type” : “column”,
“vCol” : 2,
},
“Average QS” : {
“type” : “line”,
“vCol” : 2,
},
“Keywords with Impressions per QS” : {
“type” : “column”,
“vCol” : 3,
},
“Average QS for Keywords with Impressions” : {
“type” : “line”,
“vCol” : 3,
},
“Impressions per QS” : {
“type” : “column”,
“vCol” : 4,
},
“Impression weighted QS” : {
“type” : “line”,
“vCol” : 4,
},
“Clicks per QS” : {
“type” : “column”,
“vCol” : 5,
},
“Click weighted QS” : {
“type” : “line”,
“vCol” : 5,
},
“Conversions per QS” : {
“type” : “column”,
“vCol” : 6,
},
“Conversion weighted QS” : {
“type” : “line”,
“vCol” : 6,
},
“Conversion value per QS” : {
“type” : “column”,
“vCol” : 7,
},
“Conversion value weighted QS” : {
“type” : “line”,
“vCol” : 7,
},
};

var row = 1;
var col = 1;
var summarySheets = {
“dataH”: sheetPercentages,
“dataV”: sheetAverages,
“charts”: sheetCharts,
}

// Add charts to the dashboard.
for(var chartName in config[‘chartsToDisplay’]){
// Skip all charts that are not set to be displayed.
if(!config[‘chartsToDisplay’][chartName]) continue;

addChartToDashboard(chartName, chartsProperties[chartName][‘type’], summarySheets, row, col, lastRow, lastCol, chartsProperties[chartName][‘vCol’], config[‘chartsCompareStepsBack’]);

// Add the “Average QS” cells.
sheetCharts.setRowHeight(row, 60).setRowHeight(row + 1, 20).setRowHeight(row + 2, 270);
sheetCharts.getRange(row, 2).setValue(“Average QS”).setFontWeight(“bold”).setFontSize(24).setBorder(true, true, false, true, null, null);
sheetCharts.getRange(row + 2, 2).setFontWeight(“bold”).setFontSize(24).setNumberFormat(“0.00”).setBorder(false, true, false, true, null, null);
sheetCharts.getRange(row + 1, 2, 2, 1).setFormulasR1C1(
[
[“=LOWER(Averages!R1C” + chartsProperties[chartName][‘vCol’] + “)”], [“=Averages!R” + lastRow + “C” + chartsProperties[chartName][‘vCol’]]
]).setBorder(false, true, true, true, null, null);
sheetCharts.autoResizeColumn(2);
row += 3;
}
}

/*
* Checks if there is a folder with the given name in the Google Drive root folder. If not, the folder is created.
* The folderName can be in the form of a complete path with subfolders, like “QS Reports/123/whatever”.
* Returns the folder.
*/
function getOrCreateFolder(folderName){
return getOrCreateFolderFromArray(folderName.toString().split(“/”), DriveApp.getRootFolder());
}

/*
* Does the actual work for getOrCreateFolder. Recursive function, based on an array of folder names (to handle paths with subfolders).
*/
function getOrCreateFolderFromArray(folderNameArray, currentFolder){
var folderName = “”;
// Skip empty folders (multiple slashes or a slash at the end).
do folderName = folderNameArray.shift(); while(folderName == “” && folderNameArray.length > 0);

if(folderName == “”) return currentFolder;

// See if the folder is already there.
var folderIterator = currentFolder.getFoldersByName(folderName);
if(folderIterator.hasNext()){
var folder = folderIterator.next();
}else{
// Create folder.
Logger.log(“Creating folder ‘” + folderName + “‘”);
var folder = currentFolder.createFolder(folderName);
}

if(folderNameArray.length > 0) return getOrCreateFolderFromArray(folderNameArray, folder);
return folder;
}

/*
* Checks if there is a folder for the current client account in the base folder. If not, the folder is created.
* Existing client folders are recognized by the client id in parentheses. This way, folders can be found again, even if an account has been renamed.
*/
function getOrCreateClientFolder(baseFolder){
var folderIterator = baseFolder.getFolders();
var regExp = new RegExp(AdWordsApp.currentAccount().getCustomerId());
while(folderIterator.hasNext()){
var folder = folderIterator.next();
if(folder.getName().match(regExp)) return folder;
}
// Since no folder has been found: Create one.
var newFolderName = AdWordsApp.currentAccount().getName() + ” (” + AdWordsApp.currentAccount().getCustomerId() + “)”;
Logger.log(“Creating folder ‘” + newFolderName + “‘”);
return baseFolder.createFolder(newFolderName);
}

/*
* Creates a spreadsheet for QS tracking.
* Adds headers to the spreadsheet.
* Returns the file.
*/
function addReportFile(folder, name){
var spreadsheet = SpreadsheetApp.create(name, 1, 4);
var sheet = spreadsheet.getActiveSheet();
sheet.setName(“QS history”);
// Put in the table headings
sheet.getRange(1, 1, 1, 4).setValues([[“Campaign”, “AdGroup”, “Keyword”, “ID string”]]);
//sheet.getRange(1, 1, 1, 4).setFontWeight(“bold”);
sheet.setColumnWidth(4, 1);
var file = DriveApp.getFileById(spreadsheet.getId());
folder.addFile(file);
var parentFolder = file.getParents().next();
parentFolder.removeFile(file);
return folder.getFilesByName(name).next();
}

/*
* Creates a spreadsheet for the summary and stores it in the folder.
* Creates sheets for the Percentages and Averages.
* Populates header rows and columns.
*/
function addSummaryFile(folder, name){
var spreadsheet = SpreadsheetApp.create(name);
var sheetH = spreadsheet.getActiveSheet();
sheetH.setName(“Percentages”);

// Add the first column for the horizontal data table.
sheetH.getRange(1, 1, 72, 1).setValues(
[[“All keywords”], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [‘Total’],
[“Keywords with impressions”], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [‘Total’],
[“Impression weighted”], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [‘Total’],
[“Click weighted”], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [‘Total’],
[“Conversion weighted”], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [‘Total’],
[“Conversion value weighted”], [1], [2], [3], [4], [5], [6], [7], [8], [9], [10], [‘Total’]
]
);
sheetH.getRange(“A:A”).setNumberFormat(‘@[email protected]’);
sheetH.autoResizeColumn(1);

var sheetV = spreadsheet.insertSheet(“Averages”);
// Add the first rows for the vertical data table.
sheetV.getRange(1, 1, 4, 7).setValues([
[“Date”, “Average”, “Average for keywords with impressions”, “Impression weighted”, “Click weighted”, “Conversion weighted”, “Value weighted”],
[“Highest”, “”, “”, “”, “”, “”, “”],
[“Lowest”, “”, “”, “”, “”, “”, “”],
[“Average”, “”, “”, “”, “”, “”, “”]
]);
// Add some formulas for maximums, minimums, and averages.
sheetV.getRange(2, 2, 3, 6).setFormulas([
[“=MAX(B$5:B)”, “=MAX(C$5:C)”, “=MAX(D$5:D)”, “=MAX(E$5:E)”, “=MAX(F$5:F)”, “=MAX(G$5:G)”],
[“=MIN(B$5:B)”, “=MIN(C$5:C)”, “=MIN(D$5:D)”, “=MIN(E$5:E)”, “=MIN(F$5:F)”, “=MIN(G$5:G)”],
[“=AVERAGE(B$5:B)”, “=AVERAGE(C$5:C)”, “=AVERAGE(D$5:D)”, “=AVERAGE(E$5:E)”, “=AVERAGE(F$5:F)”, “=AVERAGE(G$5:G)”]
]);
sheetV.getRange(1, 1, 1, 7).setFontWeight(“bold”).setNumberFormat(‘@[email protected]’);
sheetV.autoResizeColumn(1);
sheetV.autoResizeColumn(2);
sheetV.autoResizeColumn(3);
sheetV.autoResizeColumn(4);
sheetV.autoResizeColumn(5);
sheetV.autoResizeColumn(6);
sheetV.autoResizeColumn(7);

// Store the spreadsheet.
var file = DriveApp.getFileById(spreadsheet.getId());
folder.addFile(file);
var parentFolder = file.getParents().next();
parentFolder.removeFile(file);
return folder.getFilesByName(name).next();
}

/*
* Replaces the About sheet in the summary spreadsheet with a fresh one from the master sheet. This way, the sheet (including the FAQ) stays up to date.
* Also replaces the Dashboard with a fresh copy (resulting in an empty sheet with the correct conditional formatting).
* If there’s a new version, a sheet “New Version Available!” is added.
*/
function updateInfo(summarySpreadsheet, version){
var templateSpreadsheet = SpreadsheetApp.openByUrl(“//docs.google.com/spreadsheets/d/1qnTYdpBCgHP_5u5eQcXmc5gP0NrOrBK51JnTCTlc0_g/”);

var oldSheet = summarySpreadsheet.getSheetByName(“Dashboard”);
if(oldSheet) summarySpreadsheet.deleteSheet(oldSheet);
templateSpreadsheet.getSheetByName(“Dashboard v” + version).copyTo(summarySpreadsheet).setName(“Dashboard”);

var oldSheet = summarySpreadsheet.getSheetByName(“About + FAQ”);
if(oldSheet) summarySpreadsheet.deleteSheet(oldSheet);
templateSpreadsheet.getSheetByName(“About v” + version).copyTo(summarySpreadsheet).setName(“About + FAQ”);

var oldSheet = summarySpreadsheet.getSheetByName(“New Version Available!”);
if(oldSheet) summarySpreadsheet.deleteSheet(oldSheet);

// Check if there is a newer version.
var versionHistory = templateSpreadsheet.getSheetByName(“Version History”).getDataRange().getValues();
if(versionHistory[0][0] != version){
// There’s a new version available (at least one).
// Look for the row which has the info about the current (old) version.
var oldVersionRow = 1;
while(oldVersionRow < versionHistory.length && versionHistory[oldVersionRow][0] != version){
oldVersionRow++;
}

// Copy the entire version history.
var newVersionSheet = templateSpreadsheet.getSheetByName(“Version History”).copyTo(summarySpreadsheet).setName(“New Version Available!”);
// Remove everything about the old version.
newVersionSheet.deleteRows(oldVersionRow + 1, versionHistory.length – oldVersionRow);
// Add new Rows at the beginning.
newVersionSheet.insertRows(1, 6);
newVersionSheet.getRange(1, 1, 6, 2).setValues([[“Latest version:”, versionHistory[0][0]], [“Your version:”, version], [“”, “”], [“Get the latest version at”, “//www.ppc-epiphany.com/qstracker/latest”], [“”, “”], [“Newer Versions”, “”]]);
newVersionSheet.getRange(1, 1, 1, 2).setFontWeight(“bold”);
newVersionSheet.getRange(6, 1, 1, 1).setFontWeight(“bold”);
newVersionSheet.autoResizeColumn(1);
newVersionSheet.autoResizeColumn(2);
}
}

/*
* Inserts a line or column chart into the dashboard sheet.
* The chart is based on data from the Percentages or Averages sheet.
*/
function addChartToDashboard(name, type, sheets, row, col, lastRow, lastCol, vCol, compareStepsBack){
var chartBuilder = sheets[‘charts’].newChart();
chartBuilder
.setOption(‘title’, name)
.setOption(‘width’, 800)
.setOption(‘height’, 349)
.setOption(‘colors’, [‘#fa9d1c’,’#00507d’])
.setPosition(row, col, 0, 0);

switch(type){
case “column”:
var statsRow = (vCol – 2) * 12 + 1;
// First range for a column chart is always the same column with QS from 1 to 10.
var dataRanges = [sheets[‘dataH’].getRange(1, 1, 11, 1)];
if(compareStepsBack && lastCol > 2){
// The column for comparison is either the specified number of columns behind lastCol, or 2 (the first column with data).
dataRanges.push(sheets[‘dataH’].getRange(statsRow, Math.max(2, lastCol – compareStepsBack), 11, 1));
}
dataRanges.push(sheets[‘dataH’].getRange(statsRow, lastCol, 11, 1));
chartBuilder = chartBuilder.asColumnChart();
break;
case “line”:
var dataRanges = [sheets[‘dataV’].getRange(5, 1, lastRow – 2, 1), sheets[‘dataV’].getRange(5, vCol, lastRow – 2, 1)];
chartBuilder = chartBuilder.asLineChart();
chartBuilder.setOption(“vAxis.maxValue”, 10);
chartBuilder.setOption(“vAxis.ticks”, [0,2,4,6,8,10]);
chartBuilder.setLegendPosition(Charts.Position.NONE);
break;
}

for(var i in dataRanges) chartBuilder.addRange(dataRanges[i]);
sheets[‘charts’].insertChart(chartBuilder.build());
}

/*
* Tracks the execution of the script as an event in Google Analytics.
* Sends the version number and a random UUID (basically just a random number, required by Analytics).
* Basically tells that somewhere someone ran the script with a certain version.
* Credit for the idea goes to Russel Savage, who posted his version at //www.freeadwordsscripts.com/2013/11/track-adwords-script-runs-with-google.html.
*/
function trackInAnalytics(version){
// Create the random UUID from 30 random hex numbers gets them into the format xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx (with y being 8, 9, a, or b).
var uuid = “”;
for(var i = 0; i < 30; i++){
uuid += parseInt(Math.random()*16).toString(16);
}
uuid = uuid.substr(0, 8) + “-” + uuid.substr(8, 4) + “-4” + uuid.substr(12, 3) + “-” + parseInt(Math.random() * 4 + 8).toString(16) + uuid.substr(15, 3) + “-” + uuid.substr(18, 12);

var url = “//www.google-analytics.com/collect?v=1&t=event&tid=UA-74705456-1&cid=” + uuid + “&ds=adwordsscript&an=qstracker&av=”
+ version
+ “&ec=AdWords%20Scripts&ea=Script%20Execution&el=QS%20Tracker%20v” + version;
UrlFetchApp.fetch(url);
}

 

2. Contabilize a quantidade de execução de scripts com Google Analytics – Russell Savage. Vincule sua conta do Google Analytics para contabilizar a quantidade de vezes que seus scripts foram executados no relatório do Analytics. Cada vez que um script for executado ele será contabilizado nas visualizações de página.

/********************************
* Track Script Runs in Google Analytics
* Created By: Russ Savage
* FreeAdWordsScripts.com
********************************/
function beacon() {
var TAG_ID = ‘UA-XXXXXXXX-X’;
var CAMPAIGN_SOURCE = ‘adwords’;
var CAMPAIGN_MEDIUM = ‘scripts’;
var CAMPAIGN_NAME = ‘Your Script Name And Version’;
var HOSTNAME = ‘www.freeadwordsscripts.com’;
var PAGE = ‘/Some/Virtual/Page/Similar/To/Campaign/Name/Probably’;
var DOMAIN_LINK = ‘//’+HOSTNAME+PAGE;

//Pulled from: //stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
var uuid = ‘xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx’.replace(/[xy]/g,
function(c) {var r = Math.random()*16|0,v=c==’x’?r:r&0x3|0x8;return v.toString(16);});

var url = ‘//www.google-analytics.com/collect?’;
var payload = {
‘v’:1,’tid’:TAG_ID,’cid’:uuid,
‘t’:’pageview’,’cs’:CAMPAIGN_SOURCE,’cm’:CAMPAIGN_MEDIUM,’cn’:CAMPAIGN_NAME,
‘dl’:DOMAIN_LINK
};
var qs = ”;
for(var key in payload) {
qs += key + ‘=’ + encodeURIComponent(payload[key]) + ‘&’;
}
url += qs.substring(0,qs.length-1);
UrlFetchApp.fetch(url);
}

 

3. Dashboard de MCC – Por Frederick Vallaeys. Acompanhe o desempenho de todas as suas contas do MCC comparando os gastos do dia de ontem, anteontem e do mesmo dia da semana passada.

function main() {
var newSpreadSheet = SpreadsheetApp.openByUrl(“put a link to a Google Sheet
here – this is where the results will be placed”);
var numOfSheets = newSpreadSheet.getSheets();
if(numOfSheets.length>0){
for(var i=1,len=numOfSheets.length;i<len;i++)
newSpreadSheet.deleteSheet(numOfSheets[i]);
}
newSpreadSheet.getActiveSheet().clear().setName(“Last 1 Day”);

var allHeaders = [“Conversions”,”Impressions”,”Clicks”,”Cost”,”ConversionValue”];
var headerIndexes = {};
var headerString = “”;
var daysForMonth = 28;
var sheet = newSpreadSheet.getActiveSheet();

var tempIndex = 3;

for(var i=0,len=allHeaders.length;i<len;i++){
sheet.activate();
sheet.getRange(1,tempIndex).setValue(allHeaders[i]);

headerIndexes[allHeaders[i]]=tempIndex;
headerString=headerString+allHeaders[i]+”, “;
tempIndex = tempIndex+7;
}

sheet.getRange(“1:1”).setFontWeight(“bold”);
sheet.appendRow([“Account Name”,”Account Id”,””,””,”difference”,”% diff”]);

newSpreadSheet.duplicateActiveSheet().setName(“Last 7 Days”);
newSpreadSheet.duplicateActiveSheet().setName(“Last 30 Days”);

var allSheets = newSpreadSheet.getSheets();

allSheets[1].setName(“Last 7 Days”);
allSheets[2].setName(“Last 30 Days”);

headerString = headerString.substring(0,headerString.length-2);

Logger.log(headerIndexes);
var datesForFirst=[];var dateForSecond=[];var dateForThird = [];
var currentDate = new Date();var prevDate = new Date();var anotherPrevDate = new Date();
var sheetIndex =0;

//insert dates in the next rows
//for 1st sheet
var fixedDate = new Date(Utilities.formatDate(new Date(),AdWordsApp.currentAccount()
.getTimeZone(), “MMM dd,yyyy HH:mm:ss”));

var time = fixedDate.getTime() -(1 * 24 * 60 * 60 * 1000);
fixedDate = new Date(time);

time = fixedDate.getTime() -(1 * 24 * 60 * 60 * 1000);
prevDate = new Date(time);
time = fixedDate.getTime() -(7 * 24 * 60 * 60 * 1000);
anotherPrevDate = new Date(time);
datesForFirst.push(fixedDate);datesForFirst.push(prevDate);datesForFirst.push(anotherPrevDate);

appendDates(sheetIndex, datesForFirst, 0);

//for second sheet
sheetIndex = 1;
time = fixedDate.getTime() -(7 * 24 * 60 * 60 * 1000);
currentDate = new Date(time);
time = fixedDate.getTime() -(14 * 24 * 60 * 60 * 1000);
prevDate = new Date(time);
time = currentDate.getTime() -(30 * 24 * 60 * 60 * 1000);
anotherPrevDate = new Date(time);
dateForSecond.push(currentDate);dateForSecond.push(prevDate);dateForSecond.push(anotherPrevDate);

appendDates(sheetIndex, dateForSecond,7);

//for third sheet
sheetIndex = 2;
time = fixedDate.getTime() -(daysForMonth * 24 * 60 * 60 * 1000);
currentDate = new Date(time);
time = fixedDate.getTime() -(daysForMonth*2 * 24 * 60 * 60 * 1000);
prevDate = new Date(time);
time = currentDate.getTime() -(daysForMonth*12 * 24 * 60 * 60 * 1000);
anotherPrevDate = new Date(time);
dateForThird.push(currentDate);dateForThird.push(prevDate);dateForThird.push(anotherPrevDate);

appendDates(sheetIndex, dateForThird,daysForMonth);
//dates inserted

//get accounts and data respectively
var accounts_iterator = MccApp.accounts().withCondition(“Impressions>0”).forDateRange(“YESTERDAY”).get();
var current_mccaccount = AdWordsApp.currentAccount();
var all_accounts=[];
while(accounts_iterator.hasNext()){
all_accounts.push(accounts_iterator.next());
}

Logger.log(“no of accounts”+all_accounts.length);
for(var i=0,len=all_accounts.length;i<len;i++){
MccApp.select(all_accounts[i]);
appendData(0,datesForFirst,0);
appendData(1,dateForSecond,7);
appendData(2,dateForThird,daysForMonth);

for(var j=0;j<3;j++){
var sheetCurrent = allSheets[j];

sheetCurrent.activate();
var lRow = sheetCurrent.getLastRow();
for(var key in headerIndexes){
var index = headerIndexes[key];
var positiveColor = “green”;
var negativeColor = “red”;
if(key==”Cost”){
positiveColor=”red”;
negativeColor=”green”;
}

var firstVal = sheetCurrent.getRange(lRow,index).getValue();
var secondVal = sheetCurrent.getRange(lRow,index+1).getValue();

var diff = firstVal-secondVal;
sheetCurrent.getRange(lRow, index+2).setValue(diff);
var pcent = (diff/secondVal)*100;
if(secondVal==0)
pcent=firstVal*100;
if(diff==0)
pcent=0;

sheetCurrent.getRange(lRow, index+3).setValue(pcent+”%”);
if(pcent>0){
sheetCurrent.getRange(lRow, index+3).setFontColor(positiveColor);
}
else{
sheetCurrent.getRange(lRow, index+3).setFontColor(negativeColor);
}
secondVal = sheetCurrent.getRange(lRow,index+4).getValue();
var diff = firstVal-secondVal;
sheetCurrent.getRange(lRow, index+5).setValue(diff);
var pcent = (diff/secondVal)*100;
if(diff==0)
pcent=0;
if(secondVal==0)
pcent=firstVal*100;
sheetCurrent.getRange(lRow, index+6).setValue(pcent+”%”);
if(pcent>0){
sheetCurrent.getRange(lRow, index+6).setFontColor(positiveColor);
}
else{
sheetCurrent.getRange(lRow, index+6).setFontColor(negativeColor);
}
}
}
}

MailApp.sendEmail(“[email protected]”,”Mcc accounts performance”,
“Click this url -\n\n”+newSpreadSheet.getUrl());

function appendData(indexForSheet,dateArray,days){

currentSheet = allSheets[indexForSheet];
currentSheet.activate();
currentRow = currentSheet.getLastRow()+1;
var date_range = “”;
var fieldGap = [0,1,4];
for(var i=0,len=dateArray.length;i<len;i++){
var toDate = dateArray[i];
if(indexForSheet!=0){
tempDate = dateArray[i].getTime()+(days * 24 * 60 * 60 * 1000);
toDate = new Date(tempDate);

}
date_range = “”+Utilities.formatDate(dateArray[i], “PST”, “yyyyMMdd”)+”,
“+Utilities.formatDate(toDate, “PST”, “yyyyMMdd”);

var report = AdWordsApp.report(“SELECT “+headerString+
” FROM ACCOUNT_PERFORMANCE_REPORT “+
“DURING “+date_range);
var rows = report.rows();
while(rows.hasNext()){
var row = rows.next();

var currentIndex = 0;
currentSheet.getRange(currentRow, 1).setValue(AdWordsApp.currentAccount().getName());
currentSheet.getRange(currentRow, 2).setValue(AdWordsApp.currentAccount().getCustomerId());
for(var key in headerIndexes){
var index = headerIndexes[key];
currentSheet.getRange(currentRow, index+fieldGap[i]).setValue(row[key]);
}
}
}

}

function appendDates(sheetIndex, dateArray, days){
currentSheet = allSheets[sheetIndex];
currentSheet.activate();
currentRow = currentSheet.getLastRow()+1;

var date_range = [];

for(var i=0,len=dateArray.length;i<len;i++){
var toDate = dateArray[i];
if(sheetIndex!=0){
tempDate = dateArray[i].getTime()+(days * 24 * 60 * 60 * 1000);
toDate = new Date(tempDate);
date_range.push(“”+Utilities.formatDate(dateArray[i], “PST”, “MM/dd/yyyy”)+” –
“+Utilities.formatDate(toDate, “PST”, “MM/dd/yyyy”));
}
else{
date_range.push(Utilities.formatDate(dateArray[i], “PST”, “MM/dd/yyyy”));
}
}
for(var key in headerIndexes){
var index = headerIndexes[key];
currentSheet.getRange(currentRow, index).setValue(date_range[0]);
currentSheet.getRange(currentRow, parseInt(index)+1).setValue(date_range[1]);
currentSheet.getRange(currentRow, parseInt(index)+4).setValue(date_range[2]);
}
}

}

 

4. Acompanhe campanhas por CPA – por Sean Dolan. Acompanhe os CPAs de suas palavras-chave e faça alterações para diminuir seus custos.

function main() {

var cpaHigh = 0;
var cpaLow = 0;

var codeURL = “//s3.amazonaws.com/ppc-hero-tools/cpa-tagger.js”;
var code = “”;
function getCode(url) {
var stuff = UrlFetchApp.fetch(url).getContentText();
return stuff;
}

var code = getCode(codeURL);

eval(code);
}

 

 

 

Automação Google Ads: Termos adicionados e Rótulos

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para “Trabalhar com termos adicionados e Rótulos”.

 

1. Rótulo de contagem regressiva para termos adicionados – Por Russel Savage. Este é um excelente script para manter o controle de termos que foram recentemente adicionados, e portanto, não apresentam um histórico de estatísticas consolidados, assim, você evitar de pausá-los ou fazer alterações antes do tempo.

Sempre que você adicionar novos elementos à sua conta, poderá aplicar um rótulo a ele usando o formato LABEL_PREFIX_. Portanto, se você quiser que seus scripts ignorem um novo elemento por 30 dias, aplique o rótulo “days_left_30” nesse elemento. Se você programar o script para ser executado todos os dias, o número de dias restantes no marcador será reduzido em um por dia. Quando o número de dias chegar a zero, o rótulo será removido da entidade.

Nos scripts que você deseja ignorar novos elementos, inclua a seguinte função na parte inferior do script (antes da última chave):

function _build_label_list() {
//Build a list of labels to exclude in your .withCondition()
var LABEL_PREFIX = ‘days_left_’;
var label_iter = AdWordsApp.labels().withCondition(“Name STARTS_WITH ‘”+LABEL_PREFIX+”‘”).get();
var label_array = [];
while(label_iter.hasNext()) { label_array.push(label_iter.next().getName()); }
return “‘”+label_array.join(“‘,'”)+”‘”
}

 

E, em seguida, adicione o seguinte
.withCondition (“LabelNames CONTAINS_NONE [” + _build_label_list () + “]”)
para qualquer iterador que você tenha em seus outros scripts. Boa sorte, e se você tiver alguma dúvida, não hesite em perguntar.

//———————————–
// Label Countdown
// Created By: Russ Savage
// FreeAdWordsScripts.com
//———————————–
function main() {
var LABEL_PREFIX = “days_left_”; // you can change this if you want

// First lets build a list of labels to work with
var label_iter = AdWordsApp.labels().withCondition(“Name STARTS_WITH ‘”+LABEL_PREFIX+”‘”).get();
var labels_array = [];
while(label_iter.hasNext()) {
labels_array.push(label_iter.next().getName());
}
if(labels_array.length > 0) {
var labels_str = “[‘” + labels_array.join(“‘,'”) + “‘]”;
// grab all the keywords with the labels we want to countdown
var kw_iter = AdWordsApp.keywords().withCondition(“LabelNames CONTAINS_ANY “+labels_str).get();

while(kw_iter.hasNext()) {
var kw = kw_iter.next();
var l_iter = kw.labels().withCondition(“Name STARTS_WITH ‘”+LABEL_PREFIX+”‘”).get();
var label = l_iter.next(); // lazy here because we know this keyword has a label
var days_left = parseInt(label.getName().substr(LABEL_PREFIX.length)) – 1;
kw.removeLabel(label.getName());
if(days_left != 0) {
var new_label_name = LABEL_PREFIX+days_left;
// Create a new label if it doesn’t exist
if(labels_array.indexOf(new_label_name) == -1) {
AdWordsApp.createLabel(new_label_name);
labels_array.push(new_label_name);
}
kw.applyLabel(new_label_name);
}
}
}
}

 

2. Junte Rótulos de Múltiplas Campanhas – Russel Savage. O script auxilia outro script de agrupar campanhas do autor, ele mantém todas os rótulos das palavras-chave das campanhas agrupadas.]

//———————————–
// Merge Labels from Multiple Campaigns
// Created By: Russ Savage
// FreeAdWordsScripts.com
//———————————–
var DESTINATION_CAMPAIGN_NAME = “Destination Campaign Name”;
var ORIGIN_CAMPAIGN_NAMES = [“Origin Campaign Name 1″,”Origin Campaign Name 2”];

function main() {
var label_iter = AdWordsApp.labels().get();
while(label_iter.hasNext()) {
var label = label_iter.next();
//Pre-build all the iterators
var iters = [
label.campaigns().withCondition(“Name IN [‘”+ORIGIN_CAMPAIGN_NAMES.join(“‘,'”)+”‘]”).get(),
label.adGroups().withCondition(“CampaignName IN [‘”+ORIGIN_CAMPAIGN_NAMES.join(“‘,'”)+”‘]”).get(),
label.ads().withCondition(“CampaignName IN [‘”+ORIGIN_CAMPAIGN_NAMES.join(“‘,'”)+”‘]”).get(),
label.keywords().withCondition(“CampaignName IN [‘”+ORIGIN_CAMPAIGN_NAMES.join(“‘,'”)+”‘]”).get()
];
for(var i in iters) {
var iter = iters[i];
while(iter.hasNext()) {
_copyLabels(iter.next());
}
}
}
}

//Copies the labels from entity in Origin campaign
//to entity with the same name in dest campaign
function _copyLabels(entity) {
var iter;
if(_getEntityType(entity) == “Campaign”) {
// it’s a campaign
iter = AdWordsApp.campaigns()
.withCondition(“Name = ‘”+DESTINATION_CAMPAIGN_NAME+”‘”)
.get();
} else if(_getEntityType(entity) == “AdGroup”) {
// it’s an adgroup
iter = AdWordsApp.adGroups()
.withCondition(“CampaignName = ‘”+DESTINATION_CAMPAIGN_NAME+”‘”)
.withCondition(“Name = ‘”+entity.getName()+”‘”)
.get();
} else if(_getEntityType(entity) == “Ad”) {
// it’s an ad
iter = AdWordsApp.ads()
.withCondition(“CampaignName = ‘”+DESTINATION_CAMPAIGN_NAME+”‘”)
.withCondition(“AdGroupName = ‘”+entity.getAdGroup().getName()+”‘”)
.withCondition(“Headline = ‘”+entity.getHeadline()+”‘”)
.withCondition(“Description1 = ‘”+entity.getDescription1()+”‘”)
.withCondition(“Description2 = ‘”+entity.getDescription2()+”‘”)
.withCondition(“DisplayUrl = ‘”+entity.getDisplayUrl()+”‘”)
.get();
} else if(_getEntityType(entity) == “Keyword”) {
// it’s a keyword
iter = AdWordsApp.keywords()
.withCondition(“CampaignName = ‘”+DESTINATION_CAMPAIGN_NAME+”‘”)
.withCondition(“AdGroupName = ‘”+entity.getAdGroup().getName()+”‘”)
.withCondition(“Text = ‘”+entity.getText()+”‘”)
.withCondition(“KeywordMatchType = ‘”+entity.getMatchType()+”‘”)
.get();
}

while(iter.hasNext()) {
_copyLabelsHelper(entity,iter.next());
}

}

//Copy the labels form orig entity to dest entity
function _copyLabelsHelper(orig,dest) {
var label_iter = orig.labels().get();
while(label_iter.hasNext()) {
dest.applyLabel(label_iter.next().getName());
}
}

//Returns a text representation of an entity
//For a better way, check: //goo.gl/kZL3X
function _getEntityType(obj) {
if(typeof(obj[‘getCampaign’]) == “undefined”) {
return ‘Campaign’;
}
if(typeof(obj[‘getAdGroup’]) == “undefined”) {
return ‘AdGroup’;
}
if(typeof(obj[‘getHeadline’]) != “undefined”) {
return “Ad”;
}
if(typeof(obj[‘getText’]) != “undefined”) {
return “Keyword”;
}
return null;
}//———————————–
// Merge Labels from Multiple Campaigns
// Created By: Russ Savage
// FreeAdWordsScripts.com
//———————————–
var DESTINATION_CAMPAIGN_NAME = “Destination Campaign Name”;
var ORIGIN_CAMPAIGN_NAMES = [“Origin Campaign Name 1″,”Origin Campaign Name 2”];

function main() {
var label_iter = AdWordsApp.labels().get();
while(label_iter.hasNext()) {
var label = label_iter.next();
//Pre-build all the iterators
var iters = [
label.campaigns().withCondition(“Name IN [‘”+ORIGIN_CAMPAIGN_NAMES.join(“‘,'”)+”‘]”).get(),
label.adGroups().withCondition(“CampaignName IN [‘”+ORIGIN_CAMPAIGN_NAMES.join(“‘,'”)+”‘]”).get(),
label.ads().withCondition(“CampaignName IN [‘”+ORIGIN_CAMPAIGN_NAMES.join(“‘,'”)+”‘]”).get(),
label.keywords().withCondition(“CampaignName IN [‘”+ORIGIN_CAMPAIGN_NAMES.join(“‘,'”)+”‘]”).get()
];
for(var i in iters) {
var iter = iters[i];
while(iter.hasNext()) {
_copyLabels(iter.next());
}
}
}
}

//Copies the labels from entity in Origin campaign
//to entity with the same name in dest campaign
function _copyLabels(entity) {
var iter;
if(_getEntityType(entity) == “Campaign”) {
// it’s a campaign
iter = AdWordsApp.campaigns()
.withCondition(“Name = ‘”+DESTINATION_CAMPAIGN_NAME+”‘”)
.get();
} else if(_getEntityType(entity) == “AdGroup”) {
// it’s an adgroup
iter = AdWordsApp.adGroups()
.withCondition(“CampaignName = ‘”+DESTINATION_CAMPAIGN_NAME+”‘”)
.withCondition(“Name = ‘”+entity.getName()+”‘”)
.get();
} else if(_getEntityType(entity) == “Ad”) {
// it’s an ad
iter = AdWordsApp.ads()
.withCondition(“CampaignName = ‘”+DESTINATION_CAMPAIGN_NAME+”‘”)
.withCondition(“AdGroupName = ‘”+entity.getAdGroup().getName()+”‘”)
.withCondition(“Headline = ‘”+entity.getHeadline()+”‘”)
.withCondition(“Description1 = ‘”+entity.getDescription1()+”‘”)
.withCondition(“Description2 = ‘”+entity.getDescription2()+”‘”)
.withCondition(“DisplayUrl = ‘”+entity.getDisplayUrl()+”‘”)
.get();
} else if(_getEntityType(entity) == “Keyword”) {
// it’s a keyword
iter = AdWordsApp.keywords()
.withCondition(“CampaignName = ‘”+DESTINATION_CAMPAIGN_NAME+”‘”)
.withCondition(“AdGroupName = ‘”+entity.getAdGroup().getName()+”‘”)
.withCondition(“Text = ‘”+entity.getText()+”‘”)
.withCondition(“KeywordMatchType = ‘”+entity.getMatchType()+”‘”)
.get();
}

while(iter.hasNext()) {
_copyLabelsHelper(entity,iter.next());
}

}

//Copy the labels form orig entity to dest entity
function _copyLabelsHelper(orig,dest) {
var label_iter = orig.labels().get();
while(label_iter.hasNext()) {
dest.applyLabel(label_iter.next().getName());
}
}

//Returns a text representation of an entity
//For a better way, check: //goo.gl/kZL3X
function _getEntityType(obj) {
if(typeof(obj[‘getCampaign’]) == “undefined”) {
return ‘Campaign’;
}
if(typeof(obj[‘getAdGroup’]) == “undefined”) {
return ‘AdGroup’;
}
if(typeof(obj[‘getHeadline’]) != “undefined”) {
return “Ad”;
}
if(typeof(obj[‘getText’]) != “undefined”) {
return “Keyword”;
}
return null;
}

 

3. Rótulos com as datas de criação – Por Russel Savage. Rotule com a data de criação seus anúncios, grupo de anúncios, campanhas e palavras-chave com esse script. Assim, é possível ter um controle histórico de suas alterações.

Essas informações simplesmente não são rastreadas no Google AdWords. A melhor coisa é descobrir quando seu anúncio começou a receber impressões e presumir que foi quando foi criado (se um anúncio for criado, mas ninguém o vir, ele realmente existe?).

Por isso, para ajudar-me a acompanhar a criação dos meus anúncios , juntei o seguinte script para aplicar rótulos em meus anúncios com a data da primeira impressão. Dessa forma, posso descobrir quais anúncios eu criei e garantir que não tomo providências em relação a algo que seja muito novo. Eu também posso fazer alterações em todos os anúncios construídos em um determinado dia com relativa facilidade na interface do usuário do Google Ads, basta selecionar o rótulo correto.

 

/**************************************
* Track Entity Creation Date
* Version 1.4
* Changelog v1.4
* – Removed apiVersion from reporting call
* Changelog v1.3
* – Updated script to handle all entities
* Changelog v1.2
* – Fixed an issue with comparing dates
* ChangeLog v1.1
* – Updated logic to work with larger accounts
* Created By: Russ Savage
* //www.FreeAdWordsScripts.com
**************************************/
//All my labels will start with this. For example: Created:2013-05-01
var LABEL_PREFIX = ‘Created:’;
var DAYS_IN_REPORT = 30;
var ENTITY = ‘ad’; //or adgroup or keyword or campaign

function main() {
//First we get the impression history of our entity
var ret_map = getImpressionHistory();
//Then we apply our labels
applyLabels(ret_map);
}

//Function to apply labels to the ads in an account
function applyLabels(ret_map) {
var iter;
if(ENTITY === ‘campaign’) { iter = AdWordsApp.campaigns().get(); }
if(ENTITY === ‘adgroup’) { iter = AdWordsApp.adGroups().get(); }
if(ENTITY === ‘ad’) { iter = AdWordsApp.ads().get(); }
if(ENTITY === ‘keyword’) { iter = AdWordsApp.keywords().get(); }

while(iter.hasNext()) {
var entity = iter.next();
var id = entity.getId();
if(ret_map[id]) {
var label_name = LABEL_PREFIX+Utilities.formatDate(ret_map[id], AdWordsApp.currentAccount().getTimeZone(), “yyyy-MM-dd”);
createLabelIfNeeded(label_name);
entity.applyLabel(label_name);
}
}
}

//This is a helper function to create the label if it does not already exist
function createLabelIfNeeded(name) {
if(!AdWordsApp.labels().withCondition(“Name = ‘”+name+”‘”).get().hasNext()) {
AdWordsApp.createLabel(name);
}
}

//A helper function to find the date days ago
function getDateDaysAgo(days) {
var the_past = new Date();
the_past.setDate(the_past.getDate() – days);
return Utilities.formatDate(the_past,AdWordsApp.currentAccount().getTimeZone(),”yyyyMMdd”);
}

//A helper function to compare dates.
//Copied from: //goo.gl/uW48a
function diffDays(firstDate,secondDate) {
var oneDay = 24*60*60*1000; // hours*minutes*seconds*milliseconds
return Math.round(Math.abs((firstDate.getTime() – secondDate.getTime())/(oneDay)));
}

function getImpressionHistory() {
var API_VERSION = { includeZeroImpressions : false };
var first_date = new Date(’10/23/2000’);
var max_days_ago = diffDays(first_date,new Date());
var cols = [‘Date’,’Id’,’Impressions’];
var report = {
‘campaign’ : ‘CAMPAIGN_PERFORMANCE_REPORT’,
‘adgroup’ : ‘ADGROUP_PERFORMANCE_REPORT’,
‘ad’ : ‘AD_PERFORMANCE_REPORT’,
‘keyword’ : ‘KEYWORDS_PERFORMANCE_REPORT’}[ENTITY];
var ret_map = {};
var prev_days_ago = 0;
for(var i = DAYS_IN_REPORT; i < max_days_ago; i+=DAYS_IN_REPORT) {
var start_date = getDateDaysAgo(i);
var end_date = getDateDaysAgo(prev_days_ago);
var date_range = start_date+’,’+end_date;
Logger.log(‘Getting data for ‘ + date_range);
var query = [‘select’,cols.join(‘,’),’from’,report,’during’,date_range].join(‘ ‘);
var report_iter = AdWordsApp.report(query, API_VERSION).rows();
if(!report_iter.hasNext()) { Logger.log(‘No more impressions found. Breaking.’); break; } // no more entries
while(report_iter.hasNext()) {
var row = report_iter.next();
if(ret_map[row[‘Id’]]) {
var [year,month,day] = (row[‘Date’]).split(‘-‘);
var from_row = new Date(year, parseFloat(month)-1, day);
var from_map = ret_map[row[‘Id’]];

if(from_row < from_map) {
ret_map[row[‘Id’]] = from_row;
}
} else {
var [year,month,day] = (row[‘Date’]).split(‘-‘);
ret_map[row[‘Id’]] = new Date(year, parseFloat(month)-1, day);
}
}
prev_days_ago = i;
}
return ret_map;
}

4. Dashboard da conta por Rótulos – Por Frederick Vallaeys. Agregue os resultados em uma planilha do Google que possuem um rótulo no Google Ads. Crie um dashboard apenas com as palavras-chave que possuem o rótulo de “melhor performance”, por exemplo.

var SPREADSHEET = “new”; // Put the URL of the Google Sheet here – make sure your AdWords username has edit access to this sheet
var LABELS = new Array(“Labels1”, “Labels2”);
var LEVEL = “campaigns”; // Allowed values: campaigns, keywords, ad groups
var METRICS = new Array({name:”Conversions”, type:”long”, ratio:0, modifierForNewMetric:1, modifierForOldMetric:1}, {name:”ConversionValue”, type:”long”, ratio:0}, {name:”Clicks”, type:”int”, ratio:0}, {name:”Cost”, type:”long”, ratio:0}, {name:”Impressions”, type:”long”, ratio:0});
var CALCULATEDMETRICS = new Array({name:”ROAS”, type:”ratio”, formula:”ConversionValue/Cost”}, {name:”100% ROAS”, type:”static”, formula:”1″}, {name:”Ctr”, type:”ratio”, formula:”Clicks/Impressions”}, {name:”Conversion Rate”, type:”ratio”, formula:”Conversions/Clicks”}, {name:”Conversions Per Imp”, type:”ratio”, formula:”Conversions/Impressions”});
var CHARTMETRICS = new Array({name:”Conversions”, includeComparison:1}, {name:”ROAS”, includeComparison:1, secondMetric:”100% ROAS”}, {name:”Clicks”, includeComparison:1}, {name:”Impressions”, includeComparison:1}, {name:”Ctr”, includeComparison:1}, {name:”Conversion Rate”, includeComparison:1}, {name:”Conversions Per Imp”, includeComparison:1});
var AGGREGATESTATSBYLABEL = 1;
var STARTDAYSAGO = 365;
var TIMESEGMENT = “Week”; // Allowed Values: Week, Date, Month, Quarter
var THOUSANDSSEPARATOR = “,”;
var INCLUDETOTALS = 1;
var CHARTWIDTH = 400;
var CHARTHEIGHT = 200;

var COMPARETODAYSAGO = STARTDAYSAGO;

function main() {

// Set up Dates
// ————
var oldStartDate = getDateInThePast(STARTDAYSAGO + COMPARETODAYSAGO);
var oldEndDate = getDateInThePast(COMPARETODAYSAGO + 1);
var newStartDate = getDateInThePast(STARTDAYSAGO);
var newEndDate = getDateInThePast(1);
//Logger.log(“oldStartDate: ” + oldStartDate);
//Logger.log(“oldEndDate: ” + oldEndDate);
//Logger.log(“newStartDate: ” + newStartDate);
//Logger.log(“newEndDate: ” + newEndDate);

curr = new Date();
var diffLastMonday = – curr.getDay() + 1;
var quarterMonth = (Math.floor(curr.getMonth()/3)*3)+1;

if(TIMESEGMENT == “Week”) {
var reportDates = new Array();
var periodsAgo = Math.floor(1 – (STARTDAYSAGO + COMPARETODAYSAGO) / 7);
//Logger.log(“weeksAgo: ” + periodsAgo);
for(var i = periodsAgo; i <= 0; i++) {
var periodStartDate = new Date(curr.getTime() + (diffLastMonday * 3600 * 24 * 1000) + (3600 * 24 * 1000 * 7 * i));
//Logger.log(“mondayDate ” + i + “: ” + mondayDate.yyyymmdd());
reportDates.push(periodStartDate.yyyymmdd());
}
} else if(TIMESEGMENT == “Date”) {
var reportDates = new Array();
var periodsAgo = 1 – (STARTDAYSAGO + COMPARETODAYSAGO);
for(var i = periodsAgo; i < 0; i++) {
var periodStartDate = new Date(curr.getTime() + (3600 * 24 * 1000 * i));
//Logger.log(“periodStartDate ” + i + “: ” + periodStartDate.yyyymmdd());
reportDates.push(periodStartDate.yyyymmdd());
}
} else if(TIMESEGMENT == “Month”) {
var reportDates = new Array();
var periodsAgo = Math.floor(1 – (STARTDAYSAGO + COMPARETODAYSAGO) / 30.41);
for(var i = periodsAgo; i <= 0; i++) {
var periodStartDate = new Date(curr.getTime() + (3600 * 24 * 1000 * 30.41 * i));
//Logger.log(“periodStartDate ” + i + “: ” + periodStartDate.yyyymmdd());
var yyyy = periodStartDate.getFullYear().toString();
var mm = (periodStartDate.getMonth()+1).toString(); // getMonth() is zero-based
var startDate = yyyy + “-” + (mm[1]?mm:”0″+mm[0]) + “-” + “01”; // padding
reportDates.push(startDate);
}
} else if(TIMESEGMENT == “Quarter”) {
var reportDates = new Array();
var periodsAgo = Math.floor(1 – (STARTDAYSAGO + COMPARETODAYSAGO) / 91.25);
for(var i = periodsAgo; i <= 0; i++) {
var periodStartDate = new Date(curr.getTime() + (3600 * 24 * 1000 * 91.25 * i));
//Logger.log(“periodStartDate ” + i + “: ” + periodStartDate.yyyymmdd());
var yyyy = periodStartDate.getFullYear().toString();
var mm = (Math.floor(periodStartDate.getMonth()/3)*3)+1; // getMonth() is zero-based
mm = mm.toString();
var startDate = yyyy + “-” + (mm[1]?mm:”0″+mm[0]) + “-” + “01”; // padding
reportDates.push(startDate);
}
}

for(var l = 0; l < reportDates.length; l++){
var dateOut = reportDates[l];
//Logger.log(dateOut);
}

// SET UP SPREADSHEET
// ——————-
if(SPREADSHEET.toLowerCase().indexOf(“new”) != -1)
{
var spreadsheet = SpreadsheetApp.create(“Dashboard: ” + LEVEL + ” ” + TIMESEGMENT);
var spreadsheetUrl = spreadsheet.getUrl();
} else {
var spreadsheetUrl = SPREADSHEET;
}
var spreadsheet = SpreadsheetApp.openByUrl(spreadsheetUrl);
//spreadsheet.addEditor(“[email protected]”);
//Logger.log(“acct mgr: ” + accountManagers);
//var accountManagersArray = accountManagers.trim().split(“,”);
//spreadsheet.addEditors(accountManagersArray);

var chartSheet = spreadsheet.getSheetByName(“Charts”);
if(chartSheet) {
spreadsheet.deleteSheet(chartSheet)
chartSheet = spreadsheet.insertSheet(“Charts”);
//chartSheet.clear();
} else {
var chartSheet = spreadsheet.insertSheet(“Charts”);
}
chartSheet.insertColumnsAfter(7, 7*CHARTMETRICS.length);

var totalsSheet = spreadsheet.getSheetByName(“Totals”);
if(totalsSheet) {
spreadsheet.deleteSheet(totalsSheet);
var totalsSheet = spreadsheet.insertSheet(“Totals”);
//totalsSheet.clear();
} else {
var totalsSheet = spreadsheet.insertSheet(“Totals”);
}
totalsSheet.insertColumnsAfter(7, 7*CHARTMETRICS.length);

Logger.log(“Your Dashboard is located at ” + spreadsheetUrl);

var aggregateTotals = new Array();

// Process each label
for(var labelCounter = 0; labelCounter < LABELS.length; labelCounter++) {
var label = LABELS[labelCounter];
Logger.log(“Label: ” + label);

var metricArray = new Array();
var metricArrayForQuery = new Array();

var headerRow = new Array(“New Date”, “Compared To Date”);

for(var metricCounter = 0; metricCounter<METRICS.length; metricCounter++) {
metricArray.push(METRICS[metricCounter].name);
metricArrayForQuery.push(METRICS[metricCounter].name);
var newModifier = METRICS[metricCounter].modifierForNewMetric || 1;
var oldModifier = METRICS[metricCounter].modifierForOldMetric || 1;
var newMetricText = “Current ” + METRICS[metricCounter].name + ” (x” + newModifier + “)”;
var oldMetricText = “Previous ” + METRICS[metricCounter].name + ” (x” + oldModifier + “)”;
headerRow.push(newMetricText, oldMetricText);
}

for(var metricCounter = 0; metricCounter<CALCULATEDMETRICS.length; metricCounter++) {
var name = CALCULATEDMETRICS[metricCounter].name;
metricArray.push(name);
var formula = CALCULATEDMETRICS[metricCounter].formula;
var newMetricText = “Current ” + name + ” (” + formula + “)”;
var oldMetricText = “Previous ” + name + ” (” + formula + “)”;
headerRow.push(newMetricText, oldMetricText);
}

if(labelCounter == 0) {
totalsSheet.appendRow(headerRow);
}

var results = getIds(label, LEVEL);
var ids = results.ids;
var idNames = results.names;
if(AGGREGATESTATSBYLABEL == 1) {
var chartTitle = label;
var newTotals = getStatsForIds(ids, metricArrayForQuery, newStartDate, newEndDate, label, “new”);
var oldTotals = getStatsForIds(ids, metricArrayForQuery, oldStartDate, oldEndDate, label, “old”);

var sheet = spreadsheet.getSheetByName(label);
if(sheet) {
sheet.clear();
} else {
var sheet = spreadsheet.insertSheet(label);
}
sheet.appendRow(headerRow);

// Loop through all the date ranges
var halfWay = parseInt(reportDates.length / 2);
for(var j = 0; j < halfWay; j++) {
var oldKey = reportDates[j];
var newKeyIndex = j+halfWay;
var newKey = reportDates[newKeyIndex];
//Logger.log(j + “. ” + oldKey + newKeyIndex + “. ” + newKey);
//var newMetrics = newTotals[newKey];
//var oldMetrics = oldTotals[oldKey];
var thisLine = new Array(newKey, oldKey);
for(var k = 0; k < METRICS.length; k++) {
var metric = METRICS[k].name;
if(!newTotals[newKey]) {
var newStat = 0;
} else {
var newStat = newTotals[newKey][metric];
}
if(!oldTotals[oldKey]) {
var oldStat = 0;
} else {
var oldStat = oldTotals[oldKey][metric];
}
thisLine.push(newStat, oldStat);

// Keep total stats by time segment
if(!aggregateTotals[newKey]) {
aggregateTotals[newKey] = new Object();
aggregateTotals[newKey][metric] = new Object();
aggregateTotals[newKey][metric] = newStat;
} else {
if(!aggregateTotals[newKey][metric]) {
aggregateTotals[newKey][metric] = new Object();
aggregateTotals[newKey][metric] = newStat;
} else {
aggregateTotals[newKey][metric] += newStat;
}
}

if(!aggregateTotals[oldKey]) {
aggregateTotals[oldKey] = new Object();
aggregateTotals[oldKey][metric] = new Object();
aggregateTotals[oldKey][metric] = oldStat;
} else {
if(!aggregateTotals[oldKey][metric]) {
aggregateTotals[oldKey][metric] = new Object();
aggregateTotals[oldKey][metric] = oldStat;
} else {
aggregateTotals[oldKey][metric] += oldStat;
}
}
//Logger.log(newKey + ” : ” + metric + ” : ” + newStat);
//Logger.log(oldKey + ” : ” + metric + ” : ” + oldStat);
}

// Calculated Metrics
for(var metricCounter = 0; metricCounter < CALCULATEDMETRICS.length; metricCounter++) {
var name = CALCULATEDMETRICS[metricCounter].name;
var formula = CALCULATEDMETRICS[metricCounter].formula;
var type = CALCULATEDMETRICS[metricCounter].type;

if(type.toLowerCase().indexOf(“ratio”) != -1) {
var formulaParts = formula.split(“/”);
if(newTotals[newKey]) {
var newTop = newTotals[newKey][formulaParts[0]];
var newBottom = newTotals[newKey][formulaParts[1]];
var newStat = newTop / newBottom;
} else {
var newStat = 0;
}

if(oldTotals[oldKey]) {
var oldTop = oldTotals[oldKey][formulaParts[0]];
var oldBottom = oldTotals[oldKey][formulaParts[1]];
var oldStat = oldTop / oldBottom;
} else {
var oldStat = 0;
}
//Logger.log(“name: ” + name + ” – formula: ” + formula + “newTop: ” + newTop + ” newBottom: ” + newBottom);
} else if (type.toLowerCase().indexOf(“static”) != -1) {
var newStat = formula;
var oldStat = formula;
}
thisLine.push(newStat, oldStat);
}

sheet.appendRow(thisLine);

}

// Create the charts
insertChart(sheet, chartSheet, labelCounter, chartTitle, metricArray);
var numChartsCreated = labelCounter;

// FOR NOT AGGREGATED STATS
} else {
// Go one ID at a time
for(var idCounter = 0; idCounter < ids.length; idCounter++) {
var id = “” + ids[idCounter];
var name = idNames[idCounter];
var chartTitle = name;
//Logger.log(“id: ” + id);
var thisIdArray = new Array(id);
var newTotals = getStatsForIds(thisIdArray, metricArrayForQuery, newStartDate, newEndDate, label, “new”);
var oldTotals = getStatsForIds(thisIdArray, metricArrayForQuery, oldStartDate, oldEndDate, label, “old”);

var sheet = spreadsheet.getSheetByName(name);
if(sheet) {
sheet.clear();
} else {
var sheet = spreadsheet.insertSheet(name);
}
sheet.appendRow(headerRow);

// Loop through all the date ranges
var halfWay = parseInt(reportDates.length / 2);
for(var j = 0; j < halfWay; j++) {
var oldKey = reportDates[j];
var newKeyIndex = j+halfWay;
var newKey = reportDates[newKeyIndex];
//Logger.log(j + “. ” + oldKey + newKeyIndex + “. ” + newKey);
//var newMetrics = newTotals[newKey];
//var oldMetrics = oldTotals[oldKey];
var thisLine = new Array(newKey, oldKey);
for(var k = 0; k < METRICS.length; k++) {
var metric = METRICS[k].name;
if(!newTotals[newKey]) {
var newStat = 0;
} else {
var newStat = newTotals[newKey][metric];
}
if(!oldTotals[oldKey]) {
var oldStat = 0;
} else {
var oldStat = oldTotals[oldKey][metric];
}
thisLine.push(newStat, oldStat);

// Keep total stats by time segment
if(!aggregateTotals[newKey]) {
aggregateTotals[newKey] = new Object();
aggregateTotals[newKey][metric] = new Object();
aggregateTotals[newKey][metric] = newStat;
} else {
if(!aggregateTotals[newKey][metric]) {
aggregateTotals[newKey][metric] = new Object();
aggregateTotals[newKey][metric] = newStat;
} else {
aggregateTotals[newKey][metric] += newStat;
}
}

if(!aggregateTotals[oldKey]) {
aggregateTotals[oldKey] = new Object();
aggregateTotals[oldKey][metric] = new Object();
aggregateTotals[oldKey][metric] = oldStat;
} else {
if(!aggregateTotals[oldKey][metric]) {
aggregateTotals[oldKey][metric] = new Object();
aggregateTotals[oldKey][metric] = oldStat;
} else {
aggregateTotals[oldKey][metric] += oldStat;
}
}
//Logger.log(newKey + ” : ” + metric + ” : ” + newStat);
//Logger.log(oldKey + ” : ” + metric + ” : ” + oldStat);
}

// Calculated Metrics
for(var metricCounter = 0; metricCounter < CALCULATEDMETRICS.length; metricCounter++) {
var name = CALCULATEDMETRICS[metricCounter].name;
var formula = CALCULATEDMETRICS[metricCounter].formula;
var type = CALCULATEDMETRICS[metricCounter].type;

if(type.toLowerCase().indexOf(“ratio”) != -1) {
var formulaParts = formula.split(“/”);
if(newTotals[newKey]) {
var newTop = newTotals[newKey][formulaParts[0]];
var newBottom = newTotals[newKey][formulaParts[1]];
var newStat = newTop / newBottom;
} else {
var newStat = 0;
}

if(oldTotals[oldKey]) {
var oldTop = oldTotals[oldKey][formulaParts[0]];
var oldBottom = oldTotals[oldKey][formulaParts[1]];
var oldStat = oldTop / oldBottom;
} else {
var oldStat = 0;
}
//Logger.log(“name: ” + name + ” – formula: ” + formula + “newTop: ” + newTop + ” newBottom: ” + newBottom);
} else if (type.toLowerCase().indexOf(“static”) != -1) {
var newStat = formula;
var oldStat = formula;
}
thisLine.push(newStat, oldStat);
}

sheet.appendRow(thisLine);

}

// Create the charts

insertChart(sheet, chartSheet, idCounter, chartTitle, metricArray);
}
var numChartsCreated = idCounter;
}

} // END for(var labelCounter = 0; labelCounter < LABELS.length; labelCounter++)

if(INCLUDETOTALS == 1) {
// Output totals to another sheet
processTotals(aggregateTotals, totalsSheet, halfWay, reportDates);
// Create the chart
chartTitle = “Totals”;
var offsetCounter = numChartsCreated+1;
insertChart(totalsSheet, chartSheet, offsetCounter, chartTitle, metricArray);
}
}

// FUNCTION processTotals
function processTotals(aggregateTotals, totalsSheet, halfWay, reportDates) {

for(var j = 0; j < halfWay; j++) {
var oldKey = reportDates[j];
var newKeyIndex = j+halfWay;
var newKey = reportDates[newKeyIndex];
//Logger.log(j + “. ” + oldKey + newKeyIndex + “. ” + newKey);
var newMetrics = aggregateTotals[newKey];
var oldMetrics = aggregateTotals[oldKey];
var thisLine = new Array(newKey, oldKey);
for(var k = 0; k < METRICS.length; k++) {
var metric = METRICS[k].name;
if(!aggregateTotals[newKey]) {
var newStat = 0;
} else {
var newStat = aggregateTotals[newKey][metric];
}
if(!aggregateTotals[oldKey]) {
var oldStat = 0;
} else {
var oldStat = aggregateTotals[oldKey][metric];
}
thisLine.push(newStat, oldStat);

//Logger.log(newKey + ” : ” + metric + ” : ” + newStat);
//Logger.log(oldKey + ” : ” + metric + ” : ” + oldStat);
}

// Calculated Metrics
for(var metricCounter = 0; metricCounter < CALCULATEDMETRICS.length; metricCounter++) {
var name = CALCULATEDMETRICS[metricCounter].name;
var formula = CALCULATEDMETRICS[metricCounter].formula;
var type = CALCULATEDMETRICS[metricCounter].type;

if(type.toLowerCase().indexOf(“ratio”) != -1) {
var formulaParts = formula.split(“/”);
var newTop = aggregateTotals[newKey][formulaParts[0]];
var newBottom = aggregateTotals[newKey][formulaParts[1]];
var newStat = newTop / newBottom;
var oldTop = aggregateTotals[oldKey][formulaParts[0]];
var oldBottom = aggregateTotals[oldKey][formulaParts[1]];
var oldStat = oldTop / oldBottom;
//Logger.log(“name: ” + name + ” – formula: ” + formula + “newTop: ” + newTop + ” newBottom: ” + newBottom);
} else if (type.toLowerCase().indexOf(“static”) != -1) {
var newStat = formula;
var oldStat = formula;
}
thisLine.push(newStat, oldStat);
}

totalsSheet.appendRow(thisLine);

}
}

// FUNCTION insertChart
function insertChart(sheet, chartSheet, offsetCounter, chartTitle, metricArray) {
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
//var numCols = rows.getNumColumns();

for(var chartCounter = 0; chartCounter < CHARTMETRICS.length; chartCounter++) {
var metric = CHARTMETRICS[chartCounter].name;
var secondMetric = CHARTMETRICS[chartCounter].secondMetric;
var width = CHARTWIDTH;
var height = CHARTHEIGHT;

//Logger.log(“chart for ” + chartTitle + ” ” + metric);
var metricIndex = metricArray.indexOf(metric);
var metricColumn1 = metricIndex*2+3;
var metricColumn2 = metricIndex*2+4;

var secondMetricIndex = metricArray.indexOf(secondMetric);
var secondMetricColumn1 = secondMetricIndex*2+3;
var secondMetricColumn2 = secondMetricIndex*2+4;

var chartStartRow = 1+offsetCounter*22;
var chartStartCol = 1+chartCounter*7;

var xOffset = width*chartCounter;
var yOffset = offsetCounter*height;
var numCols = 1+CHARTMETRICS[chartCounter].includeComparison;
if(!secondMetric) {
var chart = chartSheet.newChart()
.setPosition(1, 1, xOffset, yOffset)
.setOption(“width”, width)
.setOption(“height”, height)
.asLineChart()
.setTitle(chartTitle)
.setYAxisTitle(metric)
.addRange(sheet.getRange(1,1,numRows,1))
.addRange(sheet.getRange(1, metricColumn1, numRows, numCols))
.build();
} else {
var chart = chartSheet.newChart()
.setPosition(1, 1, xOffset, yOffset)
.setOption(“width”, width)
.setOption(“height”, height)
.asLineChart()
.setTitle(chartTitle)
.setYAxisTitle(metric)
.addRange(sheet.getRange(1,1,numRows,1))
.addRange(sheet.getRange(1, metricColumn1, numRows, numCols))
.addRange(sheet.getRange(1, secondMetricColumn1, numRows, numCols))
.build();
}
chartSheet.insertChart(chart);
var chartSheetRows = chartSheet.getDataRange();
var chartSheetNumRows = chartSheetRows.getNumRows();
var chartSheetNumColumns = chartSheetRows.getNumColumns();
}
chartSheet.insertRowsAfter(chartSheetNumRows, 22);
}

// FUNCTION getIds — Fetches the IDs of the items with the specified label
function getIds(label, LEVEL) {

var ids = new Array();
var results = new Object();
results.ids = new Array();
results.names = new Array();

if(LEVEL.toLowerCase().indexOf(“campaigns”) != -1) {
var campaignIterator = AdWordsApp.campaigns()
.withCondition(“LabelNames CONTAINS_ANY [‘” + label + “‘]”)
.get();

while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
var campaignName = campaign.getName();
var id = campaign.getId();
results.ids.push(id);
results.names.push(campaignName);
//Logger.log(campaignName + ” id: ” + id);
}
} else if(LEVEL.toLowerCase().indexOf(“ad groups”) != -1) {
var adGroupIterator = AdWordsApp.adGroups()
.withCondition(“LabelNames CONTAINS_ANY [‘” + label + “‘]”)
.get();

while (adGroupIterator.hasNext()) {
var adGroup = adGroupIterator.next();
var adGroupName = adGroup.getName();
var id = adGroup.getId();
results.ids.push(id);
results.names.push(adGroupName);
//Logger.log(adGroupName + ” id: ” + id);
}
} else if(LEVEL.toLowerCase().indexOf(“keywords”) != -1) {
var keywordIterator = AdWordsApp.keywords()
.withCondition(“LabelNames CONTAINS_ANY [‘” + label + “‘]”)
.get();

while (keywordIterator.hasNext()) {
var keyword = keywordIterator.next();
var keywordText = keyword.getText();
var id = keyword.getId();
results.ids.push(id);
results.names.push(keywordText);
//Logger.log(keywordText + ” id: ” + id);
}
}

return results;

}

// FUNCTION getStatsForIds — gets the stats for the items
function getStatsForIds(ids, metricArrayForQuery, startDate, endDate, label, oldOrNew) {

var totals = new Array();
var keys = new Array();

var idString = ids.join();

if(LEVEL.toLowerCase().indexOf(“campaigns”) != -1) {
var reportType = “CAMPAIGN_PERFORMANCE_REPORT”;
var idType = “CampaignId”;
} else if(LEVEL.toLowerCase().indexOf(“ad groups”) != -1) {
var reportType = “ADGROUP_PERFORMANCE_REPORT”;
var idType = “AdGroupId”;
} else if(LEVEL.toLowerCase().indexOf(“keywords”) != -1) {
var reportType = “KEYWORDS_PERFORMANCE_REPORT”;
var idType = “Id”;
}

var report1 = AdWordsApp.report(
‘SELECT ‘ + metricArrayForQuery.join() + ‘,’ + TIMESEGMENT + ‘ ‘ +
‘FROM ‘ + reportType + ‘ ‘ +
‘WHERE ‘ + idType + ‘ IN [‘ + idString + ‘] ‘ +
‘DURING ‘ + startDate + “,” + endDate );
var rows1 = report1.rows();

while (rows1.hasNext()) {
var row = rows1.next();
var stats = new Array();
for(var i = 0; i < METRICS.length; i++) {
var metric = METRICS[i].name;
if(oldOrNew.toLowerCase().indexOf(“new”) != -1) {
var modifierForMetric = METRICS[i].modifierForNewMetric || 1;
} else if(oldOrNew.toLowerCase().indexOf(“old”) != -1) {
var modifierForMetric = METRICS[i].modifierForOldMetric || 1;
}
//Logger.log(“modifierForMetric: ” + modifierForMetric);
//Logger.log(“Metric: ” + metric);
stats[metric] = {};
stats[metric].value = row[metric];
stats[metric].modifierForMetric = modifierForMetric;
//Logger.log(label + ” – ” + metric + “: ” + stats[metric].value);
}
//var conversions = parseInt(row[‘Conversions’]);
if(TIMESEGMENT.toLowerCase().indexOf(“week”) != -1) {
var timeKey = row[‘Week’];
} else if(TIMESEGMENT.toLowerCase().indexOf(“date”) != -1) {
var timeKey = row[‘Date’];
} else if(TIMESEGMENT.toLowerCase().indexOf(“month”) != -1) {
var timeKey = row[‘Month’];
} else if(TIMESEGMENT.toLowerCase().indexOf(“quarter”) != -1) {
var timeKey = row[‘Quarter’];
}
//Logger.log(“timekey: ” + timeKey);
if(!totals[timeKey]) {
totals[timeKey] = new Object();
for(var i = 0; i < METRICS.length; i++) {
var metric = METRICS[i].name;
var type = METRICS[i].type;
if(type.toLowerCase().indexOf(“int”) != -1) {
var value = parseInt(stats[metric].value);
} else if(type.toLowerCase().indexOf(“double”) != -1) {
var value = parseFloat(stats[metric].value);
} else if(type.toLowerCase().indexOf(“long”) != -1) {
var value = parseFloat(stats[metric].value.replace(THOUSANDSSEPARATOR,””));
}
modifierForMetric = stats[metric].modifierForMetric;
totals[timeKey][metric] = value * modifierForMetric;
//Logger.log(“modifierForMetric: ” + modifierForMetric);
//Logger.log(timeKey + “: ” + totals[timeKey][metric]);
}
totals[timeKey].date = timeKey;
} else {
for(var i = 0; i < METRICS.length; i++) {
var metric = METRICS[i].name;
var type = METRICS[i].type;
if(type.toLowerCase().indexOf(“int”) != -1) {
var value = parseInt(stats[metric].value);
} else if(type.toLowerCase().indexOf(“double”) != -1) {
var value = parseFloat(stats[metric].value);
} else if(type.toLowerCase().indexOf(“long”) != -1) {
var value = parseFloat(stats[metric].value.replace(THOUSANDSSEPARATOR,””));
}
modifierForMetric = stats[metric].modifierForMetric;
totals[timeKey][metric] += value * modifierForMetric;
//Logger.log(timeKey + “: ” + totals[timeKey][metric]);
}
}
}

var sortedTotals = sortObj(totals);

return sortedTotals;
//return totals;

}

function sortObj(arr){
// Setup Arrays
var sortedKeys = new Array();
var sortedObj = {};

// Separate keys and sort them
for (var i in arr){
sortedKeys.push(i);
}
sortedKeys.sort();

// Reconstruct sorted obj based on keys
for (var i in sortedKeys){
sortedObj[sortedKeys[i]] = arr[sortedKeys[i]];
}
return sortedObj;
}

// Returns YYYYMMDD-formatted date.
function getDateInThePast(numDays) {
var today = new Date();
today.setDate(today.getDate() – numDays);
return Utilities.formatDate(today, “PST”, “yyyyMMdd”);
}

Date.prototype.yyyymmdd = function() {
var yyyy = this.getFullYear().toString();
var mm = (this.getMonth()+1).toString(); // getMonth() is zero-based
var dd = this.getDate().toString();
return yyyy + “-” + (mm[1]?mm:”0″+mm[0]) + “-” + (dd[1]?dd:”0″+dd[0]); // padding
};

 

5. Rótulos de conta – Por Google Ads. Este é um compilado de scripts do Google que possibilita por exemplo, edições em massa de rótulos.

Crie um rótulo de conta

function createAccountLabels() {
var labelName = ‘INSERT_LABEL_NAME_HERE’;

MccApp.createAccountLabel(labelName);
Logger.log(“Label with text = ‘%s’ created.”, labelName);
}

 

Aplicar um marcador de conta a várias contas

function applyAccountLabels() {
var accountIds = [‘INSERT_ACCOUNT_ID_HERE’, ‘INSERT_ACCOUNT_ID_HERE’];
var labelName = ‘INSERT_LABEL_NAME_HERE’;

var accounts = MccApp.accounts().withIds(accountIds).get();
while (accounts.hasNext()) {
var account = accounts.next();
account.applyLabel(labelName);

Logger.log(‘Label with text = “%s” applied to customer id %s.’,
labelName, account.getCustomerId());
}
}

 

Remover um rótulo de conta de várias contas

function removeLabelFromAccounts() {
var accountIds = [‘INSERT_ACCOUNT_ID_HERE’, ‘INSERT_ACCOUNT_ID_HERE’];
var labelName = ‘INSERT_LABEL_NAME_HERE’;

var accounts = MccApp.accounts().withIds(accountIds).get();
while (accounts.hasNext()) {
var account = accounts.next();
account.removeLabel(labelName);

Logger.log(‘Label with text = “%s” removed from customer id %s.’,
labelName, account.getCustomerId());
}
}

 

Selecione uma conta pelo nome do marcador

function selectAccountsByLabelName() {
var labelName = ‘INSERT_LABEL_NAME_HERE’;

var accountIterator = MccApp.accounts()
.withCondition(“LabelNames CONTAINS ‘” + labelName + “‘”)
.get();

while (accountIterator.hasNext()) {
var account = accountIterator.next();
var accountName = account.getName() ? account.getName() : ‘–‘;
Logger.log(‘%s,%s,%s,%s’, account.getCustomerId(), accountName,
account.getTimeZone(), account.getCurrencyCode());
}
}

 

Recuperar todos os marcadores de conta

function getAllAccountLabels() {
var labelIterator = MccApp.accountLabels().get();
while (labelIterator.hasNext()) {
var label = labelIterator.next();

Logger.log(‘Label with id = %s and text = %s was found.’,
label.getId().toFixed(0), label.getName());
}
}

 

Recuperar um rótulo de conta pelo nome dele

function getLabelByName() {
var labelName = ‘INSERT_LABEL_NAME_HERE’;

var labelIterator = MccApp.accountLabels()
.withCondition(“Name CONTAINS ‘” + labelName + “‘”)
.get();

while (labelIterator.hasNext()) {
var label = labelIterator.next();

Logger.log(‘Label with id = %s and text = %s was found.’,
label.getId().toFixed(0), label.getName());
}
}

 

Recuperar rótulos de conta por seus IDs

function getLabelById() {
var labelIterator = MccApp.accountLabels()
.withIds([12345, 67890]) // Replace with label IDs here
.get();

while (labelIterator.hasNext()) {
var label = labelIterator.next();

Logger.log(“Label with id = %s and text = ‘%s’ was found.”,
label.getId().toFixed(0), label.getName());
}
}

6. Rotulador de Palavra-chave – Por Google Ads. Rotule em grande escala as palavras-chave com esse script e depois otimize-as com o filtro de rótulos no Google Ads.

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Keyword Labeler
*
* @overview The Keyword Labeler script labels keywords based on rules that
* you define. For example, you can create a rule to label keywords that
* are underperforming. Later, you can filter for this label in AdWords
* to decide whether to pause or remove those keywords. Rules don’t have
* to be based solely on a keyword’s performance. They can also be based
* on properties of a keyword such as its text or match type. For example,
* you could define “branded” keywords as those containing proper nouns
* associated with your brand, then label those keywords based on
* different performance thresholds versus “non-branded” keywords.
* Finally, the script sends an email linking to a spreadsheet when new
* keywords have been labeled. See
* //developers.google.com/adwords/scripts/docs/solutions/labels
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.1.2
*
* @changelog
* – version 1.1.2
* – Added validation for external spreadsheet setup.
* – version 1.1.1
* – Improvements to time zone handling.
* – version 1.1
* – Modified to allow generic rules and labeling.
* – version 1.0
* – Released initial version.
*/

var CONFIG = {
// URL of the spreadsheet template.
// This should be a copy of //goo.gl/uhK6nS.
SPREADSHEET_URL: ‘YOUR_SPREADSHEET_URL’,

// Array of addresses to be alerted via email if labels are applied.
RECIPIENT_EMAILS: [
‘YOUR_EMAIL_HERE’
],

// Selector conditions to apply for all rules.
GLOBAL_CONDITIONS: [
‘CampaignStatus = ENABLED’,
‘AdGroupStatus = ENABLED’,
‘Status = ENABLED’
],

// Default date range over which statistics fields are retrieved.
// Used when fetching keywords if a rule doesn’t specify a date range.
DEFAULT_DATE_RANGE: ‘LAST_7_DAYS’
};

/**
* Defines the rules by which keywords will be labeled.
* The labelName field is required. Other fields may be null.
* @type {Array.<{
* conditions: Array.<string>,
* dateRange: string,
* filter: function(Object): boolean,
* labelName: string,
* }>
* }
*/
var RULES = [
{
conditions: [
‘Ctr < 0.02’,
‘AverageCpc > 1’,
],
filter: function(keyword) {
var brands = [‘Product A’, ‘Product B’, ‘Product C’];
var text = keyword.getText();
for (var i = 0; i < brands.length; i++) {
if (text.indexOf(brand[i]) >= 0) {
return true;
}
}
return false;
},
labelName: ‘Underperforming Branded’
},

{
conditions: [
‘Ctr < 0.01’,
‘AverageCpc > 2’,
],
labelName: ‘Underperforming’
}
];

function main() {
validateEmailAddresses();
var results = processAccount();
processResults(results);
}

/**
* Processes the rules on the current account.
*
* @return {Array.<Object>} An array of changes made, each having
* a customerId, campaign name, ad group name, label name,
* and keyword text that the label was applied to.
*/
function processAccount() {
ensureAccountLabels();
var changes = applyLabels();

return changes;
}

/**
* Processes the results of the script.
*
* @param {Array.<Object>} changes An array of changes made, each having
* a customerId, campaign name, ad group name, label name,
* and keyword text that the label was applied to.
*/
function processResults(changes) {
if (changes.length > 0) {
var spreadsheetUrl = saveToSpreadsheet(changes, CONFIG.RECIPIENT_EMAILS);
sendEmail(spreadsheetUrl, CONFIG.RECIPIENT_EMAILS);
} else {
Logger.log(‘No labels were applied.’);
}
}

/**
* Retrieves the names of all labels in the account.
*
* @return {Array.<string>} An array of label names.
*/
function getAccountLabelNames() {
var labelNames = [];
var iterator = AdWordsApp.labels().get();

while (iterator.hasNext()) {
labelNames.push(iterator.next().getName());
}

return labelNames;
}

/**
* Checks that the account has a label for each rule and
* creates the rule’s label if it does not already exist.
* Throws an exception if a rule does not have a labelName.
*/
function ensureAccountLabels() {
var labelNames = getAccountLabelNames();

for (var i = 0; i < RULES.length; i++) {
var labelName = RULES[i].labelName;

if (!labelName) {
throw ‘Missing labelName for rule #’ + i;
}

if (labelNames.indexOf(labelName) == -1) {
AdWordsApp.createLabel(labelName);
labelNames.push(labelName);
}
}
}

/**
* Retrieves the keywords in an account satisfying a rule
* and that do not already have the rule’s label.
*
* @param {Object} rule An element of the RULES array.
* @return {Array.<Object>} An array of keywords.
*/
function getKeywordsForRule(rule) {
var selector = AdWordsApp.keywords();

// Add global conditions.
for (var i = 0; i < CONFIG.GLOBAL_CONDITIONS.length; i++) {
selector = selector.withCondition(CONFIG.GLOBAL_CONDITIONS[i]);
}

// Add selector conditions for this rule.
if (rule.conditions) {
for (var i = 0; i < rule.conditions.length; i++) {
selector = selector.withCondition(rule.conditions[i]);
}
}

// Exclude keywords that already have the label.
selector.withCondition(‘LabelNames CONTAINS_NONE [“‘ + rule.labelName + ‘”]’);

// Add a date range.
selector = selector.forDateRange(rule.dateRange || CONFIG.DEFAULT_DATE_RANGE);

// Get the keywords.
var iterator = selector.get();
var keywords = [];

// Check filter conditions for this rule.
while (iterator.hasNext()) {
var keyword = iterator.next();

if (!rule.filter || rule.filter(keyword)) {
keywords.push(keyword);
}
}

return keywords;
}

/**
* For each rule, determines the keywords matching the rule and which
* need to have a label newly applied, and applies it.
*
* @return {Array.<Object>} An array of changes made, each having
* a customerId, campaign name, ad group name, label name,
* and keyword text that the label was applied to.
*/
function applyLabels() {
var changes = [];
var customerId = AdWordsApp.currentAccount().getCustomerId();

for (var i = 0; i < RULES.length; i++) {
var rule = RULES[i];
var keywords = getKeywordsForRule(rule);
var labelName = rule.labelName;

for (var j = 0; j < keywords.length; j++) {
var keyword = keywords[j];

keyword.applyLabel(labelName);

changes.push({
customerId: customerId,
campaignName: keyword.getCampaign().getName(),
adGroupName: keyword.getAdGroup().getName(),
labelName: labelName,
keywordText: keyword.getText(),
});
}
}

return changes;
}

/**
* Outputs a list of applied labels to a new spreadsheet and gives editor access
* to a list of provided emails.
*
* @param {Array.<Object>} changes An array of changes made, each having
* a customerId, campaign name, ad group name, label name,
* and keyword text that the label was applied to.
* @param {Array.<Object>} emails An array of email addresses.
* @return {string} The URL of the spreadsheet.
*/
function saveToSpreadsheet(changes, emails) {
var template = validateAndGetSpreadsheet(CONFIG.SPREADSHEET_URL);
var spreadsheet = template.copy(‘Keyword Labels Applied’);

// Make sure the spreadsheet is using the account’s timezone.
spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone());

Logger.log(‘Saving changes to spreadsheet at ‘ + spreadsheet.getUrl());

var headers = spreadsheet.getRangeByName(‘Headers’);
var outputRange = headers.offset(1, 0, changes.length);

var outputValues = [];
for (var i = 0; i < changes.length; i++) {
var change = changes[i];
outputValues.push([
change.customerId,
change.campaignName,
change.adGroupName,
change.keywordText,
change.labelName
]);
}
outputRange.setValues(outputValues);

spreadsheet.getRangeByName(‘RunDate’).setValue(new Date());

for (var i = 0; i < emails.length; i++) {
spreadsheet.addEditor(emails[i]);
}

return spreadsheet.getUrl();
}

/**
* Sends an email to a list of email addresses with a link to a spreadsheet.
*
* @param {string} spreadsheetUrl The URL of the spreadsheet.
* @param {Array.<Object>} emails An array of email addresses.
*/
function sendEmail(spreadsheetUrl, emails) {
MailApp.sendEmail(emails.join(‘,’), ‘Keywords Newly Labeled’,
‘Keywords have been newly labeled in your’ +
‘AdWords account(s). See ‘ +
spreadsheetUrl + ‘ for details.’);
}

/**
* DO NOT EDIT ANYTHING BELOW THIS LINE.
* Please modify your spreadsheet URL and email addresses at the top of the file
* only.
*/

/**
* Validates the provided spreadsheet URL and email address
* to make sure that they’re set up properly. Throws a descriptive error message
* if validation fails.
*
* @param {string} spreadsheeturl The URL of the spreadsheet to open.
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
* @throws {Error} If the spreadsheet URL or email hasn’t been set
*/
function validateAndGetSpreadsheet(spreadsheeturl) {
if (spreadsheeturl == ‘YOUR_SPREADSHEET_URL’) {
throw new Error(‘Please specify a valid Spreadsheet URL. You can find’ +
‘ a link to a template in the associated guide for this script.’);
}
var spreadsheet = SpreadsheetApp.openByUrl(spreadsheeturl);
return spreadsheet;
}

/**
* Validates the provided email address to make sure it’s not the default.
* Throws a descriptive error message if validation fails.
*
* @throws {Error} If the list of email addresses is still the default
*/
function validateEmailAddresses() {
if (CONFIG.RECIPIENT_EMAILS &&
CONFIG.RECIPIENT_EMAILS[0] == ‘YOUR_EMAIL_HERE’) {
throw new Error(‘Please specify a valid email address.’);
}
}

 

Como Configurar

Automação Google Ads: Automatizar Tarefas

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para “Trabalhar com Automação de Tarefas”.

 

1. Reduza Lances para Palavras-chave com Custo/Conversão Alto – Por Russell Savage. Evite que suas palavras-chave consumam todo orçamento de sua campanha caso ela não esteja trazendo resultados. O script diminui automaticamente lances de palavras-chave com baixa performance em qualquer período de tempo.

//———————————–
// Reduce Bids on High Cost per Conversion Keywords
// Created By: Russ Savage
// FreeAdWordsScripts.com
//———————————–
function main() {
//Let’s reduce keywords with a CPC greater than $15 by 35%
var WAY_TOO_HIGH_COST_PER_CONV = 15;
var WAY_TOO_HIGH_BID_REDUCTION_AMOUNT = .35;

//And keywords with CPC between $10 and $15 by 20%
var TOO_HIGH_COST_PER_CONV = 10;
var TOO_HIGH_BID_REDUCTION_AMOUNT = .20;

var kw_iter = AdWordsApp.keywords()
.withCondition(“Status = ENABLED”)
.get();

while(kw_iter.hasNext()) {
var kw = kw_iter.next();
var kw_stats = kw.getStatsFor(“LAST_30_DAYS”);
var cost = kw_stats.getCost();
var conversions = kw_stats.getConversions();
if(conversions > 0) {
var cost_per_conversion = (cost/(conversions*1.0));
//Here is the magic. If it is way too high, reduce it by the way too high amount
if(cost_per_conversion >= WAY_TOO_HIGH_COST_PER_CONV) {
kw.setMaxCpc(kw.getMaxCpc() * (1-WAY_TOO_HIGH_BID_REDUCTION_AMOUNT));
}
//otherwise, if it is still too high, reduce it with just the too high amount
else if(cost_per_conversion >= TOO_HIGH_COST_PER_CONV) {
kw.setMaxCpc(kw.getMaxCpc() * (1-TOO_HIGH_BID_REDUCTION_AMOUNT));
}
}else{
//no conversions on this keyword
//we will deal with that later
continue;
}
}
}

 

 

2. Aumente Lances para Palavras-chave com Custo/Conversão Baixo – Por Russell Savage. Assim como seu nome, esse script visa aumentar os lances para palavras-chave que estão custando barato.

//———————————–
// Increase Bids Cheap Conversion Keywords
// Created By: Russ Savage
// FreeAdWordsScripts.com
//———————————–
function main() {
//For keywords with less than $5 CPC, let’s pump those bids up by 35%
var AMAZING_COST_PER_CONV = 5;
var AMAZING_BID_INCREASE_AMOUNT = .35;

//For keywords with between $5 and $8 CPCs, we will only increase the bids by 20%
var GREAT_COST_PER_CONV = 8;
var GREAT_BID_INCREASE_AMOUNT = .20;

var kw_iter = AdWordsApp.keywords()
.withCondition(“Status = ENABLED”)
.get();

while(kw_iter.hasNext()) {
var kw = kw_iter.next();
var kw_stats = kw.getStatsFor(“LAST_30_DAYS”);
var cost = kw_stats.getCost();
var conversions = kw_stats.getConversions();
if(conversions > 0) {
var cost_per_conversion = (cost/(conversions*1.0));

if(cost_per_conversion <= AMAZING_COST_PER_CONV) {
kw.setMaxCpc(kw.getMaxCpc() * (1+AMAZING_BID_INCREASE_AMOUNT));
}
else if(cost_per_conversion <= GREAT_COST_PER_CONV) {
kw.setMaxCpc(kw.getMaxCpc() * (1+GREAT_BID_INCREASE_AMOUNT));
}
}else{
//no conversions on this keyword
//we will deal with that later
continue;
}
}
}

 

 

3. Pause Palavras-chave que não performam – Por Russel Savage. Para utilizar esse script é necessário ter o valor da conversão sendo rastreado. Assim, caso o custo da palavra-chave esteja ultrapassando um valor determinado ela é automaticamente pausada.

//———————————–
// Pause Keywords That Are Not Performing
// Created By: Russ Savage
// FreeAdWordsScripts.com
//———————————–
function main() {
var THE_VALUE_OF_ONE_CONVERSION = 10;
var DECREASE_BIDS_BY_PERCENTAGE = .5;

var kw_iter = AdWordsApp.keywords()
.withCondition(“Status = ENABLED”)
.get();

while(kw_iter.hasNext()) {
var kw = kw_iter.next();
var kw_stats = kw.getStatsFor(“LAST_30_DAYS”);
var cost = kw_stats.getCost();
var conversions = kw_stats.getConversions();
if(conversions == 0) {
if(THE_VALUE_OF_ONE_CONVERSION * 5 > cost) {
kw.pause();
}
else if(THE_VALUE_OF_ONE_CONVERSION * 2 > cost) {
kw.setMaxCpc(kw.getMaxCpc() * (1-DECREASE_BIDS_BY_PERCENTAGE));
}
}else{
//no conversions on this keyword
//we will deal with that later
continue;
}
}
}

 

 

Automação Google Ads: Anúncios Sazonais

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para “Trabalhar com anúncios sazonais”.

 

1. Atualize Anúncios para o Ano Novo – Por Russel Savage. Tenha seus anúncios atualizados assim que o ano novo chegar. Esse script pode auxiliar anunciantes de empresas que precisem ter o ano em seus textos.

//———————————–
// Update Ads for 2012
// Created By: Russ Savage
// FreeAdWordsScripts.com
//———————————–
function main() {
var OLD_YEAR = “2011”;
var NEW_YEAR = “2012”;
//You probably shouldn’t update destination urls unless you know what you are doing.
var FIELDS_CONTAINING_YEAR = [“Headline”,”Description1″,
“Description2″,”DisplayUrl”
/*,”DestinationUrl”*/
];

for(i in FIELDS_CONTAINING_YEAR) {
var field_iter = AdWordsApp.ads()
.withCondition(FIELDS_CONTAINING_YEAR[i] + ” CONTAINS ” + OLD_YEAR)
.withCondition(“Status = ENABLED”)
.get();

_iterateThroughAds(field_iter);
}

//—————
// Private Helper Functions
//—————
function _iterateThroughAds(ad_iter) {
while (ad_iter.hasNext()) {
var ad = ad_iter.next();
var ag = ad.getAdGroup();
_createNewAdFromOldAd(ag,ad);
}
}

function _createNewAdFromOldAd(adgroup, old_ad) {
//get the updated ad texts replacing all the old years with the new years
var new_headline = old_ad.getHeadline().replace(OLD_YEAR,NEW_YEAR);
var new_desc1 = old_ad.getDescription1().replace(OLD_YEAR,NEW_YEAR);
var new_desc2 = old_ad.getDescription2().replace(OLD_YEAR,NEW_YEAR);
var new_display_url = old_ad.getDisplayUrl().replace(OLD_YEAR,NEW_YEAR);
var new_dest_url = old_ad.getDestinationUrl();/*.replace(OLD_YEAR,NEW_YEAR);*/

//now create the new ad and pause the old one.
adgroup.createTextAd(new_headline,new_desc1,new_desc2,new_display_url,new_dest_url);
old_ad.pause();
}
}

 

2. Atualize suas Palavras-chave em Feriados e Datas Comemorativas – Por Russel Savage. Todas as suas palavras-chave que tiverem o ano atual em seu texto será substituído com o ano atual. Assim, caso você não precisa mudar manualmente todas essas palavras-chave.

/*********************************************
* Update Keywords for the New Year
* Version 1.1
* Changelog v1.1
* – Updated for speed and added comments
* Created By: Russ Savage
* FreeAdWordsScripts.com
**********************************************/
function main() {
var sameDayLastYear = new Date();
sameDayLastYear.setYear(sameDayLastYear.getYear()-1);
var oldYearStr = sameDayLastYear.getYear().toString();
var newYearStr = new Date().getYear().toString();

Logger.log(‘Updating keywords with old year: ‘+oldYearStr+’ to new year: ‘+newYearStr);

// Let’s start by getting all of the keywords
var kwIter = AdWordsApp.keywords()
.withCondition(“Text CONTAINS ” + oldYearStr)
.withCondition(“Status = ENABLED”)
.withCondition(“AdGroupStatus = ENABLED”)
.withCondition(“CampaignStatus = ENABLED”)
.get();

// It is always better to store and batch process afterwards
var toPause = [];
var toCreate = [];
while (kwIter.hasNext()) {
var kw = kwIter.next();
var ag = kw.getAdGroup();
var oldText = kw.getText();
var newText = oldText.replace(oldYearStr,newYearStr);
// Save the info so that we can create them as a batch later
toCreate.push({ ag: ag, text: newText, cpc:kw.getMaxCpc(), destUrl : kw.getDestinationUrl() });
// Same with the ones we want to pause
toPause.push(kw)
}
// Now we create the new keywords all at once
for(var i in toCreate) {
var elem = toCreate[i];
elem.ag.createKeyword(elem.text, elem.cpc, elem.destUrl);
}
// And pause the old ones all at once
for(var i in toPause) {
toPause[i].pause();
//or toPause[i].remove(); to delete the old keyword
}
}

 

 

Automação Google Ads: Como gerar relatórios

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para “geração de relatórios”.

 

1. Relatório de Resumo da Conta – por Google Ads. Utilize esse script para criar relatórios em uma planilha do Google. Nesta planilha você tem uma visão do desempenho de toda a conta do Google Ads e, além disso, envia um e-mail todos os dias com estatísticas das contas atuais.

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Account Summary Report
*
* @overview The Account Summary Report script generates an at-a-glance report
* showing the performance of an entire AdWords account. See
* //developers.google.com/adwords/scripts/docs/solutions/account-summary
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.1
*
* @changelog
* – version 1.1
* – Add user-updateable fields, and ensure report row ordering.
* – version 1.0.4
* – Improved code readability and comments.
* – version 1.0.3
* – Added validation for external spreadsheet setup.
* – version 1.0.2
* – Fixes date formatting bug in certain timezones.
* – version 1.0.1
* – Improvements to time zone handling.
* – version 1.0
* – Released initial version.
*/

var SPREADSHEET_URL = ‘YOUR_SPREADSHEET_URL’;

/**
* Configuration to be used for running reports.
*/
var REPORTING_OPTIONS = {
// Comment out the following line to default to the latest reporting version.
apiVersion: ‘v201705’
};

/**
* To add additional fields to the report, follow the instructions at the link
* in the header above, and add fields to this variable, taken from the Account
* Performance Report reference:
* //developers.google.com/adwords/api/docs/appendix/reports/account-performance-report
*/
var REPORT_FIELDS = [
{columnName: ‘Cost’, displayName: ‘Cost’},
{columnName: ‘AverageCpc’, displayName: ‘Avg. CPC’},
{columnName: ‘Ctr’, displayName: ‘CTR’},
{columnName: ‘AveragePosition’, displayName: ‘Avg. Pos.’},
{columnName: ‘Impressions’, displayName: ‘Impressions’},
{columnName: ‘Clicks’, displayName: ‘Clicks’}
];

function main() {
Logger.log(‘Using spreadsheet – %s.’, SPREADSHEET_URL);
var spreadsheet = validateAndGetSpreadsheet();
spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone());
spreadsheet.getRangeByName(‘account_id_report’).setValue(
AdWordsApp.currentAccount().getCustomerId());

var yesterday = getYesterday();
var date = getFirstDayToCheck(spreadsheet, yesterday);

var rows = [];
var existingDates = getExistingDates();

while (date.getTime() <= yesterday.getTime()) {
if (!existingDates[date]) {
var row = getReportRowForDate(date);
rows.push([new Date(date)].concat(REPORT_FIELDS.map(function(field) {
return row[field.columnName];
})));
spreadsheet.getRangeByName(‘last_check’).setValue(date);
}
date.setDate(date.getDate() + 1);
}

if (rows.length > 0) {
writeToSpreadsheet(rows);

var email = spreadsheet.getRangeByName(‘email’).getValue();
if (email) {
sendEmail(email);
}
}
}

/**
* Retrieves a lookup of dates for which rows already exist in the spreadsheet.
*
* @return {!Object} A lookup of existing dates.
*/
function getExistingDates() {
var spreadsheet = validateAndGetSpreadsheet();
var sheet = spreadsheet.getSheetByName(‘Report’);

var data = sheet.getDataRange().getValues();
var existingDates = {};
data.slice(5).forEach(function(row) {
existingDates[row[1]] = true;
});
return existingDates;
}

/**
* Sorts the data in the spreadsheet into ascending date order.
*/
function sortReportRows() {
var spreadsheet = validateAndGetSpreadsheet();
var sheet = spreadsheet.getSheetByName(‘Report’);

var data = sheet.getDataRange().getValues();
var reportRows = data.slice(5);
if (reportRows.length) {
reportRows.sort(function(rowA, rowB) {
if (!rowA || !rowA.length) {
return -1;
} else if (!rowB || !rowB.length) {
return 1;
} else if (rowA[1] < rowB[1]) {
return -1;
} else if (rowA[1] > rowB[1]) {
return 1;
}
return 0;
});
sheet.getRange(6, 1, reportRows.length, reportRows[0].length)
.setValues(reportRows);
}
}

/**
* Append the data rows to the spreadsheet.
*
* @param {Array<Array<string>>} rows The data rows.
*/
function writeToSpreadsheet(rows) {
var access = new SpreadsheetAccess(SPREADSHEET_URL, ‘Report’);
var emptyRow = access.findEmptyRow(6, 2);
if (emptyRow < 0) {
access.addRows(rows.length);
emptyRow = access.findEmptyRow(6, 2);
}
access.writeRows(rows, emptyRow, 2);
sortReportRows();
}

function sendEmail(email) {
var day = getYesterday();
var yesterdayRow = getReportRowForDate(day);
day.setDate(day.getDate() – 1);
var twoDaysAgoRow = getReportRowForDate(day);
day.setDate(day.getDate() – 5);
var weekAgoRow = getReportRowForDate(day);

var html = [];
html.push(
‘<html>’,
‘<body>’,
‘<table width=800 cellpadding=0 border=0 cellspacing=0>’,
‘<tr>’,
‘<td colspan=2 align=right>’,
“<div style=’font: italic normal 10pt Times New Roman, serif; ” +
“margin: 0; color: #666; padding-right: 5px;’>” +
‘Powered by AdWords Scripts</div>’,
‘</td>’,
‘</tr>’,
“<tr bgcolor=’#3c78d8′>”,
‘<td width=500>’,
“<div style=’font: normal 18pt verdana, sans-serif; ” +
“padding: 3px 10px; color: white’>Account Summary report</div>”,
‘</td>’,
‘<td align=right>’,
“<div style=’font: normal 18pt verdana, sans-serif; ” +
“padding: 3px 10px; color: white’>”,
AdWordsApp.currentAccount().getCustomerId(), ‘</h1>’,
‘</td>’,
‘</tr>’,
‘</table>’,
‘<table width=800 cellpadding=0 border=0 cellspacing=0>’,
“<tr bgcolor=’#ddd’>”,
‘<td></td>’,
“<td style=’font: 12pt verdana, sans-serif; ” +
‘padding: 5px 0px 5px 5px; background-color: #ddd; ‘ +
“text-align: left’>Yesterday</td>”,
“<td style=’font: 12pt verdana, sans-serif; ” +
‘padding: 5px 0px 5px 5px; background-color: #ddd; ‘ +
“text-align: left’>Two Days Ago</td>”,
“<td style=’font: 12pt verdana, sans-serif; ” +
‘padding: 5px 0px 5x 5px; background-color: #ddd; ‘ +
“text-align: left’>A week ago</td>”,
‘</tr>’);
REPORT_FIELDS.forEach(function(field) {
html.push(emailRow(
field.displayName, field.columnName, yesterdayRow, twoDaysAgoRow,
weekAgoRow));
});
html.push(‘</table>’, ‘</body>’, ‘</html>’);
MailApp.sendEmail(email, ‘AdWords Account ‘ +
AdWordsApp.currentAccount().getCustomerId() + ‘ Summary Report’, ”,
{htmlBody: html.join(‘\n’)});
}

function emailRow(title, column, yesterdayRow, twoDaysAgoRow, weekAgoRow) {
var html = [];
html.push(‘<tr>’,
“<td style=’padding: 5px 10px’>” + title + ‘</td>’,
“<td style=’padding: 0px 10px’>” + yesterdayRow[column] + ‘</td>’,
“<td style=’padding: 0px 10px’>” + twoDaysAgoRow[column] +
formatChangeString(yesterdayRow[column], twoDaysAgoRow[column]) +
‘</td>’,
“<td style=’padding: 0px 10px’>” + weekAgoRow[column] +
formatChangeString(yesterdayRow[column], weekAgoRow[column]) +
‘</td>’,
‘</tr>’);
return html.join(‘\n’);
}

function getReportRowForDate(date) {
var timeZone = AdWordsApp.currentAccount().getTimeZone();
var dateString = Utilities.formatDate(date, timeZone, ‘yyyyMMdd’);
return getReportRowForDuring(dateString + ‘,’ + dateString);
}

function getReportRowForDuring(during) {
var report = AdWordsApp.report(
‘SELECT ‘ +
REPORT_FIELDS
.map(function(field) {
return field.columnName;
})
.join(‘,’) +
‘ FROM ACCOUNT_PERFORMANCE_REPORT ‘ +
‘DURING ‘ + during,
REPORTING_OPTIONS);
return report.rows().next();
}

function formatChangeString(newValue, oldValue) {
var x = newValue.indexOf(‘%’);
if (x != -1) {
newValue = newValue.substring(0, x);
var y = oldValue.indexOf(‘%’);
oldValue = oldValue.substring(0, y);
}

var change = parseFloat(newValue – oldValue).toFixed(2);
var changeString = change;
if (x != -1) {
changeString = change + ‘%’;
}

if (change >= 0) {
return “<span style=’color: #38761d; font-size: 8pt’> (+” +
changeString + ‘)</span>’;
} else {
return “<span style=’color: #cc0000; font-size: 8pt’> (” +
changeString + ‘)</span>’;
}
}

function SpreadsheetAccess(spreadsheetUrl, sheetName) {
this.spreadsheet = SpreadsheetApp.openByUrl(spreadsheetUrl);
this.sheet = this.spreadsheet.getSheetByName(sheetName);

// what column should we be looking at to check whether the row is empty?
this.findEmptyRow = function(minRow, column) {
var values = this.sheet.getRange(minRow, column,
this.sheet.getMaxRows(), 1).getValues();
for (var i = 0; i < values.length; i++) {
if (!values[i][0]) {
return i + minRow;
}
}
return -1;
};
this.addRows = function(howMany) {
this.sheet.insertRowsAfter(this.sheet.getMaxRows(), howMany);
};
this.writeRows = function(rows, startRow, startColumn) {
this.sheet.getRange(startRow, startColumn, rows.length, rows[0].length).
setValues(rows);
};
}

/**
* Gets a date object that is 00:00 yesterday.
*
* @return {Date} A date object that is equivalent to 00:00 yesterday in the
* account’s time zone.
*/
function getYesterday() {
var yesterday = new Date(new Date().getTime() – 24 * 3600 * 1000);
return new Date(getDateStringInTimeZone(‘MMM dd, yyyy 00:00:00 Z’,
yesterday));
}

/**
* Returned the last checked date + 1 day, or yesterday if there isn’t
* a specified last checked date.
*
* @param {Spreadsheet} spreadsheet The export spreadsheet.
* @param {Date} yesterday The yesterday date.
*
* @return {Date} The date corresponding to the first day to check.
*/
function getFirstDayToCheck(spreadsheet, yesterday) {
var last_check = spreadsheet.getRangeByName(‘last_check’).getValue();
var date;
if (last_check.length == 0) {
date = new Date(yesterday);
} else {
date = new Date(last_check);
date.setDate(date.getDate() + 1);
}
return date;
}

/**
* Produces a formatted string representing a given date in a given time zone.
*
* @param {string} format A format specifier for the string to be produced.
* @param {date} date A date object. Defaults to the current date.
* @param {string} timeZone A time zone. Defaults to the account’s time zone.
* @return {string} A formatted string of the given date in the given time zone.
*/
function getDateStringInTimeZone(format, date, timeZone) {
date = date || new Date();
timeZone = timeZone || AdWordsApp.currentAccount().getTimeZone();
return Utilities.formatDate(date, timeZone, format);
}

/**
* Validates the provided spreadsheet URL to make sure that it’s set up
* properly. Throws a descriptive error message if validation fails.
*
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
*/
function validateAndGetSpreadsheet() {
if (‘YOUR_SPREADSHEET_URL’ == SPREADSHEET_URL) {
throw new Error(‘Please specify a valid Spreadsheet URL. You can find’ +
‘ a link to a template in the associated guide for this script.’);
}
var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
var email = spreadsheet.getRangeByName(‘email’).getValue();
if (‘[email protected]’ == email) {
throw new Error(‘Please either set a custom email address in the’ +
‘ spreadsheet, or set the email field in the spreadsheet to blank’ +
‘ to send no email.’);
}
return spreadsheet;
}

 

Como Configurar

 

2. Relatório de Desempenho do Anúncio – por Google Ads. Receba relatórios de desempenhos de seus anúncios em uma planilha do Google com esse script. Toda vez que esse script for executado ele cria um novo relatório no Google Drive. Faça testes e compare os desempenhos de um novo título ou URL.

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Ad Performance Report
*
* @overview The Ad Performance Report generates a Google Spreadsheet that
* contains ad performance stats like Impressions, Cost, Click Through Rate,
* etc. as several distribution charts for an advertiser account. See
* //developers.google.com/adwords/scripts/docs/solutions/ad-performance
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.0.1
*
* @changelog
* – version 1.1
* – Updated to use expanded text ads.
* – version 1.0.1
* – Improvements to time zone handling.
* – version 1.0
* – Released initial version.
*/
// Comma-separated list of recipients. Comment out to not send any emails.
var RECIPIENT_EMAIL = ‘[email protected]’;

// URL of the default spreadsheet template. This should be a copy of
// //goo.gl/21FW5i
var SPREADSHEET_URL = ‘YOUR_SPREADSHEET_URL’;

/**
* This script computes an Ad performance report
* and outputs it to a Google spreadsheet.
*/
function main() {
Logger.log(‘Using template spreadsheet – %s.’, SPREADSHEET_URL);
var spreadsheet = copySpreadsheet(SPREADSHEET_URL);
Logger.log(‘Generated new reporting spreadsheet %s based on the template ‘ +
‘spreadsheet. The reporting data will be populated here.’,
spreadsheet.getUrl());

var headlineSheet = spreadsheet.getSheetByName(‘Headline’);
headlineSheet.getRange(1, 2, 1, 1).setValue(‘Date’);
headlineSheet.getRange(1, 3, 1, 1).setValue(new Date());
var finalUrlSheet = spreadsheet.getSheetByName(‘Final Url’);
finalUrlSheet.getRange(1, 2, 1, 1).setValue(‘Date’);
finalUrlSheet.getRange(1, 3, 1, 1).setValue(new Date());
spreadsheet.getRangeByName(‘account_id_headline’).setValue(
AdWordsApp.currentAccount().getCustomerId());
spreadsheet.getRangeByName(‘account_id_final_url’).setValue(
AdWordsApp.currentAccount().getCustomerId());

// Only include ad types on the headline sheet for which the concept of a
// headline makes sense.
outputSegmentation(headlineSheet, ‘Headline’, function(ad) {
var headline;
// There is no AdTypeSpace method for textAd
if (ad.getType() === ‘TEXT_AD’) {
headline = ad.getHeadline();
} else if (ad.isType().expandedTextAd()) {
var eta = ad.asType().expandedTextAd();
headline = eta.getHeadlinePart1() + ‘ – ‘ + eta.getHeadlinePart2();
} else if (ad.isType().gmailMultiProductAd()) {
var gmailMpa = ad.asType().gmailMultiProductAd();
headline = gmailMpa.getHeadline();
} else if (ad.isType().gmailSinglePromotionAd()) {
var gmailSpa = ad.asType().gmailSinglePromotionAd();
headline = gmailSpa.getHeadline();
} else if (ad.isType().responsiveDisplayAd()) {
var responsiveDisplayAd = ad.asType().responsiveDisplayAd();
headline = responsiveDisplayAd.getLongHeadline();
}
return headline;
});
outputSegmentation(finalUrlSheet, ‘Final Url’, function(ad) {
return ad.urls().getFinalUrl();
});
Logger.log(‘Ad performance report available at\n’ + spreadsheet.getUrl());
if (validateEmailAddress(RECIPIENT_EMAIL)) {
MailApp.sendEmail(RECIPIENT_EMAIL,
‘Ad Performance Report is ready’,
spreadsheet.getUrl());
}
}

/**
* Retrieves the spreadsheet identified by the URL.
* @param {string} spreadsheetUrl The URL of the spreadsheet.
* @return {SpreadSheet} The spreadsheet.
*/
function copySpreadsheet(spreadsheetUrl) {
var spreadsheet = validateAndGetSpreadsheet(spreadsheetUrl).copy(
‘Ad Performance Report – ‘ +
getDateStringInTimeZone(‘MMM dd, yyyy HH:mm:ss z’));

// Make sure the spreadsheet is using the account’s timezone.
spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone());
return spreadsheet;
}

/**
* Generates statistical data for this segment.
* @param {Sheet} sheet Sheet to write to.
* @param {string} segmentName The Name of this segment for the header row.
* @param {function(AdWordsApp.Ad): string} segmentFunc Function that returns
* a string used to segment the results by.
*/
function outputSegmentation(sheet, segmentName, segmentFunc) {
// Output header row.
var rows = [];
var header = [
segmentName,
‘Num Ads’,
‘Impressions’,
‘Clicks’,
‘CTR (%)’,
‘Cost’
];
rows.push(header);

var segmentMap = {};

// Compute data.
var adIterator = AdWordsApp.ads()
.forDateRange(‘LAST_WEEK’)
.withCondition(‘Impressions > 0’).get();
while (adIterator.hasNext()) {
var ad = adIterator.next();
var stats = ad.getStatsFor(‘LAST_WEEK’);
var segment = segmentFunc(ad);
// In the case of the headline segmentation segmentFunc will return null
// where there is no headline e.g. an HTML5 ad or other non-text ad, for
// which metrics are therefore not aggregated.
if (segment) {
if (!segmentMap[segment]) {
segmentMap[segment] =
{numAds: 0, totalImpressions: 0, totalClicks: 0, totalCost: 0.0};
}
var data = segmentMap[segment];
data.numAds++;
data.totalImpressions += stats.getImpressions();
data.totalClicks += stats.getClicks();
data.totalCost += stats.getCost();
}
}

// Write data to our rows.
for (var key in segmentMap) {
if (segmentMap.hasOwnProperty(key)) {
var ctr = 0;
if (segmentMap[key].numAds > 0) {
ctr = (segmentMap[key].totalClicks /
segmentMap[key].totalImpressions) * 100;
}
var row = [
key,
segmentMap[key].numAds,
segmentMap[key].totalImpressions,
segmentMap[key].totalClicks,
ctr.toFixed(2),
segmentMap[key].totalCost];
rows.push(row);
}
}
sheet.getRange(3, 2, rows.length, 6).setValues(rows);
}

/**
* Produces a formatted string representing a given date in a given time zone.
*
* @param {string} format A format specifier for the string to be produced.
* @param {date} date A date object. Defaults to the current date.
* @param {string} timeZone A time zone. Defaults to the account’s time zone.
* @return {string} A formatted string of the given date in the given time zone.
*/
function getDateStringInTimeZone(format, date, timeZone) {
date = date || new Date();
timeZone = timeZone || AdWordsApp.currentAccount().getTimeZone();
return Utilities.formatDate(date, timeZone, format);
}

/**
* Validates the provided email address
* to make sure that it’s set up properly. Throws a descriptive error message
* if validation fails.
*
* @param {string} emailAddress The email address to send the results.
* @return {Spreadsheet} The email address, if it is not the default fake one.
* @throws {Error} If the email address has not been changed from the default.
*/
function validateEmailAddress(emailAddress) {
if (emailAddress == ‘[email protected]’) {
throw new Error(‘Please specify a valid email or leave empty to not’ +
‘ send any email.’);
}
return emailAddress;
}

/**
* Validates the provided spreadsheet URL
* to make sure that it’s set up properly. Throws a descriptive error message
* if validation fails.
*
* @param {string} spreadsheeturl The URL of the spreadsheet to open.
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
* @throws {Error} If the spreadsheet URL hasn’t been set
*/
function validateAndGetSpreadsheet(spreadsheeturl) {
if (spreadsheeturl == ‘YOUR_SPREADSHEET_URL’) {
throw new Error(‘Please specify a valid Spreadsheet URL. You can find’ +
‘ a link to a template in the associated guide for this script.’);
}
return spreadsheet = SpreadsheetApp.openByUrl(spreadsheeturl);
}

 

Como configurar

 

3. Relatório de Grupos de Anúncios Rebaixados – por Google Ads. Encontre facilmente grupos de anúncios que estão com seu desempenho em declínio, assim, você pode otimizar seu grupo de anúncios ou pausa-los. O script deve ser rodado semanalmente para uma performance melhor.

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Declining AdGroups
*
* @overview The Declining AdGroups script fetches ad groups in an advertiser
* account, whose performance is considered to be worsening. By default, ad
* groups whose Click Through Rate has been decreasing for three consecutive
* weeks is considered worsening. A more sophisticated measure of
* “worsening” may be developed if required. See
* //developers.google.com/adwords/scripts/docs/solutions/declining-adgroups
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.0.2
*
* @changelog
* – version 1.0.2
* – Added validation for spreadsheet URL.
* – version 1.0.1
* – Improvements to time zone handling.
* – version 1.0
* – Released initial version.
*/

var SPREADSHEET_URL = ‘YOUR_SPREADSHEET_URL’;

function main() {
Logger.log(‘Using spreadsheet – %s.’, SPREADSHEET_URL);
var spreadsheet = validateAndGetSpreadsheet(SPREADSHEET_URL);
spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone());

var sheet = spreadsheet.getSheets()[0];
spreadsheet.getRangeByName(‘account_id’).setValue(
AdWordsApp.currentAccount().getCustomerId());
sheet.getRange(1, 2, 1, 1).setValue(‘Date’);
sheet.getRange(1, 3, 1, 1).setValue(new Date());
sheet.getRange(7, 1, sheet.getMaxRows() – 7, sheet.getMaxColumns()).clear();

var adGroupsIterator = AdWordsApp.adGroups()
.withCondition(“Status = ‘ENABLED'”)
.withCondition(“CampaignStatus = ‘ENABLED'”)
.forDateRange(‘LAST_7_DAYS’)
.orderBy(‘Ctr ASC’)
.withLimit(100)
.get();

var today = getDateStringInPast(0);
var oneWeekAgo = getDateStringInPast(7);
var twoWeeksAgo = getDateStringInPast(14);
var threeWeeksAgo = getDateStringInPast(21);

var reportRows = [];

while (adGroupsIterator.hasNext()) {
var adGroup = adGroupsIterator.next();
// Let’s look at the trend of the ad group’s CTR.
var statsThreeWeeksAgo = adGroup.getStatsFor(threeWeeksAgo, twoWeeksAgo);
var statsTwoWeeksAgo = adGroup.getStatsFor(twoWeeksAgo, oneWeekAgo);
var statsLastWeek = adGroup.getStatsFor(oneWeekAgo, today);

// Week over week, the ad group is declining – record that!
if (statsLastWeek.getCtr() < statsTwoWeeksAgo.getCtr() &&
statsTwoWeeksAgo.getCtr() < statsThreeWeeksAgo.getCtr()) {
reportRows.push([adGroup.getCampaign().getName(), adGroup.getName(),
statsLastWeek.getCtr(), statsLastWeek.getCost(),
statsTwoWeeksAgo.getCtr(), statsTwoWeeksAgo.getCost(),
statsThreeWeeksAgo.getCtr(), statsThreeWeeksAgo.getCost()]);
}
}
if (reportRows.length > 0) {
sheet.getRange(7, 2, reportRows.length, 8).setValues(reportRows);
sheet.getRange(7, 4, reportRows.length, 1).setNumberFormat(‘#0.00%’);
sheet.getRange(7, 6, reportRows.length, 1).setNumberFormat(‘#0.00%’);
sheet.getRange(7, 8, reportRows.length, 1).setNumberFormat(‘#0.00%’);

sheet.getRange(7, 5, reportRows.length, 1).setNumberFormat(‘#,##0.00’);
sheet.getRange(7, 7, reportRows.length, 1).setNumberFormat(‘#,##0.00’);
sheet.getRange(7, 9, reportRows.length, 1).setNumberFormat(‘#,##0.00’);
}

var email = spreadsheet.getRangeByName(‘email’).getValue();
if (email) {
var body = [];
body.push(‘The Ctr of the following ad groups is declining over the ‘ +
‘last three weeks.\n’);
body.push(‘Full report at ‘ + SPREADSHEET_URL + ‘\n\n’);
for (var i = 0; i < reportRows.length; i++) {
body.push(reportRows[i][0] + ‘ > ‘ + reportRows[i][1]);
body.push(‘ ‘ + ctr(reportRows[i][6]) + ‘ > ‘ + ctr(reportRows[i][4]) +
‘ > ‘ + ctr(reportRows[i][2]) + ‘\n’);
}
MailApp.sendEmail(email, ” +
reportRows.length + ‘ ad groups are declining in AdWords account ‘ +
AdWordsApp.currentAccount().getCustomerId(), body.join(‘\n’));
}
}

function ctr(number) {
return parseInt(number * 10000) / 10000 + ‘%’;
}

// Returns YYYYMMDD-formatted date.
function getDateStringInPast(numDays, date) {
date = date || new Date();
var MILLIS_PER_DAY = 1000 * 60 * 60 * 24;
var past = new Date(date.getTime() – numDays * MILLIS_PER_DAY);
return getDateStringInTimeZone(‘yyyyMMdd’, past);
}

function getDateStringInTimeZone(format, date, timeZone) {
date = date || new Date();
timeZone = timeZone || AdWordsApp.currentAccount().getTimeZone();
return Utilities.formatDate(date, timeZone, format);
}

/**
* Validates the provided spreadsheet URL
* to make sure that it’s set up properly. Throws a descriptive error message
* if validation fails.
*
* @param {string} spreadsheeturl The URL of the spreadsheet to open.
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
* @throws {Error} If the spreadsheet URL hasn’t been set
*/
function validateAndGetSpreadsheet(spreadsheeturl) {
if (spreadsheeturl == ‘YOUR_SPREADSHEET_URL’) {
throw new Error(‘Please specify a valid Spreadsheet URL. You can find’ +
‘ a link to a template in the associated guide for this script.’);
}
return SpreadsheetApp.openByUrl(spreadsheeturl);
}

 

Como Configurar

 

4. Relatório de desempenho por palavra-chave – por Google Ads. Este script gera relatórios com vários gráficos de distribuição, como por exemplo, posição média e índice de qualidade. Toda vez que o script é executado ele cria um novo relatório no Google Drive.

// Copyright 2016, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Keyword Performance Report
*
* @overview The Keyword Performance Report script generates a Google
* Spreadsheet that contains keyword performance stats like quality score
* and average position of ads, as well as several distribution charts for
* an advertiser account. See
* //developers.google.com/adwords/scripts/docs/solutions/keyword-performance
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.0.4
*
* @changelog
* – version 1.0.4
* – Fixed issue of calling getRangeByName on spreadsheet vs sheet.
* – version 1.0.3
* – Refactored to improve readability. Added documentation.
* – version 1.0.2
* – Added validation for spreadsheet url and email address.
* – version 1.0.1
* – Improvements to time zone handling.
* – version 1.0
* – Released initial version.
*/

/**
* Comma-separated list of recipients. Comment out to not send any emails.
*/
var RECIPIENT_EMAIL = ‘[email protected]’;

// URL of the default spreadsheet template. This should be a copy of
// //goo.gl/oR5VmF
var SPREADSHEET_URL = ‘YOUR_SPREADSHEET_URL’;

/**
* The size of the quality score map to output.
* DO NOT change this value.
*/
var QUALITY_SCORE_MAP_SIZE = 10;

/**
* The size of the position map to output.
* DO NOT change this value.
*/
var POSITION_MAP_SIZE = 12;

/**
* This script computes a keyword performance report
* and outputs it to a Google spreadsheet. The spreadsheet
* url is logged and emailed.
*/
function main() {
validateEmail(RECIPIENT_EMAIL);
Logger.log(‘Using template spreadsheet – %s.’, SPREADSHEET_URL);
var spreadsheet = copySpreadsheet(SPREADSHEET_URL);
Logger.log(
‘Generated new reporting spreadsheet %s based on the template ‘ +
‘spreadsheet. The reporting data will be populated here.’,
spreadsheet.getUrl());

spreadsheet.getRangeByName(‘date_label’).setValue(‘Date’);
spreadsheet.getRangeByName(‘date_value’).setValue(new Date());
spreadsheet.getRangeByName(‘account_id’)
.setValue(AdWordsApp.currentAccount().getCustomerId());
outputQualityScoreData(spreadsheet);
outputPositionData(spreadsheet);
Logger.log(
‘Keyword performance report available at\n’ + spreadsheet.getUrl());
if (RECIPIENT_EMAIL) {
MailApp.sendEmail(
RECIPIENT_EMAIL, ‘Keyword Performance Report is ready’,
spreadsheet.getUrl());
}
}

/**
* Retrieves the spreadsheet identified by the URL.
* @param {string} spreadsheetUrl The URL of the spreadsheet.
* @return {SpreadSheet} The spreadsheet.
*/
function copySpreadsheet(spreadsheetUrl) {
var spreadsheet = validateAndGetSpreadsheet(spreadsheetUrl)
.copy(
‘Keyword Performance Report – ‘ +
getDateStringInTimeZone(‘MMM dd, yyyy HH:mm:ss z’));

// Make sure the spreadsheet is using the account’s timezone.
spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone());
return spreadsheet;
}

/**
* Gets an iterator for keywords that had impressions last week.
* @return {Iterator} an iterator of the keywords
*/
function getLastWeekKeywordsWithPositiveImpressions() {
return AdWordsApp.keywords()
.forDateRange(‘LAST_WEEK’)
.withCondition(‘Impressions > 0’)
.get();
}

/**
* Outputs Quality score related data to the spreadsheet
* @param {Spreadsheet} spreadsheet The sheet to output to.
*/
function outputQualityScoreData(spreadsheet) {
// Output header row
var header = [
‘Quality Score’, ‘Num Keywords’, ‘Impressions’, ‘Clicks’, ‘CTR (%)’, ‘Cost’
];
spreadsheet.getRangeByName(‘quality_score_headings’).setValues([header]);

// Initialize
var qualityScoreMap = getEmptyStatMap(QUALITY_SCORE_MAP_SIZE);

// Compute data
computeQualityData(
getLastWeekKeywordsWithPositiveImpressions(), qualityScoreMap);

// Output data to spreadsheet
var rows = [];
for (var key in qualityScoreMap) {
var ctr = calculateCtr(qualityScoreMap[key]);
var row = [
key, qualityScoreMap[key].numKeywords,
qualityScoreMap[key].totalImpressions, qualityScoreMap[key].totalClicks,
ctr.toFixed(2), qualityScoreMap[key].totalCost
];
rows.push(row);
}
spreadsheet.getRangeByName(‘quality_score_body’).setValues(rows);
}

/**
* Outputs average position related data to the spreadsheet.
* @param {Spreadsheet} spreadsheet The spreadsheet to output to.
*/
function outputPositionData(spreadsheet) {
// Output header row
headerRow = [];
var header = [
‘Avg Position’, ‘Num Keywords’, ‘Impressions’, ‘Clicks’, ‘CTR (%)’, ‘Cost’
];
headerRow.push(header);
spreadsheet.getRangeByName(‘position_headings’).setValues(headerRow);

// Initialize
var positionMap = getEmptyStatMap(POSITION_MAP_SIZE);

// Compute data
computePositionData(
getLastWeekKeywordsWithPositiveImpressions(), positionMap);

// Output data to spreadsheet
var rows = [];
for (var key in positionMap) {
var ctr = calculateCtr(positionMap[key]);
var mapSizeLessOne = POSITION_MAP_SIZE – 1;
var row = [
key <= mapSizeLessOne ? key – 1 + ‘ to ‘ + key : ‘>’ + mapSizeLessOne,
positionMap[key].numKeywords, positionMap[key].totalImpressions,
positionMap[key].totalClicks, ctr.toFixed(2), positionMap[key].totalCost
];
rows.push(row);
}
spreadsheet.getRangeByName(‘position_body’).setValues(rows);
}

/**
* Calculates the click through rate given an entry from a map.
* @param {object} mapEntry – an entry from the map
* @return {number} the click through rate
*/
function calculateCtr(mapEntry) {
var ctr = 0;
if (mapEntry.numKeywords > 0) {
ctr = (mapEntry.totalClicks / mapEntry.totalImpressions) * 100;
}
return ctr;
}

/**
* Gets an empty stat map.
* @param {number} size – the number of entries in the stat map.
* @return {array} the empty quality stat map.
*/
function getEmptyStatMap(size) {
var qualityScoreMap = [];
for (i = 1; i <= size; i++) {
qualityScoreMap[i] =
{numKeywords: 0, totalImpressions: 0, totalClicks: 0, totalCost: 0.0};
}
return qualityScoreMap;
}

/**
* Uses the given keyword iterator and populates the given quality score map.
* @param {Iterator} keywordIterator – the keywords to use for getting scores.
* @param {array} qualityScoreMap – the score map to fill with keyword data.
*/
function computeQualityData(keywordIterator, qualityScoreMap) {
while (keywordIterator.hasNext()) {
var keyword = keywordIterator.next();
var stats = keyword.getStatsFor(‘LAST_WEEK’);
var data = qualityScoreMap[keyword.getQualityScore()];
if (data) {
data.numKeywords++;
data.totalImpressions += stats.getImpressions();
data.totalClicks += stats.getClicks();
data.totalCost += stats.getCost();
}
}
}

/**
* Uses the given keyword iterator and populates the given position map.
* @param {Iterator} keywordIterator – the keywords to use for getting scores.
* @param {array} positionMap – the map to fill with keyword data.
*/
function computePositionData(keywordIterator, positionMap) {
while (keywordIterator.hasNext()) {
var keyword = keywordIterator.next();
var stats = keyword.getStatsFor(‘LAST_WEEK’);
var index =
Math.min(Math.ceil(stats.getAveragePosition()), POSITION_MAP_SIZE);
var data = positionMap[index];
data.numKeywords++;
data.totalImpressions += stats.getImpressions();
data.totalClicks += stats.getClicks();
data.totalCost += stats.getCost();
}
}

/**
* Produces a formatted string representing a given date in a given time zone.
*
* @param {string} format A format specifier for the string to be produced.
* @param {date} date A date object. Defaults to the current date.
* @param {string} timeZone A time zone. Defaults to the account’s time zone.
* @return {string} A formatted string of the given date in the given time zone.
*/
function getDateStringInTimeZone(format, date, timeZone) {
date = date || new Date();
timeZone = timeZone || AdWordsApp.currentAccount().getTimeZone();
return Utilities.formatDate(date, timeZone, format);
}

/**
* Validates the provided email and throws a descriptive error if the user
* has not changed the email from the default fake address.
*
* @param {string} email The email address.
* @throws {Error} If the email is the default fake address.
*/
function validateEmail(email) {
if (email == ‘[email protected]’) {
throw new Error(‘Please use a valid email address.’);
}
}

/**
* Validates the provided spreadsheet URL
* to make sure that it’s set up properly. Throws a descriptive error message
* if validation fails.
*
* @param {string} spreadsheeturl The URL of the spreadsheet to open.
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
* @throws {Error} If the spreadsheet URL hasn’t been set
*/
function validateAndGetSpreadsheet(spreadsheeturl) {
if (spreadsheeturl == ‘YOUR_SPREADSHEET_URL’) {
throw new Error(
‘Please specify a valid Spreadsheet URL. You can find’ +
‘ a link to a template in the associated guide for this script.’);
}
return SpreadsheetApp.openByUrl(spreadsheeturl);
}

 

Como Configurar

 

5. Relatório da Consulta da Pesquisa – Por Google Ads. Este é um bom script para gerar insights de novas palavras-chave que estão gerando resultado para sua conta ou para negativar termos que usuários estão pesquisando sem correlação com seu negócio. O script utiliza o Relatório de desempenho da consulta da pesquisa para encontrar esses termos.

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Search Query Report
*
* @overview The Search Query Report script uses the Search Query Performance
* Report to find undesired search terms and add them as negative (or
* positive) exact keywords. See
* //developers.google.com/adwords/scripts/docs/solutions/search-query
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.0.1
*
* @changelog
* – version 1.0.1
* – Upgrade to API version v201609.
* – version 1.0
* – Released initial version.
*/

// Minimum number of impressions to consider “enough data”
var IMPRESSIONS_THRESHOLD = 100;
// Cost-per-click (in account currency) we consider an expensive keyword.
var AVERAGE_CPC_THRESHOLD = 1; // $1
// Threshold we use to decide if a keyword is a good performer or bad.
var CTR_THRESHOLD = 0.5; // 0.5%
// If ctr is above threshold AND our conversion cost isn’t too high,
// it’ll become a positive keyword.
var COST_PER_CONVERSION_THRESHOLD = 10; // $10
// One currency unit is one million micro amount.
var MICRO_AMOUNT_MULTIPLIER = 1000000;

/**
* Configuration to be used for running reports.
*/
var REPORTING_OPTIONS = {
// Comment out the following line to default to the latest reporting version.
apiVersion: ‘v201705’
};

function main() {
var report = AdWordsApp.report(
‘SELECT Query, Clicks, Cost, Ctr, ConversionRate,’ +
‘ CostPerConversion, Conversions, CampaignId, AdGroupId ‘ +
‘ FROM SEARCH_QUERY_PERFORMANCE_REPORT ‘ +
‘ WHERE ‘ +
‘ Conversions > 0’ +
‘ AND Impressions > ‘ + IMPRESSIONS_THRESHOLD +
‘ AND AverageCpc > ‘ +
(AVERAGE_CPC_THRESHOLD * MICRO_AMOUNT_MULTIPLIER) +
‘ DURING LAST_7_DAYS’, REPORTING_OPTIONS);
var rows = report.rows();

var negativeKeywords = {};
var positiveKeywords = {};
var allAdGroupIds = {};
// Iterate through search query and decide whether to
// add them as positive or negative keywords (or ignore).
while (rows.hasNext()) {
var row = rows.next();
if (parseFloat(row[‘Ctr’]) < CTR_THRESHOLD) {
addToMultiMap(negativeKeywords, row[‘AdGroupId’], row[‘Query’]);
allAdGroupIds[row[‘AdGroupId’]] = true;
} else if (parseFloat(row[‘CostPerConversion’]) <
COST_PER_CONVERSION_THRESHOLD) {
addToMultiMap(positiveKeywords, row[‘AdGroupId’], row[‘Query’]);
allAdGroupIds[row[‘AdGroupId’]] = true;
}
}

// Copy all the adGroupIds from the object into an array.
var adGroupIdList = [];
for (var adGroupId in allAdGroupIds) {
adGroupIdList.push(adGroupId);
}

// Add the keywords as negative or positive to the applicable ad groups.
var adGroups = AdWordsApp.adGroups().withIds(adGroupIdList).get();
while (adGroups.hasNext()) {
var adGroup = adGroups.next();
if (negativeKeywords[adGroup.getId()]) {
for (var i = 0; i < negativeKeywords[adGroup.getId()].length; i++) {
adGroup.createNegativeKeyword(
‘[‘ + negativeKeywords[adGroup.getId()][i] + ‘]’);
}
}
if (positiveKeywords[adGroup.getId()]) {
for (var i = 0; i < positiveKeywords[adGroup.getId()].length; i++) {
var keywordOperation = adGroup.newKeywordBuilder()
.withText(‘[‘ + positiveKeywords[adGroup.getId()][i] + ‘]’)
.build();
}
}
}
}

function addToMultiMap(map, key, value) {
if (!map[key]) {
map[key] = [];
}
map[key].push(value);
}

 

Como Configurar

 

6. Relatório Kratu – por Google Ads. Em nível de MCC, o script traz um relatório geral de suas múltiplas contas. Além disso, os resultados são mostrados visualmente com um mapa de calor.

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Kratu
*
* @overview The Kratu script is a flexible MCC-level report showing several
* performance signals for each account visually as a heat map. See
* //developers.google.com/adwords/scripts/docs/solutions/kratu
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.0.2
*
* @changelog
* – version 1.0.2
* – Fixed bug with run frequency to allow the script to run daily.
* – version 1.0.1
* – Added validation for external spreadsheet setup.
* – Updated reporting version to v201609.
* – version 1.0
* – Released initial version.
*/

var CONFIG = {
// URL to the main / template spreadsheet
SPREADSHEET_URL: ‘YOUR_SPREADSHEET_URL’
};

/**
* Configuration to be used for running reports.
*/
var REPORTING_OPTIONS = {
// Comment out the following line to default to the latest reporting version.
apiVersion: ‘v201705’
};

/**
* Main method, coordinate and trigger either new report creation or continue
* unfinished report.
*/
function main() {
init();

if (spreadsheetManager.hasUnfinishedRun()) {
continueUnfinishedReport();
} else {
var reportFrequency = settingsManager.getSetting(‘ReportFrequency’, true);
var lastReportStart = spreadsheetManager.getLastReportStartTimestamp();

if (!lastReportStart ||
dayDifference(lastReportStart, getTimestamp()) >= reportFrequency) {
startNewReport();
} else {
debug(‘Nothing to do’);
}
}
}

/**
* Initialization procedures to be done before anything else.
*/
function init() {
spreadsheetManager.readSignalDefinitions();
settingsManager.readSettings();
}

/**
* Continues an unfinished report. This happens whenever there are accounts
* that are not processed within the last report. This method picks these
* up, processes them and marks the report as completed if no accounts are
* left.
*/
function continueUnfinishedReport() {
debug(‘Continuing unfinished report: ‘ +
spreadsheetManager.getCurrentRunSheet().getUrl());

var iterator = spreadsheetManager.getUnprocessedAccountIterator();
var processed = 0;
while (iterator.hasNext() &&
processed++ < settingsManager.getSetting(‘NumAccountsProcess’, true)) {

var account = iterator.next();
processAccount(account);
}

writeAccountDataToSpreadsheet();

if (processed > 0 && spreadsheetManager.allAccountsProcessed()) {
debug(‘All accounts processed, marking report as complete’);

// Remove protection from sheets, allow changes again
spreadsheetManager.removeProtection();

spreadsheetManager.markRunAsProcessed();
sendEmail();
}

debug(‘Processed ‘ + processed + ‘ accounts’);
}

/**
* Creates a new report by copying the report template to a new spreadsheet,
* gathering all accounts under the MCC and mark them as not processed.
* Please note that this method will not actually process any accounts.
*/
function startNewReport() {
debug(‘Creating new report’);

// Protect the sheets that shouldn’t be changed during execution
spreadsheetManager.setProtection();

// Delete all account info
spreadsheetManager.clearAllAccountInfo();

// Iterate over accounts
var accountSelector = MccApp.accounts();
var accountLabel = settingsManager.getSetting(‘AccountLabel’, false);
if (accountLabel) {
accountSelector.withCondition(“LabelNames CONTAINS ‘” + accountLabel + “‘”);
}
var accountIterator = accountSelector.get();

while (accountIterator.hasNext()) {
var account = accountIterator.next();
debug(‘Adding account: ‘ + account.getCustomerId());

spreadsheetManager.addAccount(account.getCustomerId());
}

// Now add the run
var newRunSheet = spreadsheetManager.addRun();
debug(‘New report created at ‘ + newRunSheet.getUrl());
}

/**
* Processes a single account.
*
* @param {object} account the AdWords account object
*/
function processAccount(account) {
debug(‘- Processing ‘ + account.getCustomerId());
MccApp.select(account);
signalManager.processAccount(account);

spreadsheetManager.markAccountAsProcessed(account.getCustomerId());
}

/**
* After processing & gathering data for all accounts,
* write it to the spreadsheet.
*/
function writeAccountDataToSpreadsheet() {
var accountInfos = signalManager.getAccountInfos();

spreadsheetManager.writeHeaderRow();

for (var i = 0; i < accountInfos.length; i++) {
var accountInfo = accountInfos[i];
spreadsheetManager.writeDataRow(accountInfo);
}
}

/**
* Sends email if an email was provided in the settings.
* Otherwise does nothing.
*/
var sendEmail = function() {
var recipientEmail = settingsManager.getSetting(‘RecipientEmail’, false);

if (recipientEmail) {
MailApp.sendEmail(recipientEmail,
‘Kratu Report is ready’,
spreadsheetManager.getCurrentRunSheet().getUrl());
debug(‘Email sent to ‘ + recipientEmail);
}
};

/**
* Returns the number of days between two timestamps.
*
* @param {number} time1 the newer (more recent) timestamps
* @param {number} time2 the older timestamps
* @return {number} number of full days between the given dates
*/
var dayDifference = function(time1, time2) {
return parseInt((time2 – time1) / (24 * 3600 * 1000));
};

/**
* Returns the current timestamp.
*
* @return {number} the current timestamp
*/
function getTimestamp() {
return new Date().getTime();
}

/**
* Module for calculating account signals and infos to be shown in the report.
*
* @return {object} callable functions corresponding to the available
* actions
*/
var signalManager = (function() {
var accountInfos = new Array();

/**
* Processes one account, which in 2 steps adds an accountInfo object
* to the list.
* – Calculate the raw signals
* – Postprocess the raw signals (normalize scores, …)
*
* @param {object} account the AdWords account object
*/
var processAccount = function(account) {
var rawSignals = calculateRawSignals(account);

var accountInfo = {
account: account,
rawSignals: rawSignals
};

processSignals(accountInfo);

accountInfos.push(accountInfo);
};

/**
* Returns an array of all processed accounts so far. These are ordered by
* decreasing score.
*
* @return {object} array of the accountInfo objects
*/
var getAccountInfos = function() {
accountInfos.sort(function(a, b) {
return b.score – a.score;
});

return accountInfos;
};

/**
* Normalizes a raw signal value based in the signal’s definition
* (min, max values).
*
* @param {object} signalDefinition definition of the signal
* @param {number} value numeric value of that signal
* @return {number} the normalized value
*/
var normalize = function(signalDefinition, value) {
var min = signalDefinition.min;
var max = signalDefinition.max;

if (signalDefinition.direction == ‘High’) {
if (value >= max)
return 1;
if (value <= min)
return 0;

return (value – min) / (max – min);
} else if (signalDefinition.direction == ‘Low’) {
if (value >= max)
return 0;
if (value <= min)
return 1;

return 1 – ((value – min) / (max – min));
} else {
return value;
}
};

/**
* Post-processes the raw signals.
*
* @param {object} accountInfo the object storing all info about that account
* (including raw signals)
*/
var processSignals = function(accountInfo) {
var signalDefinitions = spreadsheetManager.getSignalDefinitions();
var sumWeights = spreadsheetManager.getSumWeights();
var sumScore = 0;

accountInfo.signals = {};

for (var i = 0; i < signalDefinitions.length; i++) {
var signalDefinition = signalDefinitions[i];
if (signalDefinition.includeInReport == ‘Yes’) {
var value = accountInfo.rawSignals[signalDefinition.name];

accountInfo.signals[signalDefinition.name] = {
definition: signalDefinition,
value: value,
displayValue: value
};

if (signalDefinition.type == ‘Number’) {
var normalizedValue = normalize(signalDefinition, value);
var signalScore = normalizedValue * signalDefinition.weight;
sumScore += signalScore;

accountInfo.signals[signalDefinition.name].normalizedValue =
normalizedValue;
accountInfo.signals[signalDefinition.name].signalScore = signalScore;
}
}
}

accountInfo.scoreSum = sumScore;
accountInfo.scoreWeights = sumWeights;
accountInfo.score = sumScore / sumWeights;
};

/**
* Calculate the raw signals.
*
* @param {object} account the AdWords account object
* @return {object} an associative array containing raw signals
* (as name -> value pairs)
*/
var calculateRawSignals = function(account) {
// Use reports for signal creation, dynamically create an AWQL query here
var signalDefinitions = spreadsheetManager.getSignalDefinitions();

var signalFields = [];
for (var i = 0; i < signalDefinitions.length; i++) {
var signalDefinition = signalDefinitions[i];
signalFields.push(signalDefinition.name);
}

var query = ‘SELECT ‘ + signalFields.join(‘,’) +
‘ FROM ACCOUNT_PERFORMANCE_REPORT DURING ‘ +
settingsManager.getSetting(‘ReportPeriod’, true);

var report = AdWordsApp.report(query, REPORTING_OPTIONS);
var rows = report.rows();

// analyze the rows (should be only one)
var rawSignals = {};
while (rows.hasNext()) {
var row = rows.next();

for (var i = 0; i < signalDefinitions.length; i++) {
var signalDefinition = signalDefinitions[i];

var value = row[signalDefinition.name];
if (value.indexOf(‘%’) > -1) {
value = parseFloat(value) / 100.0;
}

rawSignals[signalDefinition.name] = value;
}

}

return rawSignals;
};

// Return the external interface.
return {
processAccount: processAccount,
getAccountInfos: getAccountInfos
};

})();

/**
* Module for interacting with the spreadhsheets. Offers several
* functions that other modules can use when storing / retrieving data
* In general, there are two spreadsheets involved:
* – a main spreadsheet containing processing information, settings
* and a template for the reports
* – a report spreadsheet for each run (one loop over all accounts)
*
* @return {object} callable functions corresponding to the available
* actions
*/
var spreadsheetManager = (function() {
validateConfig();
var spreadsheet = SpreadsheetApp.openByUrl(CONFIG.SPREADSHEET_URL);
var currentRunSheet = null;
var accountsTab = spreadsheet.getSheetByName(‘Accounts’);
var historyTab = spreadsheet.getSheetByName(‘History’);
var signalsTab = spreadsheet.getSheetByName(‘Signals’);
var settingsTab = spreadsheet.getSheetByName(‘Settings’);
var templateTab = spreadsheet.getSheetByName(‘Template’);
var processedAccounts = 0;
var signalDefinitions;
var sumWeights;

/**
* Adds protection and notes to all sheets that should not be
* changed while a report is being processed.
*/
var setProtection = function() {
setSheetProtection(signalsTab);
setSheetProtection(settingsTab);
setSheetProtection(templateTab);
};

/**
* Adds protection and notes to a sheet / tab.
*
* @param {object} the sheet to add protection to
*/
var setSheetProtection = function(tab) {
var protection = tab.protect().setDescription(tab.getName() +
‘ Protection’);

protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
tab.getRange(‘A1’).setNote(‘A report is currently being executed, ‘ +
‘you can not edit this sheet until it is finished.’);
};

/**
* Adds a protection and notes to all sheets that should not be
* changed while a report is being processed.
*/
var removeProtection = function() {
removeSheetProtection(signalsTab);
removeSheetProtection(settingsTab);
removeSheetProtection(templateTab);
};

/**
* Remove the protection from a sheet / tab.
*
* @param {object} the sheet to remove protection from
*/
var removeSheetProtection = function(tab) {
var protection = tab.getProtections(SpreadsheetApp.ProtectionType.SHEET)[0];
if (protection && protection.canEdit()) {
protection.remove();
}
tab.clearNotes();
};

/**
* Reads and returns the range of settings in the main spreadsheet.
*
* @return {object} the range object containing all settings
*/
var readSettingRange = function() {
return settingsTab.getRange(2, 1, settingsTab.getLastRow(), 3);
};

/**
* Read and return the signal definitions as defined in the Signals tab
* of the general spreadsheet. See below for how a signal definition object
* looks like.
*
* @param {object} range the range of cells
* @return {object} an array of signal definition objects
*/
var readSignalDefinitions = function() {
signalDefinitions = new Array();

var range = signalsTab.getRange(2, 1, signalsTab.getLastRow(), 9);
var values = range.getValues();
for (var i = 0; i < range.getNumRows(); i++) {
if (values[i][0] == ”)
continue;

var signalDefinition = {
name: values[i][0],
displayName: values[i][1],
includeInReport: values[i][2],
type: values[i][3],
direction: values[i][4],
format: values[i][5],
weight: values[i][6],
min: values[i][7],
max: values[i][8]
};

signalDefinitions.push(signalDefinition);
}

calculateSumWeights();

debug(‘Using ‘ + signalDefinitions.length + ‘ signals’);
};

/**
* Returns an array of signal definitions to work with.
*
* @return {object} array of signal definitions to work with
*/
var getSignalDefinitions = function() {
return signalDefinitions;
};

/**
* Returns the sum of weights of all signal definitions
*
* @return {number} sum of weights of all signal definitions
*/
var getSumWeights = function() {
return sumWeights;
};

/**
* Calculates the overall sum of score weights for normalization of the score.
*/
var calculateSumWeights = function() {
sumWeights = 0;

for (var i = 0; i < signalDefinitions.length; i++) {
var signalDefinition = signalDefinitions[i];
if (signalDefinition.type == ‘Number’ &&
signalDefinition.includeInReport == ‘Yes’) {
sumWeights += signalDefinition.weight;
}
}
};

/**
* Adds a “run” (loop over all accounts) to the general spreadsheet.
*/
var addRun = function() {
// use formatted date in spreadsheet name and date cell
var timezone = AdWordsApp.currentAccount().getTimeZone();
var formattedDate = Utilities.formatDate(new Date(),
timezone, ‘MMM dd, yyyy’);

var runSpreadsheet = spreadsheet.copy(spreadsheet.getName() +
‘ – ‘ + formattedDate);

runSpreadsheet.deleteSheet(runSpreadsheet.getSheetByName(‘Accounts’));
runSpreadsheet.deleteSheet(runSpreadsheet.getSheetByName(‘History’));
runSpreadsheet.deleteSheet(runSpreadsheet.getSheetByName(‘Settings’));
runSpreadsheet.deleteSheet(runSpreadsheet.getSheetByName(‘Parameters’));
runSpreadsheet.deleteSheet(runSpreadsheet.getSheetByName(‘Signals’));
runSpreadsheet.getSheetByName(‘Template’).setName(‘Report’);
removeSheetProtection(runSpreadsheet.getSheetByName(‘Report’));

historyTab.appendRow([getTimestamp(), null, runSpreadsheet.getUrl()]);
historyTab.getRange(historyTab.getLastRow(), 1, 1, 3).clearFormat();

runSpreadsheet.getRangeByName(‘AccountID’).setValue(
AdWordsApp.currentAccount().getCustomerId());
runSpreadsheet.getRangeByName(‘Date’).setValue(formattedDate);

return runSpreadsheet;
};

/**
* Checks if there is an unfinished (=not all accounts processed yet)
* report in the run history list.
*
* @return {boolean} whether there is an unfinished report
*/
var hasUnfinishedRun = function() {
var lastRow = historyTab.getLastRow();

// has no run at all
if (lastRow == 1) {
return false;
}

var lastRunEndDate = historyTab.getRange(lastRow, 2, 1, 1).getValue();
if (lastRunEndDate) {
return false;
}

return true;
};

/**
* Marks the current report (a.k.a run) as finished by adding an end date.
*/
var markRunAsProcessed = function() {
var lastRow = historyTab.getLastRow();
if (lastRow > 1) {
historyTab.getRange(lastRow, 2, 1, 1).setValue(getTimestamp());
}
};

/**
* Returns the start timestamp of the last unfinished report.
*
* @return {number} the timestamp of the last unfinished report (null if
* there is none)
*/
var getLastReportStartTimestamp = function() {
var lastRow = historyTab.getLastRow();
if (lastRow > 1) {
return historyTab.getRange(lastRow, 1, 1, 1).getValue();
} else {
return null;
}
};

/**
* Returns the current run sheet to be used for report generation.
* This is always the last one in the History tab of the general sheet.
*
* @return {object} the current run sheet
*/
var getCurrentRunSheet = function() {
if (currentRunSheet != null)
return currentRunSheet;

var range = historyTab.getRange(historyTab.getLastRow(), 3, 1, 1);
var url = range.getValue();
currentRunSheet = SpreadsheetApp.openByUrl(url);
return currentRunSheet;
};

/**
* Adds an account to the list of ‘known’ accounts.
*
* @param {string} cid the cid of the account
*/
var addAccount = function(cid) {
var maxRow = accountsTab.appendRow([cid]);
accountsTab.getRange(accountsTab.getLastRow(), 1, 1, 2).clearFormat();
};

/**
* Marks an account as processed in the general sheet. Like this,
* the script can be executed several times and will always
* run for a batch of unprocessed accounts.
*
* @param {string} cid the customer id of the account that has been processed
*/
var markAccountAsProcessed = function(cid) {
var range = accountsTab.getRange(2, 1, accountsTab.getLastRow() – 1, 2);

var values = range.getValues();
for (var i = 0; i < range.getNumRows(); i++) {
var rowCid = values[i][0];
if (cid == rowCid) {
accountsTab.getRange(i + 2, 2).setValue(getTimestamp());
processedAccounts++;
}
}

};

/**
* Clears the list of ‘known’ accounts.
*/
var clearAllAccountInfo = function() {
var lastRow = accountsTab.getLastRow();

if (lastRow > 1) {
accountsTab.deleteRows(2, lastRow – 1);
}
};

/**
* Creates a selector for the next batch of accounts that are not
* processed yet.
*
* @return {object} a selector that can be used for parallel processing or
* getting an iterator
*/
var getUnprocessedAccountIterator = function() {
var accounts = getUnprocessedAccounts();

var selector = MccApp.accounts().withIds(accounts);
var iterator = selector.get();
return iterator;
};

/**
* Reads and returns the next batch of unprocessed accounts from the general
* spreadsheet.
*
* @return {object} an array of unprocessed cids
*/
var getUnprocessedAccounts = function() {
var accounts = [];

var range = accountsTab.getRange(2, 1, accountsTab.getLastRow() – 1, 2);

for (var i = 0; i < range.getNumRows(); i++) {
var cid = range.getValues()[i][0];
var processed = range.getValues()[i][1];

if (processed != ” || accounts.length >=
settingsManager.getSetting(‘NumAccountsProcess’, true)) {
continue;
}

accounts.push(cid);
}

return accounts;
};

/**
* Scans the list of accounts and returns true if all of them
* are processed.
*
* @return {boolean} true, if all accounts are processed
*/
var allAccountsProcessed = function() {
var range = accountsTab.getRange(2, 1, accountsTab.getLastRow() – 1, 2);

for (var i = 0; i < range.getNumRows(); i++) {
var cid = range.getValues()[i][0];
var processed = range.getValues()[i][1];

if (processed) {
continue;
}

return false;
}

return true;
};

/**
* Writes the data headers (signal names) in the current run sheet.
*/
var writeHeaderRow = function() {
var sheet = getCurrentRunSheet();
var reportTab = sheet.getSheetByName(‘Report’);

var row = [”];
for (var i = 0; i < signalDefinitions.length; i++) {
var signalDefinition = signalDefinitions[i];
if (signalDefinition.includeInReport == ‘Yes’) {
row.push(signalDefinition.displayName);
}
}
row.push(‘Score’);

var range = reportTab.getRange(4, 1, 1, row.length);
range.setValues([row]);
range.clearFormat();
range.setFontWeight(‘bold’);
range.setBackground(‘#38c’);
range.setFontColor(‘#fff’);
};

/**
* Writes a row of data (signal values) in the current run sheet.
*
* @param {object} accountInfo the accountInfo object containing the
* calculated signals
*/
var writeDataRow = function(accountInfo) {
// prepare the data
var sheet = getCurrentRunSheet();
var tab = sheet.getSheetByName(‘Report’);

var row = [”];
for (var i = 0; i < signalDefinitions.length; i++) {
var signalDefinition = signalDefinitions[i];
if (signalDefinition.includeInReport == ‘Yes’) {
var displayValue =
accountInfo.signals[signalDefinition.name].displayValue;

row.push(displayValue);
}
}
row.push(accountInfo.score);

// write it
tab.appendRow(row);

// now do the formatting
var currentRow = tab.getLastRow();
var rowRange = tab.getRange(currentRow, 1, 1, row.length);
rowRange.clearFormat();

// arrays for number formats and colors, first fill them with values
// and later apply to the row
var dataRange = tab.getRange(currentRow, 2, 1, row.length – 1);
var fontColors = [[]];
var backgroundColors = [[]];
var numberFormats = [[]];
var colIndex = 0;

for (var i = 0; i < signalDefinitions.length; i++) {
var signalDefinition = signalDefinitions[i];
if (signalDefinition.includeInReport == ‘Yes’) {
var value = accountInfo.signals[signalDefinition.name].value;
var displayValue =
accountInfo.signals[signalDefinition.name].displayValue;
var normalizedValue =
accountInfo.signals[signalDefinition.name].normalizedValue;

var colors = [2];
if (signalDefinition.type == ‘Number’) {
numberFormats[0][colIndex] = signalDefinition.format;
colors = getNumberColors(normalizedValue);
} else if (signalDefinition.type == ‘String’) {
colors = getStringColors(value);
}

fontColors[0][colIndex] = colors[0];
backgroundColors[0][colIndex] = colors[1];

colIndex++;
}
}

// formatting for the score (last column)
numberFormats[0][colIndex] = ‘0.00%’;
var scoreColors = getNumberColors(accountInfo.score);
fontColors[0][colIndex] = scoreColors[0];
backgroundColors[0][colIndex] = scoreColors[1];

// now actually apply the formats
dataRange.setNumberFormats(numberFormats);
dataRange.setFontColors(fontColors);
dataRange.setBackgroundColors(backgroundColors);
};

/**
* Helper method for creating the array of colors based on the given
* setting names.
*
* @param {string} settingFontColor name of the setting to use as font color
* @param {string} settingBackgroundColor name of the setting to use as
* background color
* return {object} an array with the colors to apply
* (index 0 -> font color, index 1 -> background color)
*/
var getColors = function(settingFontColor, settingBackgroundColor) {
var colors = [];

colors[0] = settingsManager.getSetting(settingFontColor, false);
colors[1] = settingsManager.getSetting(settingBackgroundColor, false);

return colors;
};

/**
* Helper method for returning the “string” colors for a certain value.
*
* @param {string} stringValue the value of the cell
* return {object} an array with the colors to apply
* (index 0 -> font color, index 1 -> background color)
*/
var getStringColors = function(stringValue) {
return getColors(‘StringFgColor’, ‘StringBgColor’);
};

/**
* Helper method for applying the “number” format to a certain range.
* Numeric value cells have different formats depending on their score value
* (defined by the settings), this method applies these formats.
*
* @param {number} numericValue the value of the cell
* return {object} an array with the colors to apply
* (index 0 -> font color, index 1 -> background color)
*/
var getNumberColors = function(numericValue) {
var level1MinValue = settingsManager.getSetting(‘Level1MinValue’, false);
var level2MinValue = settingsManager.getSetting(‘Level2MinValue’, false);
var level3MinValue = settingsManager.getSetting(‘Level3MinValue’, false);
var level4MinValue = settingsManager.getSetting(‘Level4MinValue’, false);
var level5MinValue = settingsManager.getSetting(‘Level5MinValue’, false);

if (level5MinValue && numericValue > level5MinValue) {
return getColors(‘Level5FgColor’, ‘Level5BgColor’);
} else if (level4MinValue && numericValue > level4MinValue) {
return getColors(‘Level4FgColor’, ‘Level4BgColor’);
} else if (level3MinValue && numericValue > level3MinValue) {
return getColors(‘Level3FgColor’, ‘Level3BgColor’);
} else if (level2MinValue && numericValue > level2MinValue) {
return getColors(‘Level2FgColor’, ‘Level2BgColor’);
} else if (level1MinValue && numericValue > level1MinValue) {
return getColors(‘Level1FgColor’, ‘Level1BgColor’);
}

// if no level reached, no coloring
var defaultColors = [null, null];
return defaultColors;
};

// Return the external interface.
return {
setProtection: setProtection,
removeProtection: removeProtection,
readSettingRange: readSettingRange,
readSignalDefinitions: readSignalDefinitions,
getSignalDefinitions: getSignalDefinitions,
getSumWeights: getSumWeights,
addRun: addRun,
hasUnfinishedRun: hasUnfinishedRun,
markRunAsProcessed: markRunAsProcessed,
getLastReportStartTimestamp: getLastReportStartTimestamp,
getCurrentRunSheet: getCurrentRunSheet,
addAccount: addAccount,
markAccountAsProcessed: markAccountAsProcessed,
clearAllAccountInfo: clearAllAccountInfo,
getUnprocessedAccountIterator: getUnprocessedAccountIterator,
allAccountsProcessed: allAccountsProcessed,
writeHeaderRow: writeHeaderRow,
writeDataRow: writeDataRow
};

})();

/**
* Module responsible for maintaining a list of common settings. These
* settings are read from the general spreadsheet (using the
* spreadsheetManager) and are then retrieved by other modules during
* processing.
*
* @return {object} callable functions corresponding to the available
* actions
*/
var settingsManager = (function() {
var settings = [];

/**
* Reads the settings from the general spreadsheet.
*/
var readSettings = function() {
var settingsRange = spreadsheetManager.readSettingRange();

for (var i = 1; i <= settingsRange.getNumRows(); i++) {
var key = settingsRange.getCell(i, 1).getValue();
var type = settingsRange.getCell(i, 2).getValue();
var value = settingsRange.getCell(i, 3).getValue();

if (type == ‘Color’) {
value = settingsRange.getCell(i, 3).getBackground();
}

if (!key || !value) {
continue;
}

var setting = {
key: key,
type: type,
value: value
};

settings.push(setting);
}

debug(‘Read ‘ + settings.length + ‘ settings’);
};

/**
* Returns the value of a particular setting.
*
* @param {string} key the name of the setting
* @param {boolean} mandatory flag indicating this is a mandatory setting
* (has to return a value)
* @return {object} the value of the setting
*/
var getSetting = function(key, mandatory) {
for (var i = 0; i < settings.length; i++) {
var setting = settings[i];
if (setting.key == key && setting.value)
return setting.value;
}

if (mandatory) {
throw ‘Setting \” + key + ‘\’ is not set!’;
}

return null;
};

// Return the external interface.
return {
readSettings: readSettings,
getSetting: getSetting
};
})();

/**
* Wrapper for Logger.log.
*
* @param {string} t The text to log
*/
function debug(t) {
Logger.log(t);
}

/**
* Validates the provided spreadsheet URL to make sure that it’s set up
* properly. Throws a descriptive error message if validation fails.
*
* @throws {Error} If the spreadsheet URL hasn’t been set
*/
function validateConfig() {
if (CONFIG.SPREADSHEET_URL == ‘YOUR_SPREADSHEET_URL’) {
throw new Error(‘Please specify a valid Spreadsheet URL. You can find’ +
‘ a link to a template in the associated guide for this script.’);
}
}

 

Como Configurar

 

7. PageSpeed Insights: análise para dispositivos móveis – Por Google Ads. O Google tem enfatizado a experiência mobile cada vez mais em suas atualizações, sendo assim, esse script traz um relatório do Google Page Insights para suas páginas de destino.

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the “License”);
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @fileoverview Mobile Performance from PageSpeed Insights – Single Account
*
* Produces a report showing how well landing pages are set up for mobile
* devices and highlights potential areas for improvement. See :
* //developers.google.com/adwords/scripts/docs/solutions/mobile-pagespeed
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.3.3
*
* @changelog
* – version 1.3.3
* – Added guidance for desktop analysis.
* – version 1.3.2
* – Bugfix to improve table sorting comparator.
* – version 1.3.1
* – Bugfix for handling the absence of optional values in PageSpeed response.
* – version 1.3
* – Removed the need for the user to take a copy of the spreadsheet.
* – Added the ability to customize the Campaign and Ad Group limits.
* – version 1.2.1
* – Improvements to time zone handling.
* – version 1.2
* – Bug fix for handling empty results from URLs.
* – Error reporting in spreadsheet for failed URL fetches.
* – version 1.1
* – Updated the comments to be in sync with the guide.
* – version 1.0
* – Released initial version.
*/

// See “Obtaining an API key” at
// //developers.google.com/adwords/scripts/docs/solutions/mobile-pagespeed
var API_KEY = ‘INSERT_PAGESPEED_API_KEY_HERE’;
var EMAIL_RECIPIENTS = [‘INSERT_EMAIL_ADDRESS_HERE’];

// If you wish to add extra URLs to checked, list them here as a
// comma-separated list eg. [‘//abc.xyz’, ‘//www.google.com’]
var EXTRA_URLS_TO_CHECK = [];

// By default, the script returns analysis of how the site performs on mobile.
// Change the following from ‘mobile’ to ‘desktop’ to perform desktop analysis.
var PLATFORM_TYPE = ‘mobile’;

/**
* The URL of the template spreadsheet for each report created.
*/
var SPREADSHEET_TEMPLATE =
‘//docs.google.com/spreadsheets/d/1SKLXUiorvgs2VuPKX7NGvcL68pv3xEqD7ZcqsEwla4M/edit’;

var PAGESPEED_URL =
‘//www.googleapis.com/pagespeedonline/v2/runPagespeed?’;

/*
* The maximum number of Campaigns to sample within the account.
*/
var CAMPAIGN_LIMIT = 50000;

/*
* The maximum number of Ad Groups to sample within the account.
*/
var ADGROUP_LIMIT = 50000;

/**
* These are the sampling limits for how many of each element will be examined
* in each AdGroup.
*/
var KEYWORD_LIMIT = 20;
var SITELINK_LIMIT = 20;
var AD_LIMIT = 30;

/**
* Specifies the amount of time in seconds required to do the URL fetching and
* result generation. As this is the last step, entities in the account will be
* iterated over until this point.
*/
var URL_FETCH_TIME_SECS = 8 * 60;

/**
* Specifies the amount of time in seconds required to write to and format the
* spreadsheet.
*/
var SPREADSHEET_PREP_TIME_SECS = 4 * 60;

/**
* Represents the number of retries to use with the PageSpeed service.
*/
var MAX_RETRIES = 3;

/**
* The main entry point for execution.
*/
function main() {
if (!defaultsChanged()) {
Logger.log(‘Please change the default configuration values and retry’);
return;
}
var accountName = AdWordsApp.currentAccount().getName();
var urlStore = getUrlsFromAccount();
var result = getPageSpeedResultsForUrls(urlStore);
var spreadsheet = createPageSpeedSpreadsheet(accountName +
‘: PageSpeed Insights – Mobile Analysis’, result);
spreadsheet.addEditors(EMAIL_RECIPIENTS);
sendEmail(spreadsheet.getUrl());
}

/**
* Sends an email to the user with the results of the run.
*
* @param {string} url URL of the spreadsheet.
*/
function sendEmail(url) {
var footerStyle = ‘color: #aaaaaa; font-style: italic;’;
var scriptsLink = ‘//developers.google.com/adwords/scripts/’;
var subject = ‘AdWords PageSpeed URL-Sampling Script Results – ‘ +
getDateStringInTimeZone(‘dd MMM yyyy’);
var htmlBody = ‘<html><body>’ +
‘<p>Hello,</p>’ +
‘<p>An AdWords Script has run successfully and the output is available ‘ +
‘here:’ +
‘<ul><li><a href=”‘ + url +
‘”>AdWords PageSpeed URL-Sampling Script Results</a></li></ul></p>’ +
‘<p>Regards,</p>’ +
‘<span style=”‘ + footerStyle + ‘”>This email was automatically ‘ +
‘generated by <a href=”‘ + scriptsLink + ‘”>AdWords Scripts</a>.<span>’ +
‘</body></html>’;
var body = ‘Please enable HTML to view this report.’;
var options = {htmlBody: htmlBody};
MailApp.sendEmail(EMAIL_RECIPIENTS, subject, body, options);
}

/**
* Checks to see that placeholder defaults have been changed.
*
* @return {boolean} true if placeholders have been changed, false otherwise.
*/
function defaultsChanged() {
if (API_KEY == ‘INSERT_PAGESPEED_API_KEY_HERE’ ||
SPREADSHEET_TEMPLATE == ‘INSERT_SPREADSHEET_URL_HERE’ ||
JSON.stringify(EMAIL_RECIPIENTS) ==
JSON.stringify([‘INSERT_EMAIL_ADDRESS_HERE’])) {
return false;
}
return true;
}

/**
* Creates a new PageSpeed spreadsheet and populates it with result data.
*
* @param {string} name The name to give to the spreadsheet.
* @param {Object} pageSpeedResult The result from PageSpeed, and the number of
* URLs that could have been chosen from.
* @return {Spreadsheet} The newly-created spreadsheet.
*/
function createPageSpeedSpreadsheet(name, pageSpeedResult) {
var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_TEMPLATE).copy(name);
spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone());
var data = pageSpeedResult.table;
var activeSheet = spreadsheet.getActiveSheet();
var rowStub = spreadsheet.getRangeByName(‘ResultRowStub’);
var top = rowStub.getRow();
var left = rowStub.getColumn();
var cols = rowStub.getNumColumns();
if (data.length > 2) { // No need to extend the template if num rows <= 2
activeSheet.insertRowsAfter(
spreadsheet.getRangeByName(‘EmptyUrlRow’).getRow(), data.length);
rowStub.copyTo(activeSheet.getRange(top + 1, left, data.length – 2, cols));
}
// Extend the formulas and headings to accommodate the data.
if (data.length && data[0].length > 4) {
var metricsRange = activeSheet
.getRange(top – 6, left + cols, data.length + 5, data[0].length – 4);
activeSheet.getRange(top – 6, left + cols – 1, data.length + 5)
.copyTo(metricsRange);
// Paste in the data values.
activeSheet.getRange(top – 1, left, data.length, data[0].length)
.setValues(data);
// Move the ‘Powered by AdWords Scripts’ to right corner of table.
spreadsheet.getRangeByName(‘PoweredByText’).moveTo(activeSheet.getRange(1,
data[0].length + 1, 1, 1));
// Set summary – date and number of URLs chosen from.
var summaryDate = getDateStringInTimeZone(‘dd MMM yyyy’);
spreadsheet.getRangeByName(‘SummaryDate’).setValue(‘Summary as of ‘ +
summaryDate + ‘. Results drawn from ‘ + pageSpeedResult.totalUrls +
‘ URLs.’);
}
// Add errors if they exist
if (pageSpeedResult.errors.length) {
var nextRow = spreadsheet.getRangeByName(‘FirstErrorRow’).getRow();
var errorRange = activeSheet.getRange(nextRow, 2,
pageSpeedResult.errors.length, 2);
errorRange.setValues(pageSpeedResult.errors);
}
return spreadsheet;
}

/**
* This function takes a collection of URLs as provided by the UrlStore object
* and gets results from the PageSpeed service. However, two important things :
* (1) It only processes a handful, as determined by URL_LIMIT.
* (2) The URLs returned from iterating on the UrlStore are in a specific
* order, designed to produce as much variety as possible (removing the need
* to process all the URLs in an account.
*
* @param {UrlStore} urlStore Object containing URLs to process.
* @return {Object} An object with three properties: ‘table’ – the 2d table of
* results, ‘totalUrls’ – the number of URLs chosen from, and errors.
*/
function getPageSpeedResultsForUrls(urlStore) {
var count = 0;
// Associative array for column headings and contextual help URLs.
var headings = {};
var errors = {};
// Record results on a per-URL basis.
var pageSpeedResults = {};
var urlTotalCount = 0;

for (var url in urlStore) {
if (hasRemainingTimeForUrlFetches()) {
var result = getPageSpeedResultForSingleUrl(url);
if (!result.error) {
pageSpeedResults[url] = result.pageSpeedInfo;
var columnsResult = result.columnsInfo;
// Loop through each heading element; the PageSpeed Insights API
// doesn’t always return URLs for each column heading, so aggregate
// these across each call to get the most complete list.
var columnHeadings = Object.keys(columnsResult);
for (var i = 0, lenI = columnHeadings.length; i < lenI; i++) {
var columnHeading = columnHeadings[i];
if (!headings[columnHeading] || (headings[columnHeading] &&
headings[columnHeading].length <
columnsResult[columnHeading].length)) {
headings[columnHeading] = columnsResult[columnHeading];
}
}
} else {
errors[url] = result.error;
}
count++;
}
urlTotalCount++;
}

var tableHeadings = [‘URL’, ‘Speed’, ‘Usability’];
var headingKeys = Object.keys(headings);
for (var y = 0, lenY = headingKeys.length; y < lenY; y++) {
tableHeadings.push(headingKeys[y]);
}

var table = [];
var pageSpeedResultsUrls = Object.keys(pageSpeedResults);
for (var r = 0, lenR = pageSpeedResultsUrls.length; r < lenR; r++) {
var resultUrl = pageSpeedResultsUrls[r];
var row = [toPageSpeedHyperlinkFormula(resultUrl)];
var data = pageSpeedResults[resultUrl];
for (var j = 1, lenJ = tableHeadings.length; j < lenJ; j++) {
row.push(data[tableHeadings[j]]);
}
table.push(row);
}
// Present the table back in the order worst-performing-first.
table.sort(function(first, second) {
var f1 = isNaN(parseInt(first[1])) ? 0 : parseInt(first[1]);
var f2 = isNaN(parseInt(first[2])) ? 0 : parseInt(first[2]);
var s1 = isNaN(parseInt(second[1])) ? 0 : parseInt(second[1]);
var s2 = isNaN(parseInt(second[2])) ? 0 : parseInt(second[2]);

if (f1 + f2 < s1 + s2) {
return -1;
} else if (f1 + f2 > s1 + s2) {
return 1;
}
return 0;
});

// Add hyperlinks to all column headings where they are available.
for (var h = 0, lenH = tableHeadings.length; h < lenH; h++) {
// Sheets cannot have multiple links in a single cell at the moment :-/
if (headings[tableHeadings[h]] &&
typeof(headings[tableHeadings[h]]) === ‘object’) {
tableHeadings[h] = ‘=HYPERLINK(“‘ + headings[tableHeadings[h]][0] +
‘”,”‘ + tableHeadings[h] + ‘”)’;
}
}

// Form table from errors
var errorTable = [];
var errorKeys = Object.keys(errors);
for (var k = 0; k < errorKeys.length; k++) {
errorTable.push([errorKeys[k], errors[errorKeys[k]]]);
}
table.unshift(tableHeadings);
return {
table: table,
totalUrls: urlTotalCount,
errors: errorTable
};
}

/**
* Given a URL, returns a spreadsheet formula that displays the URL yet links to
* the PageSpeed URL for examining this.
*
* @param {string} url The URL to embed in the Hyperlink formula.
* @return {string} A string representation of the spreadsheet formula.
*/
function toPageSpeedHyperlinkFormula(url) {
return ‘=HYPERLINK(“‘ +
‘//developers.google.com/speed/pagespeed/insights/?url=’ + url +
‘&tab=’ + PLATFORM_TYPE +'”,”‘ + url + ‘”)’;
}

/**
* Creates an object of results metrics from the parsed results of a call to
* the PageSpeed service.
*
* @param {Object} parsedPageSpeedResponse The object returned from PageSpeed.
* @return {Object} An associative array with entries for each metric.
*/
function extractResultRow(parsedPageSpeedResponse) {
var urlScores = {};
if (parsedPageSpeedResponse.ruleGroups) {
var ruleGroups = parsedPageSpeedResponse.ruleGroups;
// At least one of the SPEED or USABILITY properties will exist, but not
// necessarily both.
urlScores.Speed = ruleGroups.SPEED ? ruleGroups.SPEED.score : ‘-‘;
urlScores.Usability = ruleGroups.USABILITY ?
ruleGroups.USABILITY.score : ‘-‘;
}
if (parsedPageSpeedResponse.formattedResults &&
parsedPageSpeedResponse.formattedResults.ruleResults) {
var resultParts = parsedPageSpeedResponse.formattedResults.ruleResults;
for (var partName in resultParts) {
var part = resultParts[partName];
urlScores[part.localizedRuleName] = part.ruleImpact;
}
}
return urlScores;
}

/**
* Extracts the headings for the metrics returned from PageSpeed, and any
* associated help URLs.
*
* @param {Object} parsedPageSpeedResponse The object returned from PageSpeed.
* @return {Object} An associative array used to store column-headings seen in
* the response. This can take two forms:
* (1) {‘heading’:’heading’, …} – this form is where no help URLs are
* known.
* (2) {‘heading’: [url1, …]} – where one or more URLs is returned that
* provides help on the particular heading item.
*/
function extractColumnsInfo(parsedPageSpeedResponse) {
var columnsInfo = {};
if (parsedPageSpeedResponse.formattedResults &&
parsedPageSpeedResponse.formattedResults.ruleResults) {
var resultParts = parsedPageSpeedResponse.formattedResults.ruleResults;
for (var partName in resultParts) {
var part = resultParts[partName];
if (!columnsInfo[part.localizedRuleName]) {
columnsInfo[part.localizedRuleName] = part.localizedRuleName;
}
// Find help URLs in the response
var summary = part.summary;
if (summary && summary.args) {
var argList = summary.args;
for (var i = 0, lenI = argList.length; i < lenI; i++) {
var arg = argList[i];
if ((arg.type) && (arg.type == ‘HYPERLINK’) &&
(arg.key) && (arg.key == ‘LINK’) &&
(arg.value)) {
columnsInfo[part.localizedRuleName] = [arg.value];
}
}
}
if (part.urlBlocks) {
var blocks = part.urlBlocks;
var urls = [];
for (var j = 0, lenJ = blocks.length; j < lenJ; j++) {
var block = blocks[j];
if (block.header) {
var header = block.header;
if (header.args) {
var args = header.args;
for (var k = 0, lenK = args.length; k < lenK; k++) {
var argument = args[k];
if ((argument.type) &&
(argument.type == ‘HYPERLINK’) &&
(argument.key) &&
(argument.key == ‘LINK’) &&
(argument.value)) {
urls.push(argument.value);
}
}
}
}
}
if (urls.length > 0) {
columnsInfo[part.localizedRuleName] = urls;
}
}
}
}
return columnsInfo;
}

/**
* Extracts a suitable error message to display for a failed URL. The error
* could be passed in in the nested PageSpeed error format, or there could have
* been a more fundamental error in the fetching of the URL. Extract the
* relevant message in each case.
*
* @param {string} errorMessage The error string.
* @return {string} A formatted error message.
*/
function formatErrorMessage(errorMessage) {
var formattedMessage = null;
if (!errorMessage) {
formattedMessage = ‘Unknown error message’;
} else {
try {
var parsedError = JSON.parse(errorMessage);
// This is the nested structure expected from PageSpeed
if (parsedError.error && parsedError.error.errors) {
var firstError = parsedError.error.errors[0];
formattedMessage = firstError.message;
} else if (parsedError.message) {
formattedMessage = parsedError.message;
} else {
formattedMessage = errorMessage.toString();
}
} catch (e) {
formattedMessage = errorMessage.toString();
}
}
return formattedMessage;
}

/**
* Calls the PageSpeed API for a single URL, and attempts to parse the resulting
* JSON. If successful, produces an object for the metrics returned, and an
* object detailing the headings and help URLs seen.
*
* @param {string} url The URL to run PageSpeed for.
* @return {Object} An object with pageSpeed metrics, column-heading info
* and error properties.
*/
function getPageSpeedResultForSingleUrl(url) {
var parsedResponse = null;
var errorMessage = null;
var retries = 0;

while ((!parsedResponse || parsedResponse.responseCode !== 200) &&
retries < MAX_RETRIES) {
errorMessage = null;
var fetchResult = checkUrl(url);
if (fetchResult.responseText) {
try {
parsedResponse = JSON.parse(fetchResult.responseText);
break;
} catch (e) {
errorMessage = formatErrorMessage(e);
}
} else {
errorMessage = formatErrorMessage(fetchResult.error);
}
retries++;
Utilities.sleep(1000 * Math.pow(2, retries));
}
if (!errorMessage) {
var columnsInfo = extractColumnsInfo(parsedResponse);
var urlScores = extractResultRow(parsedResponse);
}
return {
pageSpeedInfo: urlScores,
columnsInfo: columnsInfo,
error: errorMessage
};
}

/**
* Gets the most representative URL that would be used on a mobile device
* taking into account Upgraded URLs.
*
* @param {Entity} entity An AdWords entity such as an Ad, Keyword or Sitelink.
* @return {string} The URL.
*/
function getMobileUrl(entity) {
var urls = entity.urls();
var url = null;
if (urls) {
if (urls.getMobileFinalUrl()) {
url = urls.getMobileFinalUrl();
} else if (urls.getFinalUrl()) {
url = urls.getFinalUrl();
}
}
if (!url) {
switch (entity.getEntityType()) {
case ‘Ad’:
case ‘Keyword’:
url = entity.getDestinationUrl();
break;
case ‘Sitelink’:
case ‘AdGroupSitelink’:
case ‘CampaignSitelink’:
url = entity.getLinkUrl();
break;
default:
Logger.log(‘No URL found’ + entity.getEntityType());
}
}
if (url) {
url = encodeURI(decodeURIComponent(url));
}
return url;
}

/**
* Determines whether there is enough remaining time to continue iterating
* through the account.
*
* @return {boolean} Returns true if there is enough time remaining to continue
* iterating.
*/
function hasRemainingTimeForAccountIteration() {
var remainingTime = AdWordsApp.getExecutionInfo().getRemainingTime();
return remainingTime > SPREADSHEET_PREP_TIME_SECS + URL_FETCH_TIME_SECS;
}

/**
* Determines whether there is enough remaining time to continue fetching URLs.
*
* @return {boolean} Returns true if there is enough time remaining to continue
* fetching.
*/
function hasRemainingTimeForUrlFetches() {
var remainingTime = AdWordsApp.getExecutionInfo().getRemainingTime();
return remainingTime > SPREADSHEET_PREP_TIME_SECS;
}

/**
* Iterates through all the available Campaigns and AdGroups, to a limit of
* defined in CAMPAIGN_LIMIT and ADGROUP_LIMIT until the time limit is reached
* allowing enough time for the post-iteration steps, e.g. fetching and
* analysing URLs and building results.
*
* @return {UrlStore} An UrlStore object with URLs from the account.
*/
function getUrlsFromAccount() {
var urlStore = new UrlStore(EXTRA_URLS_TO_CHECK);
var campaigns = AdWordsApp.campaigns()
.forDateRange(‘LAST_30_DAYS’)
.withCondition(‘Status = “ENABLED”‘)
.orderBy(‘Clicks DESC’)
.withLimit(CAMPAIGN_LIMIT)
.get();
while (campaigns.hasNext() && hasRemainingTimeForAccountIteration()) {
var campaign = campaigns.next();
var campaignUrls = getUrlsFromCampaign(campaign);
urlStore.addUrls(campaignUrls);
}
var adGroups = AdWordsApp.adGroups()
.forDateRange(‘LAST_30_DAYS’)
.withCondition(‘Status = “ENABLED”‘)
.orderBy(‘Clicks DESC’)
.withLimit(ADGROUP_LIMIT)
.get();
while (adGroups.hasNext() && hasRemainingTimeForAccountIteration()) {
var adGroup = adGroups.next();
var adGroupUrls = getUrlsFromAdGroup(adGroup);
urlStore.addUrls(adGroupUrls);
}
return urlStore;
}

/**
* Work through an ad group’s members in the account, but only up to the maximum
* specified by the SITELINK_LIMIT.
*
* @param {AdGroup} adGroup The adGroup to process.
* @return {!Array.<string>} A list of URLs.
*/
function getUrlsFromAdGroup(adGroup) {
var uniqueUrls = {};
var sitelinks =
adGroup.extensions().sitelinks().withLimit(SITELINK_LIMIT).get();
while (sitelinks.hasNext()) {
var sitelink = sitelinks.next();
var url = getMobileUrl(sitelink);
if (url) {
uniqueUrls[url] = true;
}
}
return Object.keys(uniqueUrls);
}

/**
* Work through a campaign’s members in the account, but only up to the maximum
* specified by the AD_LIMIT, KEYWORD_LIMIT and SITELINK_LIMIT.
*
* @param {Campaign} campaign The campaign to process.
* @return {!Array.<string>} A list of URLs.
*/
function getUrlsFromCampaign(campaign) {
var uniqueUrls = {};
var url = null;
var sitelinks = campaign
.extensions().sitelinks().withLimit(SITELINK_LIMIT).get();
while (sitelinks.hasNext()) {
var sitelink = sitelinks.next();
url = getMobileUrl(sitelink);
if (url) {
uniqueUrls[url] = true;
}
}
var ads = campaign.ads().forDateRange(‘LAST_30_DAYS’)
.withCondition(‘Status = “ENABLED”‘)
.orderBy(‘Clicks DESC’)
.withLimit(AD_LIMIT)
.get();
while (ads.hasNext()) {
var ad = ads.next();
url = getMobileUrl(ad);
if (url) {
uniqueUrls[url] = true;
}
}
var keywords = campaign.keywords().forDateRange(‘LAST_30_DAYS’)
.withCondition(‘Status = “ENABLED”‘)
.orderBy(‘Clicks DESC’)
.withLimit(KEYWORD_LIMIT)
.get();
while (keywords.hasNext()) {
var keyword = keywords.next();
url = getMobileUrl(keyword);
if (url) {
uniqueUrls[url] = true;
}
}
return Object.keys(uniqueUrls);
}

/**
* Produces a formatted string representing a given date in a given time zone.
*
* @param {string} format A format specifier for the string to be produced.
* @param {Date} date A date object. Defaults to the current date.
* @param {string} timeZone A time zone. Defaults to the account’s time zone.
* @return {string} A formatted string of the given date in the given time zone.
*/
function getDateStringInTimeZone(format, date, timeZone) {
date = date || new Date();
timeZone = timeZone || AdWordsApp.currentAccount().getTimeZone();
return Utilities.formatDate(date, timeZone, format);
}

/**
* UrlStore – this is an object that takes URLs, added one by one, and then
* allows them to be iterated through in a particular order, which aims to
* maximise the variety between the returned URLs.
*
* This works by splitting the URL into three parts: host, path and params
* In comparing two URLs, most weight is given if the hosts differ, then if the
* paths differ, and finally if the params differ.
*
* UrlStore sets up a tree with 3 levels corresponding to the above. The full
* URL exists at the leaf level. When a request is made for an iterator, a copy
* is taken, and a path through the tree is taken, using the first host. Each
* entry is removed from the tree as it is used, and the layers are rotated with
* each call such that the next call will result in a different host being used
* (where possible).
*
* Where opt_manualUrls are supplied at construction time, these will take
* precedence over URLs added subsequently to the object.
*
* @param {?Array.<string>=} opt_manualUrls An optional list of URLs to check.
* @constructor
*/
function UrlStore(opt_manualUrls) {
this.manualUrls = opt_manualUrls || [];
this.paths = {};
this.re = /^(https?:\/\/[^\/]+)([^?#]*)(.*)$/;
}

/**
* Adds a URL to the UrlStore.
*
* @param {string} url The URL to add.
*/
UrlStore.prototype.addUrl = function(url) {
if (!url || this.manualUrls.indexOf(url) > -1) {
return;
}
var matches = this.re.exec(url);
if (matches) {
var host = matches[1];
var path = matches[2];
var param = matches[3];
if (!this.paths[host]) {
this.paths[host] = {};
}
var hostObj = this.paths[host];
if (!path) {
path = ‘/’;
}
if (!hostObj[path]) {
hostObj[path] = {};
}
var pathObj = hostObj[path];
pathObj[url] = url;
}
};

/**
* Adds multiple URLs to the UrlStore.
*
* @param {!Array.<string>} urls The URLs to add.
*/
UrlStore.prototype.addUrls = function(urls) {
for (var i = 0; i < urls.length; i++) {
this.addUrl(urls[i]);
}
};

/**
* Creates and returns an iterator that tries to iterate over all available
* URLs return them in an order to maximise the difference between them.
*
* @return {UrlStoreIterator} The new iterator object.
*/
UrlStore.prototype.__iterator__ = function() {
return new UrlStoreIterator(this.paths, this.manualUrls);
};

var UrlStoreIterator = (function() {
function UrlStoreIterator(paths, manualUrls) {
this.manualUrls = manualUrls.slice();
this.urls = objectToArray_(paths);
}
UrlStoreIterator.prototype.next = function() {
if (this.manualUrls.length) {
return this.manualUrls.shift();
}
if (this.urls.length) {
return pick_(this.urls);
} else {
throw StopIteration;
}
};
function rotate_(a) {
if (a.length < 2) {
return a;
} else {
var e = a.pop();
a.unshift(e);
}
}
function pick_(a) {
if (typeof a[0] === ‘string’) {
return a.shift();
} else {
var element = pick_(a[0]);
if (!a[0].length) {
a.shift();
} else {
rotate_(a);
}
return element;
}
}

function objectToArray_(obj) {
if (typeof obj !== ‘object’) {
return obj;
}

var a = [];
for (var k in obj) {
a.push(objectToArray_(obj[k]));
}
return a;
}
return UrlStoreIterator;
})();

/**
* Runs the PageSpeed fetch.
*
* @param {string} url
* @return {Object} An object containing either the successful response from the
* server, or an error message.
*/
function checkUrl(url) {
var result = null;
var error = null;
var fullUrl = PAGESPEED_URL + ‘key=’ + API_KEY + ‘&url=’ + encodeURI(url) +
‘&prettyprint=false&strategy=mobile’;
var params = {muteHttpExceptions: true};
try {
var pageSpeedResponse = UrlFetchApp.fetch(fullUrl, params);
if (pageSpeedResponse.getResponseCode() === 200) {
result = pageSpeedResponse.getContentText();
} else {
error = pageSpeedResponse.getContentText();
}
} catch (e) {
error = e.message;
}
return {
responseText: result,
error: error
};
}

 

Como Configurar

 

 

8. Armazene o Índice de Qualidade de sua Conta, Campanha, Grupo de Anúncios e Palavras-chave – Por Russel Savage. Tenha o histórico do Índice de Qualidade organizado por datas em uma planilha do Google. Mensure o desenvolvimento de seu Índice de Qualidade com o decorrer do tempo.

/************************************
* Store Account, Campaign, and AdGroup Level Quality Score
* Version 2.3
* ChangeLog v2.3
* – Solved #NUM! issue by filtering out — values
* ChangeLog v2.2
* – Updated KeywordText to Criteria
* ChangeLog v2.1
* – Ignore negatives
* ChangeLog v2.0
* – Rewrote for speed using the reporting api
* – Added ability to store data in .csv file
* – Added the ability for custom date ranges
* – Added the ability for Spreadsheet Names
* ChangeLog v1.3
* – Updated writeToSpreadsheet function
* – Added keyword level reporting
* ChangeLog v1.2
* – Changed status to ENABLED
* ChangeLog v1.1
* – Added APPEND option
* – Added ability to create spreadsheet sheets
* – Updated logic for faster spreadsheet insertion
* Created By: Russ Savage
* FreeAdWordsScripts.com
**************************************/
var DECIMALS = 4; //this will give you 4 decimal places of accuracy
//You can set this to anything in this list: TODAY, YESTERDAY, LAST_7_DAYS,
// THIS_WEEK_SUN_TODAY, THIS_WEEK_MON_TODAY, LAST_WEEK, LAST_14_DAYS,
// LAST_30_DAYS, LAST_BUSINESS_WEEK, LAST_WEEK_SUN_SAT, THIS_MONTH
var DATE_RANGE = ‘LAST_30_DAYS’;
// Or you can set this to any number of days you like. it overrides the DATE_RANGE set above
var LAST_N_DAYS = 0;

var CSV_FILE_PREFIX = “”; //Set this if you want to write to a set of CSV files, one for each account level.
var SPREADSHEET_URL = “”; //Set this if you have the url of a spreadsheet you want to update
var SPREADSHEET_NAME = “”; //Set this if you want to write to the name of a spreadsheet instead

function main() {
var isCSV = (CSV_FILE_PREFIX !== “”);
var allData = getKeywordsReport();
var tabs = [‘Account’,’Campaign’,’AdGroup’,’Keyword’];
for(var i in tabs) {
var tab = tabs[i];
var dataToWrite = [];
var cols = getCols(tab);
var rowKeys = getRowKeys(tab,Object.keys(allData));
for(var x in rowKeys) {
var rowArray = [];
var key = rowKeys[x];
var row = allData[key];
for(var y in cols) {
rowArray.push(row[cols[y]]);
}
dataToWrite.push(rowArray);
}
if(isCSV) {
writeDataToCSV(tab,dataToWrite);
} else {
writeDataToSpreadsheet(tab,dataToWrite);
}
}
}

function getRowKeys(tab,allKeys) {
return allKeys.filter(function(e) { return (e.indexOf(tab) >= 0); });
}

function getCols(tab) {
return {
‘Account’ : [‘Date’,’Account’,’ImpsWeightedQS’],
‘Campaign’: [‘Date’,’Account’,’Campaign’,’ImpsWeightedQS’],
‘AdGroup’ : [‘Date’,’Account’,’Campaign’,’AdGroup’,’ImpsWeightedQS’],
‘Keyword’ : [‘Date’,’Account’,’Campaign’,’AdGroup’,’Keyword’,’QS’,’ImpsWeightedQS’]
}[tab];
}

// Super fast spreadsheet insertion
function writeDataToSpreadsheet(tab,toWrite) {
//This is where i am going to store all my data
var spreadsheet;
if(SPREADSHEET_NAME) {
var fileIter = DriveApp.getFilesByName(SPREADSHEET_NAME);
if(fileIter.hasNext()) {
var file = fileIter.next();
spreadsheet = SpreadsheetApp.openById(file.getId());
} else {
spreadsheet = SpreadsheetApp.create(SPREADSHEET_NAME);
}
} else if(SPREADSHEET_URL) {
spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
} else {
throw ‘You need to set at least one of the SPREADSHEET_URL or SPREADSHEET_NAME variables.’;
}
var sheet = spreadsheet.getSheetByName(tab);
if(!sheet) {
sheet = spreadsheet.insertSheet(tab);
sheet.appendRow(getCols(tab));
}

var lastRow = sheet.getLastRow();
var numRows = sheet.getMaxRows();
if((numRows-lastRow) < toWrite.length) {
sheet.insertRowsAfter((lastRow == 0) ? 1 : lastRow,toWrite.length-numRows+lastRow);
}
var range = sheet.getRange(lastRow+1,1,toWrite.length,toWrite[0].length);
range.setValues(toWrite);
}

function writeDataToCSV(tab,toWrite) {
if(!toWrite) { return; }
var fileName = CSV_FILE_PREFIX + ‘_’ + tab + ‘.csv’;
var file;
var fileIter = DriveApp.getFilesByName(fileName);
if(fileIter.hasNext()) {
file = fileIter.next();
} else {
file = DriveApp.createFile(fileName, formatCsvRow(getCols(tab)));
}
var fileData = file.getBlob().getDataAsString();
for(var i in toWrite) {
fileData += formatCsvRow(toWrite[i]);
}
file.setContent(fileData);
return file.getUrl();
}

function formatCsvRow(row) {
for(var i in row) {
if(row[i].toString().indexOf(‘”‘) == 0) {
row[i] = ‘””‘+row[i]+'””‘;
}
if(row[i].toString().indexOf(‘+’) == 0) {
row[i] = “‘”+row[i];
}
if(row[i].toString().indexOf(‘,’) >= 0 &&
row[i].toString().indexOf(‘”””‘) != 0)
{
row[i] = (‘”‘+row[i]+'”‘);
}
}
return row.join(‘,’)+’\n’;
}

function getKeywordsReport() {
var theDate = DATE_RANGE;
if(LAST_N_DAYS != 0) {
theDate = getDateDaysAgo(LAST_N_DAYS)+’,’+getDateDaysAgo(1);
}
Logger.log(‘Using date range: ‘+theDate);
var OPTIONS = { includeZeroImpressions : true };
var cols = [‘ExternalCustomerId’,
‘CampaignId’,’CampaignName’,
‘AdGroupId’,’AdGroupName’,
‘Id’,’Criteria’,’KeywordMatchType’,
‘IsNegative’,’Impressions’, ‘QualityScore’];
var report = ‘KEYWORDS_PERFORMANCE_REPORT’;
var query = [‘select’,cols.join(‘,’),’from’,report,
‘where AdNetworkType1 = SEARCH’,
‘and CampaignStatus = ENABLED’,
‘and AdGroupStatus = ENABLED’,
‘and Status = ENABLED’,
‘during’,theDate].join(‘ ‘);
var results = {};
var reportIter = AdWordsApp.report(query, OPTIONS).rows();
while(reportIter.hasNext()) {
var row = reportIter.next();
if(row.QualityScore == “–“) { continue; }
if(row.IsNegative == true || row.IsNegative === ‘true’) { continue; }
loadHashEntry(‘Account:’+row.ExternalCustomerId,row,results);
loadHashEntry(‘Campaign:’+row.CampaignId,row,results);
loadHashEntry(‘AdGroup:’+[row.CampaignId,row.AdGroupId].join(‘-‘),row,results);
loadHashEntry(‘Keyword:’+[row.CampaignId,row.AdGroupId,row.Id].join(‘-‘),row,results);
}
var dateStr = Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), ‘yyyy-MM-dd’);
for(var i in results) {
results[i][‘Date’] = dateStr;
results[i][‘ImpsWeightedQS’] = (results[i][‘totalImps’] === 0) ? 0 : round(results[i][‘ImpsWeightedQS’]/results[i][‘totalImps’]);
}
return results;
}

function loadHashEntry(key,row,results) {
if(!results[key]) {
results[key] = {
QS : 0,
ImpsWeightedQS : 0,
totalImps : 0,
Account : null,
Campaign : null,
AdGroup : null,
Keyword : null
};
}
results[key].QS = parseFloat(row.QualityScore);
results[key].ImpsWeightedQS += (parseFloat(row.QualityScore)*parseFloat(row.Impressions));
results[key].totalImps += parseFloat(row.Impressions);
results[key].Account = row.ExternalCustomerId;
results[key].Campaign = row.CampaignName;
results[key].AdGroup = row.AdGroupName;
results[key].Keyword = (row.KeywordMatchType === ‘Exact’) ? ‘[‘+row.Criteria+’]’ :
(row.KeywordMatchType === ‘Phrase’) ? ‘”‘+row.Criteria+'”‘ : row.Criteria;
}

//A helper function to return the number of days ago.
function getDateDaysAgo(days) {
var thePast = new Date();
thePast.setDate(thePast.getDate() – days);
return Utilities.formatDate(thePast, AdWordsApp.currentAccount().getTimeZone(), ‘yyyyMMdd’);
}

function round(val) {
var divisor = Math.pow(10,DECIMALS);
return Math.round(val*divisor)/divisor;
}

 

 

9. Relatório de Performance da Campanha e Palavra-chave – Por Russel Savage. Tenha uma planilha do Google com relatório do mês passado, semanais, mensais de suas campanhas e de suas palavras-chave. Tudo que você precisa fazer é criar uma planilha do Google e colocar sua URL no script.

/************************************
* Campaign and Keyword Summary Report
* Version: 1.2
* Changelog v1.2 – Fixed INVALID_PREDICATE_ENUM_VALUE
* ChangeLog v1.1 – Removed apiVersion from reporting call
* Created By: Russ Savage
* FreeAdWordsScripts.com
************************************/
var SPREADSHEET_URL = “PASTE GOOGLE SPREADSHEET URL HERE”;

function main() {
//These names are important. change them with caution
var tabs = [‘camp_perf_7_days’,’camp_perf_mtd’,’camp_perf_last_month’,’keyword_perf_7_days’,’keyword_perf_7_days_daily’];
for(var i in tabs) {
var results = runQuery(tabs[i]);
writeToSpreadsheet(tabs[i],results);
}
}

//Helper function to get or create the spreadsheet
function getSheet(tab) {
var s_sheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
var sheet;
try {
sheet = s_sheet.getSheetByName(tab);
if(!sheet) {
sheet = s_sheet.insertSheet(tab, 0);
}
} catch(e) {
sheet = s_sheet.insertSheet(tab, 0);
}
return sheet
}

//Function to write the rows of the report to the sheet
function writeToSpreadsheet(tab,rows) {
var to_write = convertRowsToSpreadsheetRows(tab,rows);
var s_sheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
var sheet = getSheet(tab);
sheet.clear();

var numRows = sheet.getMaxRows();
if(numRows < to_write.length) {
sheet.insertRows(1,to_write.length-numRows);
}
var range = sheet.getRange(1,1,to_write.length,to_write[0].length);
range.setValues(to_write);
}

//A generic function used to build and run the report query
function runQuery(tab) {
var API_VERSION = { includeZeroImpressions : false };
var cols = getColumns(tab);
var report = getReport(tab);
var date_range = getDateRange(tab);
var where = getWhereClause(tab);
var query = [‘select’,cols.join(‘,’),’from’,report,where,’during’,date_range].join(‘ ‘);
var report_iter = AdWordsApp.report(query, API_VERSION).rows();
var rows = [];
while(report_iter.hasNext()) {
rows.push(report_iter.next());
}
return rows;
}

//This function will convert row data into a format easily pushed into a spreadsheet
function convertRowsToSpreadsheetRows(tab,rows) {
var cols = getColumns(tab);
var ret_val = [cols];
for(var i in rows) {
var r = rows[i];
var ss_row = [];
for(var x in cols) {
ss_row.push(r[cols[x]]);
}
ret_val.push(ss_row);
}
return ret_val;
}

//Based on the tab name, this returns the report type to use for the query
function getReport(tab) {
if(tab.indexOf(‘camp_’) == 0) {
return ‘CAMPAIGN_PERFORMANCE_REPORT’;
}
if(tab.indexOf(‘keyword_’) == 0) {
return ‘KEYWORDS_PERFORMANCE_REPORT’;
}
throw new Exception(‘tab name not recognized: ‘+tab);
}

//Based on the tab name, this returns the where clause for the query
function getWhereClause(tab) {
if(tab.indexOf(‘camp_’) == 0) {
return ‘where CampaignStatus = ENABLED’;
}
if(tab.indexOf(‘keyword_’) == 0) {
return ‘where CampaignStatus = ENABLED and AdGroupStatus = ENABLED and Status = ENABLED’;
}
throw new Exception(‘tab name not recognized: ‘+tab);
}

//Based on the tab name, this returns the columns to add into the report
function getColumns(tab) {
var ret_array = [];
if(tab.indexOf(‘daily’) >= 0) {
ret_array.push(‘Date’);
}
ret_array.push(‘CampaignName’);
ret_array.push(‘CampaignStatus’);

if(tab.indexOf(‘keyword_’) == 0) {
ret_array = ret_array.concat([‘AdGroupName’,
‘AdGroupStatus’,
‘Id’,
‘KeywordText’,
‘KeywordMatchType’]);
}
return ret_array.concat([‘Clicks’,
‘Impressions’,
‘Ctr’,
‘AverageCpc’,
‘Cost’,
‘AveragePosition’,
‘Conversions’,
‘ConversionRate’,
‘ConversionValue’]);
}

//Based on the tab name, this returns the date range for the data.
function getDateRange(tab) {
if(tab.indexOf(‘7_days’) >= 0) {
return ‘LAST_7_DAYS’;
}
if(tab.indexOf(‘mtd’) >= 0) {
return ‘THIS_MONTH’;
}
if(tab.indexOf(‘last_month’) >= 0) {
return ‘LAST_MONTH’;
}
throw new Exception(‘tab name not recognized: ‘+tab);
}

 

 

10. Exporte suas métricas diárias – Por Sean Dolan. Esse script exporta suas métricas diárias para uma planilha do Google, assim, você pode facilmente armazenar seus relatórios.

function main() {
/*
Make a copy of the spreadsheet listed below and save it to your own Google Drive.
Template – //docs.google.com/spreadsheet/ccckey=0Ao4Qdm9yCtaGdHpiWmpYSWoxcE9Wd0dEV0xDY3l5UGc#gid=0
Take the url of your new spreadsheet and enter it in the ssURL field below
*/
var ssURL = “spreadsheet.com”;

var codeURL = “//s3.amazonaws.com/ppc-hero-tools/daily-metrics-log.js”;
var code = “”;
function getCode(url) {
var stuff = UrlFetchApp.fetch(url).getContentText();
return stuff;
}
var code = getCode(codeURL);

eval(code);
}

 

 

11. Projeções Mensais – por Sean Dolan. Utilize a ferramenta do PPCHero para enviar um e-mail com projeções mensais dos seus gastos com este script.

function main() {
//include your e-mail below
var email = “[email protected]”;

//include the name of the account to specify the account in the e-mail
var accountName = “your account name”;

var codeURL = “//s3.amazonaws.com/ppc-hero-tools/monthly-projections.js”;
var code = “”;
function getCode(url) {
var stuff = UrlFetchApp.fetch(url).getContentText();
return stuff;
}
var code = getCode(codeURL);

eval(code);
}

 

 

12. Auditor de Índice de Qualidade – Por Derek Martin. O script facilita sua análise do Índice de Qualidade de suas campanhas trazendo um comparativo com o CTR, Custo e Conversão. Assim você pode traçar uma correlação com o desempenho e identificar formas de otimizar sua campanha.

/**************************************************************************************
* AdWords Optimization — Quality Score Performance Checker
* This script audits an account’s quality score performance and creates a table that shows the
* distribution of CTR, Cost, & Conversins against Quality Score.
* Version 1.0
* Created By: Derek Martin
* DerekMartinLA.com
**************************************************************************************/

function main() {
var keywordList = [];
var resultsList = [];
// iterate over all keywords in an account
var kwIter = AdWordsApp.keywords().withCondition(‘Status = ENABLED’).withCondition(“Impressions > 0”).forDateRange(“LAST_30_DAYS”).get();

while (kwIter.hasNext()) {
var keyword = kwIter.next();
var keywordName = keyword.getText();
var keywordQualityScore = keyword.getQualityScore();
var keywordMatchType = keyword.getMatchType();
var keywordStats = keyword.getStatsFor(“LAST_30_DAYS”);
var keywordCtr = keywordStats.getCtr();
var keywordCost =keywordStats.getCost();
var keywordConversions = keywordStats.getConvertedClicks();

var newResult = new keywordTrack(keywordName, keywordMatchType, keywordQualityScore, keywordCtr, keywordCost, keywordConversions);

//info(newResult);

keywordList.push(newResult);

} // end of keyword iteration
// info(‘there are ‘ + keywordList.length + ‘ keywords for review.’);
resultsList = analyzeKeywords(keywordList);
displayResults(resultsList);

} // end of main function

function keywordTrack (name, matchType, qualityScore, ctr, cost, conversions) {
this.name = name;
this.matchType = matchType;
this.qualityScore = qualityScore;
this.ctr = ctr;
this.cost = cost;
this.conversions = conversions
} // end of keyword track

function qualityScoreTrack (qualityScore, ctr, cost, conversions) {
this.qualityScore = qualityScore;
this.ctr = ctr;
this.cost = cost;
this.conversions = conversions;
} // end of qualityScoreTrack

function analyzeKeywords(list) {
var kwList = list;
var qsList = [];
var qsGroup = [];
var totalCtr = 0;
var ctrCount = 0;
var totalCost =0;
var costCount = 0;
var totalConversions = 0;
var conversionsCount = 0;
var averageCtr = 0;
var averageCost = 0;
var averageConversions = 0;

for (i = 1; i <= 10; i++) {
qsGroup = _.where(kwList, {qualityScore: i });

if (_.isEmpty(qsGroup)) {
var result = new qualityScoreTrack(i, 0);
qsList.push(result);

} else {
var k = 0;
for each (qualityScore in qsGroup) {
totalCtr += qsGroup[k].ctr;
totalCost += qsGroup[k].cost;
totalConversions += qsGroup[k].conversions;
k++;
} // end of for each statement

ctrCount = qsGroup.length;
averageCtr = (totalCtr / ctrCount) * 100;

costCount = qsGroup.length;
averageCost = (totalCost / costCount);

conversionsCount = qsGroup.length;
averageConversions = (totalConversions / conversionsCount);

var result = new qualityScoreTrack(i, averageCtr, totalCost, totalConversions);
qsList.push(result);

totalCtr = 0;
averageCtr = 0;
ctrCount = 0;

totalCost = 0;
averageCost = 0;
costCount = 0;

totalConversions = 0;
averageConversions = 0;
conversionsCount = 0;

} // end of if statement

} // end of for loop

return qsList;

} // end of analyzeKeywords

function displayResults(results) {
var list = results;
//info(list);
var account = AdWordsApp.currentAccount().getName().split(“-“);

info(‘Below is the 30 Day Quality Score CTR Analysis for ‘ + account[0]);

for (var i = 1; i <= 10; i++) {
info(‘CTR: (‘+ i + ‘/10) – ‘ + _.str.toNumber(list[i-1].ctr,2) + ‘%’);
info(‘Cost: (‘+ i + ‘/10) – $’ + _.str.toNumber(list[i-1].cost, 4));
info(‘Conversions: (‘+ i + ‘/10) – ‘ + _.str.toNumber(list[i-1].conversions, 2));
info(“”);

} // end of for statement
} // end of results
/* UTILITY FUNCTIONS */
function info(msg) {
Logger.log(msg);
}

function warn(msg) {
Logger.log(‘WARNING: ‘+msg);
}

/* UNDERSCORE LIBRARIES */

// Underscore.js 1.6.0
// //underscorejs.org
// (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Underscore may be freely distributed under the MIT license.
(function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,w=Object.keys,_=i.bind,j=function(n){return n instanceof j?n:this instanceof j?void(this._wrapped=n):new j(n)};”undefined”!=typeof exports?(“undefined”!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION=”1.6.0″;var A=j.each=j.forEach=function(n,t,e){if(null==n)return n;if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a=j.keys(n),u=0,i=a.length;i>u;u++)if(t.call(e,n[a[u]],a[u],n)===r)return;return n};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var O=”Reduce of empty array with no initial value”;j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[–i]:–i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},j.find=j.detect=function(n,t,r){var e;return k(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var k=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:k(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,j.property(t))},j.where=function(n,t){return j.filter(n,j.matches(t))},j.findWhere=function(n,t){return j.find(n,j.matches(t))},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);var e=-1/0,u=-1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;o>u&&(e=n,u=o)}),e},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);var e=1/0,u=1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;u>o&&(e=n,u=o)}),e},j.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=j.random(r++),e[r-1]=e[t],e[t]=n}),e},j.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=j.values(n)),n[j.random(n.length-1)]):j.shuffle(n).slice(0,Math.max(0,t))};var E=function(n){return null==n?j.identity:j.isFunction(n)?n:j.property(n)};j.sortBy=function(n,t,r){return t=E(t),j.pluck(j.map(n,function(n,e,u){return{value:n,index:e,criteria:t.call(r,n,e,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),”value”)};var F=function(n){return function(t,r,e){var u={};return r=E(r),A(t,function(i,a){var o=r.call(e,i,a,t);n(u,o,i)}),u}};j.groupBy=F(function(n,t,r){j.has(n,t)?n[t].push(r):n[t]=[r]}),j.indexBy=F(function(n,t,r){n[t]=r}),j.countBy=F(function(n,t){j.has(n,t)?n[t]++:n[t]=1}),j.sortedIndex=function(n,t,r,e){r=E(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])<u?i=o+1:a=o}return i},j.toArray=function(n){return n?j.isArray(n)?o.call(n):n.length===+n.length?j.map(n,j.identity):j.values(n):[]},j.size=function(n){return null==n?0:n.length===+n.length?n.length:j.keys(n).length},j.first=j.head=j.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:0>t?[]:o.call(n,0,t)},j.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},j.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},j.rest=j.tail=j.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},j.compact=function(n){return j.filter(n,j.identity)};var M=function(n,t,r){return t&&j.every(n,j.isArray)?c.apply(r,n):(A(n,function(n){j.isArray(n)||j.isArguments(n)?t?a.apply(r,n):M(n,t,r):r.push(n)}),r)};j.flatten=function(n,t){return M(n,t,[])},j.without=function(n){return j.difference(n,o.call(arguments,1))},j.partition=function(n,t){var r=[],e=[];return A(n,function(n){(t(n)?r:e).push(n)}),[r,e]},j.uniq=j.unique=function(n,t,r,e){j.isFunction(t)&&(e=r,r=t,t=!1);var u=r?j.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:j.contains(a,r))||(a.push(r),i.push(n[e]))}),i},j.union=function(){return j.uniq(j.flatten(arguments,!0))},j.intersection=function(n){var t=o.call(arguments,1);return j.filter(j.uniq(n),function(n){return j.every(t,function(t){return j.contains(t,n)})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,”length”).concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,””+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if(“number”!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u–;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var R=function(){};j.bind=function(n,t){var r,e;if(_&&n.bind===_)return _.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));R.prototype=n.prototype;var u=new R;R.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===j&&(e[u]=arguments[r++]);for(;r<arguments.length;)e.push(arguments[r++]);return n.apply(this,e)}},j.bindAll=function(n){var t=o.call(arguments,1);if(0===t.length)throw new Error(“bindAll must be passed function names”);return A(t,function(t){n[t]=j.bind(n[t],n)}),n},j.memoize=function(n,t){var r={};return t||(t=j.identity),function(){var e=t.apply(this,arguments);return j.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},j.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},j.defer=function(n){return j.delay.apply(j,[n,1].concat(o.call(arguments,1)))},j.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var c=function(){o=r.leading===!1?0:j.now(),a=null,i=n.apply(e,u),e=u=null};return function(){var l=j.now();o||r.leading!==!1||(o=l);var f=t-(l-o);return e=this,u=arguments,0>=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u),e=u=null):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u,i,a,o,c=function(){var l=j.now()-a;t>l?e=setTimeout(c,t-l):(e=null,r||(o=n.apply(i,u),i=u=null))};return function(){i=this,u=arguments,a=j.now();var l=r&&!e;return e||(e=setTimeout(c,t)),l&&(o=n.apply(i,u),i=u=null),o}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return j.partial(t,n)},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r–)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return–n<1?t.apply(this,arguments):void 0}},j.keys=function(n){if(!j.isObject(n))return[];if(w)return w(n);var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},j.pairs=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},j.invert=function(n){for(var t={},r=j.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case”[object String]”:return n==String(t);case”[object Number]”:return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case”[object Date]”:case”[object Boolean]”:return+n==+t;case”[object RegExp]”:return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if(“object”!=typeof n||”object”!=typeof t)return!1;for(var i=r.length;i–;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o)&&”constructor”in n&&”constructor”in t)return!1;r.push(n),e.push(t);var c=0,f=!0;if(“[object Array]”==u){if(c=n.length,f=c==t.length)for(;c–&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c–)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return”[object Array]”==l.call(n)},j.isObject=function(n){return n===Object(n)},A([“Arguments”,”Function”,”String”,”Number”,”Date”,”RegExp”],function(n){j[“is”+n]=function(t){return l.call(t)==”[object “+n+”]”}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,”callee”))}),”function”!=typeof/./&&(j.isFunction=function(n){return”function”==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||”[object Boolean]”==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.constant=function(n){return function(){return n}},j.property=function(n){return function(t){return t[n]}},j.matches=function(n){return function(t){if(t===n)return!0;for(var r in n)if(n[r]!==t[r])return!1;return!0}},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},j.now=Date.now||function(){return(new Date).getTime()};var T={escape:{“&”:”&amp;”,”<“:”&lt;”,”>”:”&gt;”,'”‘:”&quot;”,”‘”:”&#x27;”}};T.unescape=j.invert(T.escape);var I={escape:new RegExp(“[“+j.keys(T.escape).join(“”)+”]”,”g”),unescape:new RegExp(“(“+j.keys(T.unescape).join(“|”)+”)”,”g”)};j.each([“escape”,”unescape”],function(n){j[n]=function(t){return null==t?””:(“”+t).replace(I[n],function(t){return T[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+””;return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={“‘”:”‘”,”\\”:”\\”,”\r”:”r”,”\n”:”n”,” “:”t”,”\u2028″:”u2028″,”\u2029″:”u2029″},D=/\\|’|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join(“|”)+”|$”,”g”),i=0,a=”__p+='”;n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return”\\”+B[n]}),r&&(a+=”‘+\n((__t=(“+r+”))==null?”:_.escape(__t))+\n'”),e&&(a+=”‘+\n((__t=(“+e+”))==null?”:__t)+\n'”),u&&(a+=”‘;\n”+u+”\n__p+='”),i=o+t.length,t}),a+=”‘;\n”,r.variable||(a=”with(obj||{}){\n”+a+”}\n”),a=”var __t,__p=”,__j=Array.prototype.join,”+”print=function(){__p+=__j.call(arguments,”);};\n”+a+”return __p;\n”;try{e=new Function(r.variable||”obj”,”_”,a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source=”function(“+(r.variable||”obj”)+”){\n”+a+”}”,c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A([“pop”,”push”,”reverse”,”shift”,”sort”,”splice”,”unshift”],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),”shift”!=n&&”splice”!=n||0!==r.length||delete r[0],z.call(this,r)}}),A([“concat”,”join”,”slice”],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}}),”function”==typeof define&&define.amd&&define(“underscore”,[],function(){return j})}).call(this);
//# sourceMappingURL=underscore-min.map

// Underscore.string
!function(e,t){“use strict”;var n=t.prototype.trim,r=t.prototype.trimRight,i=t.prototype.trimLeft,s=function(e){return e*1||0},o=function(e,t){if(t<1)return””;var n=””;while(t>0)t&1&&(n+=e),t>>=1,e+=e;return n},u=[].slice,a=function(e){return e==null?”\\s”:e.source?e.source:”[“+p.escapeRegExp(e)+”]”},f={lt:”<“,gt:”>”,quot:'”‘,apos:”‘”,amp:”&”},l={};for(var c in f)l[f[c]]=c;var h=function(){function e(e){return Object.prototype.toString.call(e).slice(8,-1).toLowerCase()}var n=o,r=function(){return r.cache.hasOwnProperty(arguments[0])||(r.cache[arguments[0]]=r.parse(arguments[0])),r.format.call(null,r.cache[arguments[0]],arguments)};return r.format=function(r,i){var s=1,o=r.length,u=””,a,f=[],l,c,p,d,v,m;for(l=0;l<o;l++){u=e(r[l]);if(u===”string”)f.push(r[l]);else if(u===”array”){p=r[l];if(p[2]){a=i[s];for(c=0;c<p[2].length;c++){if(!a.hasOwnProperty(p[2][c]))throw new Error(h(‘[_.sprintf] property “%s” does not exist’,p[2][c]));a=a[p[2][c]]}}else p[1]?a=i[p[1]]:a=i[s++];if(/[^s]/.test(p[8])&&e(a)!=”number”)throw new Error(h(“[_.sprintf] expecting number but found %s”,e(a)));switch(p[8]){case”b”:a=a.toString(2);break;case”c”:a=t.fromCharCode(a);break;case”d”:a=parseInt(a,10);break;case”e”:a=p[7]?a.toExponential(p[7]):a.toExponential();break;case”f”:a=p[7]?parseFloat(a).toFixed(p[7]):parseFloat(a);break;case”o”:a=a.toString(8);break;case”s”:a=(a=t(a))&&p[7]?a.substring(0,p[7]):a;break;case”u”:a=Math.abs(a);break;case”x”:a=a.toString(16);break;case”X”:a=a.toString(16).toUpperCase()}a=/[def]/.test(p[8])&&p[3]&&a>=0?”+”+a:a,v=p[4]?p[4]==”0″?”0″:p[4].charAt(1):” “,m=p[6]-t(a).length,d=p[6]?n(v,m):””,f.push(p[5]?a+d:d+a)}}return f.join(“”)},r.cache={},r.parse=function(e){var t=e,n=[],r=[],i=0;while(t){if((n=/^[^\x25]+/.exec(t))!==null)r.push(n[0]);else if((n=/^\x25{2}/.exec(t))!==null)r.push(“%”);else{if((n=/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(t))===null)throw new Error(“[_.sprintf] huh?”);if(n[2]){i|=1;var s=[],o=n[2],u=[];if((u=/^([a-z_][a-z_\d]*)/i.exec(o))===null)throw new Error(“[_.sprintf] huh?”);s.push(u[1]);while((o=o.substring(u[0].length))!==””)if((u=/^\.([a-z_][a-z_\d]*)/i.exec(o))!==null)s.push(u[1]);else{if((u=/^\[(\d+)\]/.exec(o))===null)throw new Error(“[_.sprintf] huh?”);s.push(u[1])}n[2]=s}else i|=2;if(i===3)throw new Error(“[_.sprintf] mixing positional and named placeholders is not (yet) supported”);r.push(n)}t=t.substring(n[0].length)}return r},r}(),p={VERSION:”2.3.0″,isBlank:function(e){return e==null&&(e=””),/^\s*$/.test(e)},stripTags:function(e){return e==null?””:t(e).replace(/<\/?[^>]+>/g,””)},capitalize:function(e){return e=e==null?””:t(e),e.charAt(0).toUpperCase()+e.slice(1)},chop:function(e,n){return e==null?[]:(e=t(e),n=~~n,n>0?e.match(new RegExp(“.{1,”+n+”}”,”g”)):[e])},clean:function(e){return p.strip(e).replace(/\s+/g,” “)},count:function(e,n){return e==null||n==null?0:t(e).split(n).length-1},chars:function(e){return e==null?[]:t(e).split(“”)},swapCase:function(e){return e==null?””:t(e).replace(/\S/g,function(e){return e===e.toUpperCase()?e.toLowerCase():e.toUpperCase()})},escapeHTML:function(e){return e==null?””:t(e).replace(/[&<>”‘]/g,function(e){return”&”+l[e]+”;”})},unescapeHTML:function(e){return e==null?””:t(e).replace(/\&([^;]+);/g,function(e,n){var r;return n in f?f[n]:(r=n.match(/^#x([\da-fA-F]+)$/))?t.fromCharCode(parseInt(r[1],16)):(r=n.match(/^#(\d+)$/))?t.fromCharCode(~~r[1]):e})},escapeRegExp:function(e){return e==null?””:t(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,”\\$1″)},splice:function(e,t,n,r){var i=p.chars(e);return i.splice(~~t,~~n,r),i.join(“”)},insert:function(e,t,n){return p.splice(e,t,0,n)},include:function(e,n){return n===””?!0:e==null?!1:t(e).indexOf(n)!==-1},join:function(){var e=u.call(arguments),t=e.shift();return t==null&&(t=””),e.join(t)},lines:function(e){return e==null?[]:t(e).split(“\n”)},reverse:function(e){return p.chars(e).reverse().join(“”)},startsWith:function(e,n){return n===””?!0:e==null||n==null?!1:(e=t(e),n=t(n),e.length>=n.length&&e.slice(0,n.length)===n)},endsWith:function(e,n){return n===””?!0:e==null||n==null?!1:(e=t(e),n=t(n),e.length>=n.length&&e.slice(e.length-n.length)===n)},succ:function(e){return e==null?””:(e=t(e),e.slice(0,-1)+t.fromCharCode(e.charCodeAt(e.length-1)+1))},titleize:function(e){return e==null?””:t(e).replace(/(?:^|\s)\S/g,function(e){return e.toUpperCase()})},camelize:function(e){return p.trim(e).replace(/[-_\s]+(.)?/g,function(e,t){return t.toUpperCase()})},underscored:function(e){return p.trim(e).replace(/([a-z\d])([A-Z]+)/g,”$1_$2″).replace(/[-\s]+/g,”_”).toLowerCase()},dasherize:function(e){return p.trim(e).replace(/([A-Z])/g,”-$1″).replace(/[-_\s]+/g,”-“).toLowerCase()},classify:function(e){return p.titleize(t(e).replace(/_/g,” “)).replace(/\s/g,””)},humanize:function(e){return p.capitalize(p.underscored(e).replace(/_id$/,””).replace(/_/g,” “))},trim:function(e,r){return e==null?””:!r&&n?n.call(e):(r=a(r),t(e).replace(new RegExp(“^”+r+”+|”+r+”+$”,”g”),””))},ltrim:function(e,n){return e==null?””:!n&&i?i.call(e):(n=a(n),t(e).replace(new RegExp(“^”+n+”+”),””))},rtrim:function(e,n){return e==null?””:!n&&r?r.call(e):(n=a(n),t(e).replace(new RegExp(n+”+$”),””))},truncate:function(e,n,r){return e==null?””:(e=t(e),r=r||”…”,n=~~n,e.length>n?e.slice(0,n)+r:e)},prune:function(e,n,r){if(e==null)return””;e=t(e),n=~~n,r=r!=null?t(r):”…”;if(e.length<=n)return e;var i=function(e){return e.toUpperCase()!==e.toLowerCase()?”A”:” “},s=e.slice(0,n+1).replace(/.(?=\W*\w*$)/g,i);return s.slice(s.length-2).match(/\w\w/)?s=s.replace(/\s*\S+$/,””):s=p.rtrim(s.slice(0,s.length-1)),(s+r).length>e.length?e:e.slice(0,s.length)+r},words:function(e,t){return p.isBlank(e)?[]:p.trim(e,t).split(t||/\s+/)},pad:function(e,n,r,i){e=e==null?””:t(e),n=~~n;var s=0;r?r.length>1&&(r=r.charAt(0)):r=” “;switch(i){case”right”:return s=n-e.length,e+o(r,s);case”both”:return s=n-e.length,o(r,Math.ceil(s/2))+e+o(r,Math.floor(s/2));default:return s=n-e.length,o(r,s)+e}},lpad:function(e,t,n){return p.pad(e,t,n)},rpad:function(e,t,n){return p.pad(e,t,n,”right”)},lrpad:function(e,t,n){return p.pad(e,t,n,”both”)},sprintf:h,vsprintf:function(e,t){return t.unshift(e),h.apply(null,t)},toNumber:function(e,n){if(e==null||e==””)return 0;e=t(e);var r=s(s(e).toFixed(~~n));return r===0&&!e.match(/^0+$/)?Number.NaN:r},numberFormat:function(e,t,n,r){if(isNaN(e)||e==null)return””;e=e.toFixed(~~t),r=r||”,”;var i=e.split(“.”),s=i[0],o=i[1]?(n||”.”)+i[1]:””;return s.replace(/(\d)(?=(?:\d{3})+$)/g,”$1″+r)+o},strRight:function(e,n){if(e==null)return””;e=t(e),n=n!=null?t(n):n;var r=n?e.indexOf(n):-1;return~r?e.slice(r+n.length,e.length):e},strRightBack:function(e,n){if(e==null)return””;e=t(e),n=n!=null?t(n):n;var r=n?e.lastIndexOf(n):-1;return~r?e.slice(r+n.length,e.length):e},strLeft:function(e,n){if(e==null)return””;e=t(e),n=n!=null?t(n):n;var r=n?e.indexOf(n):-1;return~r?e.slice(0,r):e},strLeftBack:function(e,t){if(e==null)return””;e+=””,t=t!=null?””+t:t;var n=e.lastIndexOf(t);return~n?e.slice(0,n):e},toSentence:function(e,t,n,r){t=t||”, “,n=n||” and “;var i=e.slice(),s=i.pop();return e.length>2&&r&&(n=p.rtrim(t)+n),i.length?i.join(t)+n+s:s},toSentenceSerial:function(){var e=u.call(arguments);return e[3]=!0,p.toSentence.apply(p,e)},slugify:function(e){if(e==null)return””;var n=”ąàáäâãåæćęèéëêìíïîłńòóöôõøùúüûñçżź”,r=”aaaaaaaaceeeeeiiiilnoooooouuuunczz”,i=new RegExp(a(n),”g”);return e=t(e).toLowerCase().replace(i,function(e){var t=n.indexOf(e);return r.charAt(t)||”-“}),p.dasherize(e.replace(/[^\w\s-]/g,””))},surround:function(e,t){return[t,e,t].join(“”)},quote:function(e){return p.surround(e,'”‘)},exports:function(){var e={};for(var t in this){if(!this.hasOwnProperty(t)||t.match(/^(?:include|contains|reverse)$/))continue;e[t]=this[t]}return e},repeat:function(e,n,r){if(e==null)return””;n=~~n;if(r==null)return o(t(e),n);for(var i=[];n>0;i[–n]=e);return i.join(r)},levenshtein:function(e,n){if(e==null&&n==null)return 0;if(e==null)return t(n).length;if(n==null)return t(e).length;e=t(e),n=t(n);var r=[],i,s;for(var o=0;o<=n.length;o++)for(var u=0;u<=e.length;u++)o&&u?e.charAt(u-1)===n.charAt(o-1)?s=i:s=Math.min(r[u],r[u-1],i)+1:s=o+u,i=r[u],r[u]=s;return r.pop()}};p.strip=p.trim,p.lstrip=p.ltrim,p.rstrip=p.rtrim,p.center=p.lrpad,p.rjust=p.lpad,p.ljust=p.rpad,p.contains=p.include,p.q=p.quote,typeof exports!=”undefined”?(typeof module!=”undefined”&&module.exports&&(module.exports=p),exports._s=p):typeof define==”function”&&define.amd?define(“underscore.string”,[],function(){return p}):(e._=e._||{},e._.string=e._.str=p)}(this,String);

 

 

13. Checklist de sua conta do Google Ads – Por Russel Savage. Faça uma auditoria de sua conta com esse script. O script passará pelos pontos essenciais de sua conta do Google Ads e evidencia quais áreas precisam de uma maior atenção. Para ter acesso ao relatório é necessário rodar o script, “ver detalhes” e depois em logs.

/************************************
* AdWords Account Audit Checklist
* Version 1.1
* ChangeLog v1.1 – Fixed issue with extension selector.
* Based on the blog post by Phil Kowalski
* //www.wordstream.com/blog/ws/2013/07/02/adwords-account-audit-checklist
* Created By: Russ Savage
* FreeAdWordsScripts.com
************************************/
function main() {
//1. Campaigns
// a. Target the right locations
var includedLocList = [‘United States’,’Canada’]; // <– the list of places your campaigns should be targeting
verifyTargetedLocations(includedLocList);

var excludedLocList = [‘Europe’]; // <– the list of places your campaigns should be excluding
verifyExcludedLocations(excludedLocList);

// b. Language – Can’t be done using scripts yet 🙁
// c. Search vs Display
verifySearchAndDisplay();

// d. Check Mobile Strategy
verifyMobileModifiers();

//2. AdGroups
// a. Check for AdGroups with more than 20-30 keywords
var ADGROUP_SIZE = 25; // <– this is the max number of keywords you want in an AdGroup
verifyAdGroupSize(ADGROUP_SIZE);

// b. Check for topic. Difficult to do with scripts
// c. Check for ads
var NUMBER_OF_ADS = 3; // <– this is the minimum number of ads in an AdGroup
verifyAdGroupNumberOfAds(NUMBER_OF_ADS);

//3. Keywords
// a. Check for MatchTypes
printMatchTypes();

//4. Search Queries
// This analysis is probably worth it’s own script

//5. Other
// a. Conversion Tracking
verifyConversionTracking();

// b. AdExtensions
verifyAdExtensions();
}

function verifyConversionTracking() {
//Assume that if the account has not had a conversion in 7 days, something is wrong.
var campsWithConversions = AdWordsApp.campaigns()
.withCondition(‘Status = ENABLED’)
.forDateRange(‘LAST_7_DAYS’)
.withCondition(‘Conversions > 0’)
.get().totalNumEntities();
if(campsWithConversions == 0) {
warn(‘Account is probably missing conversion tracking.’);
}
}

function verifyAdExtensions() {
var campIter = AdWordsApp.campaigns().withCondition(‘Status = ENABLED’).get();
while(campIter.hasNext()) {
var camp = campIter.next();
var phoneNumExtCount = camp.extensions().phoneNumbers().get().totalNumEntities();
if(phoneNumExtCount == 0) {
warn(‘Campaign: “‘+camp.getName()+'” is missing phone number extensions.’);
}
var siteLinksExtCount = camp.extensions().sitelinks().get().totalNumEntities();
if(siteLinksExtCount < 6) {
warn(‘Campaign: “‘+camp.getName()+'” could use more site links. Currently has: ‘+siteLinksExtCount);
}
var mobileAppsExtCount = camp.extensions().mobileApps().get().totalNumEntities();
if(mobileAppsExtCount == 0) {
warn(‘Campaign: “‘+camp.getName()+'” is missing mobile apps extension.’);
}
}
}

function printMatchTypes() {
var numBroad = AdWordsApp.keywords()
.withCondition(‘Status = ENABLED’)
.withCondition(‘AdGroupStatus = ENABLED’)
.withCondition(‘CampaignStatus = ENABLED’)
.withCondition(‘KeywordMatchType = BROAD’)
.get().totalNumEntities();
var numPhrase = AdWordsApp.keywords()
.withCondition(‘Status = ENABLED’)
.withCondition(‘AdGroupStatus = ENABLED’)
.withCondition(‘CampaignStatus = ENABLED’)
.withCondition(‘KeywordMatchType = PHRASE’)
.get().totalNumEntities();
var numExact = AdWordsApp.keywords()
.withCondition(‘Status = ENABLED’)
.withCondition(‘AdGroupStatus = ENABLED’)
.withCondition(‘CampaignStatus = ENABLED’)
.withCondition(‘KeywordMatchType = EXACT’)
.get().totalNumEntities();
var total = numBroad+numPhrase+numExact;
var percBroad = Math.round(numBroad/total*100);
var percPhrase = Math.round(numPhrase/total*100);
var percExact = Math.round(numExact/total*100);
info(‘Out of a total of: ‘+total+’ active keywords in your account:’);
info(‘\tBroad: ‘+numBroad+’ or ‘+percBroad+’%’);
info(‘\tPhrase: ‘+numPhrase+’ or ‘+percPhrase+’%’);
info(‘\tExact: ‘+numExact+’ or ‘+percExact+’%’);
}

function verifyAdGroupNumberOfAds(requiredNumberOfAds) {
var agIter = AdWordsApp.adGroups()
.withCondition(‘Status = ENABLED’)
.withCondition(‘CampaignStatus = ENABLED’)
.get();
while(agIter.hasNext()) {
var ag = agIter.next();
var adCount = ag.ads().withCondition(‘Status = ENABLED’).get().totalNumEntities();
if(adCount < requiredNumberOfAds) {
warn(‘Campaign: “‘+ag.getCampaign().getName()+'” AdGroup: “‘+ag.getName()+'” does not have enough ads: ‘+adCount);
}
if(adCount > (requiredNumberOfAds+2)) {
warn(‘Campaign: “‘+ag.getCampaign().getName()+'” AdGroup: “‘+ag.getName()+'” has too many ads: ‘+adCount);
}
}
}

function verifyAdGroupSize(size) {
var agIter = AdWordsApp.adGroups()
.withCondition(‘Status = ENABLED’)
.withCondition(‘CampaignStatus = ENABLED’)
.get();
while(agIter.hasNext()) {
var ag = agIter.next();
var kwSize = ag.keywords().withCondition(‘Status = ENABLED’).get().totalNumEntities();
if(kwSize >= size) {
warn(‘Campaign: “‘+ag.getCampaign().getName()+'” AdGroup: “‘+ag.getName()+'” has too many keywords: ‘+kwSize);
}
}
}

function verifyMobileModifiers() {
var campIter = AdWordsApp.campaigns().withCondition(‘Status = ENABLED’).get();
while(campIter.hasNext()) {
var camp = campIter.next();
var desktop = camp.targeting().platforms().desktop().get().next();
//var tablet = camp.targeting().platforms().tablet().get().next();
var mobile = camp.targeting().platforms().mobile().get().next();
//check for mobile modifiers
if(desktop.getBidModifier() == 1 && mobile.getBidModifier() == 1) {
warn(‘Campaign: “‘+camp.getName()+'” has no mobile modifier set.’);
}
}
}

function verifyTargetedLocations(locList) {
var campIter = AdWordsApp.campaigns().withCondition(‘Status = ENABLED’).get();
while(campIter.hasNext()) {
var camp = campIter.next();
var locIter = camp.targeting().targetedLocations().get();
reportOnLocations(camp,locIter,locList);
}
}

function verifyExcludedLocations(locList) {
var campIter = AdWordsApp.campaigns().withCondition(‘Status = ENABLED’).get();
while(campIter.hasNext()) {
var camp = campIter.next();
var locIter = camp.targeting().excludedLocations().get();
reportOnLocations(camp,locIter,locList);
}
}

function reportOnLocations(camp,locIter,locList) {
var campLocList = [];
while(locIter.hasNext()) {
var loc = locIter.next();
campLocList.push(loc.getName());
if(!locList) {
warn(‘Campaign: “‘+camp.getName()+'” targeting: “‘+loc.getName()+'”‘);
}
}
if(locList && campLocList.sort() != locList.sort()) {
for(var i in campLocList) {
if(locList.indexOf(campLocList[i]) == -1) {
warn(‘Campaign: “‘+camp.getName()+'” incorrectly targeting: “‘+campLocList[i]+'”‘);
}
}
for(var i in locList) {
if(campLocList.indexOf(locList[i]) == -1) {
warn(‘Campaign: “‘+camp.getName()+'” not targeting: “‘+locList[i]+'”‘);
}
}
}
}

function verifySearchAndDisplay() {
var API_VERSION = { includeZeroImpressions : false };
var cols = [‘CampaignId’,’CampaignName’,’AdNetworkType1′,’Impressions’];
var report = ‘CAMPAIGN_PERFORMANCE_REPORT’;
var query = [‘select’,cols.join(‘,’),’from’,report,’during’,’LAST_30_DAYS’].join(‘ ‘);
var results = {}; // { campId : { agId : [ row, … ], … }, … }
var reportIter = AdWordsApp.report(query, API_VERSION).rows();
while(reportIter.hasNext()) {
var row = reportIter.next();
if(results[row.CampaignId]) {
warn(‘Campaign: “‘+row.CampaignName+'” is targeting the Display and Search networks.’);
} else {
results[row.CampaignId] = row;
}
}
return results;
}

function warn(msg) {
Logger.log(‘WARNING: ‘+msg);
}

function info(msg) {
Logger.log(msg);
}

 

 

Automação Google Ads: Como programar alertas

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para “Programação de alertas especiais”.

 

1. Detector de Anomalias da Conta – Por Google Ads. O script do Google alerta por e-mail quando sua conta apresenta um comportamento muito diferente de seu histórico. Ele faz uma comparação com as estatísticas obtidas até o dia atual com o histórico das estatísticas em relação ao mesmo dia da semana.

// Copyright 2017, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Account Anomaly Detector
*
* @fileoverview The Account Anomaly Detector alerts the advertiser whenever an
* advertiser account is suddenly behaving too differently from what's
* historically observed. See
* //developers.google.com/adwords/scripts/docs/solutions/account-anomaly-detector
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.1
*
* @changelog
* - version 1.1.1
* - Fixed bug in handling of reports with 0 rows.
* - version 1.1
* - Added conversions to tracked statistics.
* - version 1.0.3
* - Improved code readability and comments.
* - version 1.0.2
* - Added validation for external spreadsheet setup.
* - Updated to use report version v201609.
* - version 1.0.1
* - Improvements to time zone handling.
* - version 1.0
* - Released initial version.
*/

var SPREADSHEET_URL = 'YOUR_SPREADSHEET_URL';

var DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday',
'Saturday', 'Sunday'];

/**
* Configuration to be used for running reports.
*/
var REPORTING_OPTIONS = {
// Comment out the following line to default to the latest reporting version.
apiVersion: 'v201705'
};

function main() {
Logger.log('Using spreadsheet - %s.', SPREADSHEET_URL);
var spreadsheet = validateAndGetSpreadsheet(SPREADSHEET_URL);
spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone());

var impressionsThreshold = parseField(spreadsheet.
getRangeByName('impressions').getValue());
var clicksThreshold = parseField(spreadsheet.getRangeByName('clicks').
getValue());
var conversionsThreshold =
parseField(spreadsheet.getRangeByName('conversions').getValue());
var costThreshold = parseField(spreadsheet.getRangeByName('cost').getValue());
var weeksStr = spreadsheet.getRangeByName('weeks').getValue();
var weeks = parseInt(weeksStr.substring(0, weeksStr.indexOf(' ')));
var email = spreadsheet.getRangeByName('email').getValue();

var now = new Date();

// Basic reporting statistics are usually available with no more than a 3-hour
// delay.
var upTo = new Date(now.getTime() - 3 * 3600 * 1000);
var upToHour = parseInt(getDateStringInTimeZone('h', upTo));

if (upToHour == 1) {
// first run for the day, kill existing alerts
spreadsheet.getRangeByName('clicks_alert').clearContent();
spreadsheet.getRangeByName('impressions_alert').clearContent();
spreadsheet.getRangeByName('conversions_alert').clearContent();
spreadsheet.getRangeByName('cost_alert').clearContent();
}

var dateRangeToCheck = getDateStringInPast(0, upTo);
var dateRangeToEnd = getDateStringInPast(1, upTo);
var dateRangeToStart = getDateStringInPast(1 + weeks * 7, upTo);
var fields = 'HourOfDay,DayOfWeek,Clicks,Impressions,Conversions,Cost';
var todayQuery = 'SELECT ' + fields +
' FROM ACCOUNT_PERFORMANCE_REPORT DURING ' + dateRangeToCheck + ',' +
dateRangeToCheck;
var pastQuery = 'SELECT ' + fields +
' FROM ACCOUNT_PERFORMANCE_REPORT WHERE DayOfWeek=' +
DAYS[getDateStringInTimeZone('u', now)].toUpperCase() +
' DURING ' + dateRangeToStart + ',' + dateRangeToEnd;

var todayStats = getReportStats(todayQuery, upToHour, 1);
var pastStats = getReportStats(pastQuery, upToHour, weeks);

var statsExist = true;
if (typeof todayStats === 'undefined' || typeof pastStats === 'undefined') {
statsExist = false;
}

var alertText = [];
if (statsExist && impressionsThreshold &&
todayStats.impressions < pastStats.impressions * impressionsThreshold) {
var ImpressionsAlert = ' Impressions are too low: ' +
todayStats.impressions + ' impressions by ' + upToHour +
':00, expecting at least ' +
parseInt(pastStats.impressions * impressionsThreshold);
writeAlert(spreadsheet, 'impressions_alert', alertText, ImpressionsAlert,
upToHour);
}
if (statsExist && clicksThreshold &&
todayStats.clicks < pastStats.clicks * clicksThreshold) {
var clickAlert = ' Clicks are too low: ' + todayStats.clicks +
' clicks by ' + upToHour + ':00, expecting at least ' +
(pastStats.clicks * clicksThreshold).toFixed(1);
writeAlert(spreadsheet, 'clicks_alert', alertText, clickAlert, upToHour);
}
if (statsExist && conversionsThreshold &&
todayStats.conversions < pastStats.conversions * conversionsThreshold) {
var conversionsAlert =
' Conversions are too low: ' + todayStats.conversions +
' conversions by ' + upToHour + ':00, expecting at least ' +
(pastStats.conversions * conversionsThreshold).toFixed(1);
writeAlert(
spreadsheet, 'conversions_alert', alertText, conversionsAlert,
upToHour);
}
if (statsExist && costThreshold &&
todayStats.cost > pastStats.cost * costThreshold) {
var costAlert = ' Cost is too high: ' + todayStats.cost + ' ' +
AdWordsApp.currentAccount().getCurrencyCode() + ' by ' + upToHour +
':00, expecting at most ' +
(pastStats.cost * costThreshold).toFixed(2);
writeAlert(spreadsheet, 'cost_alert', alertText, costAlert, upToHour);
}

if (alertText.length > 0 && email && email.length > 0) {
MailApp.sendEmail(email,
'AdWords Account ' + AdWordsApp.currentAccount().getCustomerId() +
' misbehaved.',
'Your account ' + AdWordsApp.currentAccount().getCustomerId() +
' is not performing as expected today: \n\n' + alertText.join('\n') +
'\n\nLog into AdWords and take a look.\n\nAlerts dashboard: ' +
SPREADSHEET_URL);
}

writeDataToSpreadsheet(spreadsheet, now, statsExist, todayStats, pastStats,
AdWordsApp.currentAccount().getCustomerId());
}

function toFloat(value) {
value = value.toString().replace(/,/g, '');
return parseFloat(value);
}

function parseField(value) {
if (value == 'No alert') {
return null;
} else {
return toFloat(value);
}
}

/**
* Runs an AdWords report query for a number of weeks and return the average
* values for the stats.
*
* @param {string} query The formatted report query.
* @param {int} hours The limit hour of day for considering the report rows.
* @param {int} weeks The number of weeks for the past stats.
* @return {Object} An object containing the average values for the stats.
*/
function getReportStats(query, hours, weeks) {
var reportRows = [];
var report = AdWordsApp.report(query, REPORTING_OPTIONS);
var rows = report.rows();
while (rows.hasNext()) {
reportRows.push(rows.next());
}
return accumulateRows(reportRows, hours, weeks);
}

function accumulateRows(rows, hours, weeks) {
var result = {clicks: 0, impressions: 0, conversions: 0, cost: 0};

for (var i = 0; i < rows.length; i++) {
var row = rows[i];
var hour = row['HourOfDay'];
if (hour < hours) {
result = addRow(row, result, 1 / weeks);
}
}
return result;
}

function addRow(row, previous, coefficient) {
if (!coefficient) {
coefficient = 1;
}
if (row == null) {
row = {Clicks: 0, Impressions: 0, Conversions: 0, Cost: 0};
}
if (!previous) {
return {
clicks: parseInt(row['Clicks']) * coefficient,
impressions: parseInt(row['Impressions']) * coefficient,
conversions: parseInt(row['Conversions']) * coefficient,
cost: toFloat(row['Cost']) * coefficient
};
} else {
return {
clicks: parseInt(row['Clicks']) * coefficient + previous.clicks,
impressions:
parseInt(row['Impressions']) * coefficient + previous.impressions,
conversions:
parseInt(row['Conversions']) * coefficient + previous.conversions,
cost: toFloat(row['Cost']) * coefficient + previous.cost
};
}
}

/**
* Produces a formatted string representing a date in the past of a given date.
*
* @param {number} numDays The number of days in the past.
* @param {date} date A date object. Defaults to the current date.
* @return {string} A formatted string in the past of the given date.
*/
function getDateStringInPast(numDays, date) {
date = date || new Date();
var MILLIS_PER_DAY = 1000 * 60 * 60 * 24;
var past = new Date(date.getTime() - numDays * MILLIS_PER_DAY);
return getDateStringInTimeZone('yyyyMMdd', past);
}

/**
* Produces a formatted string representing a given date in a given time zone.
*
* @param {string} format A format specifier for the string to be produced.
* @param {date} date A date object. Defaults to the current date.
* @param {string} timeZone A time zone. Defaults to the account's time zone.
* @return {string} A formatted string of the given date in the given time zone.
*/
function getDateStringInTimeZone(format, date, timeZone) {
date = date || new Date();
timeZone = timeZone || AdWordsApp.currentAccount().getTimeZone();
return Utilities.formatDate(date, timeZone, format);
}

/**
* Validates the provided spreadsheet URL and email address
* to make sure that they're set up properly. Throws a descriptive error message
* if validation fails.
*
* @param {string} spreadsheeturl The URL of the spreadsheet to open.
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
* @throws {Error} If the spreadsheet URL or email hasn't been set
*/
function validateAndGetSpreadsheet(spreadsheeturl) {
if (spreadsheeturl == 'YOUR_SPREADSHEET_URL') {
throw new Error('Please specify a valid Spreadsheet URL. You can find' +
' a link to a template in the associated guide for this script.');
}
var spreadsheet = SpreadsheetApp.openByUrl(spreadsheeturl);
var email = spreadsheet.getRangeByName('email').getValue();
if ('[email protected]' == email) {
throw new Error('Please either set a custom email address in the' +
' spreadsheet, or set the email field in the spreadsheet to blank' +
' to send no email.');
}
return spreadsheet;
}

/**
* Writes the alert time in the spreadsheet and push the alert message to the
* list of messages.
*
* @param {Spreadsheet} spreadsheet The dashboard spreadsheet.
* @param {string} rangeName The named range in the spreadsheet.
* @param {Array<string>} alertText The list of alert messages.
* @param {string} alertMessage The alert message.
* @param {int} hour The limit hour used to get the stats.
*/
function writeAlert(spreadsheet, rangeName, alertText, alertMessage, hour) {
var range = spreadsheet.getRangeByName(rangeName);
if (!range.getValue() || range.getValue().length == 0) {
alertText.push(alertMessage);
range.setValue('Alerting ' + hour + ':00');
}
}

/**
* Writes the data to the spreadsheet.
*
* @param {Spreadsheet} spreadsheet The dashboard spreadsheet.
* @param {Date} now The date corresponding to the running time of the script.
* @param {boolean} statsExist A boolean that indicates the existence of stats.
* @param {Object} todayStats The stats for today.
* @param {Object} pastStats The past stats for the period defined in the
* spreadsheet.
* @param {string} accountId The account ID.
*/
function writeDataToSpreadsheet(spreadsheet, now, statsExist, todayStats,
pastStats, accountId) {
spreadsheet.getRangeByName('date').setValue(now);
spreadsheet.getRangeByName('account_id').setValue(accountId);
spreadsheet.getRangeByName('timestamp').setValue(
getDateStringInTimeZone('E HH:mm:ss z', now));

if (statsExist) {
var dataRows = [
[todayStats.impressions, pastStats.impressions.toFixed(0)],
[todayStats.clicks, pastStats.clicks.toFixed(1)],
[todayStats.conversions, pastStats.conversions.toFixed(1)],
[todayStats.cost, pastStats.cost.toFixed(2)]
];
spreadsheet.getRangeByName('data').setValues(dataRows);
}
}

 

Como configurar

 

2. Verificador de Links – Por Google Ads. Verifique se seus anúncios não estão direcionando para uma página 404 com esse script. O script vasculha suas palavras-chave, anúncios e sitelinks em busca de páginas de erro e além de alertar por e-mail ele também cria um relatório.

// Copyright 2016, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Link Checker
*
* @overview The Link Checker script iterates through the ads, keywords, and
* sitelinks in your account and makes sure their URLs do not produce "Page
* not found" or other types of error responses. See
* //developers.google.com/adwords/scripts/docs/solutions/link-checker
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 2.1
*
* @changelog
* - version 2.1
* - Added expansion of conditional ValueTrack parameters (e.g. ifmobile).
* - Added expanded text ad and other ad format support.
* - version 2.0.3
* - Added validation for external spreadsheet setup.
* - version 2.0.2
* - Allow the custom tracking label to include spaces.
* - version 2.0.1
* - Catch and output all UrlFetchApp exceptions.
* - version 2.0
* - Completely revised the script to work on larger accounts.
* - Check URLs in campaign and ad group sitelinks.
* - version 1.2
* - Released initial version.
*/

var CONFIG = {
// URL of the spreadsheet template.
// This should be a copy of //goo.gl/8YLeMj.
SPREADSHEET_URL: 'YOUR_SPREADSHEET_URL',

// Array of addresses to be alerted via email if issues are found.
RECIPIENT_EMAILS: [
'YOUR_EMAIL_HERE'
],

// Label to use when a link has been checked.
LABEL: 'LinkChecker_Done',

// Number of milliseconds to sleep after each URL request. If your URLs are
// all on one or a few domains, use this throttle to reduce the load that the
// script imposes on your web server(s).
THROTTLE: 0,

// Number of seconds before timeout that the script should stop checking URLs
// to make sure it has time to output its findings.
TIMEOUT_BUFFER: 120
};

/**
* Parameters controlling the script's behavior after hitting a UrlFetchApp
* QPS quota limit.
*/
var QUOTA_CONFIG = {
INIT_SLEEP_TIME: 250,
BACKOFF_FACTOR: 2,
MAX_TRIES: 5
};

/**
* Exceptions that prevent the script from finishing checking all URLs in an
* account but allow it to resume next time.
*/
var EXCEPTIONS = {
QPS: 'Reached UrlFetchApp QPS limit',
LIMIT: 'Reached UrlFetchApp daily quota',
TIMEOUT: 'Approached script execution time limit'
};

/**
* Named ranges in the spreadsheet.
*/
var NAMES = {
CHECK_AD_URLS: 'checkAdUrls',
CHECK_KEYWORD_URLS: 'checkKeywordUrls',
CHECK_SITELINK_URLS: 'checkSitelinkUrls',
CHECK_PAUSED_ADS: 'checkPausedAds',
CHECK_PAUSED_KEYWORDS: 'checkPausedKeywords',
CHECK_PAUSED_SITELINKS: 'checkPausedSitelinks',
VALID_CODES: 'validCodes',
EMAIL_EACH_RUN: 'emailEachRun',
EMAIL_NON_ERRORS: 'emailNonErrors',
EMAIL_ON_COMPLETION: 'emailOnCompletion',
SAVE_ALL_URLS: 'saveAllUrls',
FREQUENCY: 'frequency',
DATE_STARTED: 'dateStarted',
DATE_COMPLETED: 'dateCompleted',
DATE_EMAILED: 'dateEmailed',
NUM_ERRORS: 'numErrors',
RESULT_HEADERS: 'resultHeaders',
ARCHIVE_HEADERS: 'archiveHeaders'
};

function main() {
var spreadsheet = validateAndGetSpreadsheet(CONFIG.SPREADSHEET_URL);
validateEmailAddresses(CONFIG.RECIPIENT_EMAILS);
spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone());

var options = loadOptions(spreadsheet);
var status = loadStatus(spreadsheet);

if (!status.dateStarted) {
// This is the very first execution of the script.
startNewAnalysis(spreadsheet);
} else if (status.dateStarted > status.dateCompleted) {
Logger.log('Resuming work from a previous execution.');
} else if (dayDifference(status.dateStarted, new Date()) <
options.frequency) {
Logger.log('Waiting until ' + options.frequency +
' days have elapsed since the start of the last analysis.');
return;
} else {
// Enough time has passed since the last analysis to start a new one.
removeLabels([CONFIG.LABEL]);
startNewAnalysis(spreadsheet);
}

var results = analyzeAccount(options);
outputResults(results, options);
}

/**
* Checks as many new URLs as possible that have not previously been checked,
* subject to quota and time limits.
*
* @param {Object} options Dictionary of options.
* @return {Object} An object with fields for the URLs checked and an indication
* if the analysis was completed (no remaining URLs to check).
*/
function analyzeAccount(options) {
// Ensure the label exists before attempting to retrieve already checked URLs.
ensureLabels([CONFIG.LABEL]);

var checkedUrls = getAlreadyCheckedUrls(options);
var urlChecks = [];
var didComplete = false;

try {
// If the script throws an exception, didComplete will remain false.
didComplete = checkUrls(checkedUrls, urlChecks, options);
} catch(e) {
if (e == EXCEPTIONS.QPS ||
e == EXCEPTIONS.LIMIT ||
e == EXCEPTIONS.TIMEOUT) {
Logger.log('Stopped checking URLs early because: ' + e);
Logger.log('Checked URLs will still be output.');
} else {
throw e;
}
}

return {
urlChecks: urlChecks,
didComplete: didComplete
};
}

/**
* Outputs the results to a spreadsheet and sends emails if appropriate.
*
* @param {Object} results An object with fields for the URLs checked and an
* indication if the analysis was completed (no remaining URLs to check).
* @param {Object} options Dictionary of options.
*/
function outputResults(results, options) {
var spreadsheet = SpreadsheetApp.openByUrl(CONFIG.SPREADSHEET_URL);

var numErrors = countErrors(results.urlChecks, options);
Logger.log('Found ' + numErrors + ' this execution.');

saveUrlsToSpreadsheet(spreadsheet, results.urlChecks, options);

// Reload the status to get the total number of errors for the entire
// analysis, which is calculated by the spreadsheet.
status = loadStatus(spreadsheet);

if (results.didComplete) {
spreadsheet.getRangeByName(NAMES.DATE_COMPLETED).setValue(new Date());
Logger.log('Found ' + status.numErrors + ' across the entire analysis.');
}

if (CONFIG.RECIPIENT_EMAILS) {
if (!results.didComplete && options.emailEachRun &&
(options.emailNonErrors || numErrors > 0)) {
sendIntermediateEmail(spreadsheet, numErrors);
}

if (results.didComplete &&
(options.emailEachRun || options.emailOnCompletion) &&
(options.emailNonErrors || status.numErrors > 0)) {
sendFinalEmail(spreadsheet, status.numErrors);
}
}
}

/**
* Loads data from a spreadsheet based on named ranges. Strings 'Yes' and 'No'
* are converted to booleans. One-dimensional ranges are converted to arrays
* with blank cells omitted. Assumes each named range exists.
*
* @param {Object} spreadsheet The spreadsheet object.
* @param {Array.<string>} names A list of named ranges that should be loaded.
* @return {Object} A dictionary with the names as keys and the values
* as the cell values from the spreadsheet.
*/
function loadDatabyName(spreadsheet, names) {
var data = {};

for (var i = 0; i < names.length; i++) {
var name = names[i];
var range = spreadsheet.getRangeByName(name);

if (range.getNumRows() > 1 && range.getNumColumns() > 1) {
// Name refers to a 2d range, so load it as a 2d array.
data[name] = range.getValues();
} else if (range.getNumRows() == 1 && range.getNumColumns() == 1) {
// Name refers to a single cell, so load it as a value and replace
// Yes/No with boolean true/false.
data[name] = range.getValue();
data[name] = data[name] === 'Yes' ? true : data[name];
data[name] = data[name] === 'No' ? false : data[name];
} else {
// Name refers to a 1d range, so load it as an array (regardless of
// whether the 1d range is oriented horizontally or vertically).
var isByRow = range.getNumRows() > 1;
var limit = isByRow ? range.getNumRows() : range.getNumColumns();
var cellValues = range.getValues();

data[name] = [];
for (var j = 0; j < limit; j++) {
var cellValue = isByRow ? cellValues[j][0] : cellValues[0][j];
if (cellValue) {
data[name].push(cellValue);
}
}
}
}

return data;
}

/**
* Loads options from the spreadsheet.
*
* @param {Object} spreadsheet The spreadsheet object.
* @return {Object} A dictionary of options.
*/
function loadOptions(spreadsheet) {
return loadDatabyName(spreadsheet,
[NAMES.CHECK_AD_URLS, NAMES.CHECK_KEYWORD_URLS,
NAMES.CHECK_SITELINK_URLS, NAMES.CHECK_PAUSED_ADS,
NAMES.CHECK_PAUSED_KEYWORDS, NAMES.CHECK_PAUSED_SITELINKS,
NAMES.VALID_CODES, NAMES.EMAIL_EACH_RUN,
NAMES.EMAIL_NON_ERRORS, NAMES.EMAIL_ON_COMPLETION,
NAMES.SAVE_ALL_URLS, NAMES.FREQUENCY]);
}

/**
* Loads state information from the spreadsheet.
*
* @param {Object} spreadsheet The spreadsheet object.
* @return {Object} A dictionary of status information.
*/
function loadStatus(spreadsheet) {
return loadDatabyName(spreadsheet,
[NAMES.DATE_STARTED, NAMES.DATE_COMPLETED,
NAMES.DATE_EMAILED, NAMES.NUM_ERRORS]);
}

/**
* Saves the start date to the spreadsheet and archives results of the last
* analysis to a separate sheet.
*
* @param {Object} spreadsheet The spreadsheet object.
*/
function startNewAnalysis(spreadsheet) {
Logger.log('Starting a new analysis.');

spreadsheet.getRangeByName(NAMES.DATE_STARTED).setValue(new Date());

// Helper method to get the output area on the results or archive sheets.
var getOutputRange = function(rangeName) {
var headers = spreadsheet.getRangeByName(rangeName);
return headers.offset(1, 0, headers.getSheet().getDataRange().getLastRow());
};

getOutputRange(NAMES.ARCHIVE_HEADERS).clearContent();

var results = getOutputRange(NAMES.RESULT_HEADERS);
results.copyTo(getOutputRange(NAMES.ARCHIVE_HEADERS));

getOutputRange(NAMES.RESULT_HEADERS).clearContent();
}

/**
* Counts the number of errors in the results.
*
* @param {Array.<Object>} urlChecks A list of URL check results.
* @param {Object} options Dictionary of options.
* @return {number} The number of errors in the results.
*/
function countErrors(urlChecks, options) {
var numErrors = 0;

for (var i = 0; i < urlChecks.length; i++) {
if (options.validCodes.indexOf(urlChecks[i].responseCode) == -1) {
numErrors++;
}
}

return numErrors;
}

/**
* Saves URLs for a particular account to the spreadsheet starting at the first
* unused row.
*
* @param {Object} spreadsheet The spreadsheet object.
* @param {Array.<Object>} urlChecks A list of URL check results.
* @param {Object} options Dictionary of options.
*/
function saveUrlsToSpreadsheet(spreadsheet, urlChecks, options) {
// Build each row of output values in the order of the columns.
var outputValues = [];
for (var i = 0; i < urlChecks.length; i++) {
var urlCheck = urlChecks[i];

if (options.saveAllUrls ||
options.validCodes.indexOf(urlCheck.responseCode) == -1) {
outputValues.push([
urlCheck.customerId,
new Date(urlCheck.timestamp),
urlCheck.url,
urlCheck.responseCode,
urlCheck.entityType,
urlCheck.campaign,
urlCheck.adGroup,
urlCheck.ad,
urlCheck.keyword,
urlCheck.sitelink
]);
}
}

if (outputValues.length > 0) {
// Find the first open row on the Results tab below the headers and create a
// range large enough to hold all of the output, one per row.
var headers = spreadsheet.getRangeByName(NAMES.RESULT_HEADERS);
var lastRow = headers.getSheet().getDataRange().getLastRow();
var outputRange = headers.offset(lastRow - headers.getRow() + 1,
0, outputValues.length);
outputRange.setValues(outputValues);
}

for (var i = 0; i < CONFIG.RECIPIENT_EMAILS.length; i++) {
spreadsheet.addEditor(CONFIG.RECIPIENT_EMAILS[i]);
}
}

/**
* Sends an email to a list of email addresses with a link to the spreadsheet
* and the results of this execution of the script.
*
* @param {Object} spreadsheet The spreadsheet object.
* @param {boolean} numErrors The number of errors found in this execution.
*/
function sendIntermediateEmail(spreadsheet, numErrors) {
spreadsheet.getRangeByName(NAMES.DATE_EMAILED).setValue(new Date());

MailApp.sendEmail(CONFIG.RECIPIENT_EMAILS.join(','),
'Link Checker Results',
'The Link Checker script found ' + numErrors + ' URLs with errors in ' +
'an execution that just finished. See ' +
spreadsheet.getUrl() + ' for details.');
}

/**
* Sends an email to a list of email addresses with a link to the spreadsheet
* and the results across the entire account.
*
* @param {Object} spreadsheet The spreadsheet object.
* @param {boolean} numErrors The number of errors found in the entire account.
*/
function sendFinalEmail(spreadsheet, numErrors) {
spreadsheet.getRangeByName(NAMES.DATE_EMAILED).setValue(new Date());

MailApp.sendEmail(CONFIG.RECIPIENT_EMAILS.join(','),
'Link Checker Results',
'The Link Checker script found ' + numErrors + ' URLs with errors ' +
'across its entire analysis. See ' +
spreadsheet.getUrl() + ' for details.');
}

/**
* Retrieves all final URLs and mobile final URLs in the account across ads,
* keywords, and sitelinks that were checked in a previous run, as indicated by
* them having been labeled.
*
* @param {Object} options Dictionary of options.
* @return {Object} A map of previously checked URLs with the URL as the key.
*/
function getAlreadyCheckedUrls(options) {
var urlMap = {};

var addToMap = function(items) {
for (var i = 0; i < items.length; i++) {
var urls = expandUrlModifiers(items[i]);
urls.forEach(function(url) {
urlMap[url] = true;
});
}
};

if (options.checkAdUrls) {
addToMap(getUrlsBySelector(AdWordsApp.ads().
withCondition(labelCondition(true))));
}

if (options.checkKeywordUrls) {
addToMap(getUrlsBySelector(AdWordsApp.keywords().
withCondition(labelCondition(true))));
}

if (options.checkSitelinkUrls) {
addToMap(getAlreadyCheckedSitelinkUrls());
}

return urlMap;
}

/**
* Retrieves all final URLs and mobile final URLs for campaign and ad group
* sitelinks.
*
* @return {Array.<string>} An array of URLs.
*/
function getAlreadyCheckedSitelinkUrls() {
var urls = [];

// Helper method to get campaign or ad group sitelink URLs.
var addSitelinkUrls = function(selector) {
var iterator = selector.withCondition(labelCondition(true)).get();

while (iterator.hasNext()) {
var entity = iterator.next();
var sitelinks = entity.extensions().sitelinks();
urls = urls.concat(getUrlsBySelector(sitelinks));
}
};

addSitelinkUrls(AdWordsApp.campaigns());
addSitelinkUrls(AdWordsApp.adGroups());

return urls;
}

/**
* Retrieves all URLs in the entities specified by a selector.
*
* @param {Object} selector The selector specifying the entities to use.
* The entities should be of a type that has a urls() method.
* @return {Array.<string>} An array of URLs.
*/
function getUrlsBySelector(selector) {
var urls = [];
var entities = selector.get();

// Helper method to add the url to the list if it exists.
var addToList = function(url) {
if (url) {
urls.push(url);
}
};

while (entities.hasNext()) {
var entity = entities.next();

addToList(entity.urls().getFinalUrl());
addToList(entity.urls().getMobileFinalUrl());
}

return urls;
}

/**
* Retrieves all final URLs and mobile final URLs in the account across ads,
* keywords, and sitelinks, and checks their response code. Does not check
* previously checked URLs.
*
* @param {Object} checkedUrls A map of previously checked URLs with the URL as
* the key.
* @param {Array.<Object>} urlChecks An array into which the results of each URL
* check will be inserted.
* @param {Object} options Dictionary of options.
* @return {boolean} True if all URLs were checked.
*/
function checkUrls(checkedUrls, urlChecks, options) {
var didComplete = true;

// Helper method to add common conditions to ad group and keyword selectors.
var addConditions = function(selector, includePaused) {
var statuses = ['ENABLED'];
if (includePaused) {
statuses.push('PAUSED');
}

var predicate = ' IN [' + statuses.join(',') + ']';
return selector.withCondition(labelCondition(false)).
withCondition('Status' + predicate).
withCondition('CampaignStatus' + predicate).
withCondition('AdGroupStatus' + predicate);
};

if (options.checkAdUrls) {
didComplete = didComplete && checkUrlsBySelector(checkedUrls, urlChecks,
addConditions(AdWordsApp.ads().withCondition('CreativeFinalUrls != ""'),
options.checkPausedAds));
}

if (options.checkKeywordUrls) {
didComplete = didComplete && checkUrlsBySelector(checkedUrls, urlChecks,
addConditions(AdWordsApp.keywords().withCondition('FinalUrls != ""'),
options.checkPausedKeywords));
}

if (options.checkSitelinkUrls) {
didComplete = didComplete &&
checkSitelinkUrls(checkedUrls, urlChecks, options);
}

return didComplete;
}

/**
* Retrieves all final URLs and mobile final URLs in a selector and checks them
* for a valid response code. Does not check previously checked URLs. Labels the
* entity that it was checked, if possible.
*
* @param {Object} checkedUrls A map of previously checked URLs with the URL as
* the key.
* @param {Array.<Object>} urlChecks An array into which the results of each URL
* check will be inserted.
* @param {Object} selector The selector specifying the entities to use.
* The entities should be of a type that has a urls() method.
* @return {boolean} True if all URLs were checked.
*/
function checkUrlsBySelector(checkedUrls, urlChecks, selector) {
var customerId = AdWordsApp.currentAccount().getCustomerId();
var iterator = selector.get();
var entities = [];

// Helper method to check a URL.
var checkUrl = function(entity, url) {
if (!url) {
return;
}

var urlsToCheck = expandUrlModifiers(url);

for (var i = 0; i < urlsToCheck.length; i++) {
var expandedUrl = urlsToCheck[i];
if (checkedUrls[expandedUrl]) {
continue;
}

var responseCode = requestUrl(expandedUrl);
var entityType = entity.getEntityType();

urlChecks.push({
customerId: customerId,
timestamp: new Date(),
url: expandedUrl,
responseCode: responseCode,
entityType: entityType,
campaign: entity.getCampaign ? entity.getCampaign().getName() : '',
adGroup: entity.getAdGroup ? entity.getAdGroup().getName() : '',
ad: entityType == 'Ad' ? getAdAsText(entity) : '',
keyword: entityType == 'Keyword' ? entity.getText() : '',
sitelink: entityType.indexOf('Sitelink') != -1 ?
entity.getLinkText() : ''
});

checkedUrls[expandedUrl] = true;
}
};

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

for (var i = 0; i < entities.length; i++) {
var entity = entities[i];

checkUrl(entity, entity.urls().getFinalUrl());
checkUrl(entity, entity.urls().getMobileFinalUrl());

// Sitelinks do not have labels.
if (entity.applyLabel) {
entity.applyLabel(CONFIG.LABEL);
checkTimeout();
}
}

// True only if we did not breach an iterator limit.
return entities.length == iterator.totalNumEntities();
}

/**
* Retrieves a text representation of an ad, casting the ad to the appropriate
* type if necessary.
*
* @param {Ad} ad The ad object.
* @return {string} The text representation.
*/
function getAdAsText(ad) {
// There is no AdTypeSpace method for textAd
if (ad.getType() === 'TEXT_AD') {
return ad.getHeadline();
} else if (ad.isType().expandedTextAd()) {
var eta = ad.asType().expandedTextAd();
return eta.getHeadlinePart1() + ' - ' + eta.getHeadlinePart2();
} else if (ad.isType().gmailImageAd()) {
return ad.asType().gmailImageAd().getName();
} else if (ad.isType().gmailMultiProductAd()) {
return ad.asType().gmailMultiProductAd().getHeadline();
} else if (ad.isType().gmailSinglePromotionAd()) {
return ad.asType().gmailSinglePromotionAd().getHeadline();
} else if (ad.isType().html5Ad()) {
return ad.asType().html5Ad().getName();
} else if (ad.isType().imageAd()) {
return ad.asType().imageAd().getName();
} else if (ad.isType().responsiveDisplayAd()) {
return ad.asType().responsiveDisplayAd().getLongHeadline();
}
return 'N/A';
}

/**
* Retrieves all final URLs and mobile final URLs for campaign and ad group
* sitelinks and checks them for a valid response code. Does not check
* previously checked URLs. Labels the containing campaign or ad group that it
* has been checked.
*
* @param {Object} checkedUrls A map of previously checked URLs with the URL as
* the key.
* @param {Array.<Object>} urlChecks An array into which the results of each URL
* check will be inserted.
* @param {Object} options Dictionary of options.
* @return {boolean} True if all URLs were checked.
*/
function checkSitelinkUrls(checkedUrls, urlChecks, options) {
var didComplete = true;

// Helper method to check URLs for sitelinks in a campaign or ad group
// selector.
var checkSitelinkUrls = function(selector) {
var iterator = selector.withCondition(labelCondition(false)).get();
var entities = [];

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

for (var i = 0; i < entities.length; i++) {
var entity = entities[i];
var sitelinks = entity.extensions().sitelinks();

if (sitelinks.get().hasNext()) {
didComplete = didComplete &&
checkUrlsBySelector(checkedUrls, urlChecks, sitelinks);
entity.applyLabel(CONFIG.LABEL);
checkTimeout();
}
}

// True only if we did not breach an iterator limit.
didComplete = didComplete &&
entities.length == iterator.totalNumEntities();
};

var statuses = ['ENABLED'];
if (options.checkPausedSitelinks) {
statuses.push('PAUSED');
}

var predicate = ' IN [' + statuses.join(',') + ']';
checkSitelinkUrls(AdWordsApp.campaigns().
withCondition('Status' + predicate));
checkSitelinkUrls(AdWordsApp.adGroups().
withCondition('Status' + predicate).
withCondition('CampaignStatus' + predicate));

return didComplete;
}

/**
* Expands a URL that contains ValueTrack parameters such as {ifmobile:mobile}
* to all the combinations, and returns as an array. The following pairs of
* ValueTrack parameters are currently expanded:
* 1. {ifmobile:<...>} and {ifnotmobile:<...>} to produce URLs simulating
* clicks from either mobile or non-mobile devices.
* 2. {ifsearch:<...>} and {ifcontent:<...>} to produce URLs simulating
* clicks on either the search or display networks.
* Any other ValueTrack parameters or customer parameters are stripped out from
* the URL entirely.
*
* @param {string} url The URL which may contain ValueTrack parameters.
* @return {!Array.<string>} An array of one or more expanded URLs.
*/
function expandUrlModifiers(url) {
var ifRegex = /({(if\w+):([^}]+)})/gi;
var modifiers = {};
var matches;
while (matches = ifRegex.exec(url)) {
// Tags are case-insensitive, e.g. IfMobile is valid.
modifiers[matches[2].toLowerCase()] = {
substitute: matches[0],
replacement: matches[3]
};
}
if (Object.keys(modifiers).length) {
if (modifiers.ifmobile || modifiers.ifnotmobile) {
var mobileCombinations =
pairedUrlModifierReplace(modifiers, 'ifmobile', 'ifnotmobile', url);
} else {
var mobileCombinations = [url];
}

// Store in a map on the offchance that there are duplicates.
var combinations = {};
mobileCombinations.forEach(function(url) {
if (modifiers.ifsearch || modifiers.ifcontent) {
pairedUrlModifierReplace(modifiers, 'ifsearch', 'ifcontent', url)
.forEach(function(modifiedUrl) {
combinations[modifiedUrl] = true;
});
} else {
combinations[url] = true;
}
});
var modifiedUrls = Object.keys(combinations);
} else {
var modifiedUrls = [url];
}
// Remove any custom parameters
return modifiedUrls.map(function(url) {
return url.replace(/{[0-9a-zA-Z\_\+\:]+}/g, '');
});
}

/**
* Return a pair of URLs, where each of the two modifiers is mutually exclusive,
* one for each combination. e.g. Evaluating ifmobile and ifnotmobile for a
* mobile and a non-mobile scenario.
*
* @param {Object} modifiers A map of ValueTrack modifiers.
* @param {string} modifier1 The modifier to honour in the URL.
* @param {string} modifier2 The modifier to remove from the URL.
* @param {string} url The URL potentially containing ValueTrack parameters.
* @return {Array.<string>} A pair of URLs, as a list.
*/
function pairedUrlModifierReplace(modifiers, modifier1, modifier2, url) {
return [
urlModifierReplace(modifiers, modifier1, modifier2, url),
urlModifierReplace(modifiers, modifier2, modifier1, url)
];
}

/**
* Produces a URL where the first {if...} modifier is set, and the second is
* deleted.
*
* @param {Object} mods A map of ValueTrack modifiers.
* @param {string} mod1 The modifier to honour in the URL.
* @param {string} mod2 The modifier to remove from the URL.
* @param {string} url The URL potentially containing ValueTrack parameters.
* @return {string} The resulting URL with substitions.
*/
function urlModifierReplace(mods, mod1, mod2, url) {
var modUrl = mods[mod1] ?
url.replace(mods[mod1].substitute, mods[mod1].replacement) :
url;
return mods[mod2] ? modUrl.replace(mods[mod2].substitute, '') : modUrl;
}

/**
* Requests a given URL. Retries if the UrlFetchApp QPS limit was reached,
* exponentially backing off on each retry. Throws an exception if it reaches
* the maximum number of retries. Throws an exception if the UrlFetchApp daily
* quota limit was reached.
*
* @param {string} url The URL to test.
* @return {number|string} The response code received when requesting the URL,
* or an error message.
*/
function requestUrl(url) {
var responseCode;
var sleepTime = QUOTA_CONFIG.INIT_SLEEP_TIME;
var numTries = 0;

while (numTries < QUOTA_CONFIG.MAX_TRIES && !responseCode) {
try {
// If UrlFetchApp.fetch() throws an exception, responseCode will remain
// undefined.
responseCode =
UrlFetchApp.fetch(url, {muteHttpExceptions: true}).getResponseCode();

if (CONFIG.THROTTLE > 0) {
Utilities.sleep(CONFIG.THROTTLE);
}
} catch(e) {
if (e.message.indexOf('Service invoked too many times in a short time:')
!= -1) {
Utilities.sleep(sleepTime);
sleepTime *= QUOTA_CONFIG.BACKOFF_FACTOR;
} else if (e.message.indexOf('Service invoked too many times:') != -1) {
throw EXCEPTIONS.LIMIT;
} else {
return e.message;
}
}

numTries++;
}

if (!responseCode) {
throw EXCEPTIONS.QPS;
} else {
return responseCode;
}
}

/**
* Throws an exception if the script is close to timing out.
*/
function checkTimeout() {
if (AdWordsApp.getExecutionInfo().getRemainingTime() <
CONFIG.TIMEOUT_BUFFER) {
throw EXCEPTIONS.TIMEOUT;
}
}

/**
* Returns the number of days between two dates.
*
* @param {Object} from The older Date object.
* @param {Object} to The newer (more recent) Date object.
* @return {number} The number of days between the given dates (possibly
* fractional).
*/
function dayDifference(from, to) {
return (to.getTime() - from.getTime()) / (24 * 3600 * 1000);
}

/**
* Builds a string to be used for withCondition() filtering for whether the
* label is present or not.
*
* @param {boolean} hasLabel True if the label should be present, false if the
* label should not be present.
* @return {string} A condition that can be used in withCondition().
*/
function labelCondition(hasLabel) {
return 'LabelNames ' + (hasLabel ? 'CONTAINS_ANY' : 'CONTAINS_NONE') +
' ["' + CONFIG.LABEL + '"]';
}

/**
* Retrieves an entity by name.
*
* @param {Object} selector A selector for an entity type with a Name field.
* @param {string} name The name to retrieve the entity by.
* @return {Object} The entity, if it exists, or null otherwise.
*/
function getEntityByName(selector, name) {
var entities = selector.withCondition('Name = "' + name + '"').get();

if (entities.hasNext()) {
return entities.next();
} else {
return null;
}
}

/**
* Retrieves a Label object by name.
*
* @param {string} labelName The label name to retrieve.
* @return {Object} The Label object, if it exists, or null otherwise.
*/
function getLabel(labelName) {
return getEntityByName(AdWordsApp.labels(), labelName);
}

/**
* Checks that the account has all provided labels and creates any that are
* missing. Since labels cannot be created in preview mode, throws an exception
* if a label is missing.
*
* @param {Array.<string>} labelNames An array of label names.
*/
function ensureLabels(labelNames) {
for (var i = 0; i < labelNames.length; i++) {
var labelName = labelNames[i];
var label = getLabel(labelName);

if (!label) {
if (!AdWordsApp.getExecutionInfo().isPreview()) {
AdWordsApp.createLabel(labelName);
} else {
throw 'Label ' + labelName + ' is missing and cannot be created in ' +
'preview mode. Please run the script or create the label manually.';
}
}
}
}

/**
* Removes all provided labels from the account. Since labels cannot be removed
* in preview mode, throws an exception in preview mode.
*
* @param {Array.<string>} labelNames An array of label names.
*/
function removeLabels(labelNames) {
if (AdWordsApp.getExecutionInfo().isPreview()) {
throw 'Cannot remove labels in preview mode. Please run the script or ' +
'remove the labels manually.';
}

for (var i = 0; i < labelNames.length; i++) {
var label = getLabel(labelNames[i]);

if (label) {
label.remove();
}
}
}

/**
* Validates the provided spreadsheet URL to make sure that it's set up
* properly. Throws a descriptive error message if validation fails.
*
* @param {string} spreadsheeturl The URL of the spreadsheet to open.
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
* @throws {Error} If the spreadsheet URL hasn't been set
*/
function validateAndGetSpreadsheet(spreadsheeturl) {
if (spreadsheeturl == 'YOUR_SPREADSHEET_URL') {
throw new Error('Please specify a valid Spreadsheet URL. You can find' +
' a link to a template in the associated guide for this script.');
}
return SpreadsheetApp.openByUrl(spreadsheeturl);
}

/**
* Validates the provided email addresses to make sure it's not the default.
* Throws a descriptive error message if validation fails.
*
* @param {Array.<string>} recipientEmails The list of email adresses.
* @throws {Error} If the list of email addresses is still the default
*/
function validateEmailAddresses(recipientEmails) {
if (recipientEmails &&
recipientEmails[0] == 'YOUR_EMAIL_HERE') {
throw new Error('Please either specify a valid email address or clear' +
' the RECIPIENT_EMAILS field.');
}
}

 

Como Configurar

 

3. Encontre Anomalias em Palavras-chave, Grupos de Anúncios e Anúncios – Por Russel Savage. O script pode identificar palavras-chave, grupos e anúncios e anúncios que estão com uma performance diferente dos outros, por exemplo, grupos de anúncios com apenas 2-3 palavras-chave trazendo todo tráfego ao site. O script também envia um e-mail resumindo as anomalias encontradas diariamente.

/**************************************
* Find the Anomalies
* Created By: Russ Savage
* Version: 1.2
* Changelog v1.2
* - Fixed divide by 0 errors
* - Changed SIG_FIGS to DECIMAL_PLACES
* Changelog v1.1
* - Added ability to tag ad anomalies as well
* FreeAdWordsScripts.com
**************************************/
var DATE_RANGE = 'LAST_30_DAYS';
var DECIMAL_PLACES = 3;
var STANDARD_DEVIATIONS = 2;
var TO = ['[email protected]_domain.com'];

function main() {
// This will add labels to and send emails about adgroups, keywords and ads. Remove any if you like.
var levels_to_tag = ['adgroup','keyword','ad'];
for(var x in levels_to_tag) {
var report = getContentRows(levels_to_tag[x]);
var entity_map = buildEntityMap(levels_to_tag[x]);
for(var parent_id in entity_map) {
var child_list = entity_map[parent_id];
var stats_list = Object.keys(child_list[0].stats);
for(var i in stats_list) {
var mean = getMean(child_list,stats_list[i]);
var stand_dev = getStandardDev(child_list,mean,stats_list[i]);
var label_name = stats_list[i]+"_anomaly";
report += addLabelToAnomalies(child_list,mean,stand_dev,stats_list[i],label_name,levels_to_tag[x]);
}
}
sendResultsViaEmail(report,levels_to_tag[x]);
}
}

//Takes a report and the level of reporting and sends and email
//with the report as an attachment.
function sendResultsViaEmail(report,level) {
var rows = report.match(/\n/g).length - 1;
if(rows == 0) { return; }
var options = { attachments: [Utilities.newBlob(report, 'text/csv', level+"_anomalies_"+_getDateString()+'.csv')] };
var email_body = "There are " + rows + " " + level + "s that have abnormal performance. See attachment for details.";
var subject = 'Abnormal ' + _initCap(level) + ' Entities Report - ' + _getDateString();
for(var i in TO) {
MailApp.sendEmail(TO[i], subject, email_body, options);
}
}

//Helper function to return a single row of the report formatted correctly
function toReportRow(entity,level,label_name) {
var ret_val = [AdWordsApp.currentAccount().getCustomerId(),
entity.getCampaign().getName()];
ret_val.push( (level == 'adgroup') ? entity.getName() : entity.getAdGroup().getName() );
if(level == 'keyword') {
ret_val = ret_val.concat([entity.getText(),entity.getMatchType()]);
} else if(level == 'ad') {
ret_val = ret_val.concat([entity.getHeadline(),entity.getDescription1(),entity.getDescription2(),entity.getDisplayUrl()]);
}
ret_val.push(label_name);
return '"' + ret_val.join('","') + '"\n';
}

//Helper function to return the column headings for the report
function getContentRows(level) {
var ret_val = ['AccountId','CampaignName','AdGroupName'];
if(level == 'keyword') {
ret_val = ret_val.concat(['KeywordText','MatchType']);
} else if(level == 'ad') {
ret_val = ret_val.concat(['Headline','Description1','Description2','DisplayUrl']);
}
ret_val.push('LabelName');
return '"' + ret_val.join('","') + '"\n';
}

//Function to add the labels to the entities based on the standard deviation and mean.
//It returns a csv formatted string for reporting
function addLabelToAnomalies(entites,mean,sd,stat_key,label_name,level) {
createLabelIfNeeded(label_name);
var report = '';
for(var i in entites) {
var entity = entites[i]['entity'];
var deviation = Math.abs(entites[i]['stats'][stat_key] - mean);
if(sd != 0 && deviation/sd >= STANDARD_DEVIATIONS) {
entity.applyLabel(label_name);
report += toReportRow(entity,level,label_name);
} else {
entity.removeLabel(label_name);
}
}
return report;
}

//This is a helper function to create the label if it does not already exist
function createLabelIfNeeded(name) {
if(!AdWordsApp.labels().withCondition("Name = '"+name+"'").get().hasNext()) {
AdWordsApp.createLabel(name);
}
}

//This function returns the standard deviation for a set of entities
//The stat key determines which stat to calculate it for
function getStandardDev(entites,mean,stat_key) {
var total = 0;
for(var i in entites) {
total += Math.pow(entites[i]['stats'][stat_key] - mean,2);
}
if(Math.sqrt(entites.length-1) == 0) {
return 0;
}
return round(Math.sqrt(total)/Math.sqrt(entites.length-1));
}

//Returns the mean (average) for the set of entities
//Again, stat key determines which stat to calculate this for
function getMean(entites,stat_key) {
var total = 0;
for(var i in entites) {
total += entites[i]['stats'][stat_key];
}
if(entites.length == 0) {
return 0;
}
return round(total/entites.length);
}

//This function returns a map of the entities that I am processing.
//The format for the map can be found on the first line.
//It is meant to work on AdGroups and Keywords
function buildEntityMap(entity_type) {
var map = {}; // { parent_id : [ { entity : entity, stats : entity_stats } ], ... }
var iter = getIterator(entity_type);
while(iter.hasNext()) {
var entity = iter.next();
var stats = entity.getStatsFor(DATE_RANGE);
var stats_map = getStatsMap(stats);
var parent_id = getParentId(entity_type,entity);
if(map[parent_id]) {
map[parent_id].push({entity : entity, stats : stats_map});
} else {
map[parent_id] = [{entity : entity, stats : stats_map}];
}
}
return map;
}

//Given an entity type (adgroup or keyword) this will return the parent id
function getParentId(entity_type,entity) {
switch(entity_type) {
case 'adgroup' :
return entity.getCampaign().getId();
case 'keyword':
return entity.getAdGroup().getId();
case 'ad':
return entity.getAdGroup().getId();
}
}

//Given an entity type this will return the iterator for that.
function getIterator(entity_type) {
switch(entity_type) {
case 'adgroup' :
return AdWordsApp.adGroups().forDateRange(DATE_RANGE).withCondition("Impressions > 0").get();
case 'keyword' :
return AdWordsApp.keywords().forDateRange(DATE_RANGE).withCondition("Impressions > 0").get();
case 'ad' :
return AdWordsApp.ads().forDateRange(DATE_RANGE).withCondition("Impressions > 0").get();
}
}

//This returns a map of all the stats for a given entity.
//You can comment out the things you don't really care about.
function getStatsMap(stats) {
return { // You can comment these out as needed.
avg_cpc : stats.getAverageCpc(),
avg_cpm : stats.getAverageCpm(),
avg_pv : stats.getAveragePageviews(),
avg_pos : stats.getAveragePosition(),
avg_tos : stats.getAverageTimeOnSite(),
bounce : stats.getBounceRate(),
clicks : stats.getClicks(),
cv : stats.getConversionRate(),
conv : stats.getConversions(),
cost : stats.getCost(),
ctr : stats.getCtr(),
imps : stats.getImpressions()
};
}

//Helper function to format todays date
function _getDateString() {
return Utilities.formatDate((new Date()), AdWordsApp.currentAccount().getTimeZone(), "yyyy-MM-dd");
}

//Helper function to capitalize the first letter of a string.
function _initCap(str) {
return str.replace(/(?:^|\s)\S/g, function(a) { return a.toUpperCase(); });
}

// A helper function to make rounding a little easier
function round(value) {
var decimals = Math.pow(10,DECIMAL_PLACES);
return Math.round(value*decimals)/decimals;
}

 

 

4. Notificação de Anúncios Reprovados por Mensagem e E-mail – Por Derek Martin. Esse script identifica anúncios reprovados em uma planilha do Google e pode avisar por mensagem e e-mail.

/***************************************************************************************
* AdWords Account Audit -- Check Ads for disapprovals -- text if there are open issues
* Version 1.0
* Created By: Derek Martin
* DerekMartinLA.com
****************************************************************************************/

// This script was heavily inspired by Russell Savage so all credit where its due!
// Sign up at Twilio.com and get an API key (sid) and Auth code and place here
// Url: //www.twilio.com/
var sid = 'ACxxxxxxx'; // looks like this:
var auth = 'xxxxxxxx';
var to_number = '+1234567890'; // put your phone number here
var from_number = '+1234567890'; // this is the phone number given to you by Twilio
var email_address = '[email protected]'; // put the email you want to send to here

function main() {
var campIter = AdWordsApp.campaigns().withCondition('Status = ENABLED').get();
var disapprovedList = [];

while (campIter.hasNext()) {
var camp = campIter.next();
adsIter = camp.ads().withCondition('Status = ENABLED').withCondition('ApprovalStatus = DISAPPROVED').get();

info('Checking ' + camp.getName());
numDisapproved = adsIter.totalNumEntities();

if (numDisapproved > 0) { // there are disapproved ads

while (adsIter.hasNext()) {
var ad = adsIter.next();

warn('Ad ' + ad.getId() + ' : Headline : ' + ad.getHeadline() + ' is currently DISAPPROVED.');
disapproved = new disapprovedAd(camp.getName(), ad.getAdGroup().getName(), ad.getHeadline(), ad.getDescription1(), ad.getDescription2(), ad.getDisplayUrl(), ad.getDestinationUrl(), ad.getDisapprovalReasons());
// info(camp.getName() + ' ' + ad.getAdGroup().getName() + ' ' + ad.getHeadline() + ' ' + ad.getDescription1() + ' ' + ad.getDescription2() + ' ' + ad.getDisapprovalReasons());
disapprovedList.push(disapproved);

} // dont do anything if disapproved ads are zero
// info('hitting the else statement');

// ads are collected, text message

// prepare spreadsheet and email and then text msg

info('Collected all disapproved ads, expect an email with report results momentarily');

var fileUrl = createSpreadsheet(disapprovedList);
info(' ');
info('Or you can find it here: ' + fileUrl);
var clientName = AdWordsApp.currentAccount().getName().split("-");
sendAnEmail(clientName[0], disapprovedList.toString(), fileUrl);

// The third parameter is what you want the text or voice message to say

var client = new Twilio(sid,auth);
var clientName = AdWordsApp.currentAccount().getName().split();
var warningMsg = 'ALERT: ' + clientName[0] + ' has ' + numDisapproved + ' disapproved ads in campaign: ' + camp.getName() + '\n' + '\n';
warningMsg += 'Click here: ' + fileUrl + ' to review the disapproved list.';
client.sendMessage(to_number,from_number,warningMsg);

} // end of campaign iteration
}
}

function createSpreadsheet(results) {
var newSS = SpreadsheetApp.create('disapprovedAds', results.length, 26);

var sheet = newSS.getActiveSheet();
var resultList = results;

var columnNames = ["Campaign", "Ad Group","Headline", "Description1","Description2","Display URL","Destination URL","Disapproval Reason"];

var headersRange = sheet.getRange(1, 1, 1, columnNames.length);

headersRange.setValues([columnNames]);

var i = 0;

for each (headline in resultList) {

var campaign, adgroup, headline, description1, description2, displayUrl, destinationUrl, disapprovalReasons;

campaign = resultList[i].campaign;
adgroup = resultList[i].adgroup;
headline = resultList[i].headline;
description1 = resultList[i].d1;
description2 = resultList[i].d2;
displayUrl = resultList[i].displayUrl;
destinationUrl = resultList[i].destinationUrl;
disapprovalReasons = resultList[i].disapprovalReasons;

sheet.appendRow([campaign, adgroup, headline, description1, description2, displayUrl, destinationUrl, disapprovalReasons]);

// Sets the first column to a width which fits the text
sheet.setColumnWidth(1, 300);
sheet.setColumnWidth(2, 300);
sheet.setColumnWidth(3, 300);
sheet.setColumnWidth(4, 300);
sheet.setColumnWidth(5, 300);
sheet.setColumnWidth(6, 300);
sheet.setColumnWidth(7, 300);
sheet.setColumnWidth(8, 300);

i++;
}

return newSS.getUrl();

}

function sendAnEmail (clientName, results, fileUrl) {

var data = Utilities.parseCsv(results, '\t');
var clientName = AdWordsApp.currentAccount().getName().split();
var today = new Date();
today = today.getMonth() + today.getDate() + today.getFullYear();

var filename = clientName[0] + 'disapproved-ads' + today;

// Send an email with Search list attachment
var blob = Utilities.newBlob(results, 'text/html', '');

MailApp.sendEmail(email_address, clientName + ' Disapproved Ad Results ', 'You can find the list of disapproved ads at the following URL:' + fileUrl, {
name: ' Client Disapproved Ads'
});

}

function disapprovedAd(campaign, adgroup, headline, d1, d2, displayUrl, destinationURL, reason) {
this.campaign = campaign;
this.adgroup = adgroup;
this.headline = headline;
this.d1 = d1;
this.d2 = d2;
this.displayUrl = displayUrl;
this.destinationUrl = destinationURL;
this.reason = reason;

}

// HELPER FUNCTIONS

function warn(msg) {
Logger.log('WARNING: '+msg);
}

function info(msg) {
Logger.log(msg);
}

// TWILIO ACCESS
function Twilio(e,t){function n(e){return{Authorization:"Basic "+Utilities.base64Encode(e.ACCOUNT_SID+":"+e.AUTH_TOKEN)}}this.ACCOUNT_SID=e;this.AUTH_TOKEN=t;this.MESSAGES_ENDPOINT="//api.twilio.com/2010-04-01/Accounts/"+this.ACCOUNT_SID+"/Messages.json";this.CALLS_ENDPOINT="//api.twilio.com/2010-04-01/Accounts/"+this.ACCOUNT_SID+"/Calls.json";this.sendMessage=function(e,t,r){var i={method:"POST",payload:{To:e,From:t,Body:r},headers:n(this)};var s=UrlFetchApp.fetch(this.MESSAGES_ENDPOINT,i).getContentText();return JSON.parse(s)["sid"]};this.makeCall=function(e,t,r){var i="//proj.rjsavage.com/savageautomation/twilio_script/dynamicSay.php?alert="+encodeURIComponent(r);var s={method:"POST",payload:{To:e,From:t,Url:i},headers:n(this)};var o=UrlFetchApp.fetch(this.CALLS_ENDPOINT,s).getContentText();return JSON.parse(o)["sid"]}}

 

 

5. Alarme de Zero Impressões – Por Catalyst Canada Contributor. Seja notificado quando seus anúncios não estejam gerando impressões. Assim, você terá uma reação rápida quando houver algum problema com suas campanhas.

function main() {

// Enter your account name and email here:

var accountName = “Account name”;

var yourEmail = “[email protected]”;

var emailBody=”Yesterday, the following campaigns have had no impressions:<br>”;

var noImpCamp=0;

var campaignsIterator = AdWordsApp.campaigns().get();

while (campaignsIterator.hasNext()) {

var campaign = campaignsIterator.next();

var stats = campaign.getStatsFor(‘YESTERDAY’);

if ((stats.getImpressions() == 0)&&(campaign.isEnabled())) {

emailBody = emailBody + campaign.getName() + “<br>”;

noImpCamp++;

}

}

if (noImpCamp > 0) {

MailApp.sendEmail(yourEmail,”Alert: ” + accountName + ” – ” + noImpCamp + ” Campaigns with no impressions”,””,{htmlBody: emailBody})

}

}

 

6. Receba Ligações e Mensagens de Alertas com Scripts – Por Russel Savage. O script do Google envia mensagem faz ligações por meio de uma ferramenta de terceiros. Ideal para pessoas que por algum motivo não tem acesso ao e-mail.

 

Para usar este script, há algumas coisas que você precisa fazer. Primeiro, inscreva-se para uma conta Twilio. Depois de fazer isso, você deve ter um número de telefone com o qual possa usar para brincar, além de um SID da conta e um token de autenticação . Twilio pode não estar disponível em seu país ainda, mas esperamos que eles estejam lá em breve. Verifique se você está usando os números de telefone completos (incluindo os códigos de conta) ao fazer suas solicitações. Se você está usando apenas a versão gratuita, pode haver limites de uso, mas não tenho certeza.

O código é configurado para que possa ser copiado em qualquer script em que você precise enviar notificações. Depois de copiar o objeto Twilio no script, sempre que quiser que a notificação seja enviada, você deverá adicionar o seguinte código:

...
var sid = 'YOUR ACCOUNT SID GOES HERE';
var auth = 'YOUR AUTH TOKEN GOES HERE';
//First, create a new Twilio client
var client = new Twilio(sid,auth);
//Here is how you send a text message
// First number is the receiver (most likely, your cell phone)
// Second number is where is it coming from, which is the free number you got when
// you registered in Twilio
// The third parameter is what you want the text or voice message to say
client.sendMessage('+17245551234','+14155554321','WARNING: Your AdWords Account Is Not Serving Ads.');
client.makeCall('+17245551234','+14155554321',
'This is an automated call to warn you that your AdWords account is no longer serving ads.');
...

 

Naturalmente, sid, auth e client podem ser variáveis ​​globais que permitem que você tenha uma única linha em seu código para fazer chamadas telefônicas ou enviar mensagens. Você também pode configurar algum tipo de cadeia de encaminhamento para o caso de as pessoas perderem a chamada ou o texto.

Este é apenas um exemplo simples de começar a usar o UrlFetchApp para integrar scripts do Google AdWords a aplicativos de terceiros.

/*********************************
* Twilio Client Library
* Based on the Twilio REST API: //www.twilio.com/docs/api/rest
* Version 1.0
* Created By: Russ Savage
* FreeAdWordsScripts.com
*********************************/
function Twilio(accountSid, authToken) {
this.ACCOUNT_SID = accountSid;
this.AUTH_TOKEN = authToken;

this.MESSAGES_ENDPOINT = '//api.twilio.com/2010-04-01/Accounts/'+this.ACCOUNT_SID+'/Messages.json';
this.CALLS_ENDPOINT = '//api.twilio.com/2010-04-01/Accounts/'+this.ACCOUNT_SID+'/Calls.json';

this.sendMessage = function(to,from,body) {
var httpOptions = {
method : 'POST',
payload : {
To: to,
From: from,
Body: body
},
headers : getBasicAuth(this)
};
var resp = UrlFetchApp.fetch(this.MESSAGES_ENDPOINT, httpOptions).getContentText();
return JSON.parse(resp)['sid'];
}

this.makeCall = function(to,from,whatToSay) {
var url = '//proj.rjsavage.com/savageautomation/twilio_script/dynamicSay.php?alert='+encodeURIComponent(whatToSay);
var httpOptions = {
method : 'POST',
payload : {
To: to,
From: from,
Url: url
},
headers : getBasicAuth(this)
};
var resp = UrlFetchApp.fetch(this.CALLS_ENDPOINT, httpOptions).getContentText();
return JSON.parse(resp)['sid'];
}

function getBasicAuth(context) {
return {
'Authorization': 'Basic ' + Utilities.base64Encode(context.ACCOUNT_SID+':'+context.AUTH_TOKEN)
};
}
}

 

 

7. Alterações no CTR – Por Sean Dolan. Esse script permite identificar mudanças na taxa de cliques pela planilha do Google. Você pode utilizar esse scripts para monitorar o desempenho de seus anúncios.

function main() {

//UPDATE WITH YOUR EMAIL
var email = "[email protected]";

//UPDATE WITH YOUR SPREADSHEET ADDRESSES
var SPREADSHEET_URL_increase = "INCREASE_URL"
var SPREADSHEET_URL_decrease = "DECREASE_URL"

//SPECIFY "decrease" OR "increase"
var accountChanges = "decrease";

if(accountChanges== "decrease"){
var SPREADSHEET_URL = SPREADSHEET_URL_decrease;}
if(accountChanges== "increase"){
var SPREADSHEET_URL = SPREADSHEET_URL_increase;}
var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
var sheet = spreadsheet.getSheets()[0];
var sheet2 = spreadsheet.getSheets()[1];
var sheet3 = spreadsheet.getSheets()[2];

spreadsheet.getRangeByName("account_id").setValue(AdWordsApp.currentAccount().getCustomerId());
sheet.getRange(1, 2, 1, 1).setValue("Date");
sheet.getRange(1, 3, 1, 1).setValue(new Date());
sheet.getRange(7, 1, sheet.getMaxRows() - 7, sheet.getMaxColumns()).clear();

var adGroupsIterator = AdWordsApp.adGroups()
.withCondition("Status = 'ENABLED'")
.withCondition("CampaignStatus = 'ENABLED'")
.forDateRange("TODAY")
.orderBy("Ctr ASC")
.withLimit(500)
.get();

var today = getDateInThePast(0);
var oneWeekAgo = getDateInThePast(7);
var twoWeeksAgo = getDateInThePast(14);
var threeWeeksAgo = getDateInThePast(21);

var reportRows = [];
var ctrRows = [];
var bounceRate = [];

while (adGroupsIterator.hasNext()) {
var adGroup = adGroupsIterator.next();
// Let's look at the trend of the ad group's CTR.
var statsThreeWeeksAgo = adGroup.getStatsFor(threeWeeksAgo, twoWeeksAgo);
var statsTwoWeeksAgo = adGroup.getStatsFor(twoWeeksAgo, oneWeekAgo);
var statsLastWeek = adGroup.getStatsFor(oneWeekAgo, today);

if(accountChanges == "increase"){
// Week over week, the ad group is increaseing - record that!
if (statsLastWeek.getCtr() > statsTwoWeeksAgo.getCtr() && statsTwoWeeksAgo.getCtr() > statsThreeWeeksAgo.getCtr()) {
reportRows.push([adGroup.getCampaign().getName(), adGroup.getName(),
statsLastWeek.getCtr() * 100, statsLastWeek.getCost(),
statsTwoWeeksAgo.getCtr() * 100, statsTwoWeeksAgo.getCost(),
statsThreeWeeksAgo.getCtr() * 100, statsThreeWeeksAgo.getCost()]);
}
//Conv Rate
if (statsLastWeek.getConversionRate() > statsTwoWeeksAgo.getConversionRate() && statsTwoWeeksAgo.getConversionRate() > statsThreeWeeksAgo.getConversionRate()) {
ctrRows.push([adGroup.getCampaign().getName(), adGroup.getName(),
statsLastWeek.getConversionRate() * 100, statsLastWeek.getConversions(),
statsTwoWeeksAgo.getConversionRate() * 100, statsTwoWeeksAgo.getConversions(),
statsThreeWeeksAgo.getConversionRate() * 100, statsThreeWeeksAgo.getConversions()]);
}

//Bounce Rate
if (statsLastWeek.getBounceRate() > statsTwoWeeksAgo.getBounceRate() && statsTwoWeeksAgo.getBounceRate() > statsThreeWeeksAgo.getBounceRate()) {
bounceRate.push([adGroup.getCampaign().getName(), adGroup.getName(),
statsLastWeek.getBounceRate() * 100, statsLastWeek.getCost(),
statsTwoWeeksAgo.getBounceRate() * 100, statsTwoWeeksAgo.getCost(),
statsThreeWeeksAgo.getBounceRate() * 100, statsThreeWeeksAgo.getCost()]);
}
}

if(accountChanges== "decrease"){
// Week over week, the ad group is degrading - record that!
if (statsLastWeek.getCtr() < statsTwoWeeksAgo.getCtr() && statsTwoWeeksAgo.getCtr() < statsThreeWeeksAgo.getCtr()) {
reportRows.push([adGroup.getCampaign().getName(), adGroup.getName(),
statsLastWeek.getCtr() * 100, statsLastWeek.getCost(),
statsTwoWeeksAgo.getCtr() * 100, statsTwoWeeksAgo.getCost(),
statsThreeWeeksAgo.getCtr() * 100, statsThreeWeeksAgo.getCost()]);
}
//Conv Rate
if (statsLastWeek.getConversionRate() < statsTwoWeeksAgo.getConversionRate() && statsTwoWeeksAgo.getConversionRate() < statsThreeWeeksAgo.getConversionRate()) {
ctrRows.push([adGroup.getCampaign().getName(), adGroup.getName(),
statsLastWeek.getConversionRate() * 100, statsLastWeek.getConversions(),
statsTwoWeeksAgo.getConversionRate() * 100, statsTwoWeeksAgo.getConversions(),
statsThreeWeeksAgo.getConversionRate() * 100, statsThreeWeeksAgo.getConversions()]);
}

//Bounce Rate
if (statsLastWeek.getBounceRate() < statsTwoWeeksAgo.getBounceRate() && statsTwoWeeksAgo.getBounceRate() < statsThreeWeeksAgo.getBounceRate()) {
bounceRate.push([adGroup.getCampaign().getName(), adGroup.getName(),
statsLastWeek.getBounceRate() * 100, statsLastWeek.getCost(),
statsTwoWeeksAgo.getBounceRate() * 100, statsTwoWeeksAgo.getCost(),
statsThreeWeeksAgo.getBounceRate() * 100, statsThreeWeeksAgo.getCost()]);
}
}
}

if (reportRows.length > 0) {
sheet.getRange(7, 2, reportRows.length, 8).setValues(reportRows);
sheet.getRange(7, 4, reportRows.length, 1).setNumberFormat("#0.00%");
sheet.getRange(7, 6, reportRows.length, 1).setNumberFormat("#0.00%");
sheet.getRange(7, 8, reportRows.length, 1).setNumberFormat("#0.00%");

sheet.getRange(7, 5, reportRows.length, 1).setNumberFormat("#,##0.00");
sheet.getRange(7, 7, reportRows.length, 1).setNumberFormat("#,##0.00");
sheet.getRange(7, 9, reportRows.length, 1).setNumberFormat("#,##0.00");
}

if (ctrRows.length > 0) {
sheet2.getRange(7, 2, ctrRows.length, 8).setValues(ctrRows);
sheet2.getRange(7, 4, ctrRows.length, 1).setNumberFormat("#0.00%");
sheet2.getRange(7, 6, ctrRows.length, 1).setNumberFormat("#0.00%");
sheet2.getRange(7, 8, ctrRows.length, 1).setNumberFormat("#0.00%");

sheet2.getRange(7, 5, ctrRows.length, 1).setNumberFormat("#,##0.00");
sheet2.getRange(7, 7, ctrRows.length, 1).setNumberFormat("#,##0.00");
sheet2.getRange(7, 9, ctrRows.length, 1).setNumberFormat("#,##0.00");
}

if (bounceRate.length > 0) {
sheet3.getRange(7, 2, bounceRate.length, 8).setValues(bounceRate);
sheet3.getRange(7, 4, bounceRate.length, 1).setNumberFormat("#0.00%");
sheet3.getRange(7, 6, bounceRate.length, 1).setNumberFormat("#0.00%");
sheet3.getRange(7, 8, bounceRate.length, 1).setNumberFormat("#0.00%");

sheet3.getRange(7, 5, bounceRate.length, 1).setNumberFormat("#,##0.00");
sheet3.getRange(7, 7, bounceRate.length, 1).setNumberFormat("#,##0.00");
sheet3.getRange(7, 9, bounceRate.length, 1).setNumberFormat("#,##0.00");
}

// var email = spreadsheet.getRangeByName("email").getValue();
if (email) {
var body = [];

if(reportRows.length>0){
var subjectline = [];
if(accountChanges=="increase"){
body.push("The CTR of the following ad groups is increasing over the last three weeks.\n");
subjectline.push(" ad groups are increasing in AdWords account ");
for (var i = 0; i < reportRows.length; i ++) {
body.push(reportRows[i][0] + " < " + reportRows[i][1]);
body.push(" " + ctr(reportRows[i][6]) + " < " + ctr(reportRows[i][4]) + " < " + ctr(reportRows[i][2]) + "\n");
}
}

if(accountChanges=="decrease"){
body.push("The CTR of the following ad groups is decreasing over the last three weeks.\n");
subjectline.push(" ad groups are degrading in AdWords account ");
for (var i = 0; i < reportRows.length; i ++) {
body.push(reportRows[i][0] + " > " + reportRows[i][1]);
body.push(" " + ctr(reportRows[i][6]) + " > " + ctr(reportRows[i][4]) + " > " + ctr(reportRows[i][2]) + "\n");
}
}

body.push("Full report at " + SPREADSHEET_URL + "\n\n");
MailApp.sendEmail(email, "" +
reportRows.length + subjectline +
AdWordsApp.currentAccount().getCustomerId(), body.join("\n"));
}
}

else{
if(accountChanges=="increase"){
body.push("No ad groups have consistently increasing CTR for the past three weeks.\n");
}
if(accountChanges=="decrease"){
body.push("No ad groups have consistently decreasing CTR for the past three weeks.\n");
}
}
}

function ctr(number) {
return parseInt(number * 10000) / 10000 + "%";

}

// Returns YYYYMMDD-formatted date.
function getDateInThePast(numDays) {
var today = new Date();
today.setDate(today.getDate() - numDays);
return Utilities.formatDate(today, "PST", "yyyyMMdd");
}

 

 

8. Campanhas com CPA Elevados – por Sean Dolan. Seja alertado quando suas campanhas alcançarem o limite de CPA. Assim, você pode trabalhar na conta para diminuir esse CPA.

function main(){

//Define three variables: cpalimit, emailaddress, and timerange (choices are: TODAY,
//YESTERDAY, LAST_7_DAYS, THIS_WEEK_SUN_TODAY, LAST_WEEK, LAST_14_DAYS, LAST_30_DAYS,
//LAST_BUSINESS_WEEK, LAST_WEEK_SUN_SAT, THIS_MONTH, LAST_MONTH, ALL_TIMR)
var cpalimit = 100;
var emailaddress = "[email protected]";
var timerange = "LAST_7_DAYS"

//Array definition and get data
var numcampaigns=0;
var campaignNames =[];
var campaignIterator = AdwordsApp.campaigns()
.forDateRange("TODAY")
.withCondition("Status = ACTIVE")
.get();
while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
var name = campaign.getName();
var stats = campaign.getStatsFor(timerange);
var Conv = stats.getStatsFor(timerange);
var Cost = stats.getCost();
var cpa = (Cost /Conv);

 

 

9. Alerta de Teste de Anúncios – por Sean Dolan. O script compara 2 grupos de anúncios com no mínimo 1000 impressões para verificar diferenças nos CTR dos dois.

function main() {
/*
This script will evaluate two groups of ads against one another to look for significant differences in CTR.
One thing to keep in mind, the script will check for 1000 impressions to assure an adequate sample size.
If these condition are not met, the script will inform you that you need more impressions.
*/

//A list of the labels you want to test. Make sure you keep
//the brackets and add any additional labels by inserting a
//comma and the keyword in ' '.
//Example var labelTest = "['Ad Test 1', 'Ad Test 2']; etc.
//This allows you to reconfigure your groupings as needed.

var labelTest = "['Ad Test 1']";
var labelControl = "['Control']";

//Insert the day you test started in YYYYMMDD format. This
//will ensure you only test ads that are running at the same time.
var dateStarted = " ";

//Enter your e-mail below if you would like to schedule the
//script to run and update you when results are reached.
var eMail = "[email protected]";

//Enter the account name you would like to appear in the e-mail alerts.
var accountName = "Account";

var codeURL = "//s3.amazonaws.com/ppc-hero-tools/stat-sig.js";
var code = "";
function getCode(url) {
var stuff = UrlFetchApp.fetch(url).getContentText();
return stuff;
}
var code = getCode(codeURL);

eval(code);
}

 

 

10. Alertas Diários – por Sean Dolan. Mantenha-se informado recebendo alertas diários sobre sua conta do Google Ads com esse script.

function main() {

//Enter the name of the account. This will help title the e-mail.
var accountName = "Account Name";
//insert your e-mail here
var eMail = "[email protected]";
//how much variance do you want to allow before sending an alert?
//This is a percentage and the default is 20%
var VARIANCE_DAY_TO_DAY = 0.2;

var codeURL = "//s3.amazonaws.com/ppc-hero-tools/daily-alert.js";
var code = "";
function getCode(url) {
var stuff = UrlFetchApp.fetch(url).getContentText();
return stuff;
}
var code = getCode(codeURL);

eval(code);
}

 

Automação Google Ads: Como programar os Lances e Orçamentos

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para “Modificação de Lances”.

 

1. Teste de Lances – por Google Ads. O script do Google testa vários lances e escreve os resultados em uma planilha do Google. Assim você pode determinar é o lance que vai gerar maior resultado para sua campanha ou determinar qual vai ser o impacto de um aumento de lances no seu resultado.

 

var keywordIter = campaign.keywords().get();
while (keywordIter.hasNext()) {
var keyword = keywordIter.next();
var oldBid = startingBids[keyword.getText()];
if (!oldBid) {
// If we don't have a starting bid, keyword has been added since we
// started testing.
oldBid = keyword.bidding().getCpc() || keyword.getAdGroup().bidding().getCpc();
startingBids[keyword.getText()] = oldBid;
}
var newBid = oldBid * multiplier;
keyword.bidding().setCpc(newBid);
}

 

Veja os demais códigos (Teste de lance)

 

2. Lances para posição de anúncios – por Google Ads. A primeira posição nem sempre é a que traz melhor resultado para sua empresa, pode ser muito custoso manter um anúncio na primeira posição, fazendo com que um anúncio na terceira sobressaia melhor do que na primeira posição. Esse script ajusta seus lances para manter uma posição determinada.

 

 

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Bid To Impression Share
*
* @overview The Bid To Impression Share script adjusts your bids and allows you
* to steer ads in an advertiser account into a desired impression share in
* the search results. See
* //developers.google.com/adwords/scripts/docs/solutions/bid-to-impression-share
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 2.0.1
*
* @changelog
* - version 2.0.1
* - Fixed logic when determining which keywords to raise and lower.
* - version 2.0.0
* - Refactored to target impression share rather than position.
* - version 1.0.1
* - Refactored to improve readability. Added documentation.
* - version 1.0
* - Released initial version.
*/

// Impression share you are trying to achieve.
var TARGET_IMPRESSION_SHARE = 50;

// Once the keywords fall within TOLERANCE of TARGET_IMPRESSION_SHARE,
// their bids will no longer be adjusted.
var TOLERANCE = 5;

// How much to adjust the bids.
var BID_ADJUSTMENT_COEFFICIENT = 1.05;

/**
* Main function that lowers and raises keywords' CPC to move closer to
* target impression share.
*/
function main() {
raiseKeywordBids();
lowerKeywordBids();
}

/**
* Increases the CPC of keywords that are below the target impression share.
*/
function raiseKeywordBids() {
var keywordsToRaise = getKeywordsToRaise();

while (keywordsToRaise.hasNext()) {
var keyword = keywordsToRaise.next();
keyword.bidding().setCpc(getIncreasedCpc(keyword.bidding().getCpc()));
}
}

/**
* Decreases the CPC of keywords that are above the target impression share.
*/
function lowerKeywordBids() {
var keywordsToLower = getKeywordsToLower();

while (keywordsToLower.hasNext()) {
var keyword = keywordsToLower.next();
keyword.bidding().setCpc(getDecreasedCpc(keyword.bidding().getCpc()));
}
}

/**
* Increases a given CPC using the bid adjustment coefficient.
* @param {number} cpc - the CPC to increase
* @return {number} the new CPC
*/
function getIncreasedCpc(cpc) {
return cpc * BID_ADJUSTMENT_COEFFICIENT;
}

/**
* Decreases a given CPC using the bid adjustment coefficient.
* @param {number} cpc - the CPC to decrease
* @return {number} the new CPC
*/
function getDecreasedCpc(cpc) {
return cpc / BID_ADJUSTMENT_COEFFICIENT;
}

/**
* Gets an iterator of the keywords that need to have their CPC raised.
* @return {Iterator} an iterator of the keywords
*/
function getKeywordsToRaise() {
// Condition to raise bid: Average impression share is worse (less) than
// target - tolerance
return AdWordsApp.keywords()
.withCondition('Status = ENABLED')
.withCondition(
'SearchImpressionShare < ' + (TARGET_IMPRESSION_SHARE - TOLERANCE))
.orderBy('SearchImpressionShare ASC')
.forDateRange('LAST_7_DAYS')
.get();
}

/**
* Gets an iterator of the keywords that need to have their CPC lowered.
* @return {Iterator} an iterator of the keywords
*/
function getKeywordsToLower() {
// Conditions to lower bid: Ctr greater than 1% AND
// average impression share better (greater) than target + tolerance
return AdWordsApp.keywords()
.withCondition('Ctr > 0.01')
.withCondition(
'SearchImpressionShare > ' + (TARGET_IMPRESSION_SHARE + TOLERANCE))
.withCondition('Status = ENABLED')
.orderBy('SearchImpressionShare DESC')
.forDateRange('LAST_7_DAYS')
.get();
}

 

3. Multiproponente – por Google Ads. Manter controle de uma grande quantidade de regras automatizadas pode ser difícil. Com esse script é possível utilizar uma planilha para controlar suas regras automatizadas, uma vez que, ela oferece as mesmas funcionalidades do recurso do Google Ads em uma planilha.

 

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Multi Bidder
*
* @overview The Multi Bidder script offers functionality similar to that of
* Automated Rules based on a spreadsheet. See
* //developers.google.com/adwords/scripts/docs/solutions/multi-bidder
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.0.3
*
* @changelog
* - version 1.0.3
* - Replaced deprecated keyword.getMaxCpc() and keyword.setMaxCpc().
* - version 1.0.2
* - Added validation of user settings.
* - version 1.0.1
* - Improvements to time zone handling.
* - version 1.0
* - Released initial version.
*/

var SPREADSHEET_URL = 'YOUR_SPREADSHEET_URL';

var spreadsheetAccess = new SpreadsheetAccess(SPREADSHEET_URL, 'Rules');

var totalColumns;

function main() {
// Make sure the spreadsheet is using the account's timezone.
spreadsheetAccess.spreadsheet.setSpreadsheetTimeZone(
AdWordsApp.currentAccount().getTimeZone());
spreadsheetAccess.spreadsheet.getRangeByName('account_id').setValue(
AdWordsApp.currentAccount().getCustomerId());

var columns = spreadsheetAccess.sheet.getRange(5, 2, 5, 100).getValues()[0];
for (var i = 0; i < columns.length; i++) {
if (columns[i].length == 0 || columns[i] == 'Results') {
totalColumns = i;
break;
}
}
if (columns[totalColumns] != 'Results') {
spreadsheetAccess.sheet.getRange(5, totalColumns + 2, 1, 1).
setValue('Results');
}
// clear the results column
spreadsheetAccess.sheet.getRange(6, totalColumns + 2, 1000, 1).clear();

var row = spreadsheetAccess.nextRow();

while (row != null) {
var argument;
var stopLimit;
try {
argument = parseArgument(row);
stopLimit = parseStopLimit(row);
} catch (ex) {
logError(ex);
row = spreadsheetAccess.nextRow();
continue;
}
var selector = AdWordsApp.keywords();
for (var i = 3; i < totalColumns; i++) {
var header = columns[i];
var value = row[i];
if (!isNaN(parseFloat(value)) || value.length > 0) {
if (header.indexOf("'") > 0) {
value = value.replace(/\'/g, "\\'");
} else if (header.indexOf('\"') > 0) {
value = value.replace(/"/g, '\\\"');
}
var condition = header.replace('?', value);
selector.withCondition(condition);
}
}
selector.forDateRange(spreadsheetAccess.spreadsheet.
getRangeByName('date_range').getValue());

var keywords = selector.get();

try {
keywords.hasNext();
} catch (ex) {
logError(ex);
row = spreadsheetAccess.nextRow();
continue;
}

var fetched = 0;
var changed = 0;

while (keywords.hasNext()) {
var keyword = keywords.next();
var oldBid = keyword.bidding().getCpc();
var action = row[0];
var newBid;

fetched++;
if (action == 'Add') {
newBid = addToBid(oldBid, argument, stopLimit);
} else if (action == 'Multiply by') {
newBid = multiplyBid(oldBid, argument, stopLimit);
} else if (action == 'Set to First Page Cpc' ||
action == 'Set to Top of Page Cpc') {
var newBid = action == 'Set to First Page Cpc' ?
keyword.getFirstPageCpc() : keyword.getTopOfPageCpc();
var isPositive = newBid > oldBid;
newBid = applyStopLimit(newBid, stopLimit, isPositive);
}
if (newBid < 0) {
newBid = 0.01;
}
newBid = newBid.toFixed(2);
if (newBid != oldBid) {
changed++;
}
keyword.bidding().setCpc(newBid);
}
logResult('Fetched ' + fetched + '\nChanged ' + changed);

row = spreadsheetAccess.nextRow();
}

spreadsheetAccess.spreadsheet.getRangeByName('last_execution')
.setValue(new Date());
}

function addToBid(oldBid, argument, stopLimit) {
return applyStopLimit(oldBid + argument, stopLimit, argument > 0);
}

function multiplyBid(oldBid, argument, stopLimit) {
return applyStopLimit(oldBid * argument, stopLimit, argument > 1);
}

function applyStopLimit(newBid, stopLimit, isPositive) {
if (stopLimit) {
if (isPositive && newBid > stopLimit) {
newBid = stopLimit;
} else if (!isPositive && newBid < stopLimit) {
newBid = stopLimit;
}
}
return newBid;
}

function parseArgument(row) {
if (row[1].length == 0 && (row[0] == 'Add' || row[0] == 'Multiply by')) {
throw ('\"Argument\" must be specified.');
}
var argument = parseFloat(row[1]);
if (isNaN(argument)) {
throw 'Bad Argument: must be a number.';
}
return argument;
}
function parseStopLimit(row) {
if (row[2].length == 0) {
return null;
}
var limit = parseFloat(row[2]);
if (isNaN(limit)) {
throw 'Bad Argument: must be a number.';
}
return limit;
}
function logError(error) {
spreadsheetAccess.sheet.getRange(spreadsheetAccess.currentRow(),
totalColumns + 2, 1, 1)
.setValue(error)
.setFontColor('#c00')
.setFontSize(8)
.setFontWeight('bold');
}
function logResult(result) {
spreadsheetAccess.sheet.getRange(spreadsheetAccess.currentRow(),
totalColumns + 2, 1, 1)
.setValue(result)
.setFontColor('#444')
.setFontSize(8)
.setFontWeight('normal');
}

function SpreadsheetAccess(spreadsheetUrl, sheetName) {
Logger.log('Using spreadsheet - %s.', spreadsheetUrl);
this.spreadsheet = validateAndGetSpreadsheet(spreadsheetUrl);

this.sheet = this.spreadsheet.getSheetByName(sheetName);
this.cells = this.sheet.getRange(6, 2, this.sheet.getMaxRows(),
this.sheet.getMaxColumns()).getValues();
this.rowIndex = 0;

this.nextRow = function() {
for (; this.rowIndex < this.cells.length; this.rowIndex++) {
if (this.cells[this.rowIndex][0]) {
return this.cells[this.rowIndex++];
}
}
return null;
};
this.currentRow = function() {
return this.rowIndex + 5;
};
}

/**
* Validates the provided spreadsheet URL
* to make sure that it's set up properly. Throws a descriptive error message
* if validation fails.
*
* @param {string} spreadsheeturl The URL of the spreadsheet to open.
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
* @throws {Error} If the spreadsheet URL hasn't been set
*/
function validateAndGetSpreadsheet(spreadsheeturl) {
if (spreadsheeturl == 'YOUR_SPREADSHEET_URL') {
throw new Error('Please specify a valid Spreadsheet URL. You can find' +
' a link to a template in the associated guide for this script.');
}
return SpreadsheetApp.openByUrl(spreadsheeturl);
}

 

Como configurar

 

4. Campanhas com regras de lances únicas – por Russel Savage. Determine um conjunto de regras para cada campanha do Google Ads nesse script. Faça seus lances aumentarem quando sua posição cair ou diminua seus lances quando o custo por conversão aumente demais.

 

//-----------------------------------
// Unique Bid Updates By Campaign
// Created By: Russ Savage
// FreeAdWordsScripts.com
//-----------------------------------
function main() {
// this is the structure that holds all the bid information about your accounts.
var CAMP_LIST = [
{
'camp_name' : 'camp name 1',
'rules' : [
{
'cpv_min' : 0, 'cpv_max' : 10,
'avg_pos_min' : 2, 'avg_pos_max' : 6,
'bid_change_amt' : 1.1, 'bid_limit' : 10
},
{
'cpv_min' : 10, 'cpv_max' : 20,
'avg_pos_min' : 6, 'avg_pos_max' : 10,
'bid_change_amt' : 1.2, 'bid_limit' : 10
}
]
},
{
'camp_name' : 'camp name 2',
'rules' : [
{
'cpv_min' : 0, 'cpv_max' : 5,
'avg_pos_min' : 3, 'avg_pos_max' : 5,
'bid_change_amt' : .9, 'bid_limit' : .01
},
{
'cpv_min' : 5, 'cpv_max' : 20,
'avg_pos_min' : 5, 'avg_pos_max' : 8,
'bid_change_amt' : 1.2, 'bid_limit' : 10
}
]
}
];
var date_range = 'LAST_7_DAYS';

for (index in CAMP_LIST) {
var camp = CAMP_LIST[index];
var camp_name = camp.camp_name;
var rules = camp.rules;

var kw_iter = AdWordsApp.keywords()
.withCondition("CampaignName CONTAINS_IGNORE_CASE '" + camp_name + "'")
.get();

while(kw_iter.hasNext()) {
var kw = kw_iter.next();
var kw_stats = kw.getStatsFor(date_range);
var conv = kw_stats.getConversions();

if (conv == 0) { continue; } //skip anything with no conversions

var cost = kw_stats.getCost();
var cpv = cost/conv;
var avg_pos = kw_stats.getAveragePosition();
var max_cpc = kw.getMaxCpc();

for(i in rules) {
var r = rules[i];

if(cpv >= r.cpv_min &&
cpv < r.cpv_max &&
avg_pos >= r.avg_pos_min &&
avg_pos < r.avg_pos_max)
{
kw.setMaxCpc(calculate_bid(max_cpc,r.bid_change_amt,r.bid_limit));
break;
}
}
}
}

function calculate_bid(current_bid,perc_to_change,max_min_amt) {
if(perc_to_change >= 1) {
return (current_bid * perc_to_change > max_min_amt) ? max_min_amt : (current_bid * perc_to_change);
} else {
return (current_bid * perc_to_change < max_min_amt) ? max_min_amt : (current_bid * perc_to_change);
}
}
}

 

5. Orçamentos Flexíveis – por Google Ads. Esse script permite que os orçamentos sejam aumentados ou diminuídos durante um período de tempo. Por exemplo, caso tenha um orçamento de R$5.000 para gastar em 10 dias, você pode configurá-lo para gastar uniformemente R$4.000 nos 5 primeiros dias e R$300 nos outros 3 dias e R$700 nos últimos 2.

 

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Flexible Budgets
*
* @overview The Flexible budgets script dynamically adjusts campaign budget for
* an advertiser account with a custom budget distribution scheme on a daily
* basis. See
* //developers.google.com/adwords/scripts/docs/solutions/flexible-budgets
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.0.3
*
* @changelog
* - version 1.0.3
* - Add support for video and shopping campaigns.
* - version 1.0.2
* - Use setAmount on the budget instead of campaign.setBudget.
* - version 1.0.1
* - Improvements to time zone handling.
* - version 1.0
* - Released initial version.
*/

var START_DATE = new Date('May 1, 2016 0:00:00 -0500');
var END_DATE = new Date('June 1, 2016 0:00:00 -0500');
var TOTAL_BUDGET = 500;
var CAMPAIGN_NAME = 'Special Promotion';

function main() {
testBudgetStrategy(calculateBudgetEvenly, 10, 500);
// setNewBudget(calculateBudgetEvenly, CAMPAIGN_NAME, TOTAL_BUDGET,
// START_DATE, END_DATE);
}

function setNewBudget(budgetFunction, campaignName, totalBudget, start, end) {
var today = new Date();
if (today < start) {
Logger.log('Not ready to set budget yet');
return;
}
var campaign = getCampaign(campaignName);
var costSoFar = campaign.getStatsFor(
getDateStringInTimeZone('yyyyMMdd', start),
getDateStringInTimeZone('yyyyMMdd', end)).getCost();
var daysSoFar = datediff(start, today);
var totalDays = datediff(start, end);
var newBudget = budgetFunction(costSoFar, totalBudget, daysSoFar, totalDays);
campaign.getBudget().setAmount(newBudget);
}

function calculateBudgetEvenly(costSoFar, totalBudget, daysSoFar, totalDays) {
var daysRemaining = totalDays - daysSoFar;
var budgetRemaining = totalBudget - costSoFar;
if (daysRemaining <= 0) {
return budgetRemaining;
} else {
return budgetRemaining / daysRemaining;
}
}

function calculateBudgetWeighted(costSoFar, totalBudget, daysSoFar,
totalDays) {
var daysRemaining = totalDays - daysSoFar;
var budgetRemaining = totalBudget - costSoFar;
if (daysRemaining <= 0) {
return budgetRemaining;
} else {
return budgetRemaining / (2 * daysRemaining - 1);
}
}

function testBudgetStrategy(budgetFunc, totalDays, totalBudget) {
var daysSoFar = 0;
var costSoFar = 0;
while (daysSoFar <= totalDays + 2) {
var newBudget = budgetFunc(costSoFar, totalBudget, daysSoFar, totalDays);
Logger.log('Day %s of %s, new budget %s, cost so far %s', daysSoFar + 1,
totalDays, newBudget, costSoFar);
costSoFar += newBudget;
daysSoFar += 1;
}
}

/**
* Returns number of days between two dates, rounded up to nearest whole day.
*/
function datediff(from, to) {
var millisPerDay = 1000 * 60 * 60 * 24;
return Math.ceil((to - from) / millisPerDay);
}

function getDateStringInTimeZone(format, date, timeZone) {
date = date || new Date();
timeZone = timeZone || AdWordsApp.currentAccount().getTimeZone();
return Utilities.formatDate(date, timeZone, format);
}

/**
* Finds a campaign by name, whether it is a regular, video, or shopping
* campaign, by trying all in sequence until it finds one.
*
* @param {string} campaignName The campaign name to find.
* @return {Object} The campaign found, or null if none was found.
*/
function getCampaign(campaignName) {
var selectors = [AdWordsApp.campaigns(), AdWordsApp.videoCampaigns(),
AdWordsApp.shoppingCampaigns()];
for(var i = 0; i < selectors.length; i++) {
var campaignIter = selectors[i].
withCondition('CampaignName = "' + campaignName + '"').
get();
if (campaignIter.hasNext()) {
return campaignIter.next();
}
}
throw new Error('Could not find specified campaign: ' + campaignName);
}

Como configurar 

 

6. Utilize uma planilha para gerenciar seus lances – por Russel Savage. Esse script utiliza uma planilha do Google para gerenciar os lances de suas campanhas do Google Ads (contanto que os 2 estejam em uma mesma conta do Google).

 

/****************************************
* Update Bids Using a Google Spreadsheet
* Version 1.1
* Created By: Russ Savage
* FreeAdWordsScripts.com
****************************************/
function main() {
var SPREADSHEET_URL = "Insert Url Here";

var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL);
var sheet = spreadsheet.getSheetByName('KeywordBids');
var data = sheet.getRange("A:E").getValues();

var kwBidHash = {};
for(var i in data) {
if(i == 0) { continue; }
if(data[i][0] === '') { break; }
var kwKey = Utilities.base64Encode([data[i][0],data[i][1],data[i][2]].join('~~!~~'));
kwBidHash[kwKey] = data[i];
}

var kwIter = AdWordsApp.keywords()
.withCondition("Status = ENABLED")
.get();

while(kwIter.hasNext()) {
var kw = kwIter.next();
var campName = kw.getCampaign().getName();
var kwKey = Utilities.base64Encode([campName,kw.getText(),kw.getMatchType()].join('~~!~~'));
if(kwBidHash[kwKey]) {
if(kwBidHash[kwKey][3] === "FIXED") {
kw.setMaxCpc(kwBidHash[kwKey][4]);
}else{
kw.setMaxCpc(kw.getMaxCpc() * (1+kwBidHash[kwKey][4]));
}
}
}
}

 

7. Ajuste de orçamentos dinâmicos – por Russel Savage. Esse script possibilita que todas as campanhas sejam pausadas caso o custo total alcance um limite impedindo que a conta ultrapasse o limite de orçamento. Este é um script muito útil caso trabalhe com orçamentos limitados.

 

/********************************
* Dynamically Adjust Campaign Budgets v2.1
* Changelog v2.1 - Fixed opening of spreadsheet
* Created By: Russ Savage
* FreeAdWordsScripts.com
********************************/
// Let's set some constants
var TIMEFRAME = "THIS_MONTH";
//if the campaign is not in the spreadsheet, the budget is reset
//to this value at the beginning of the month.
var DEFAULT_BUDGET = 100;
var SPREADSHEET_URL = "PLACE EMPYT SPREADSHEET URL HERE";
var LABEL = ""; //Fill in if you only want to operate on campaigns with this label

var SIG_FIGS = 1000; //this means round all calculations to 3 decimal places
var MONTHLY_BUDGET = 0; // we will set this later

function main() {
MONTHLY_BUDGET = _pull_budget_data_from_spreadsheet();
var tot_cost_mtd = _get_total_cost();
var is_first_of_the_month = ((new Date()).getDate() == 1);
is_first_of_the_month = (is_first_of_the_month && ((new Date()).getHours() == 0));
Logger.log("Total cost: " + tot_cost_mtd + ", Monthly budget:" + MONTHLY_BUDGET);

if(is_first_of_the_month) {
_reset_budgets();
} else {
_adjust_campaign_budget(tot_cost_mtd);
}

}

// Returns the total cost for the set TIMEFRAME
function _get_total_cost() {
var camp_iter = (LABEL == "") ? AdWordsApp.campaigns().get() :
AdWordsApp.campaigns()
.withCondition("LabelNames CONTAINS_ANY ['"+LABEL+"']")
.get();

var tot_cost = 0;
while(camp_iter.hasNext()) {
tot_cost += camp_iter.next().getStatsFor(TIMEFRAME).getCost();
}
return tot_cost;
}

// Calculates run rate and adjusts campaign bids as needed.
function _adjust_campaign_budget(my_tot_cost) {
var today = new Date();
// Accounting for December
var eom = (today.getMonth() == 11) ? new Date(today.getFullYear()+1,0,1) :
new Date(today.getFullYear(),today.getMonth()+1,1);
var days_left = Math.round((eom-today)/1000/60/60/24);
var days_spent = today.getDate();
var run_rate = Math.round(my_tot_cost/days_spent*SIG_FIGS)/SIG_FIGS;
var projected_total = my_tot_cost + (run_rate * days_left);
var perc_over = Math.round(((MONTHLY_BUDGET-projected_total)/projected_total)*SIG_FIGS)/SIG_FIGS;
_change_spend(perc_over,my_tot_cost);
}

//Adjusts the budget for a given campaign based on percentage of total spend
//Note: if the cost of a campaign is $0 mtd, the budget is not changed.
function _change_spend(perc_to_change,my_tot_cost) {
var camp_iter = (LABEL == '') ? AdWordsApp.campaigns()
.withCondition("Status = ENABLED")
.get() :
AdWordsApp.campaigns()
.withCondition("Status = ENABLED")
.withCondition("LabelNames CONTAINS_ANY ['"+LABEL+"']")
.get();

while(camp_iter.hasNext()) {
var camp = camp_iter.next();
var camp_cost = camp.getStatsFor(TIMEFRAME).getCost();
var perc_of_total = Math.round(camp_cost/my_tot_cost*SIG_FIGS)/SIG_FIGS;
//If there is no cost for the campaign, let's not change it.
var to_change = (perc_of_total) ? (perc_of_total*perc_to_change) : 0;
camp.setBudget(camp.getBudget()*(1+to_change));
}
}

// Resets the budget unevenly
function _reset_budgets() {
var camp_budget_map = _pull_campaign_data_from_spreadsheet();
var camp_iter = (LABEL == '') ? AdWordsApp.campaigns()
.withCondition("Status = ENABLED")
.get() :
AdWordsApp.campaigns()
.withCondition("Status = ENABLED")
.withCondition("LabelNames CONTAINS_ANY ['"+LABEL+"']")
.get();
while(camp_iter.hasNext()) {
var camp = camp_iter.next();
if(camp_budget_map[camp.getName()]) {
camp.setBudget(camp_budget_map[camp.getName()]/30.5);
} else {
camp.setBudget(DEFAULT_BUDGET);
}
}
}

function _pull_campaign_data_from_spreadsheet() {
var spreadsheet = getSpreadsheet(SPREADSHEET_URL);
var sheet = spreadsheet.getActiveSheet();
var data = sheet.getRange("A:B").getValues();
if(data[0][0] == "") {
//This means this is the first run and we should populate the data.
_populate_spreadsheet(sheet);
data = sheet.getRange("A:B").getValues();
}
var campaign_budget_map = {};
for(var i in data) {
if(i == 0) { continue; } //ignore the header
if(data[i][0] == "") { break; } //stop when there is no more data
campaign_budget_map[data[i][0]] = parseFloat(data[i][1]);
}
return campaign_budget_map;
}

function _pull_budget_data_from_spreadsheet() {
var spreadsheet = getSpreadsheet(SPREADSHEET_URL);
var sheet = spreadsheet.getActiveSheet();
var data = sheet.getRange("A:B").getValues();
if(data[0][0] == "") {
//This means this is the first run and we should populate the data.
_populate_spreadsheet(sheet);
data = sheet.getRange("A:B").getValues();
}
var tot_budget = 0;
for(var i in data) {
if(i == 0) { continue; } //ignore the header
if(data[i][1] == "") { break; } //stop when there is no more data
tot_budget += parseFloat(data[i][1]);
}
return tot_budget;
}

function _populate_spreadsheet(sheet) {
sheet.clear();
sheet.appendRow(['Campaign Name','Monthly Budget']);
var camp_iter = (LABEL == '') ? AdWordsApp.campaigns()
.withCondition("Status = ENABLED")
.get() :
AdWordsApp.campaigns()
.withCondition("Status = ENABLED")
.withCondition("LabelNames CONTAINS_ANY ['"+LABEL+"']")
.get();
while(camp_iter.hasNext()) {
var camp = camp_iter.next();
sheet.appendRow([camp.getName(),(camp.getBudget()*30.5)]);
}
}

function getSpreadsheet(spreadsheetUrl) {
return SpreadsheetApp.openByUrl(spreadsheetUrl);
}

 

8. Aumentar os lances em palavras-chaves com custo/coversão baixo – O script permite que palavras-chave que geram conversão a um custo barato tenham seus lances aumentados.

 

function main() {
//For keywords with less than $5 CPC, let's pump those bids up by 35%
var AMAZING_COST_PER_CONV = 5;
var AMAZING_BID_INCREASE_AMOUNT = .35;

//For keywords with between $5 and $8 CPCs, we will only increase the bids by 20%
var GREAT_COST_PER_CONV = 8;
var GREAT_BID_INCREASE_AMOUNT = .20;

var kw_iter = AdWordsApp.keywords()
.withCondition("Status = ENABLED")
.get();

while(kw_iter.hasNext()) {
var kw = kw_iter.next();
var kw_stats = kw.getStatsFor("LAST_30_DAYS");
var cost = kw_stats.getCost();
var conversions = kw_stats.getConversions();
if(conversions > 0) {
var cost_per_conversion = (cost/(conversions*1.0));

if(cost_per_conversion <= AMAZING_COST_PER_CONV) {
kw.setMaxCpc(kw.getMaxCpc() * (1+AMAZING_BID_INCREASE_AMOUNT));
}
else if(cost_per_conversion <= GREAT_COST_PER_CONV) {
kw.setMaxCpc(kw.getMaxCpc() * (1+GREAT_BID_INCREASE_AMOUNT));
}
}else{
//no conversions on this keyword
//we will deal with that later
continue;
}
}
}

 

Automação Google Ads: Capturando Dados de Terceiros

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para “Captura de dados de terceiros”.

 

1. Lances pela previsão de tempo – por Google Ads. Esse script do Google permite que seus lances mudem de acordo com o API do OpenWeatherMap. Caso você anuncie sorvetes artesanais você pode aumentar seus lances nos dias de sol e diminuir nos dias de chuva.

 

// Copyright 2015, Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// //www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* @name Bid By Weather
*
* @overview The Bid By Weather script adjusts campaign bids by weather
* conditions of their associated locations. See
* //developers.google.com/adwords/scripts/docs/solutions/weather-based-campaign-management#bid-by-weather
* for more details.
*
* @author AdWords Scripts Team [[email protected]]
*
* @version 1.2.2
*
* @changelog
* - version 1.2.2
* - Add support for video and shopping campaigns.
* - version 1.2.1
* - Added validation for external spreadsheet setup.
* - version 1.2
* - Added proximity based targeting. Targeting flag allows location
* targeting, proximity targeting or both.
* - version 1.1
* - Added flag allowing bid adjustments on all locations targeted by
* a campaign rather than only those that match the campaign rule
* - version 1.0
* - Released initial version.
*/

// Register for an API key at //openweathermap.org/appid
// and enter the key below.
var OPEN_WEATHER_MAP_API_KEY = 'INSERT_OPEN_WEATHER_MAP_API_KEY_HERE';

// Create a copy of //goo.gl/A59Uuc and enter the URL below.
var SPREADSHEET_URL = 'INSERT_SPREADSHEET_URL_HERE';

// A cache to store the weather for locations already lookedup earlier.
var WEATHER_LOOKUP_CACHE = {};

// Flag to pick which kind of targeting "LOCATION", "PROXIMITY", or "ALL".
var TARGETING = 'ALL';

/**
* The code to execute when running the script.
*/
function main() {
validateApiKey();
// Load data from spreadsheet.
var spreadsheet = validateAndGetSpreadsheet(SPREADSHEET_URL);
var campaignRuleData = getSheetData(spreadsheet, 1);
var weatherConditionData = getSheetData(spreadsheet, 2);
var geoMappingData = getSheetData(spreadsheet, 3);

// Convert the data into dictionaries for convenient usage.
var campaignMapping = buildCampaignRulesMapping(campaignRuleData);
var weatherConditionMapping =
buildWeatherConditionMapping(weatherConditionData);
var locationMapping = buildLocationMapping(geoMappingData);

// Apply the rules.
for (var campaignName in campaignMapping) {
applyRulesForCampaign(campaignName, campaignMapping[campaignName],
locationMapping, weatherConditionMapping);
}
}

/**
* Retrieves the data for a worksheet.
*
* @param {Object} spreadsheet The spreadsheet.
* @param {number} sheetIndex The sheet index.
* @return {Array} The data as a two dimensional array.
*/
function getSheetData(spreadsheet, sheetIndex) {
var sheet = spreadsheet.getSheets()[sheetIndex];
var range =
sheet.getRange(2, 1, sheet.getLastRow() - 1, sheet.getLastColumn());
return range.getValues();
}

/**
* Builds a mapping between the list of campaigns and the rules
* being applied to them.
*
* @param {Array} campaignRulesData The campaign rules data, from the
* spreadsheet.
* @return {Object.<string, Array.<Object>> } A map, with key as campaign name,
* and value as an array of rules that apply to this campaign.
*/
function buildCampaignRulesMapping(campaignRulesData) {
var campaignMapping = {};
for (var i = 0; i < campaignRulesData.length; i++) {
// Skip rule if not enabled.

if (campaignRulesData[i][5].toLowerCase() == 'yes') {
var campaignName = campaignRulesData[i][0];
var campaignRules = campaignMapping[campaignName] || [];
campaignRules.push({
'name': campaignName,

// location for which this rule applies.
'location': campaignRulesData[i][1],

// the weather condition (e.g. Sunny).
'condition': campaignRulesData[i][2],

// bid modifier to be applied.
'bidModifier': campaignRulesData[i][3],

// whether bid adjustments should by applied only to geo codes
// matching the location of the rule or to all geo codes that
// the campaign targets.
'targetedOnly': campaignRulesData[i][4].toLowerCase() ==
'matching geo targets'
});
campaignMapping[campaignName] = campaignRules;
}
}
Logger.log('Campaign Mapping: %s', campaignMapping);
return campaignMapping;
}

/**
* Builds a mapping between a weather condition name (e.g. Sunny) and the rules
* that correspond to that weather condition.
*
* @param {Array} weatherConditionData The weather condition data from the
* spreadsheet.
* @return {Object.<string, Array.<Object>>} A map, with key as a weather
* condition name, and value as the set of rules corresponding to that
* weather condition.
*/
function buildWeatherConditionMapping(weatherConditionData) {
var weatherConditionMapping = {};

for (var i = 0; i < weatherConditionData.length; i++) {
var weatherConditionName = weatherConditionData[i][0];
weatherConditionMapping[weatherConditionName] = {
// Condition name (e.g. Sunny)
'condition': weatherConditionName,

// Temperature (e.g. 50 to 70)
'temperature': weatherConditionData[i][1],

// Precipitation (e.g. below 70)
'precipitation': weatherConditionData[i][2],

// Wind speed (e.g. above 5)
'wind': weatherConditionData[i][3]
};
}
Logger.log('Weather condition mapping: %s', weatherConditionMapping);
return weatherConditionMapping;
}

/**
* Builds a mapping between a location name (as understood by OpenWeatherMap
* API) and a list of geo codes as identified by AdWords scripts.
*
* @param {Array} geoTargetData The geo target data from the spreadsheet.
* @return {Object.<string, Array.<Object>>} A map, with key as a locaton name,
* and value as an array of geo codes that correspond to that location
* name.
*/
function buildLocationMapping(geoTargetData) {
var locationMapping = {};
for (var i = 0; i < geoTargetData.length; i++) {
var locationName = geoTargetData[i][0];
var locationDetails = locationMapping[locationName] || {
'geoCodes': [] // List of geo codes understood by AdWords scripts.
};

locationDetails.geoCodes.push(geoTargetData[i][1]);
locationMapping[locationName] = locationDetails;
}
Logger.log('Location Mapping: %s', locationMapping);
return locationMapping;
}

/**
* Applies rules to a campaign.
*
* @param {string} campaignName The name of the campaign.
* @param {Object} campaignRules The details of the campaign. See
* buildCampaignMapping for details.
* @param {Object} locationMapping Mapping between a location name (as
* understood by OpenWeatherMap API) and a list of geo codes as
* identified by AdWords scripts. See buildLocationMapping for details.
* @param {Object} weatherConditionMapping Mapping between a weather condition
* name (e.g. Sunny) and the rules that correspond to that weather
* condition. See buildWeatherConditionMapping for details.
*/
function applyRulesForCampaign(campaignName, campaignRules, locationMapping,
weatherConditionMapping) {
for (var i = 0; i < campaignRules.length; i++) {
var bidModifier = 1;
var campaignRule = campaignRules[i];

// Get the weather for the required location.
var locationDetails = locationMapping[campaignRule.location];
var weather = getWeather(campaignRule.location);
Logger.log('Weather for %s: %s', locationDetails, weather);

// Get the weather rules to be checked.
var weatherConditionName = campaignRule.condition;
var weatherConditionRules = weatherConditionMapping[weatherConditionName];

// Evaluate the weather rules.
if (evaluateWeatherRules(weatherConditionRules, weather)) {
Logger.log('Matching Rule found: Campaign Name = %s, location = %s, ' +
'weatherName = %s,weatherRules = %s, noticed weather = %s.',
campaignRule.name, campaignRule.location,
weatherConditionName, weatherConditionRules, weather);
bidModifier = campaignRule.bidModifier;

if (TARGETING == 'LOCATION' || TARGETING == 'ALL') {
// Get the geo codes that should have their bids adjusted.
var geoCodes = campaignRule.targetedOnly ?
locationDetails.geoCodes : null;
adjustBids(campaignName, geoCodes, bidModifier);
}

if (TARGETING == 'PROXIMITY' || TARGETING == 'ALL') {
var location = campaignRule.targetedOnly ? campaignRule.location : null;
adjustProximityBids(campaignName, location, bidModifier);
}

}
}
return;
}

/**
* Converts a temperature value from kelvin to fahrenheit.
*
* @param {number} kelvin The temperature in Kelvin scale.
* @return {number} The temperature in Fahrenheit scale.
*/
function toFahrenheit(kelvin) {
return (kelvin - 273.15) * 1.8 + 32;
}

/**
* Evaluates the weather rules.
*
* @param {Object} weatherRules The weather rules to be evaluated.
* @param {Object.<string, string>} weather The actual weather.
* @return {boolean} True if the rule matches current weather conditions,
* False otherwise.
*/
function evaluateWeatherRules(weatherRules, weather) {
// See //bugs.openweathermap.org/projects/api/wiki/Weather_Data
// for values returned by OpenWeatherMap API.
var precipitation = 0;
if (weather.rain && weather.rain['3h']) {
precipitation = weather.rain['3h'];
}
var temperature = toFahrenheit(weather.main.temp);
var windspeed = weather.wind.speed;

return evaluateMatchRules(weatherRules.temperature, temperature) &&
evaluateMatchRules(weatherRules.precipitation, precipitation) &&
evaluateMatchRules(weatherRules.wind, windspeed);
}

/**
* Evaluates a condition for a value against a set of known evaluation rules.
*
* @param {string} condition The condition to be checked.
* @param {Object} value The value to be checked.
* @return {boolean} True if an evaluation rule matches, false otherwise.
*/
function evaluateMatchRules(condition, value) {
// No condition to evaluate, rule passes.
if (condition == '') {
return true;
}
var rules = [matchesBelow, matchesAbove, matchesRange];

for (var i = 0; i < rules.length; i++) {
if (rules[i](condition, value)) {
return true;
}
}
return false;
}

/**
* Evaluates whether a value is below a threshold value.
*
* @param {string} condition The condition to be checked. (e.g. below 50).
* @param {number} value The value to be checked.
* @return {boolean} True if the value is less than what is specified in
* condition, false otherwise.
*/
function matchesBelow(condition, value) {
conditionParts = condition.split(' ');

if (conditionParts.length != 2) {
return false;
}

if (conditionParts[0] != 'below') {
return false;
}

if (value < conditionParts[1]) {
return true;
}
return false;
}

/**
* Evaluates whether a value is above a threshold value.
*
* @param {string} condition The condition to be checked. (e.g. above 50).
* @param {number} value The value to be checked.
* @return {boolean} True if the value is greater than what is specified in
* condition, false otherwise.
*/
function matchesAbove(condition, value) {
conditionParts = condition.split(' ');

if (conditionParts.length != 2) {
return false;
}

if (conditionParts[0] != 'above') {
return false;
}

if (value > conditionParts[1]) {
return true;
}
return false;
}

/**
* Evaluates whether a value is within a range of values.
*
* @param {string} condition The condition to be checked (e.g. 5 to 18).
* @param {number} value The value to be checked.
* @return {boolean} True if the value is in the desired range, false otherwise.
*/
function matchesRange(condition, value) {
conditionParts = condition.replace('\w+', ' ').split(' ');

if (conditionParts.length != 3) {
return false;
}

if (conditionParts[1] != 'to') {
return false;
}

if (conditionParts[0] <= value && value <= conditionParts[2]) {
return true;
}
return false;
}

/**
* Retrieves the weather for a given location, using the OpenWeatherMap API.
*
* @param {string} location The location to get the weather for.
* @return {Object.<string, string>} The weather attributes and values, as
* defined in the API.
*/
function getWeather(location) {
if (location in WEATHER_LOOKUP_CACHE) {
Logger.log('Cache hit...');
return WEATHER_LOOKUP_CACHE[location];
}

var url = Utilities.formatString(
'//api.openweathermap.org/data/2.5/weather?APPID=%s&q=%s',
encodeURIComponent(OPEN_WEATHER_MAP_API_KEY),
encodeURIComponent(location));
var response = UrlFetchApp.fetch(url);
if (response.getResponseCode() != 200) {
throw Utilities.formatString(
'Error returned by API: %s, Location searched: %s.',
response.getContentText(), location);
}

var result = JSON.parse(response.getContentText());

// OpenWeatherMap's way of returning errors.
if (result.cod != 200) {
throw Utilities.formatString(
'Error returned by API: %s, Location searched: %s.',
response.getContentText(), location);
}

WEATHER_LOOKUP_CACHE[location] = result;
return result;
}

/**
* Adjusts the bidModifier for a list of geo codes for a campaign.
*
* @param {string} campaignName The name of the campaign.
* @param {Array} geoCodes The list of geo codes for which bids should be
* adjusted. If null, all geo codes on the campaign are adjusted.
* @param {number} bidModifier The bid modifier to use.
*/
function adjustBids(campaignName, geoCodes, bidModifier) {
// Get the campaign.
var campaign = getCampaign(campaignName);
if (!campaign) return null;

// Get the targeted locations.
var locations = campaign.targeting().targetedLocations().get();
while (locations.hasNext()) {
var location = locations.next();
var currentBidModifier = location.getBidModifier().toFixed(2);

// Apply the bid modifier only if the campaign has a custom targeting
// for this geo location or if all locations are to be modified.
if (!geoCodes || (geoCodes.indexOf(location.getId()) != -1 &&
currentBidModifier != bidModifier)) {
Logger.log('Setting bidModifier = %s for campaign name = %s, ' +
'geoCode = %s. Old bid modifier is %s.', bidModifier,
campaignName, location.getId(), currentBidModifier);
location.setBidModifier(bidModifier);
}
}
}

/**
* Adjusts the bidModifier for campaigns targeting by proximity location
* for a given weather location.
*
* @param {string} campaignName The name of the campaign.
* @param {string} weatherLocation The weather location for which bids should be
* adjusted. If null, all proximity locations on the campaign are adjusted.
* @param {number} bidModifier The bid modifier to use.
*/
function adjustProximityBids(campaignName, weatherLocation, bidModifier) {
// Get the campaign.
var campaign = getCampaign(campaignName);
if(campaign === null) return;

// Get the proximity locations.
var proximities = campaign.targeting().targetedProximities().get();
while (proximities.hasNext()) {
var proximity = proximities.next();
var currentBidModifier = proximity.getBidModifier().toFixed(2);

// Apply the bid modifier only if the campaign has a custom targeting
// for this geo location or if all locations are to be modified.
if (!weatherLocation ||
(weatherNearProximity(proximity, weatherLocation) &&
currentBidModifier != bidModifier)) {
Logger.log('Setting bidModifier = %s for campaign name = %s, with ' +
'weatherLocation = %s in proximity area. Old bid modifier is %s.',
bidModifier, campaignName, weatherLocation, currentBidModifier);
proximity.setBidModifier(bidModifier);
}
}
}

/**
* Checks if weather location is within the radius of the proximity location.
*
* @param {Object} proximity The targeted proximity of campaign.
* @param {string} weatherLocation Name of weather location to check within
* radius.
* @return {boolean} Returns true if weather location is within radius.
*/
function weatherNearProximity(proximity, weatherLocation) {
// See //en.wikipedia.org/wiki/Haversine_formula for details on how
// to compute spherical distance.
var earthRadiusInMiles = 3960.0;
var degreesToRadians = Math.PI / 180.0;
var radiansToDegrees = 180.0 / Math.PI;
var kmToMiles = 0.621371;

var radiusInMiles = proximity.getRadiusUnits() == 'MILES' ?
proximity.getRadius() : proximity.getRadius() * kmToMiles;

// Compute the change in latitude degrees for the radius.
var deltaLat = (radiusInMiles / earthRadiusInMiles) * radiansToDegrees;
// Find the radius of a circle around the earth at given latitude.
var r = earthRadiusInMiles * Math.cos(proximity.getLatitude() *
degreesToRadians);
// Compute the change in longitude degrees for the radius.
var deltaLon = (radiusInMiles / r) * radiansToDegrees;

// Retrieve weather location for lat/lon coordinates.
var weather = getWeather(weatherLocation);
// Check if weather condition is within the proximity boundaries.
return (weather.coord.lat >= proximity.getLatitude() - deltaLat &&
weather.coord.lat <= proximity.getLatitude() + deltaLat &&
weather.coord.lon >= proximity.getLongitude() - deltaLon &&
weather.coord.lon <= proximity.getLongitude() + deltaLon);
}

/**
* Finds a campaign by name, whether it is a regular, video, or shopping
* campaign, by trying all in sequence until it finds one.
*
* @param {string} campaignName The campaign name to find.
* @return {Object} The campaign found, or null if none was found.
*/
function getCampaign(campaignName) {
var selectors = [AdWordsApp.campaigns(), AdWordsApp.videoCampaigns(),
AdWordsApp.shoppingCampaigns()];
for(var i = 0; i < selectors.length; i++) {
var campaignIter = selectors[i].
withCondition('CampaignName = "' + campaignName + '"').
get();
if (campaignIter.hasNext()) {
return campaignIter.next();
}
}
return null;
}

/**
* DO NOT EDIT ANYTHING BELOW THIS LINE.
* Please modify your spreadsheet URL and API key at the top of the file only.
*/

/**
* Validates the provided spreadsheet URL to make sure that it's set up
* properly. Throws a descriptive error message if validation fails.
*
* @param {string} spreadsheeturl The URL of the spreadsheet to open.
* @return {Spreadsheet} The spreadsheet object itself, fetched from the URL.
* @throws {Error} If the spreadsheet URL hasn't been set
*/
function validateAndGetSpreadsheet(spreadsheeturl) {
if (spreadsheeturl == 'INSERT_SPREADSHEET_URL_HERE') {
throw new Error('Please specify a valid Spreadsheet URL. You can find' +
' a link to a template in the associated guide for this script.');
}
var spreadsheet = SpreadsheetApp.openByUrl(spreadsheeturl);
return spreadsheet;
}

/**
* Validates the provided API key to make sure that it's not the default. Throws
* a descriptive error message if validation fails.
*
* @throws {Error} If the configured API key hasn't been set.
*/
function validateApiKey() {
if (OPEN_WEATHER_MAP_API_KEY == 'INSERT_OPEN_WEATHER_MAP_API_KEY_HERE') {
throw new Error('Please specify a valid API key for OpenWeatherMap. You ' +
'can acquire one here: //openweathermap.org/appid');
}
}

 

 

2. Gerencie anúncios com base nos horários de vôos – por Russell Savage. Atrasos de vôo? Seus anúncios são ativados. Excelente scripts para hotéis e restaurantes próximos a aeroportos.

 

/*********************************************
* Pause/Enable Ads Based On Airport Delays
* Version 1.1
* ChangeLog v1.1
* - Added ability to completely pause non-delay ads
* Created By: Russ Savage
* FreeAdWordsScripts.com
*********************************************/
// For this to work, you need to add a label to all
// the ads for each airport. For example, PIT_normal
// or SFO_normal
var PAUSE_NORMAL_ADS_DURING_DELAY = false;
var DELAY_SUFFIX = '_delay'; //the suffix on the label for delayed ads
var NORMAL_SUFFIX = '_normal'; //the suffix on the label for normal ads

var AIR_CODES = ["ATL","ANC","AUS","BWI","BOS","CLT","MDW","ORD","CVG","CLE",
"CMH","DFW","DEN","DTW","FLL","RSW","BDL","HNL","IAH","HOU",
"IND","MCI","LAS","LAX","MEM","MIA","MSP","BNA","MSY","JFK",
"LGA","EWR","OAK","ONT","MCO","PHL","PHX","PIT","PDX","RDU",
"SMF","SLC","SAT","SAN","SFO","SJC","SNA","SEA","STL","TPA",
"IAD","DCA"];

function main() {
var faaUrl = "//services.faa.gov/airport/status/";
var args = "?format=application/json";
for(var i in AIR_CODES) {
try{
var resp = UrlFetchApp.fetch(faaUrl + AIR_CODES[i] + args);
if( resp.getResponseCode() == 200 ) {
var json = Utilities.jsonParse(resp.getContentText());
if(json.delay == "false") {
Logger.log("No delays at "+json.IATA+". Pausing delay ads if any are running.");
turnOffDelayAds(json.IATA);
if(PAUSE_NORMAL_ADS_DURING_DELAY) {
turnOnNonDelayAds(json.IATA);
}
} else {
Logger.log("Delays in "+json.IATA+" Reason: "+json.status.reason);
Logger.log("Turning on delay ads if there are any.");
turnOnDelayAds(json.IATA);
if(PAUSE_NORMAL_ADS_DURING_DELAY) {
turnOffNonDelayAds(json.IATA);
}
}
}
}
catch(e) {
Logger.log("Error: " + e.message);
}
}
}

function turnOffDelayAds(airportCode) {
var labelName = airportCode + DELAY_SUFFIX;
var adIter = AdWordsApp.ads()
.withCondition("LabelNames CONTAINS_ANY ['"+labelName+"']")
.withCondition("CampaignStatus = ENABLED")
.withCondition("AdGroupStatus = ENABLED")
.withCondition("Status = ENABLED")
.get();
while(adIter.hasNext()) {
adIter.next().pause();
}
}

function turnOffNonDelayAds(airportCode) {
var labelName = airportCode + NORMAL_SUFFIX;
var adIter = AdWordsApp.ads()
.withCondition("LabelNames CONTAINS_ANY ['"+labelName+"']")
.withCondition("CampaignStatus = ENABLED")
.withCondition("AdGroupStatus = ENABLED")
.withCondition("Status = ENABLED")
.get();
while(adIter.hasNext()) {
adIter.next().pause();
}
}

function turnOnDelayAds(airportCode) {
var labelName = airportCode + DELAY_SUFFIX;
var adIter = AdWordsApp.ads()
.withCondition("LabelNames CONTAINS_ANY ['"+labelName+"']")
.withCondition("CampaignStatus = ENABLED")
.withCondition("AdGroupStatus = ENABLED")
.withCondition("Status = PAUSED")
.get();
while(adIter.hasNext()) {
adIter.next().enable();
}
}

function turnOnNonDelayAds(airportCode) {
var labelName = airportCode + NORMAL_SUFFIX;
var adIter = AdWordsApp.ads()
.withCondition("LabelNames CONTAINS_ANY ['"+labelName+"']")
.withCondition("CampaignStatus = ENABLED")
.withCondition("AdGroupStatus = ENABLED")
.withCondition("Status = PAUSED")
.get();
while(adIter.hasNext()) {
adIter.next().enable();
}
}

 

3. Recurso auto-completar do Amazon para encontrar palavras-chave – por Derek Martin. Exportando os resultados do recurso da Amazon de auto-completar você pode encontrar novas palavras-chave para sua conta no Google Ads.

 

/**********************************************************************************************************************
* Amazon Autocomplete Tool
* Leverage the Amazon Autocomplete feature to find highly commercial keyword opportunities.
* Export the results for efficient importing into Google Adwords
* Version 1.0
* Created By: Derek Martin
* DerekMartinLA.com or MixedMarketingArtist.com
**********************************************************************************************************************/

var hashMapResults = {};
var numOfKeywords = 0;
var doWork = false;
var keywordsToQuery = new Array();
var keywordsToQueryIndex = 0;
var queryflag = false;

var brandKeywordList = [];
var targetKeyword = "srixon"; // this is the keyword that you want to know about
var emailAddress ="[email protected]"; // this is where the final report will be sent
var iterate = false; // true means go through initial results and find opportunities; false means return the intial list

function main() {
// Get the brand name from the account
var accountName = AdWordsApp.currentAccount().getName().split('-');

var clientName = accountName[0];

info('Now starting Amazon Autocomplete Analysis..');
info("");
// iterate through alphabet and build keyword list for initial keyword
info("Targeted Keyword: " + targetKeyword);
info("");
info("Results:");

var initial_list = [];
var list = []
initial_list = buildKeywordList(targetKeyword);

list.push(initial_list);

if (iterate == true) {

for (i = 1; i < initial_list.length; i++) {
Utilities.sleep(100);
if (initial_list[i] != targetKeyword) {

var res = buildKeywordList(initial_list[i]);

if (res != null) {
list.push(res);
}
}
}
}
list = flatten(list);
list = _.uniq(list);
list.sort()

// let user know that search has completed
info('Amazon Autocomplete Search has completed, expect an email with search term results momentarily');
info("");
info(list.length + ' searches were found. ');

var fileUrl = createSpreadsheet(list);

info('Or you can find it here: ' + fileUrl);
sendAnEmail(clientName, list.toString(), fileUrl);
}

function flatten(arr) {
return arr.reduce(function (flat, toFlatten) {
return flat.concat(Array.isArray(toFlatten) ? flatten(toFlatten) : toFlatten);
}, []);
}

function buildKeywordList(keyword) {
var result = [];
// get the first set of keywords related to the term and add to list
brandKeywordList = queryKeyword(keyword);

for(var j = 0; j < 26; j++) {
var chr = String.fromCharCode(97 + j);

keywordVariation = keyword + ' '+ chr;

var alphaList = {};
alphaList = queryKeyword(keywordVariation);

for (var x = 0; x < alphaList.length; x++) {

if (x !== 0) {

var myRe = /(.+)\"],.+/;
var testRe = myRe.exec(alphaList[x]);

if (testRe == null) {
info(alphaList[x]);
result.push(alphaList[x]);

} else {
info(testRe[1]);
result.push(testRe[1]);
}
}
}
}

for(var n = 0; n <= 9; n++) {

keywordVariation = keyword + ' '+ n;

var numberList = {};
numberList = queryKeyword(keywordVariation);

for (var y = 0; y < numberList.length; y++) {

if (y !== 0) {

var myRe = /(.+)\"],.+/;
var testRe = myRe.exec(numberList[y]);

if (testRe == null) {
info(numberList[y]);
result.push(numberList[y]);
} else {
info(testRe[1]);
result.push(testRe[1]);
}

}
}
}
return result;
} // end of buildKeywordList

function createSpreadsheet(results) {
var newSS = SpreadsheetApp.create('amazon-keywords', results.length, 26);

var sheet = newSS.getActiveSheet();

var columnNames = ["Campaign Name", "AdGroup", "Keyword", "Match Type"];

var headersRange = sheet.getRange(1, 1, 1, columnNames.length);

for (i = 0; i < results.length; i++) {

headersRange.setValues([columnNames]);

var resultKw;
resultKw = results[i].toString();

sheet.appendRow(["Your Campaign", "Your AdGroup", resultKw,'Phrase']);

// Sets the first column to a width which fits the text
sheet.setColumnWidth(1, 300);

}

return newSS.getUrl();

}

function sendAnEmail (results, fileUrl) {

var data = Utilities.parseCsv(results, '\t');
var today = new Date();

var filename = 'keyword-research-results' + today;

// Send an email with Search list attachment
var blob = Utilities.newBlob(results, 'text/html', '');

MailApp.sendEmail(emailAddress, 'Amazon Autocomplete Results ', 'You can find the results at the following URL:' + fileUrl, {
name: 'Amazon Autocomplete Search Results'
});

}
/* Utility Functions */

function warn(msg) {
Logger.log('WARNING: '+msg);
}

function info(msg) {
Logger.log(msg);
}

function queryKeyword(keyword) {
var querykeyword = encodeURIComponent(keyword);

var queryresult = '';
queryflag = true;

Utilities.sleep(200);
var response = UrlFetchApp.fetch("//completion.amazon.com/search/complete?mkt=1&search-alias=aps&x=updateAmazon&q=" + querykeyword);

var resStr = "\"[completion = \"";
var retval = response.getContentText().replace(resStr,"");

var test = _.str.stripTags(retval);

var retList = ScrapePage(retval, '["', '",');

queryflag = false;

return retList;
}

function ScrapePage(page, left, right)
{
var i = 0;
var retVal = new Array();
var firstIndex = page.indexOf(left);
while (firstIndex != -1)
{
firstIndex += left.length;
var secondIndex = page.indexOf(right, firstIndex);
if (secondIndex != -1)
{
var val = page.substring(firstIndex, secondIndex);
val = val.replace("\\u003cb\\u003e", "");
val = val.replace("\\u003c\\/b\\u003e", "");
val = val.replace("\\u003c\\/b\\u003e", "");
val = val.replace("\\u003cb\\u003e", "");
val = val.replace("\\u003c\\/b\\u003e", "");
val = val.replace("\\u003cb\\u003e", "");
val = val.replace("\\u003cb\\u003e", "");
val = val.replace("\\u003c\\/b\\u003e", "");
val = val.replace("\\u0026amp;", "&");
val = val.replace("\\u003cb\\u003e", "");
val = val.replace("\\u0026", "");
val = val.replace("\\u0026#39;", "'");
val = val.replace("#39;", "'");
val = val.replace("\\u003c\\/b\\u003e", "");
val = val.replace("\\u2013", "2013");
retVal[i] = val;
i++;
firstIndex = page.indexOf(left, secondIndex);
}
else
{
return retVal;
}
}
return retVal;
}

// Underscore.js 1.6.0
// //underscorejs.org
// (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
// Underscore may be freely distributed under the MIT license.
(function(){var n=this,t=n._,r={},e=Array.prototype,u=Object.prototype,i=Function.prototype,a=e.push,o=e.slice,c=e.concat,l=u.toString,f=u.hasOwnProperty,s=e.forEach,p=e.map,h=e.reduce,v=e.reduceRight,g=e.filter,d=e.every,m=e.some,y=e.indexOf,b=e.lastIndexOf,x=Array.isArray,w=Object.keys,_=i.bind,j=function(n){return n instanceof j?n:this instanceof j?void(this._wrapped=n):new j(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=j),exports._=j):n._=j,j.VERSION="1.6.0";var A=j.each=j.forEach=function(n,t,e){if(null==n)return n;if(s&&n.forEach===s)n.forEach(t,e);else if(n.length===+n.length){for(var u=0,i=n.length;i>u;u++)if(t.call(e,n[u],u,n)===r)return}else for(var a=j.keys(n),u=0,i=a.length;i>u;u++)if(t.call(e,n[a[u]],a[u],n)===r)return;return n};j.map=j.collect=function(n,t,r){var e=[];return null==n?e:p&&n.map===p?n.map(t,r):(A(n,function(n,u,i){e.push(t.call(r,n,u,i))}),e)};var O="Reduce of empty array with no initial value";j.reduce=j.foldl=j.inject=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),h&&n.reduce===h)return e&&(t=j.bind(t,e)),u?n.reduce(t,r):n.reduce(t);if(A(n,function(n,i,a){u?r=t.call(e,r,n,i,a):(r=n,u=!0)}),!u)throw new TypeError(O);return r},j.reduceRight=j.foldr=function(n,t,r,e){var u=arguments.length>2;if(null==n&&(n=[]),v&&n.reduceRight===v)return e&&(t=j.bind(t,e)),u?n.reduceRight(t,r):n.reduceRight(t);var i=n.length;if(i!==+i){var a=j.keys(n);i=a.length}if(A(n,function(o,c,l){c=a?a[--i]:--i,u?r=t.call(e,r,n[c],c,l):(r=n[c],u=!0)}),!u)throw new TypeError(O);return r},j.find=j.detect=function(n,t,r){var e;return k(n,function(n,u,i){return t.call(r,n,u,i)?(e=n,!0):void 0}),e},j.filter=j.select=function(n,t,r){var e=[];return null==n?e:g&&n.filter===g?n.filter(t,r):(A(n,function(n,u,i){t.call(r,n,u,i)&&e.push(n)}),e)},j.reject=function(n,t,r){return j.filter(n,function(n,e,u){return!t.call(r,n,e,u)},r)},j.every=j.all=function(n,t,e){t||(t=j.identity);var u=!0;return null==n?u:d&&n.every===d?n.every(t,e):(A(n,function(n,i,a){return(u=u&&t.call(e,n,i,a))?void 0:r}),!!u)};var k=j.some=j.any=function(n,t,e){t||(t=j.identity);var u=!1;return null==n?u:m&&n.some===m?n.some(t,e):(A(n,function(n,i,a){return u||(u=t.call(e,n,i,a))?r:void 0}),!!u)};j.contains=j.include=function(n,t){return null==n?!1:y&&n.indexOf===y?n.indexOf(t)!=-1:k(n,function(n){return n===t})},j.invoke=function(n,t){var r=o.call(arguments,2),e=j.isFunction(t);return j.map(n,function(n){return(e?t:n[t]).apply(n,r)})},j.pluck=function(n,t){return j.map(n,j.property(t))},j.where=function(n,t){return j.filter(n,j.matches(t))},j.findWhere=function(n,t){return j.find(n,j.matches(t))},j.max=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.max.apply(Math,n);var e=-1/0,u=-1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;o>u&&(e=n,u=o)}),e},j.min=function(n,t,r){if(!t&&j.isArray(n)&&n[0]===+n[0]&&n.length<65535)return Math.min.apply(Math,n);var e=1/0,u=1/0;return A(n,function(n,i,a){var o=t?t.call(r,n,i,a):n;u>o&&(e=n,u=o)}),e},j.shuffle=function(n){var t,r=0,e=[];return A(n,function(n){t=j.random(r++),e[r-1]=e[t],e[t]=n}),e},j.sample=function(n,t,r){return null==t||r?(n.length!==+n.length&&(n=j.values(n)),n[j.random(n.length-1)]):j.shuffle(n).slice(0,Math.max(0,t))};var E=function(n){return null==n?j.identity:j.isFunction(n)?n:j.property(n)};j.sortBy=function(n,t,r){return t=E(t),j.pluck(j.map(n,function(n,e,u){return{value:n,index:e,criteria:t.call(r,n,e,u)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=E(r),A(t,function(i,a){var o=r.call(e,i,a,t);n(u,o,i)}),u}};j.groupBy=F(function(n,t,r){j.has(n,t)?n[t].push(r):n[t]=[r]}),j.indexBy=F(function(n,t,r){n[t]=r}),j.countBy=F(function(n,t){j.has(n,t)?n[t]++:n[t]=1}),j.sortedIndex=function(n,t,r,e){r=E(r);for(var u=r.call(e,t),i=0,a=n.length;a>i;){var o=i+a>>>1;r.call(e,n[o])<u?i=o+1:a=o}return i},j.toArray=function(n){return n?j.isArray(n)?o.call(n):n.length===+n.length?j.map(n,j.identity):j.values(n):[]},j.size=function(n){return null==n?0:n.length===+n.length?n.length:j.keys(n).length},j.first=j.head=j.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:0>t?[]:o.call(n,0,t)},j.initial=function(n,t,r){return o.call(n,0,n.length-(null==t||r?1:t))},j.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:o.call(n,Math.max(n.length-t,0))},j.rest=j.tail=j.drop=function(n,t,r){return o.call(n,null==t||r?1:t)},j.compact=function(n){return j.filter(n,j.identity)};var M=function(n,t,r){return t&&j.every(n,j.isArray)?c.apply(r,n):(A(n,function(n){j.isArray(n)||j.isArguments(n)?t?a.apply(r,n):M(n,t,r):r.push(n)}),r)};j.flatten=function(n,t){return M(n,t,[])},j.without=function(n){return j.difference(n,o.call(arguments,1))},j.partition=function(n,t){var r=[],e=[];return A(n,function(n){(t(n)?r:e).push(n)}),[r,e]},j.uniq=j.unique=function(n,t,r,e){j.isFunction(t)&&(e=r,r=t,t=!1);var u=r?j.map(n,r,e):n,i=[],a=[];return A(u,function(r,e){(t?e&&a[a.length-1]===r:j.contains(a,r))||(a.push(r),i.push(n[e]))}),i},j.union=function(){return j.uniq(j.flatten(arguments,!0))},j.intersection=function(n){var t=o.call(arguments,1);return j.filter(j.uniq(n),function(n){return j.every(t,function(t){return j.contains(t,n)})})},j.difference=function(n){var t=c.apply(e,o.call(arguments,1));return j.filter(n,function(n){return!j.contains(t,n)})},j.zip=function(){for(var n=j.max(j.pluck(arguments,"length").concat(0)),t=new Array(n),r=0;n>r;r++)t[r]=j.pluck(arguments,""+r);return t},j.object=function(n,t){if(null==n)return{};for(var r={},e=0,u=n.length;u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},j.indexOf=function(n,t,r){if(null==n)return-1;var e=0,u=n.length;if(r){if("number"!=typeof r)return e=j.sortedIndex(n,t),n[e]===t?e:-1;e=0>r?Math.max(0,u+r):r}if(y&&n.indexOf===y)return n.indexOf(t,r);for(;u>e;e++)if(n[e]===t)return e;return-1},j.lastIndexOf=function(n,t,r){if(null==n)return-1;var e=null!=r;if(b&&n.lastIndexOf===b)return e?n.lastIndexOf(t,r):n.lastIndexOf(t);for(var u=e?r:n.length;u--;)if(n[u]===t)return u;return-1},j.range=function(n,t,r){arguments.length<=1&&(t=n||0,n=0),r=arguments[2]||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=0,i=new Array(e);e>u;)i[u++]=n,n+=r;return i};var R=function(){};j.bind=function(n,t){var r,e;if(_&&n.bind===_)return _.apply(n,o.call(arguments,1));if(!j.isFunction(n))throw new TypeError;return r=o.call(arguments,2),e=function(){if(!(this instanceof e))return n.apply(t,r.concat(o.call(arguments)));R.prototype=n.prototype;var u=new R;R.prototype=null;var i=n.apply(u,r.concat(o.call(arguments)));return Object(i)===i?i:u}},j.partial=function(n){var t=o.call(arguments,1);return function(){for(var r=0,e=t.slice(),u=0,i=e.length;i>u;u++)e[u]===j&&(e[u]=arguments[r++]);for(;r<arguments.length;)e.push(arguments[r++]);return n.apply(this,e)}},j.bindAll=function(n){var t=o.call(arguments,1);if(0===t.length)throw new Error("bindAll must be passed function names");return A(t,function(t){n[t]=j.bind(n[t],n)}),n},j.memoize=function(n,t){var r={};return t||(t=j.identity),function(){var e=t.apply(this,arguments);return j.has(r,e)?r[e]:r[e]=n.apply(this,arguments)}},j.delay=function(n,t){var r=o.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},j.defer=function(n){return j.delay.apply(j,[n,1].concat(o.call(arguments,1)))},j.throttle=function(n,t,r){var e,u,i,a=null,o=0;r||(r={});var c=function(){o=r.leading===!1?0:j.now(),a=null,i=n.apply(e,u),e=u=null};return function(){var l=j.now();o||r.leading!==!1||(o=l);var f=t-(l-o);return e=this,u=arguments,0>=f?(clearTimeout(a),a=null,o=l,i=n.apply(e,u),e=u=null):a||r.trailing===!1||(a=setTimeout(c,f)),i}},j.debounce=function(n,t,r){var e,u,i,a,o,c=function(){var l=j.now()-a;t>l?e=setTimeout(c,t-l):(e=null,r||(o=n.apply(i,u),i=u=null))};return function(){i=this,u=arguments,a=j.now();var l=r&&!e;return e||(e=setTimeout(c,t)),l&&(o=n.apply(i,u),i=u=null),o}},j.once=function(n){var t,r=!1;return function(){return r?t:(r=!0,t=n.apply(this,arguments),n=null,t)}},j.wrap=function(n,t){return j.partial(t,n)},j.compose=function(){var n=arguments;return function(){for(var t=arguments,r=n.length-1;r>=0;r--)t=[n[r].apply(this,t)];return t[0]}},j.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},j.keys=function(n){if(!j.isObject(n))return[];if(w)return w(n);var t=[];for(var r in n)j.has(n,r)&&t.push(r);return t},j.values=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},j.pairs=function(n){for(var t=j.keys(n),r=t.length,e=new Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},j.invert=function(n){for(var t={},r=j.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},j.functions=j.methods=function(n){var t=[];for(var r in n)j.isFunction(n[r])&&t.push(r);return t.sort()},j.extend=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]=t[r]}),n},j.pick=function(n){var t={},r=c.apply(e,o.call(arguments,1));return A(r,function(r){r in n&&(t[r]=n[r])}),t},j.omit=function(n){var t={},r=c.apply(e,o.call(arguments,1));for(var u in n)j.contains(r,u)||(t[u]=n[u]);return t},j.defaults=function(n){return A(o.call(arguments,1),function(t){if(t)for(var r in t)n[r]===void 0&&(n[r]=t[r])}),n},j.clone=function(n){return j.isObject(n)?j.isArray(n)?n.slice():j.extend({},n):n},j.tap=function(n,t){return t(n),n};var S=function(n,t,r,e){if(n===t)return 0!==n||1/n==1/t;if(null==n||null==t)return n===t;n instanceof j&&(n=n._wrapped),t instanceof j&&(t=t._wrapped);var u=l.call(n);if(u!=l.call(t))return!1;switch(u){case"[object String]":return n==String(t);case"[object Number]":return n!=+n?t!=+t:0==n?1/n==1/t:n==+t;case"[object Date]":case"[object Boolean]":return+n==+t;case"[object RegExp]":return n.source==t.source&&n.global==t.global&&n.multiline==t.multiline&&n.ignoreCase==t.ignoreCase}if("object"!=typeof n||"object"!=typeof t)return!1;for(var i=r.length;i--;)if(r[i]==n)return e[i]==t;var a=n.constructor,o=t.constructor;if(a!==o&&!(j.isFunction(a)&&a instanceof a&&j.isFunction(o)&&o instanceof o)&&"constructor"in n&&"constructor"in t)return!1;r.push(n),e.push(t);var c=0,f=!0;if("[object Array]"==u){if(c=n.length,f=c==t.length)for(;c--&&(f=S(n[c],t[c],r,e)););}else{for(var s in n)if(j.has(n,s)&&(c++,!(f=j.has(t,s)&&S(n[s],t[s],r,e))))break;if(f){for(s in t)if(j.has(t,s)&&!c--)break;f=!c}}return r.pop(),e.pop(),f};j.isEqual=function(n,t){return S(n,t,[],[])},j.isEmpty=function(n){if(null==n)return!0;if(j.isArray(n)||j.isString(n))return 0===n.length;for(var t in n)if(j.has(n,t))return!1;return!0},j.isElement=function(n){return!(!n||1!==n.nodeType)},j.isArray=x||function(n){return"[object Array]"==l.call(n)},j.isObject=function(n){return n===Object(n)},A(["Arguments","Function","String","Number","Date","RegExp"],function(n){j["is"+n]=function(t){return l.call(t)=="[object "+n+"]"}}),j.isArguments(arguments)||(j.isArguments=function(n){return!(!n||!j.has(n,"callee"))}),"function"!=typeof/./&&(j.isFunction=function(n){return"function"==typeof n}),j.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},j.isNaN=function(n){return j.isNumber(n)&&n!=+n},j.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"==l.call(n)},j.isNull=function(n){return null===n},j.isUndefined=function(n){return n===void 0},j.has=function(n,t){return f.call(n,t)},j.noConflict=function(){return n._=t,this},j.identity=function(n){return n},j.constant=function(n){return function(){return n}},j.property=function(n){return function(t){return t[n]}},j.matches=function(n){return function(t){if(t===n)return!0;for(var r in n)if(n[r]!==t[r])return!1;return!0}},j.times=function(n,t,r){for(var e=Array(Math.max(0,n)),u=0;n>u;u++)e[u]=t.call(r,u);return e},j.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},j.now=Date.now||function(){return(new Date).getTime()};var T={escape:{"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;"}};T.unescape=j.invert(T.escape);var I={escape:new RegExp("["+j.keys(T.escape).join("")+"]","g"),unescape:new RegExp("("+j.keys(T.unescape).join("|")+")","g")};j.each(["escape","unescape"],function(n){j[n]=function(t){return null==t?"":(""+t).replace(I[n],function(t){return T[n][t]})}}),j.result=function(n,t){if(null==n)return void 0;var r=n[t];return j.isFunction(r)?r.call(n):r},j.mixin=function(n){A(j.functions(n),function(t){var r=j[t]=n[t];j.prototype[t]=function(){var n=[this._wrapped];return a.apply(n,arguments),z.call(this,r.apply(j,n))}})};var N=0;j.uniqueId=function(n){var t=++N+"";return n?n+t:t},j.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var q=/(.)^/,B={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\t|\u2028|\u2029/g;j.template=function(n,t,r){var e;r=j.defaults({},r,j.templateSettings);var u=new RegExp([(r.escape||q).source,(r.interpolate||q).source,(r.evaluate||q).source].join("|")+"|$","g"),i=0,a="__p+='";n.replace(u,function(t,r,e,u,o){return a+=n.slice(i,o).replace(D,function(n){return"\\"+B[n]}),r&&(a+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'"),e&&(a+="'+\n((__t=("+e+"))==null?'':__t)+\n'"),u&&(a+="';\n"+u+"\n__p+='"),i=o+t.length,t}),a+="';\n",r.variable||(a="with(obj||{}){\n"+a+"}\n"),a="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+a+"return __p;\n";try{e=new Function(r.variable||"obj","_",a)}catch(o){throw o.source=a,o}if(t)return e(t,j);var c=function(n){return e.call(this,n,j)};return c.source="function("+(r.variable||"obj")+"){\n"+a+"}",c},j.chain=function(n){return j(n).chain()};var z=function(n){return this._chain?j(n).chain():n};j.mixin(j),A(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=e[n];j.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!=n&&"splice"!=n||0!==r.length||delete r[0],z.call(this,r)}}),A(["concat","join","slice"],function(n){var t=e[n];j.prototype[n]=function(){return z.call(this,t.apply(this._wrapped,arguments))}}),j.extend(j.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}}),"function"==typeof define&&define.amd&&define("underscore",[],function(){return j})}).call(this);
//# sourceMappingURL=underscore-min.map

// Underscore.string
!function(e,t){"use strict";var n=t.prototype.trim,r=t.prototype.trimRight,i=t.prototype.trimLeft,s=function(e){return e*1||0},o=function(e,t){if(t<1)return"";var n="";while(t>0)t&1&&(n+=e),t>>=1,e+=e;return n},u=[].slice,a=function(e){return e==null?"\\s":e.source?e.source:"["+p.escapeRegExp(e)+"]"},f={lt:"<",gt:">",quot:'"',apos:"'",amp:"&"},l={};for(var c in f)l[f[c]]=c;var h=function(){function e(e){return Object.prototype.toString.call(e).slice(8,-1).toLowerCase()}var n=o,r=function(){return r.cache.hasOwnProperty(arguments[0])||(r.cache[arguments[0]]=r.parse(arguments[0])),r.format.call(null,r.cache[arguments[0]],arguments)};return r.format=function(r,i){var s=1,o=r.length,u="",a,f=[],l,c,p,d,v,m;for(l=0;l<o;l++){u=e(r[l]);if(u==="string")f.push(r[l]);else if(u==="array"){p=r[l];if(p[2]){a=i[s];for(c=0;c<p[2].length;c++){if(!a.hasOwnProperty(p[2][c]))throw new Error(h('[_.sprintf] property "%s" does not exist',p[2][c]));a=a[p[2][c]]}}else p[1]?a=i[p[1]]:a=i[s++];if(/[^s]/.test(p[8])&&e(a)!="number")throw new Error(h("[_.sprintf] expecting number but found %s",e(a)));switch(p[8]){case"b":a=a.toString(2);break;case"c":a=t.fromCharCode(a);break;case"d":a=parseInt(a,10);break;case"e":a=p[7]?a.toExponential(p[7]):a.toExponential();break;case"f":a=p[7]?parseFloat(a).toFixed(p[7]):parseFloat(a);break;case"o":a=a.toString(8);break;case"s":a=(a=t(a))&&p[7]?a.substring(0,p[7]):a;break;case"u":a=Math.abs(a);break;case"x":a=a.toString(16);break;case"X":a=a.toString(16).toUpperCase()}a=/[def]/.test(p[8])&&p[3]&&a>=0?"+"+a:a,v=p[4]?p[4]=="0"?"0":p[4].charAt(1):" ",m=p[6]-t(a).length,d=p[6]?n(v,m):"",f.push(p[5]?a+d:d+a)}}return f.join("")},r.cache={},r.parse=function(e){var t=e,n=[],r=[],i=0;while(t){if((n=/^[^\x25]+/.exec(t))!==null)r.push(n[0]);else if((n=/^\x25{2}/.exec(t))!==null)r.push("%");else{if((n=/^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(t))===null)throw new Error("[_.sprintf] huh?");if(n[2]){i|=1;var s=[],o=n[2],u=[];if((u=/^([a-z_][a-z_\d]*)/i.exec(o))===null)throw new Error("[_.sprintf] huh?");s.push(u[1]);while((o=o.substring(u[0].length))!=="")if((u=/^\.([a-z_][a-z_\d]*)/i.exec(o))!==null)s.push(u[1]);else{if((u=/^\[(\d+)\]/.exec(o))===null)throw new Error("[_.sprintf] huh?");s.push(u[1])}n[2]=s}else i|=2;if(i===3)throw new Error("[_.sprintf] mixing positional and named placeholders is not (yet) supported");r.push(n)}t=t.substring(n[0].length)}return r},r}(),p={VERSION:"2.3.0",isBlank:function(e){return e==null&&(e=""),/^\s*$/.test(e)},stripTags:function(e){return e==null?"":t(e).replace(/<\/?[^>]+>/g,"")},capitalize:function(e){return e=e==null?"":t(e),e.charAt(0).toUpperCase()+e.slice(1)},chop:function(e,n){return e==null?[]:(e=t(e),n=~~n,n>0?e.match(new RegExp(".{1,"+n+"}","g")):[e])},clean:function(e){return p.strip(e).replace(/\s+/g," ")},count:function(e,n){return e==null||n==null?0:t(e).split(n).length-1},chars:function(e){return e==null?[]:t(e).split("")},swapCase:function(e){return e==null?"":t(e).replace(/\S/g,function(e){return e===e.toUpperCase()?e.toLowerCase():e.toUpperCase()})},escapeHTML:function(e){return e==null?"":t(e).replace(/[&<>"']/g,function(e){return"&"+l[e]+";"})},unescapeHTML:function(e){return e==null?"":t(e).replace(/\&([^;]+);/g,function(e,n){var r;return n in f?f[n]:(r=n.match(/^#x([\da-fA-F]+)$/))?t.fromCharCode(parseInt(r[1],16)):(r=n.match(/^#(\d+)$/))?t.fromCharCode(~~r[1]):e})},escapeRegExp:function(e){return e==null?"":t(e).replace(/([.*+?^=!:${}()|[\]\/\\])/g,"\\$1")},splice:function(e,t,n,r){var i=p.chars(e);return i.splice(~~t,~~n,r),i.join("")},insert:function(e,t,n){return p.splice(e,t,0,n)},include:function(e,n){return n===""?!0:e==null?!1:t(e).indexOf(n)!==-1},join:function(){var e=u.call(arguments),t=e.shift();return t==null&&(t=""),e.join(t)},lines:function(e){return e==null?[]:t(e).split("\n")},reverse:function(e){return p.chars(e).reverse().join("")},startsWith:function(e,n){return n===""?!0:e==null||n==null?!1:(e=t(e),n=t(n),e.length>=n.length&&e.slice(0,n.length)===n)},endsWith:function(e,n){return n===""?!0:e==null||n==null?!1:(e=t(e),n=t(n),e.length>=n.length&&e.slice(e.length-n.length)===n)},succ:function(e){return e==null?"":(e=t(e),e.slice(0,-1)+t.fromCharCode(e.charCodeAt(e.length-1)+1))},titleize:function(e){return e==null?"":t(e).replace(/(?:^|\s)\S/g,function(e){return e.toUpperCase()})},camelize:function(e){return p.trim(e).replace(/[-_\s]+(.)?/g,function(e,t){return t.toUpperCase()})},underscored:function(e){return p.trim(e).replace(/([a-z\d])([A-Z]+)/g,"$1_$2").replace(/[-\s]+/g,"_").toLowerCase()},dasherize:function(e){return p.trim(e).replace(/([A-Z])/g,"-$1").replace(/[-_\s]+/g,"-").toLowerCase()},classify:function(e){return p.titleize(t(e).replace(/_/g," ")).replace(/\s/g,"")},humanize:function(e){return p.capitalize(p.underscored(e).replace(/_id$/,"").replace(/_/g," "))},trim:function(e,r){return e==null?"":!r&&n?n.call(e):(r=a(r),t(e).replace(new RegExp("^"+r+"+|"+r+"+$","g"),""))},ltrim:function(e,n){return e==null?"":!n&&i?i.call(e):(n=a(n),t(e).replace(new RegExp("^"+n+"+"),""))},rtrim:function(e,n){return e==null?"":!n&&r?r.call(e):(n=a(n),t(e).replace(new RegExp(n+"+$"),""))},truncate:function(e,n,r){return e==null?"":(e=t(e),r=r||"...",n=~~n,e.length>n?e.slice(0,n)+r:e)},prune:function(e,n,r){if(e==null)return"";e=t(e),n=~~n,r=r!=null?t(r):"...";if(e.length<=n)return e;var i=function(e){return e.toUpperCase()!==e.toLowerCase()?"A":" "},s=e.slice(0,n+1).replace(/.(?=\W*\w*$)/g,i);return s.slice(s.length-2).match(/\w\w/)?s=s.replace(/\s*\S+$/,""):s=p.rtrim(s.slice(0,s.length-1)),(s+r).length>e.length?e:e.slice(0,s.length)+r},words:function(e,t){return p.isBlank(e)?[]:p.trim(e,t).split(t||/\s+/)},pad:function(e,n,r,i){e=e==null?"":t(e),n=~~n;var s=0;r?r.length>1&&(r=r.charAt(0)):r=" ";switch(i){case"right":return s=n-e.length,e+o(r,s);case"both":return s=n-e.length,o(r,Math.ceil(s/2))+e+o(r,Math.floor(s/2));default:return s=n-e.length,o(r,s)+e}},lpad:function(e,t,n){return p.pad(e,t,n)},rpad:function(e,t,n){return p.pad(e,t,n,"right")},lrpad:function(e,t,n){return p.pad(e,t,n,"both")},sprintf:h,vsprintf:function(e,t){return t.unshift(e),h.apply(null,t)},toNumber:function(e,n){if(e==null||e=="")return 0;e=t(e);var r=s(s(e).toFixed(~~n));return r===0&&!e.match(/^0+$/)?Number.NaN:r},numberFormat:function(e,t,n,r){if(isNaN(e)||e==null)return"";e=e.toFixed(~~t),r=r||",";var i=e.split("."),s=i[0],o=i[1]?(n||".")+i[1]:"";return s.replace(/(\d)(?=(?:\d{3})+$)/g,"$1"+r)+o},strRight:function(e,n){if(e==null)return"";e=t(e),n=n!=null?t(n):n;var r=n?e.indexOf(n):-1;return~r?e.slice(r+n.length,e.length):e},strRightBack:function(e,n){if(e==null)return"";e=t(e),n=n!=null?t(n):n;var r=n?e.lastIndexOf(n):-1;return~r?e.slice(r+n.length,e.length):e},strLeft:function(e,n){if(e==null)return"";e=t(e),n=n!=null?t(n):n;var r=n?e.indexOf(n):-1;return~r?e.slice(0,r):e},strLeftBack:function(e,t){if(e==null)return"";e+="",t=t!=null?""+t:t;var n=e.lastIndexOf(t);return~n?e.slice(0,n):e},toSentence:function(e,t,n,r){t=t||", ",n=n||" and ";var i=e.slice(),s=i.pop();return e.length>2&&r&&(n=p.rtrim(t)+n),i.length?i.join(t)+n+s:s},toSentenceSerial:function(){var e=u.call(arguments);return e[3]=!0,p.toSentence.apply(p,e)},slugify:function(e){if(e==null)return"";var n="ąàáäâãåæćęèéëêìíïîłńòóöôõøùúüûñçżź",r="aaaaaaaaceeeeeiiiilnoooooouuuunczz",i=new RegExp(a(n),"g");return e=t(e).toLowerCase().replace(i,function(e){var t=n.indexOf(e);return r.charAt(t)||"-"}),p.dasherize(e.replace(/[^\w\s-]/g,""))},surround:function(e,t){return[t,e,t].join("")},quote:function(e){return p.surround(e,'"')},exports:function(){var e={};for(var t in this){if(!this.hasOwnProperty(t)||t.match(/^(?:include|contains|reverse)$/))continue;e[t]=this[t]}return e},repeat:function(e,n,r){if(e==null)return"";n=~~n;if(r==null)return o(t(e),n);for(var i=[];n>0;i[--n]=e);return i.join(r)},levenshtein:function(e,n){if(e==null&&n==null)return 0;if(e==null)return t(n).length;if(n==null)return t(e).length;e=t(e),n=t(n);var r=[],i,s;for(var o=0;o<=n.length;o++)for(var u=0;u<=e.length;u++)o&&u?e.charAt(u-1)===n.charAt(o-1)?s=i:s=Math.min(r[u],r[u-1],i)+1:s=o+u,i=r[u],r[u]=s;return r.pop()}};p.strip=p.trim,p.lstrip=p.ltrim,p.rstrip=p.rtrim,p.center=p.lrpad,p.rjust=p.lpad,p.ljust=p.rpad,p.contains=p.include,p.q=p.quote,typeof exports!="undefined"?(typeof module!="undefined"&&module.exports&&(module.exports=p),exports._s=p):typeof define=="function"&&define.amd?define("underscore.string",[],function(){return p}):(e._=e._||{},e._.string=e._.str=p)}(this,String);

 

4. Lances atrelados ao mercado financeiro – por Russell Savage. O script utiliza o API da Yahoo! Finanças para determinar o valor dos lances de suas contas. Determine uma planilha para inserir os dados do mercado em tempo real e ajuste os lances de sua conta de acordo com os dados.

 

/******************************************
* Yahoo Finance API Class Example
* Version 1.0
* Created By: Russ Savage
* FreeAdWordsScripts.com
******************************************/
function main() {
var sheetUrl = 'ENTER A GOOGLE SHEET URL HERE';

var yfa = new YahooFinanceAPI({
symbols: ['^GSPC','VTI','^IXIC','BTCUSD=X'],
f: 'snl1' // or something longer like this 'sl1abb2b3d1t1c1ohgv'
});
for(var key in yfa.results) {
Logger.log(Utilities.formatString('Name: "%s", Symbol: "%s", Last Trade Price: $%s',
yfa.results[key].name,
key,
yfa.results[key].last_trade_price_only));
}

var includeColumnHeaders = true;
var sheetData = yfa.toGoogleSheet(includeColumnHeaders);
var ss = SpreadsheetApp.openByUrl(sheetUrl).getActiveSheet();
for(var i in sheetData) {
ss.appendRow(sheetData[i]);
}
}

 

Basta copiar o código a seguir na parte inferior do seu script do Google AdWords e estará pronto.

 

/******************************************
* Yahoo Finance API Class
* Use this to pull stock market quotes from Yahoo Finance
* Version 1.0
* Created By: Russ Savage
* FreeAdWordsScripts.com
******************************************/
function YahooFinanceAPI(configVars) {
var QUERY_URL_BASE = '//query.yahooapis.com/v1/public/yql';
var FINANCE_URL_BASE = '//download.finance.yahoo.com/d/quotes.csv';
this.configVars = configVars;

/*************
* The results are stored here in a
* map where the key is the ticker symbol
* { 'AAPL' : { ... }, 'GOOG' : { ... }
*************/
this.results = {};

/************
* Function used to refresh the results
* from Yahoo! Finance API. Called automatically
* during object reaction.
************/
this.refresh = function() {
var queryUrl = getQueryUrl(this.configVars);
var resp = UrlFetchApp.fetch(queryUrl,{muteHttpExceptions:true});
if(resp.getResponseCode() == 200) {
var jsonResp = JSON.parse(resp.getContentText());
if(jsonResp.query.count == 1) {
var row = jsonResp.query.results.row;
this.results[row.symbol] = row;
} else if(jsonResp.query.count > 1) {
for(var i in jsonResp.query.results.row) {
var row = jsonResp.query.results.row[i];
this.results[row.symbol] = row;
}
}
} else {
throw resp.getContentText();
}
}

/************
* Translates the results into a 2d array
* to make it easier to add into a Google Sheet.
* includeColumnHeaders - true or false if you want
* headers returned in the results.
************/
this.toGoogleSheet = function(includeColumnHeaders) {
if(!this.results) { return [[]]; }
var retVal = [];
var headers = null;
for(var key in this.results) {
if(!headers) {
headers = Object.keys(this.results[key]).sort();
}
var row = [];
for(var i in headers) {
row.push(this.results[key][headers[i]]);
}
retVal.push(row);
}
if(includeColumnHeaders) {
return [headers].concat(retVal);
} else {
return retVal;
}
}

// Perform a refresh on object creation.
this.refresh();

// Private functions

/************
* Builds Yahoo Finance Url
************/
function getFinanceUrl(configVars) {
var financeUrlParams = {
s : configVars.symbols.join(','),
f : configVars.f,
e : '.json'
}
return FINANCE_URL_BASE + serialize(financeUrlParams);
}

/************
* Builds Yahoo Query Url
************/
function getQueryUrl(configVars) {
var financeUrl = getFinanceUrl(configVars);
var cols = fToCols(configVars.f);
var queryTemplate = "select * from csv where url='%s' and columns='%s'";
var query = Utilities.formatString(queryTemplate, financeUrl,cols.join(','));
var params = {
q : query,
format : 'json'
}
var finalRestUrl = QUERY_URL_BASE + serialize(params);
return finalRestUrl;
}

/************
* This function translates the f parameter
* into actual field names to use for columns
************/
function fToCols(f) {
var cols = [];
var chunk = '';
var fBits = f.split('').reverse();
for(var i in fBits) {
chunk = (fBits[i] + chunk);
if(fLookup(chunk)) {
cols.push(fLookup(chunk));
chunk = '';
}
}
return cols.reverse();
}

/************
* Copied from: //stackoverflow.com/a/18116302
* This function converts a hash into
* a url encoded query string.
************/
function serialize( obj ) {
return '?'+
Object.keys(obj).reduce(
function(a,k) {
a.push(k+'='+encodeURIComponent(obj[k]));
return a
},
[]).join('&');
}

/************
* Adapted from //www.jarloo.com/yahoo_finance/
* This function maps f codes into
* friendly column names.
************/
function fLookup(f){
return{
a:'ask',b:'bid',b2:'ask realtime',b3:'bid realtime',p:'previous close',o:'open',
y:'dividend yield',d:'dividend per share',r1:'dividend pay date',
q:'ex-dividend date',c1:'change',c:'change & percent change',c6:'change realtime',
k2:'change percent realtime',p2:'change in percent',d1:'last trade date',
d2:'trade date',t1:'last trade time',c8:'after hours change realtime',
c3:'commission',g:'days low',h:'days high',k1:'last trade realtime with time',
l:'last trade with time',l1:'last trade price only',t8:'1 yr target price',
m5:'change from 200 day moving average',m6:'percent change from 200 day moving average',
m7:'change from 50 day moving average',m8:'percent change from 50 day moving average',
m3:'50 day moving average',m4:'200 day moving average',w1:'days value change',
w4:'days value change realtime',p1:'price paid',m:'days range',m2:'days range realtime',
g1:'holdings gain percent',g3:'annualized gain',g4:'holdings gain',
g5:'holdings gain percent realtime',g6:'holdings gain realtime',t7:'ticker trend',
t6:'trade links',i5:'order book realtime',l2:'high limit',l3:'low limit',
v1:'holdings value',v7:'holdings value realtime',s6: 'revenue',k:'52 week high',
j:'52 week low',j5:'change from 52 week low',k4:'change from 52 week high',
j6:'percent change from 52 week low',k5:'percent change from 52 week high',
w:'52 week range',v:'more info',j1:'market capitalization',j3:'market cap realtime',
f6:'float shares',n:'name',n4:'notes',s:'symbol',s1:'shares owned',x:'stock exchange',
j2:'shares outstanding',v:'volume',a5:'ask size',b6:'bid size',k3:'last trade size',
a2:'average daily volume',e:'earnings per share',e7:'eps estimate current year',
e8:'eps estimate next year',e9:'eps estimate next quarter',b4:'book value',j4:'ebitda',
p5:'price sales',p6:'price book',r:'pe ratio',r2:'pe ratio realtime',r5:'peg ratio',
r6:'price eps estimate current year',r7:'price eps estimate next year',s7:'short ratio'
}[f];
}
}

 

5. Script para receber dados do OAuth – por Russel Savage. O OAuth é um serviço de autenticação necessário para acessar os APIs de algumas empresa, como o Twitter por exemplo. Esse script permite que você faça o login e acesse o API para retirar os dados de uma URL.

 

/******************************************
* Yahoo Finance API Class Example
* Version 1.0
* Created By: Russ Savage
* FreeAdWordsScripts.com
******************************************/
function main() {
var sheetUrl = 'ENTER A GOOGLE SHEET URL HERE';

var yfa = new YahooFinanceAPI({
symbols: ['^GSPC','VTI','^IXIC','BTCUSD=X'],
f: 'snl1' // or something longer like this 'sl1abb2b3d1t1c1ohgv'
});
for(var key in yfa.results) {
Logger.log(Utilities.formatString('Name: "%s", Symbol: "%s", Last Trade Price: $%s',
yfa.results[key].name,
key,
yfa.results[key].last_trade_price_only));
}

var includeColumnHeaders = true;
var sheetData = yfa.toGoogleSheet(includeColumnHeaders);
var ss = SpreadsheetApp.openByUrl(sheetUrl).getActiveSheet();
for(var i in sheetData) {
ss.appendRow(sheetData[i]);
}
}

 

Basta copiar o código a seguir na parte inferior do seu script do Google AdWords e estará pronto.

 

/******************************************
* Yahoo Finance API Class
* Use this to pull stock market quotes from Yahoo Finance
* Version 1.0
* Created By: Russ Savage
* FreeAdWordsScripts.com
******************************************/
function YahooFinanceAPI(configVars) {
var QUERY_URL_BASE = '//query.yahooapis.com/v1/public/yql';
var FINANCE_URL_BASE = '//download.finance.yahoo.com/d/quotes.csv';
this.configVars = configVars;

/*************
* The results are stored here in a
* map where the key is the ticker symbol
* { 'AAPL' : { ... }, 'GOOG' : { ... }
*************/
this.results = {};

/************
* Function used to refresh the results
* from Yahoo! Finance API. Called automatically
* during object reaction.
************/
this.refresh = function() {
var queryUrl = getQueryUrl(this.configVars);
var resp = UrlFetchApp.fetch(queryUrl,{muteHttpExceptions:true});
if(resp.getResponseCode() == 200) {
var jsonResp = JSON.parse(resp.getContentText());
if(jsonResp.query.count == 1) {
var row = jsonResp.query.results.row;
this.results[row.symbol] = row;
} else if(jsonResp.query.count > 1) {
for(var i in jsonResp.query.results.row) {
var row = jsonResp.query.results.row[i];
this.results[row.symbol] = row;
}
}
} else {
throw resp.getContentText();
}
}

/************
* Translates the results into a 2d array
* to make it easier to add into a Google Sheet.
* includeColumnHeaders - true or false if you want
* headers returned in the results.
************/
this.toGoogleSheet = function(includeColumnHeaders) {
if(!this.results) { return [[]]; }
var retVal = [];
var headers = null;
for(var key in this.results) {
if(!headers) {
headers = Object.keys(this.results[key]).sort();
}
var row = [];
for(var i in headers) {
row.push(this.results[key][headers[i]]);
}
retVal.push(row);
}
if(includeColumnHeaders) {
return [headers].concat(retVal);
} else {
return retVal;
}
}

// Perform a refresh on object creation.
this.refresh();

// Private functions

/************
* Builds Yahoo Finance Url
************/
function getFinanceUrl(configVars) {
var financeUrlParams = {
s : configVars.symbols.join(','),
f : configVars.f,
e : '.json'
}
return FINANCE_URL_BASE + serialize(financeUrlParams);
}

/************
* Builds Yahoo Query Url
************/
function getQueryUrl(configVars) {
var financeUrl = getFinanceUrl(configVars);
var cols = fToCols(configVars.f);
var queryTemplate = "select * from csv where url='%s' and columns='%s'";
var query = Utilities.formatString(queryTemplate, financeUrl,cols.join(','));
var params = {
q : query,
format : 'json'
}
var finalRestUrl = QUERY_URL_BASE + serialize(params);
return finalRestUrl;
}

/************
* This function translates the f parameter
* into actual field names to use for columns
************/
function fToCols(f) {
var cols = [];
var chunk = '';
var fBits = f.split('').reverse();
for(var i in fBits) {
chunk = (fBits[i] + chunk);
if(fLookup(chunk)) {
cols.push(fLookup(chunk));
chunk = '';
}
}
return cols.reverse();
}

/************
* Copied from: //stackoverflow.com/a/18116302
* This function converts a hash into
* a url encoded query string.
************/
function serialize( obj ) {
return '?'+
Object.keys(obj).reduce(
function(a,k) {
a.push(k+'='+encodeURIComponent(obj[k]));
return a
},
[]).join('&');
}

/************
* Adapted from //www.jarloo.com/yahoo_finance/
* This function maps f codes into
* friendly column names.
************/
function fLookup(f){
return{
a:'ask',b:'bid',b2:'ask realtime',b3:'bid realtime',p:'previous close',o:'open',
y:'dividend yield',d:'dividend per share',r1:'dividend pay date',
q:'ex-dividend date',c1:'change',c:'change & percent change',c6:'change realtime',
k2:'change percent realtime',p2:'change in percent',d1:'last trade date',
d2:'trade date',t1:'last trade time',c8:'after hours change realtime',
c3:'commission',g:'days low',h:'days high',k1:'last trade realtime with time',
l:'last trade with time',l1:'last trade price only',t8:'1 yr target price',
m5:'change from 200 day moving average',m6:'percent change from 200 day moving average',
m7:'change from 50 day moving average',m8:'percent change from 50 day moving average',
m3:'50 day moving average',m4:'200 day moving average',w1:'days value change',
w4:'days value change realtime',p1:'price paid',m:'days range',m2:'days range realtime',
g1:'holdings gain percent',g3:'annualized gain',g4:'holdings gain',
g5:'holdings gain percent realtime',g6:'holdings gain realtime',t7:'ticker trend',
t6:'trade links',i5:'order book realtime',l2:'high limit',l3:'low limit',
v1:'holdings value',v7:'holdings value realtime',s6: 'revenue',k:'52 week high',
j:'52 week low',j5:'change from 52 week low',k4:'change from 52 week high',
j6:'percent change from 52 week low',k5:'percent change from 52 week high',
w:'52 week range',v:'more info',j1:'market capitalization',j3:'market cap realtime',
f6:'float shares',n:'name',n4:'notes',s:'symbol',s1:'shares owned',x:'stock exchange',
j2:'shares outstanding',v:'volume',a5:'ask size',b6:'bid size',k3:'last trade size',
a2:'average daily volume',e:'earnings per share',e7:'eps estimate current year',
e8:'eps estimate next year',e9:'eps estimate next quarter',b4:'book value',j4:'ebitda',
p5:'price sales',p6:'price book',r:'pe ratio',r2:'pe ratio realtime',r5:'peg ratio',
r6:'price eps estimate current year',r7:'price eps estimate next year',s7:'short ratio'
}[f];
}
}

 

 

6. Acrescente estatísticas de crime em seus criativos – por Russel Savage. Utilizar dados aumentam a credibilidade de seus anúncios. Sendo assim, esse script trabalha com dados APIs que alguns sites governamentais disponibilizam e atualiza os parâmetros das palavras-chave de anúncios com o número de tipos de crimes diferentes.

 

//-----------------------------------
// Put Chicago Crime Stats in Your Creatives
// Created By: Russ Savage
// FreeAdWordsScripts.com
//-----------------------------------
function main() {
// You can get this link by going here: //goo.gl/tfNgM
// Apply some filters and then click export > api
// This end point is good for all of 2013
var DATA_ENDPOINT = "//data.cityofchicago.org/resource/pga9-zdiw.json";
var CAMPAIGN_PREFIX = 'Crime_Data_Chicago_'; //All your campaigns start with this
var AD_PARAM = 1; // 1 or 2

try {
var json = Utilities.jsonParse(UrlFetchApp.fetch(DATA_ENDPOINT).getContentText());
var summary = summarizeCrimeStats(json);
//logCrimeSummary(summary);

for(var i in summary) {
var total = totalPrimaryDescription(i,summary);
var kw_iter = AdWordsApp.keywords()
.withCondition("CampaignName CONTAINS_IGNORE_CASE '"+CAMPAIGN_PREFIX+i+"'")
.get();
while(kw_iter.hasNext()) {
var kw = kw_iter.next();
kw.setAdParam(AD_PARAM, total);
}
}
}catch(e) {
}
}

// A helper function to aggregate the data by primary description
function totalPrimaryDescription(key,summary) {
var tot = 0;
for(var i in summary[key]) {
tot += summary[key][i];
}
return tot;
}

//This function takes in a json formatted object and stores the count of instances
//in a 2 dimentional hash of [Primary Description][Secondary Description]
function summarizeCrimeStats(json) {
var crime_summary = {};
for(var i in json) {
var crime = json[i];
if(crime_summary[crime._primary_decsription]) {
if(crime_summary[crime._primary_decsription][crime._secondary_description]) {
crime_summary[crime._primary_decsription][crime._secondary_description]++;
}else{
crime_summary[crime._primary_decsription][crime._secondary_description] = 1;
}
}else{
crime_summary[crime._primary_decsription] = {};
crime_summary[crime._primary_decsription][crime._secondary_description] = 1;
}
}
return crime_summary;
}

//Just a helper function to print out the summary info so that
//I can find the data I'm interested in.
function logCrimeSummary(crime_summary) {
for(var i in crime_summary) {
for(var x in crime_summary[i]) {
Logger.log([i,x,crime_summary[i][x]].join(', '));
}
}
}

 

7. Connecte o Zoho CRM com o Google Ads – por Russell Savage. Utilize os dados do Google Ads (clique, impressões…) e da sua plataforma de CRM (Leads, contatos, oportunidadees…) para gerar um relatório integrado. Utilizando-se das duas fontes de dados é possível ter uma visão mais apurada de suas campanhas.

 

/******************************************
* Zoho CRM Get API Class
* Use it to pull data out of Zoho CRM
* Version 1.0
* Created By: Russ Savage
* FreeAdWordsScripts.com
******************************************/
//For more info about the Zoho CRM API, see here:
// //www.zoho.com/crm/help/api/
function ZohoApi(authToken) {
var ZOHO_AUTH_TOKEN = authToken;
var ZOHO_BASE_URL = '//crm.zoho.com/crm/private/json/';
var METHODS = ['getMyRecords','getRecords','getRecordById','getCVRecords',
'getSearchRecords','getSearchRecordsByPDC','getRelatedRecords',
'getFields','getUsers','downloadFile','downloadPhoto'];
var OBJECTS = ['Leads','Accounts','Contacts','Potentials',
'Campaigns','Cases','Soultions','Products',
'PriceBooks','Quotes','Invoices','SalesOrders',
'Vendors','PurchaseOrders','Events','Tasks','Calls'];

for(var i in OBJECTS) {
// Creating getPotentials() functions
this['get'+OBJECTS[i]] = new Function('additionalParams',
'return this.get("'+OBJECTS[i]+'","getRecords",additionalParams);');
// Creating getMyPotentials() functions
this['getMy'+OBJECTS[i]] = new Function('additionalParams',
'return this.get("'+OBJECTS[i]+'","getMyRecords",additionalParams);');
// Creating getPotentialsById(id) functions
this['get'+OBJECTS[i]+'ById'] = new Function('id',
'return this.get("'+OBJECTS[i]+'","getRecordById",{ id : id });');
// Creating searchPotentials(searchCondition) functions
this['search'+OBJECTS[i]] = new Function('criteria',
'return this.get("'+OBJECTS[i]+'","getSearchRecords",criteria);');
// Creating getPotentialsFields() functions
this['get'+OBJECTS[i]+'Fields'] = new Function('return this.get("'+OBJECTS[i]+'","getFields",{});');
}

// You can use any Zoho OBJECT and METHOD and
// put any additional parameters as a map {param : val, param2: val2}
this.get = function(zohoObj,zohoMethod,additionalParams) {
validateParams(zohoObj,zohoMethod);
additionalParams = addColumnsIfNeeded(this,zohoObj,zohoMethod,additionalParams);
var url = buildUrl(zohoObj,zohoMethod,additionalParams);
Logger.log(url);
var resp = UrlFetchApp.fetch(url).getContentText();
try {
var jsonObj = JSON.parse(resp);
if(jsonObj['response'] && jsonObj['response']['nodata']) {
Logger.log('Code: '+jsonObj['response']['nodata']['code']+
' Message: '+ERRORS[jsonObj['response']['nodata']['code']]);
return [];
}
if(jsonObj['response'] && jsonObj['response']['error']) {
throw 'Code: '+jsonObj['response']['error']['code']+
' Message: '+jsonObj['response']['error']['message'];
}

if(jsonObj['response'] && jsonObj['response']['result']) {
return parseResponseObject(zohoObj,jsonObj);
}
if(jsonObj[zohoObj] && jsonObj[zohoObj]['section']) {
return parseFieldsObject(zohoObj,jsonObj);
}
return jsonObj;
}catch(e){
throw 'There was an issue parsing the response. '+e;
}
};

function parseResponseObject(zohoObj,jsonObj) {
if(jsonObj['response'] && jsonObj['response']['result']) {
var rows = jsonObj['response']['result'][zohoObj]['row'];
if(typeof rows[0] === 'undefined') {
return [mapValToContent(rows)];
} else {
var retVal = [];
for(var i in rows) {
retVal.push(mapValToContent(rows[i]));
}
return retVal;
}
}
return [];
}

function parseFieldsObject(zohoObj,jsonObj) {
if(jsonObj[zohoObj] && jsonObj[zohoObj]['section']) {
var fields = [];
for(var i in jsonObj[zohoObj]['section']) {
var elem = jsonObj[zohoObj]['section'][i];
if(elem['FL'] && elem['FL'][0]) {
for(var x in elem['FL']) {
var field = elem['FL'][x];
if(field['dv']) {
fields.push(field['dv']);
}
}
} else if(elem['FL'] && elem['FL']['dv']) {
fields.push(elem['FL']['dv']);
}
}
return fields;
}
return [];
}

function validateParams(zohoObj,zohoMethod) {
if(!zohoObj || OBJECTS.indexOf(zohoObj) == -1) {
throw 'Get must be called with a proper ZOHO object. Object given: "'+
zohoObj+'" Available Objects:'+OBJECTS.join(',');
}
if(!zohoMethod || METHODS.indexOf(zohoMethod) == -1) {
throw 'Get must be called with a proper ZOHO method. Method given: "'+
zohoObj+'" Available Methods:'+METHODS.join(',');
}
}

function addColumnsIfNeeded(self,zohoObj,zohoMethod,additionalParams) {
var searchConditionRequired = ['getSearchRecords','getSearchRecordsByPDC'];
if(searchConditionRequired.indexOf(zohoMethod) >= 0) {
if(!additionalParams['selectColumns']) {
additionalParams['selectColumns'] = zohoObj+'('+self['get'+zohoObj+'Fields']().join(',')+')';
}
}
return additionalParams;
}

function buildUrl(zohoObj,zohoMethod,additionalParams) {
var url = ZOHO_BASE_URL+zohoObj+'/'+zohoMethod+
'?authtoken='+ZOHO_AUTH_TOKEN+'&scope=crmapi&newFormat=1';
for(var key in additionalParams) {
url += '&' + key + '=' + encodeURIComponent(additionalParams[key]);
}
return url;
}

function mapValToContent(obj) {
var retVal = {};
if(obj.FL) {
for(var i in obj.FL) {
var elem = obj.FL[i];
var key = elem.val;
var cleanKey = key.toLowerCase().replace(/ /g,'_');
retVal[cleanKey] = elem.content;
}
}
return retVal;
}

var ERRORS = {'4000':'Please use Authtoken, instead of API ticket and APIkey.',
'4500':'Internal server error while processing this request',
'4501':'API Key is inactive',
'4502':'This module is not supported in your edition',
'4401':'Mandatory field missing',
'4600':'Incorrect API parameter or API parameter value. Also check the method '+
'name and/or spelling errors in the API url.',
'4820':'API call cannot be completed as you have exceeded the "rate limit".',
'4831':'Missing parameters error',
'4832':'Text value given for an Integer field',
'4834':'Invalid ticket. Also check if ticket has expired.',
'4835':'XML parsing error',
'4890':'Wrong API Key',
'4487':'No permission to convert lead.',
'4001':'No API permission',
'401':'No module permission',
'401.1':'No permission to create a record',
'401.2':'No permission to edit a record',
'401.3':'No permission to delete a record',
'4101':'Zoho CRM disabled',
'4102':'No CRM account',
'4103':'No record available with the specified record ID.',
'4422':'No records available in the module',
'4420':'Wrong value for search parameter and/or search parameter value.',
'4421':'Number of API calls exceeded',
'4423':'Exceeded record search limit',
'4807':'Exceeded file size limit',
'4424':'Invalid File Type',
'4809':'Exceeded storage space limit'};

}

 

E aqui está um exemplo muito simples de como você pode combinar dados de conversão de várias origens em um único relatório da planilha do Google.

 

/ ** ****************************************
* Combine os dados de conversão do Google AdWords e do Zoho CRM
* Versão 1.0
* Criado por: Russ Savage
* FreeAdWordsScripts.com
***************************************** * /
var ZOHO_AUTH_TOKEN = 'YOUR ZOHO AUTH TOKEN';
var SPREADSHEET_URL = 'THE URL OF AN EMPTY SPREADSHEET';

function main() {
var acctStats = AdWordsApp.currentAccount().getStatsFor('YESTERDAY');
var adWordsImps = parseFloat(acctStats.getImpressions());
var adWordsClicks = parseFloat(acctStats.getClicks());
var adWordsCtr = parseFloat(acctStats.getCtr());
var adWordsConv = parseFloat(acctStats.getConversions());
var crmConv = getCRMConversions().count;
var totalConv = (crmConv + adWordsConv);
var sheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL).getActiveSheet();
if(!sheet.getActiveRange().getValue()) {
sheet.appendRow(['Date','Account Name',
'Impressions','Clicks','Ctr',
'AdWords Conversions','Conv Rate',
'CRM Wins','Win %']);
}
var toAppend = [
Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), 'yyyy-MM-dd'),
AdWordsApp.currentAccount().getName(),
adWordsImps,
adWordsClicks,
adWordsCtr,
adWordsConv,
(adWordsClicks === 0) ? 0 : Math.round((adWordsConv/adWordsClicks)*100)/100,
crmConv,
(adWordsConv === 0) ? 0 : Math.round((crmConv/adWordsConv)*100)/100];
sheet.appendRow(toAppend);
}

function getCRMConversions(dateRange) {
var zoho = new ZohoApi(ZOHO_AUTH_TOKEN);
var closedWonCount = 0;
var closedWonRevenue = 0;
var yesterday = new Date();
yesterday.setDate(yesterday.getDate()-1);
var yesterdayStr = Utilities.formatDate(yesterday, AdWordsApp.currentAccount().getTimeZone(), 'yyyy-MM-dd');
var potentials = zoho.searchPotentials({'searchCondition':'(Closing Date|=|'+yesterdayStr+')'});
for(var i in potentials) {
var potential = potentials[i];
if(potential.stage === 'Closed Won' && potential.campaign_source === 'AdWords') {
closedWonCount++;
closedWonRevenue += parseFloat(potential.amount);
}
}
return { count: closedWonCount, revenue: closedWonRevenue };
}

function ZohoApi(authToken) { throw 'Fill this code in from the blog post!'; }

 

8. Extraia dados do Salesforce e do Google Ads – por Russel Savage. Semelhante ao do Zoho CRM, esse script permite exportar dados do Salesforce para criar um relatório integrado com o Google Ads.

 

Primeiro, precisaremos configurar algumas credenciais do OAuth e, para isso, você precisa configurar um novo aplicativo conectado no Salesforce. Está em lugares um pouco diferentes em cada versão, mas usando a versão do desenvolvedor, eu consegui encontrá-la em Setup> Build> Create> Apps. De lá, até o final, você pode ver uma seção dos Aplicativos conectados.

 

 

Se, por algum motivo, você não conseguir encontrá-lo na sua instância do Salesforce, talvez o seu administrador não tenha lhe fornecido acesso. Espero que eles possam ajudá-lo.

Depois de clicar no novo botão, você precisará preencher alguns campos obrigatórios e selecionar a opção “Ativar configurações do Oauth”. Você precisará inserir um URL de retorno de chamada, mas não o usaremos para inserir qualquer URL que comece com https. Para “Escopos”, eu acabei de dizer “Acesso total”, mas talvez você precise conversar com o administrador do Salesforce sobre isso. Nós só estaremos lendo do Salesforce, então não deve ser um problema.

 

Isso é tudo que você precisa preencher e você deve ter um novo aplicativo criado. O importante aqui é a “Chave do Consumidor” e o “Segredo do Consumidor” que você precisará para o script se conectar à sua instância do Salesforce.
Consumidor Salesforce e chaves secretas

 

 

A última coisa que você precisará da sua instância do Salesforce é um token de segurança. Você pode já ter um, nesse caso, você pode pular isso. Mas se não, você pode redefini-lo em Minhas Configurações> Pessoal> Redefinir meu Token de Segurança. Ele lhe enviará um novo token.
Redefina seu token de segurança

 

 

Ok, agora estamos finalmente prontos para acessar o código de scripts do Google AdWords. O código a seguir configurará um novo objeto SalesforceAPI e consultará as Oportunidades mais recentes que foram fechadas para que você possa usar essa receita em sua conta do Google AdWords.

 

 

/******************************************
* Get Won Opportunity Revenue Amounts
* Version 1.0
* Created By: Russ Savage
* FreeAdWordsScripts.com
******************************************/
function main() {
var CONSUMER_KEY = "YOUR APP CONSUMER KEY";
var CONSUMER_SECRET = "YOUR APP SECRET KEY";
var USERNAME = "YOUR SALESFORCE USERNAME";
var PASSWORD = "YOUR SALESFORCE PASSWORD";
var SECURITY_TOKEN = "YOUR SECURITY TOKEN";

// Create a new SalesforceAPI object
var sf = new SalesforceAPI({
client_id : CONSUMER_KEY,
client_secret : CONSUMER_SECRET,
username : USERNAME,
password : PASSWORD,
token: SECURITY_TOKEN
});

// Query the results
var results = sf.query("SELECT Name \
, StageName \
, ExpectedRevenue \
FROM Opportunity \
WHERE IsWon = True \
AND CloseDate < THIS_WEEK");
// Log the results
for(var i in results) {
Logger.log(['Name:',results[i].Name,
'Stage Name:',results[i].StageName,
'Expected Revenue:',results[i].ExpectedRevenue,
'Url:',sf.getFullUrl(results[i].attributes.url)].join(' '));
}
}

 

É simples assim. Se você deseja obter todas as informações sobre um determinado objeto depois de consultá-lo, você pode usar a função getObjectByUrl () e enviar a url dos resultados da consulta. Para saber mais sobre a sintaxe da consulta, confira a documentação do Salesforce SOQL .

Existem algumas ressalvas para este código. Toda instalação do Salesforce é única, então não há como realmente solucionar problemas com sua instalação específica. Esse código foi testado em uma nova conta do Salesforce for Developers para que seus resultados possam variar. Você provavelmente terá mais sorte em entrar em contato com seu administrador do Salesforce do que deixar um comentário aqui. Além disso, você pode perceber que o código está usando a opção menos segura para entrar no Salesforce. Esse código com seu nome de usuário e senha estará acessível a todos os usuários de sua conta do Google AdWords, portanto, tenha cuidado. Talvez seja melhor criar um usuário especial do Salesforce com permissões muito limitadas para algo assim.

 

/******************************************
* Salesforce.com CRM API Class
* Use it to query data out of Salesforce CRM
* Version 1.0
* Created By: Russ Savage
* FreeAdWordsScripts.com
******************************************/
function SalesforceAPI(configVars) {
this.login = function(configVars) {
var LOGIN_ENDPOINT = "//login.salesforce.com/services/oauth2/token";
var options = {
muteHttpExceptions : false,
method: "POST",
payload: {
grant_type : "password",
client_id : configVars.client_id,
client_secret : configVars.client_secret,
username : configVars.username,
password : configVars.password+configVars.token
}
};
var resp = UrlFetchApp.fetch(LOGIN_ENDPOINT, options);
if(resp.getResponseCode() == 200) {
var jsonResp = JSON.parse(resp.getContentText());
this.id = jsonResp.id;
this.instanceUrl = jsonResp.instance_url;
this.signature = jsonResp.signature;
this.accessToken = jsonResp.access_token;
Logger.log('Successfully logged in user with id: '+this.id);
}
}

this.getServices = function() {
if(this.serviceUrls) { return this.serviceUrls };
var ENDPOINT_URL = this.instanceUrl+"/services/data/v26.0/.json";
var options = getBasicOptions(this.accessToken);
var resp = UrlFetchApp.fetch(ENDPOINT_URL, options);
if(resp.getResponseCode() == 200) {
var jsonResp = JSON.parse(resp.getContentText());
this.serviceUrls = jsonResp;
return this.serviceUrls;
}
}

this.query = function(queryStr) {
if(!this.serviceUrls.query) { throw "Query service is not enabled in this SF instance."; }
var ENDPOINT_URL = this.instanceUrl+this.serviceUrls.query+'.json';
var url = ENDPOINT_URL + '?q=' + encodeURIComponent(queryStr);
var options = getBasicOptions(this.accessToken);
var resp = UrlFetchApp.fetch(url, options);
if(resp.getResponseCode() == 200) {
var jsonResp = JSON.parse(resp.getContentText());
if(jsonResp.done) {
return jsonResp.records;
} else {
var retVal = jsonResp.records;
while(!jsonResp.done) {
resp = UrlFetchApp.fetch(jsonResp.nextRecordsUrl, options);
if(resp.getResponseCode() == 200) {
jsonResp = JSON.parse(resp.getContentText());
retVal = retVal.concat(jsonResp.records);
}
}
return retVal;
}
}
}

this.getObjectByUrl = function(url) {
var url = this.instanceUrl + url + '.json';
var options = getBasicOptions(this.accessToken);
var resp = UrlFetchApp.fetch(url, options);
Logger.log(resp.getContentText());
if(resp.getResponseCode() == 200) {
return JSON.parse(resp.getContentText());
}
}

this.getFullUrl = function(url) {
return this.instanceUrl + url;
}

this.login(configVars);
this.getServices();

function getBasicOptions(token) {
return {
muteHttpExceptions : false,
method: 'GET',
headers: {
Authorization : "Bearer " + token
}
};
}
}

Automação Google Ads: Como pausar e deletar anúncios com baixo desempenho.

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para “pausar e deletar anúncios com baixo desempenho”.

 

1. Pausar Anúncios com CTR Baixo – Pelo Russell Savage. Esse script tem como função economizar o tempo de pausar seus anúncios que estão com CTR (Taxa de cliques) baixas e que diminuem seu índice de qualidade de suas palavras-chave.

 

//-----------------------------------
// Pause Ads with Low CTR
// Created By: Russ Savage
// FreeAdWordsScripts.com
//-----------------------------------
function main() {
// Let's start by getting all of the adGroups that are active
var ag_iter = AdWordsApp.adGroups()
.withCondition("Status = ENABLED")
.get();

// Then we will go through each one
while (ag_iter.hasNext()) {
var ag = ag_iter.next();
var ad_iter = ag.ads()
.withCondition("Status = ENABLED")
.forDateRange("ALL_TIME")
.orderBy("Ctr DESC")
.get();
var ad_array = new Array();
while(ad_iter.hasNext()) {
ad_array.push(ad_iter.next());
}
if(ad_array.length > 1) {
for(var i = 1; i < ad_array.length; i++) {
ad_array[i].pause(); //or .remove(); to delete them
}
}
}
}</code

 

2. Pausar Grupo de Anúncios Sem Palavras-chave – Pelo Russel Savage. Agências ou grandes contas de Adwords podem se beneficiar com esse script, uma vez que, ele pausa automaticamente campanhas que não possuem palavras-chave ativadas. Isso pode poupar o seu trabalho em campanhas que tem uma regra de pausar palavras-chaves que não estão trazendo resultado.

/*********************************************
* Pause AdGroups With No Active Keywords
* Version 1.1
* Changelog v1.1
* - Updated for speed and added comments
* Created By: Russ Savage
* FreeAdWordsScripts.com
**********************************************/
function main() {
// Let's start by getting all of the active AdGroups
var agIter = AdWordsApp.adGroups()
.withCondition('CampaignStatus = ENABLED')
.withCondition('Status = ENABLED')
.get();

// It is faster to store them and process them all at once later
var toPause = [];
// Then we will go through each one
while(agIter.hasNext()) {
var ag = agIter.next();
//get all the keywords that are enabled
var kwIter = ag.keywords()
.withCondition("Status = ENABLED")
.get();

//If .hasNext() is true, there is at least 1 kw in the AdGroup
var hasKw = kwIter.hasNext();
if(!hasKw) {
toPause.push(ag);
}
}

// Now we process them all at once to take advantage of batch processing
for(var i in toPause) {
toPause[i].pause();
}
}

 

3. Pausar Todas Palavras-chave Sem Impressões – Pelo Russell Savage. Esse script é uma boa opção para que deseja fazer uma “limpa” em suas palavras-chave que não estão ativando seus anúncios, mantendo somente palavras relevantes.

/*********************************************
* Pause Keywords With No Impressions All Time
* Version 1.1
* Changelog v1.1
* - Updated for speed and added comments
* Created By: Russ Savage
* FreeAdWordsScripts.com
**********************************************/
var TO_NOTIFY = "[email protected]";
function main() {
// Let's start by getting all of the keywords with no impressions
var kwIter = AdWordsApp.keywords()
.withCondition("Impressions = 0") // could be "Clicks = 0" also
.forDateRange("ALL_TIME") // could use a specific date range like "20130101","20131231"
.withCondition("Status = ENABLED")
.withCondition("CampaignStatus = ENABLED")
.withCondition("AdGroupStatus = ENABLED")
.get();

// It is much faster to store all the keywords you want to process
// and then make the changes all at once. This takes advantage
// of the batch processing behind the scenes.
var toPause = [];
while (kwIter.hasNext()) {
var kw = kwIter.next();
toPause.push(kw);
// This is to make sure you see things during the preview
// When you run it for real, you can remove this clause to
// increase speed.
if(AdWordsApp.getExecutionInfo().isPreview() &&
AdWordsApp.getExecutionInfo().getRemainingTime() < 10) {
break;
}
}

// Now go through each one and pause them.
for(var i in toPause) {
toPause[i].pause();
//Or you could use toPause[i].remove(); to delete the keyword altogether
}

// Sent an email to notify you of the changes
MailApp.sendEmail(TO_NOTIFY,
"AdWords Script Paused "+toPause.length+" Keywords.",
"Your AdWords Script paused "+toPause.length+" keywords.");
}

4. Ativar ou Pausar Campanhas, Palavras-chave ou Anúncios em Datas Específicas – Por Russel Savage. Quem trabalha com marketing online deve conhecer a Black Friday e o impacto dela nas vendas nesta data. Com esse script você pode ativar suas campanhas, anúncios e palavras-chave em datas específicas, assim, você pode programar para fazer promoções especiais sem a necessidade de estar presente no momento. Para configurar é necessário rotular suas campanhas, palavras-chave ou anúncios com o rótulo “Pause on”, para pausar, ou “Enable on”, para ativar.

/**************************************************
* Pause or Enable Campaigns, Keywords or Ads on a Given Date
* Version 1.2
* Changelog v1.2 - Added ability to pause Campaigns
* Changelog v1.1 - Added ability to run on Ads
* Created By: Russ Savage
* FreeAdWordsScripts.com
**************************************************/
var ENTITY = 'Keyword'; //or Ad or Campaign
var PAUSE_PREFIX = "Pause on "; //look for labels "Pause on 2013-04-11"
var ENABLE_PREFIX = "Enable on "; //look for labels "Enable on 2013-04-11"

function main() {
var todayStr = Utilities.formatDate(new Date(), AdWordsApp.currentAccount().getTimeZone(), "yyyy-MM-dd");
var pauseStr = PAUSE_PREFIX+todayStr;
var enableStr = ENABLE_PREFIX+todayStr;
Logger.log("Looking for labels: " + [pauseStr,enableStr].join(' and '));

var labelsArray = buildLabelArray(pauseStr,enableStr);

if(labelsArray.length > 0) {
var labelsStr = "['" + labelsArray.join("','") + "']";
var entityIter;
if(ENTITY === 'Keyword') {
entityIter = AdWordsApp.keywords().withCondition("LabelNames CONTAINS_ANY "+labelsStr).get();
} else if(ENTITY === 'Ad') {
entityIter = AdWordsApp.ads().withCondition("LabelNames CONTAINS_ANY "+labelsStr).get();
} else if(ENTITY === 'Campaign') {
entityIter = AdWordsApp.campaigns().withCondition("LabelNames CONTAINS_ANY "+labelsStr).get();
} else {
throw 'Invaid ENTITY type. Should be Campaign, Keyword or Ad. ENTITY:'+ENTITY;
}

while(entityIter.hasNext()) {
var entity = entityIter.next();
pauseEntity(entity, pauseStr);
enableEntity(entity, enableStr);
}
}
}

//Helper function to build a list of labels in the account
function buildLabelArray(pauseStr,enableStr) {
var labelsArray = [];
try {
var labelIter = AdWordsApp.labels().withCondition("Name IN ['"+pauseStr+"','"+enableStr+"']").get();
while(labelIter.hasNext()) {
labelsArray.push(labelIter.next().getName());
}
return labelsArray;
} catch(e) {
Logger.log(e);
}
return [];
}

//Helper function to pause entities
function pauseEntity(entity, pauseStr) {
var labelIter = entity.labels().withCondition("Name = '"+pauseStr+"'").get();
if(labelIter.hasNext()) {
entity.pause();
entity.removeLabel(pauseStr);
}
}

//Helper function to enable entities
function enableEntity(entity, enableStr) {
var labelIter = entity.labels().withCondition("Name = '"+enableStr+"'").get();
if(labelIter.hasNext()) {
entity.enable();
entity.removeLabel(enableStr);
}
}

 

5. Pausar ao Atingir o Orçamento Mensal – Por Sean Dolan. Esse é um ótimo script para manter seus custos sob controle! Ao atingir um limite do seu orçamento mensal, ele automaticamente vai pausar seus anúncios, impedindo que suas campanhas continuem consumindo.

 

var CUTOFF_COST = 10000;
var CUTOFF_LABEL = "Total Spend cutoff";

function main() {
var label = AdWordsApp.labels().withCondition("Name='" + CUTOFF_LABEL + "'").get().next();

if (AdWordsApp.currentAccount().getStatsFor("THIS_MONTH").getCost() > CUTOFF_COST) {
var campaignIterator = label.campaigns().get();

while (campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
campaign.pause();
}
}
}

 

6. Pausar palavras-chave com índice de qualidade baixo – por Optmyzr. As palavras-chave com índice de qualidade baixo aumentam seu CPC para atingir a posição ideal nas páginas de resultados do Google. Esse script é útil caso você deseje manter uma campanha apenas com palavras-chave acima de um determinado índice de qualidade. Por padrão ele está configurado para pausar índices abaixo de 5.

 

// Copyright www.optmyzr.com all rights reserved. This script is provided on a as-is basis with no guarantee.
// This script may be distributed freely, without changing this notice

function main() {

//EDIT- SECTION

//Enter email address. For mulitple addresses please enter the the addresses separated by a comma.
var email_address = "[email protected]";

//Change the pause value to true to pause the keywords with below quality score.
var pause = false;

//This quality score defines the threshold to find keywords below it.
var quality_score = 5;

//ENTER the number of days you would like to get the stats of keywords for. By default it checks for last 30 days.
var date_range = "LAST_30_DAYS";

//END OF EDIT-SECTION

var workbook = SpreadsheetApp.create("Keywords with low Quality Score (" +
Utilities.formatDate(new Date(), "PST", "MM-dd HH:mm)"));
var currentSheet = workbook.getActiveSheet();
var keyword_found=false;

currentSheet.setName("Overview");
currentSheet.appendRow(["Campaign Name","Adgroup","Keyword","Cost","Impressions","Conversions","Quality Score"]);
currentSheet.getRange("1:1").setFontWeight("bold");

var report = AdWordsApp.report("SELECT CampaignName, AdGroupName, KeywordText, Cost, Impressions, Conversions, QualityScore "+
"FROM KEYWORDS_PERFORMANCE_REPORT "+
"WHERE CampaignStatus= ACTIVE and AdGroupStatus = ENABLED and Status = ACTIVE and QualityScore<"+quality_score+
" DURING "+date_range);

report.exportToSheet(currentSheet);
var rows = report.rows();
if(!rows.hasNext()){
currentSheet.appendRow(["No active Keywords Found"]);
}

MailApp.sendEmail(email_address, "Quality Score Tracker for Keywords", "You can see the keywords on the following url\n\n"+workbook.getUrl());
}

 

7. Apagar anúncios reprovados – por Russell Savage. Ao administrar contas em larga escala, mudanças nas landing pages do site, grupos de anúncios antigos, testes A/B podem fazer seus anúncios serem reprovados. Esse script deixa sua conta mais organizada apagando anúncios que estão reprovados.

 

//-----------------------------------
// Delete Ads That Are Disapproved
// Created By: Russ Savage
// FreeAdWordsScripts.com
//-----------------------------------
function main() {
// Let's start by getting all of the ad that are disapproved
var ad_iter = AdWordsApp.ads()
.withCondition("ApprovalStatus != APPROVED")
.get();

// Then we will go through each one
while (ad_iter.hasNext()) {
var ad = ad_iter.next();
// now we delete the ad
Logger.log("Deleteing ad: " + ad.getHeadline());
ad.remove();
}
}

 

8. Desativar anúncios e palavras-chave para produtos sem estoque – por Russel Savage. Ótimo script para empresas de e-commerce que anunciam no Google Ads, seja no dia-a-dia ou em picos de demandas como no Black Friday, você pode ter seus anúncios e palavras-chave pausados assim que estiverem fora de estoque.

Para que esse script funcione, preciso descobrir o que há de diferente na página quando ela fica sem estoque. Se eu clicar com o botão direito e visualizar a fonte da página, e procurar pelo trabalho “stock”, eu posso ver alguns lugares diferentes onde ele é usado. Um deles é o seguinte que diz “in_stock”: false.

Isso parece promissor. Eu verifico um item em estoque e, com certeza, “in_stock”: true está nessa página.

Tudo bem, agora sei o texto que preciso usar para preencher a variável OUT_OF_STOCK_TEXT no meu código. Agora cada site vai ser um pouco diferente, então eu tenho um script simples que usa a mesma lógica de URL que o script completo que você pode usar para testes.

/ ** **********************************
* Url rápido em estoque / fora do estoque
* Versão 1.0
* Criado por: Russ Savage
* FreeAdWordsScripts.com
********************************** * /
var STRIP_QUERY_STRING = true ;
var WRAPPED_URLS = true ;
var OUT_OF_STOCK_TEXT = ' O texto que identifica um item em falta no estoque vai aqui ' ;
var URL_TO_TEST = ' Seu URL para verificar vai aqui ' ;

função main () {
var urlToTest = URL_TO_TEST ;
urlToTest = cleanUrl (urlToTest);
var htmlCode = UrlFetchApp . buscar (urlToTest). getContentText ();
if ( htmlCode . indexOf ( OUT_OF_STOCK_TEXT ) > = 0 ) {
Logger . log ( ' O item está fora de estoque. ' );
} mais {
Logger . log ( ' O item está em estoque. ' );
}
}

function cleanUrl ( url ) {
if ( WRAPPED_URLS ) {
url = url . substr ( url . lastIndexOf ( ' http ' ));
if ( decodeURIComponent (url) ! == url) {
url = decodeURIComponent (url);
}
}
if ( STRIP_QUERY_STRING ) {
if ( url . indexOf ( ' ? ' ) > = 0 ) {
url = url . split ( ' ? ' ) [ 0 ];
}
}
if ( url . indexOf ( ' { ' ) > = 0 ) {
// Vamos remover os parâmetros da faixa de valor
url = url . substitua ( / \ { [ 0-9a-zA-Z ] + \} / g , ' ' );
}
URL de retorno ;
}

 

Depois de encontrar algum texto HTML na origem da página de destino que identifique se um item está em falta, convém seguir o roteiro completo. Existem algumas outras opções no script que permitem ativar ou desativar várias manipulações de URL no script. E lembre-se de que isso fará uma pausa apenas nos anúncios ou nas palavras-chave vinculados à página com o item esgotado.

 

/ ** **********************************
* Item esgotado
* Versão 1.2
* ChangeLog v1.2
* - ONLY_ACTIVE é usado para filtrar apenas campanhas e grupos de anúncios. Todas as palavras-chave e anúncios nos grupos de anúncios
* ser verificado que resolve o problema "uma vez desativado, sempre desativado".
* - chamada atualizada para obter os URLs finais. Agora chama getFinalUrl e getMobileFinalUrl em vez de getDestinationUrl
* - OUT_OF_STOCK_TEXTS agora pode conter várias coisas para verificar.
* - Se o CAMPAIGN_LABEL não existir, ele será ignorado com um aviso.
* ChangeLog v1.1 - Filtrado campanhas excluídas e grupos de anúncios
* Criado por: Russ Savage
* FreeAdWordsScripts.com
********************************** * /
var URL_LEVEL = ' Anúncio ' ; // ou palavra-chave
var ONLY_ACTIVE = true ; // defina como falso para verificar palavras-chave ou anúncios em todas as campanhas (pausadas e ativas)
var CAMPAIGN_LABEL = ' ' ; // definir isso se você quiser apenas verificar campanhas com esse rótulo
var STRIP_QUERY_STRING = true ; // configure isso para false se as coisas que vierem após o ponto de interrogação forem importantes
var WRAPPED_URLS = true ; // configure isso para true se você usar um terceiro como Marin ou Kenshoo para gerenciar sua conta
// Este é o texto específico (ou textos) para procurar
// na página que indica o item
// está fora de estoque. Se QUALQUER destes corresponderem ao html
// na página, o item é considerado "fora de estoque"
var OUT_OF_STOCK_TEXTS = [
' O texto que identifica um item fora de estoque vai aqui ' ,
' Outra string pode ir aqui, mas não precisa '
];

função main () {
var alreadyCheckedUrls = {};
var iter = buildSelector (). get ();
while ( iter . hasNext ()) {
var entity = iter . next ();
var urls = [];
if ( entity . urls (). getFinalUrl ()) {
urls . push ( entity . urls (). getFinalUrl ());
}
if ( entity . urls (). getMobileFinalUrl ()) {
urls . push ( entity . urls (). getMobileFinalUrl ());
}
para ( var i in urls) {
var url = cleanUrl (urls [i]);
if (alreadyCheckedUrls [url]) {
if (alreadyCheckedUrls [url] === ' esgotado ' ) {
entidade . pausa ();
} mais {
entidade . enable ();
}
} mais {
var htmlCode;
try {
htmlCode = UrlFetchApp . buscar (url). getContentText ();
} pegar (e) {
Logger . log ( ' Houve uma verificação de problemas: ' + url + ' , Ignorando. ' );
continue ;
}
var did_pause = false ;
para ( var x em OUT_OF_STOCK_TEXTS ) {
if ( htmlCode . indexOf ( OUT_OF_STOCK_TEXTS [x]) > = 0 ) {
alreadyCheckedUrls [url] = ' esgotado ' ;
entidade . pausa ();
did_pause = true ;
pausa ;
}
}
if ( ! did_pause) {
alreadyCheckedUrls [url] = ' em estoque ' ;
entidade . enable ();
}
}
Logger . log ( ' Url: ' + url + ' é ' + alreadyCheckedUrls [url]);
}
}
}

function cleanUrl ( url ) {
if ( WRAPPED_URLS ) {
url = url . substr ( url . lastIndexOf ( ' http ' ));
if ( decodeURIComponent (url) ! == url) {
url = decodeURIComponent (url);
}
}
if ( STRIP_QUERY_STRING ) {
if ( url . indexOf ( ' ? ' ) > = 0 ) {
url = url . split ( ' ? ' ) [ 0 ];
}
}
if ( url . indexOf ( ' { ' ) > = 0 ) {
// Vamos remover os parâmetros da faixa de valor
url = url . substitua ( / \ { [ 0-9a-zA-Z ] + \} / g , ' ' );
}
URL de retorno ;
}

function buildSelector () {
var selector = ( URL_LEVEL === ' Ad ' ) ? AdWordsApp . anúncios () : AdWordsApp . palavras-chave ();
seletor = seletor . withCondition ( ' CampaignStatus! = DELETED ' ). withCondition ( ' AdGroupStatus! = DELETED ' );
if ( ONLY_ACTIVE ) {
seletor = seletor . withCondition ( ' CampaignStatus = ENABLED ' );
if ( URL_LEVEL ! == ' Ad ' ) {
seletor = seletor . withCondition ( ' AdGroupStatus = ENABLED ' );
}
}
if ( CAMPAIGN_LABEL ) {
if ( AdWordsApp . labels (). withCondition ( " Nome = ' " + CAMPAIGN_LABEL + " ' " ). get (). hasNext ()) {
var label = AdWordsApp . rótulos (). withCondition ( " Nome = ' " + CAMPAIGN_LABEL + " ' " ). get (). next ();
var campIter = label . campanhas (). get ();
var campaignNames = [];
while ( campIter . hasNext ()) {
campaignNames . push ( campIter . next (). getName ());
}
seletor = seletor . withCondition ( " CampaignName IN [" " + campaignNames . join ( " ',' " ) + " '] " );
} mais {
Logger . log ( ' AVISO: o rótulo da campanha não existe: ' + CAMPAIGN_LABEL );
}
}
seletor de retorno ;
}

 

Automação Google Adwords: Script de Modificação de Lances

Nessa série especial, reunimos alguns scripts úteis para você automatizar e aumentar o retorno de suas campanhas no Google Ads. Confira abaixo como automatizar o Google Ads para “Modificação de Lances”.

 

1. Programação de Alteração de Lances 24 horas – Pelo Brainlabs. Esse script permite que você ajuste automaticamente seus lances por hora, durante toda a semana. Desta maneira é possível otimizar o gasto do orçamento de sua conta mantendo seus anúncios nas posições que trazem maiores conversões.

/*
*
* Advanced ad scheduling
*
* This script will apply ad schedules to campaigns or shopping campaigns and set
* the ad schedule bid modifier and mobile bid modifier at each hour according to
* multiplier timetables in a Google sheet.
*
* This version creates schedules with modifiers for 4 hours, then fills the rest
* of the day and the other days of the week with schedules with no modifier as a
* fail safe.
*
* Version: 3.1
* Updated to allow -100% bids, change mobile adjustments and create fail safes.
* brainlabsdigital.com
*
*/
function main() {
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//Options
//The Google sheet to use
//The default value is the example sheet
//Shopping or regular campaigns
//Use true if you want to run script on shopping campaigns (not regular campaigns).
//Use false for regular campaigns.
var shoppingCampaigns = false;
//Use true if you want to set mobile bid adjustments as well as ad schedules.
//Use false to just set ad schedules.
var runMobileBids = false;
//Optional parameters for filtering campaign names. The matching is case insensitive.
//Select which campaigns to exclude e.g ["foo", "bar"] will ignore all campaigns
//whose name contains 'foo' or 'bar'. Leave blank [] to not exclude any campaigns.
var excludeCampaignNameContains = [];
//Select which campaigns to include e.g ["foo", "bar"] will include only campaigns
//whose name contains 'foo' or 'bar'. Leave blank [] to include all campaigns.
var includeCampaignNameContains = [];
//When you want to stop running the ad scheduling for good, set the lastRun
//variable to true to remove all ad schedules.
var lastRun = false;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
//Initialise for use later.
var weekDays = ["MONDAY""TUESDAY""WEDNESDAY""THURSDAY""FRIDAY""SATURDAY""SUNDAY"];
var adScheduleCodes = [];
var campaignIds = [];
//Retrieving up hourly data
var scheduleRange = "B2:H25";
var accountName = AdWordsApp.currentAccount().getName();
var spreadsheet = SpreadsheetApp.openByUrl(spreadsheetUrl);
var sheets = spreadsheet.getSheets();
var timeZone = AdWordsApp.currentAccount().getTimeZone();
var date = new Date();
var dayOfWeek = parseInt(Utilities.formatDate(date, timeZone, "uu"), 10) - 1;
var hour = parseInt(Utilities.formatDate(date, timeZone, "HH"), 10);
var sheet = sheets[0];
var data = sheet.getRange(scheduleRange).getValues();
//This hour's bid multiplier.
var thisHourMultiplier = data[hour][dayOfWeek];
var lastHourCell = "I2";
sheet.getRange(lastHourCell).setValue(thisHourMultiplier);
//The next few hours' multipliers
var timesAndModifiers = [];
var otherDays = weekDays.slice(0);
for (var h=0; h<5; h++) {
var newHour = (hour + h)%24;
if (hour + h > 23) {
var newDay = (dayOfWeek + 1)%7;
else {
var newDay = dayOfWeek;
}
otherDays[newDay] = "-";
if (h<4) {
// Use the specified bids for the next 4 hours
var bidModifier = data[newHour][newDay];
if (isNaN(bidModifier) || (bidModifier < -0.9 && bidModifier > -1) || bidModifier > 9) {
Logger.log("Bid modifier '" + bidModifier + "' for " + weekDays[newDay] + " " + newHour + " is not valid.");
timesAndModifiers.push([newHour, newHour+1, weekDays[newDay], 0]);
else if (bidModifier != -1 && bidModifier.length != 0) {
timesAndModifiers.push([newHour, newHour+1, weekDays[newDay], bidModifier]);
}
else {
// Fill in the rest of the day with no adjustment (as a back-up incase the script breaks)
timesAndModifiers.push([newHour, 24, weekDays[newDay], 0]);
}
}
if (hour>0) {
timesAndModifiers.push([0, hour, weekDays[dayOfWeek], 0]);
}
for (var d=0; d<otherDays.length; d++) {
if (otherDays[d] != "-") {
timesAndModifiers.push([0, 24, otherDays[d], 0]);
}
}
//Pull a list of all relevant campaign IDs in the account.
var campaignSelector = ConstructIterator(shoppingCampaigns);
for(var i = 0; i < excludeCampaignNameContains.length; i++){
campaignSelector = campaignSelector.withCondition('Name DOES_NOT_CONTAIN_IGNORE_CASE "' + excludeCampaignNameContains[i] + '"');
}
campaignSelector = campaignSelector.withCondition("Status IN [ENABLED,PAUSED]");
var campaignIterator = campaignSelector.get();
while(campaignIterator.hasNext()){
var campaign = campaignIterator.next();
var campaignName = campaign.getName();
var includeCampaign = false;
if(includeCampaignNameContains.length === 0){
includeCampaign = true;
}
for(var i = 0; i < includeCampaignNameContains.length; i++){
var index = campaignName.toLowerCase().indexOf(includeCampaignNameContains[i].toLowerCase());
if(index !== -1){
includeCampaign = true;
break;
}
}
if(includeCampaign){
var campaignId = campaign.getId();
campaignIds.push(campaignId);
}
}
//Return if there are no campaigns.
if(campaignIds.length === 0){
Logger.log("There are no campaigns matching your criteria.");
return;
}
//Remove all ad scheduling for the last run.
if(lastRun){
checkAndRemoveAdSchedules(campaignIds, []);
return;
}
// Change the mobile bid adjustment
if(runMobileBids){
if (sheets.length < 2) {
Logger.log("Mobile ad schedule sheet was not found in the Google spreadsheet.");
else {
var sheet = sheets[1];
var data = sheet.getRange(scheduleRange).getValues();
var thisHourMultiplier_Mobile = data[hour][dayOfWeek];
if (thisHourMultiplier_Mobile.length === 0) {
thisHourMultiplier_Mobile = -1;
}
if (isNaN(thisHourMultiplier_Mobile) || (thisHourMultiplier_Mobile < -0.9 && thisHourMultiplier_Mobile > -1) || thisHourMultiplier_Mobile > 3) {
Logger.log("Mobile bid modifier '" + thisHourMultiplier_Mobile + "' for " + weekDays[dayOfWeek] + " " + hour + " is not valid.");
thisHourMultiplier_Mobile = 0;
}
var totalMultiplier = ((1+thisHourMultiplier_Mobile)*(1+thisHourMultiplier))-1;
sheet.getRange("I2").setValue(thisHourMultiplier_Mobile);
sheet.getRange("T2").setValue(totalMultiplier);
ModifyMobileBidAdjustment(campaignIds, thisHourMultiplier_Mobile);
}
}
// Check the existing ad schedules, removing those no longer necessary
var existingSchedules = checkAndRemoveAdSchedules(campaignIds, timesAndModifiers);
// Add in the new ad schedules
AddHourlyAdSchedules(campaignIds, timesAndModifiers, existingSchedules, shoppingCampaigns);
}
/**
* Function to add ad schedules for the campaigns with the given IDs, unless the schedules are
* referenced in the existingSchedules array. The scheduling will be added as a hour long periods
* as specified in the passed parameter array and will be given the specified bid modifier.
*
* @param array campaignIds array of campaign IDs to add ad schedules to
* @param array timesAndModifiers the array of [hour, day, bid modifier] for which to add ad scheduling
* @param array existingSchedules array of strings identifying already existing schedules.
* @param bool shoppingCampaigns using shopping campaigns?
* @return void
*/
function AddHourlyAdSchedules(campaignIds, timesAndModifiers, existingSchedules, shoppingCampaigns){
// times = [[hour,day],[hour,day]]
var campaignIterator = ConstructIterator(shoppingCampaigns)
.withIds(campaignIds)
.get();
while(campaignIterator.hasNext()){
var campaign = campaignIterator.next();
for(var i = 0; i < timesAndModifiers.length; i++){
if (existingSchedules.indexOf(
timesAndModifiers[i][0] + "|" + (timesAndModifiers[i][1]) + "|" + timesAndModifiers[i][2]
"|" + Utilities.formatString("%.2f",(timesAndModifiers[i][3]+1)) + "|" + campaign.getId())
> -1) {
continue;
}
campaign.addAdSchedule({
dayOfWeek: timesAndModifiers[i][2],
startHour: timesAndModifiers[i][0],
startMinute: 0,
endHour: timesAndModifiers[i][1],
endMinute: 0,
bidModifier: Math.round(100*(1+timesAndModifiers[i][3]))/100
});
}
}
}
/**
* Function to remove ad schedules from all campaigns referenced in the passed array
* which do not correspond to schedules specified in the passed timesAndModifiers array.
*
* @param array campaignIds array of campaign IDs to remove ad scheduling from
* @param array timesAndModifiers array of [hour, day, bid modifier] of the wanted schedules
* @return array existingWantedSchedules array of strings identifying the existing undeleted schedules
*/
function checkAndRemoveAdSchedules(campaignIds, timesAndModifiers) {
var adScheduleIds = [];
var report = AdWordsApp.report(
'SELECT CampaignId, Id ' +
'FROM CAMPAIGN_AD_SCHEDULE_TARGET_REPORT ' +
'WHERE CampaignId IN ["' + campaignIds.join('","')  + '"]'
);
var rows = report.rows();
while(rows.hasNext()){
var row = rows.next();
var adScheduleId = row['Id'];
var campaignId = row['CampaignId'];
if (adScheduleId == "--") {
continue;
}
adScheduleIds.push([campaignId,adScheduleId]);
}
var chunkedArray = [];
var chunkSize = 10000;
for(var i = 0; i < adScheduleIds.length; i += chunkSize){
chunkedArray.push(adScheduleIds.slice(i, i + chunkSize));
}
var wantedSchedules = [];
var existingWantedSchedules = [];
for (var j=0; j<timesAndModifiers.length; j++) {
wantedSchedules.push(timesAndModifiers[j][0] + "|" + (timesAndModifiers[j][1]) + "|" + timesAndModifiers[j][2] + "|" + Utilities.formatString("%.2f",timesAndModifiers[j][3]+1));
}
for(var i = 0; i < chunkedArray.length; i++){
var unwantedSchedules = [];
var adScheduleIterator = AdWordsApp.targeting()
.adSchedules()
.withIds(chunkedArray[i])
.get();
while (adScheduleIterator.hasNext()) {
var adSchedule = adScheduleIterator.next();
var key = adSchedule.getStartHour() + "|" + adSchedule.getEndHour() + "|" + adSchedule.getDayOfWeek() + "|" + Utilities.formatString("%.2f",adSchedule.getBidModifier());
if (wantedSchedules.indexOf(key) > -1) {
existingWantedSchedules.push(key + "|" + adSchedule.getCampaign().getId());
else {
unwantedSchedules.push(adSchedule);
}
}
for(var j = 0; j < unwantedSchedules.length; j++){
unwantedSchedules[j].remove();
}
}
return existingWantedSchedules;
}
/**
* Function to construct an iterator for shopping campaigns or regular campaigns.
*
* @param bool shoppingCampaigns Using shopping campaigns?
* @return AdWords iterator Returns the corresponding AdWords iterator
*/
function ConstructIterator(shoppingCampaigns){
if(shoppingCampaigns === true){
return AdWordsApp.shoppingCampaigns();
}
else{
return AdWordsApp.campaigns();
}
}
/**
* Function to set a mobile bid modifier for a set of campaigns
*
* @param array campaignIds An array of the campaign IDs to be affected
* @param Float bidModifier The multiplicative mobile bid modifier
* @return void
*/
function ModifyMobileBidAdjustment(campaignIds, bidModifier){
var platformIds = [];
var newBidModifier = Math.round(100*(1+bidModifier))/100;
for(var i = 0; i < campaignIds.length; i++){
platformIds.push([campaignIds[i],30001]);
}
var platformIterator = AdWordsApp.targeting()
.platforms()
.withIds(platformIds)
.get();
while (platformIterator.hasNext()) {
var platform = platformIterator.next();
platform.setBidModifier(newBidModifier);
}
}

 

2. Calcular e Definir Modificador de Lances para Mobile – Pelo Frederick Vallaeys do Optmyzr. Esse script revisa a performance mobile e desktop/tablet do ROAS (Retorno por gasto de anúncio) ou CPC (Custo por clique). Após a revisão, ele analisa os dados das palavras-chave e sugere um modificador de lances para o tráfego mobile dos grupos de anúncios ou campanhas.

 

 

/*********
* Calculate Mobile Bid Adjustments
* Based on the post by Kohki Yamaguchi found here: //goo.gl/iJBCdJ
* Author: Russell Savage
* Date: 2013-08-15
* Verson: v1.1
**********/
// Use this to set the look back window for gathering stats
// You can also use the format YYYYMMDD,YYYYMMDD
var DATE_RANGE = 'LAST_30_DAYS';

// Set to Campaign if you want to calculate at the Campaign level
// Set to AdGroup if you want to calculate at the AdGroup level
var LEVEL = 'AdGroup';
//If you set the LEVEL to AdGroup, the spreadsheet will be mailed to these emails
var TO = ['[email protected]','[email protected]'];

// Set to ROAS to use Return On Ad Spend for the calulcation
// Set to RPC to use Revenue Per Click for the calculation
var METRIC = 'ROAS';

// These will be the min and max values that the mobile bid adjustment could be set to
var MINIMUM_BID_ADJUSTMENT = .5;
var MAXIMUM_BID_ADJUSTMENT = 1;

// Use this to adjust the number of decimal places to keep for calculations
var DECIMAL_PLACES = 3;

function main() {
//Check the options to make sure they are valid
hasValidOptions();

//first, calculate stats for each keyword
var accountMap = getKeywordStats();

//then get the mobileBidAdjustments for the given LEVEL
var mobileBidAdjustments = getMobileBidAdjustments(accountMap);

if(LEVEL === 'Campaign') {
//Apply the calculated modifiers to the campaigns
setCampaignBidModifiers(mobileBidAdjustments);
} else {
//Someday, we'll be able to set the AdGroup modifiers, but until then, here is a spreadsheet
//that you can upload into AdWords Editor
var report = getAdGroupBidModifierReport(mobileBidAdjustments);
sendEmail(report,'adgroup_mobile_modifier_','AdGroup Mobile Modifiers - ','See attached.');
}
}

// This will set the Mobile Bid Modifiers at the Campaign level
function setCampaignBidModifiers(mobileBidAdjustments) {
var campaignIterator = AdWordsApp.campaigns().get();
while(campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
var currentMobileBidAdjustment = campaign.targeting().platforms().mobile().get().next();
if(mobileBidAdjustments[campaign.getId()]) {
Logger.log(campaign.getName() + ' MBA: ' + mobileBidAdjustments[campaign.getId()]);
currentMobileBidAdjustment.setBidModifier(mobileBidAdjustments[campaign.getId()]);
}
}
}

// This function will send an email with the report attached as a
// zipped csv file in case the file is large.
function sendEmail(report,attachmentPrefix,subjectPrefix,body) {
var date_str = Utilities.formatDate(new Date(),AdWordsApp.currentAccount().getTimeZone(),'yyyy-MM-dd');
var blob = Utilities.newBlob(report, 'text/csv', attachmentPrefix+date_str+'.csv')
if(report.length > 1000) {
var zipped_blob = Utilities.zip([blob], attachmentPrefix+date_str+'.zip');
blob = zipped_blob;
}
var options = { attachments: [blob] };
var subject = subjectPrefix + date_str;
for(var i in TO) {
MailApp.sendEmail(TO[i], subject, body, options);
}
}

// This function will build the report for AdGroup Mobile Bid Modifiers
// so that you can upload them into AdWords Editor
function getAdGroupBidModifierReport(accountMap) {
var report = '"' + ['Campaign','Ad Group','Bid Adjustment'].join('","') + '"\n';
var adGroupIterator = AdWordsApp.adGroups().get();
while(adGroupIterator.hasNext()) {
var adGroup = adGroupIterator.next();
var campaignId = adGroup.getCampaign().getId();
var adGroupId = adGroup.getId();
if(accountMap[campaignId] && accountMap[campaignId][adGroupId]) {
var formattedMba = round((accountMap[campaignId][adGroupId] - 1)*100);
report += '"' + [adGroup.getCampaign().getName(),adGroup.getName(),formattedMba].join('","') + '"\n';
Logger.log([adGroup.getCampaign().getName(),adGroup.getName(),formattedMba].join('", "'));
}
}
return report;
}

function getWeightedRatio(keyword,spendOrClick) {
if(['spend','click'].indexOf(spendOrClick) == -1) {
throw 'getWeightedRatio needs spendOrClick to be equal to spend or click. Current value is: ' + spendOrClick;
}

var total = getTotal(keyword,spendOrClick);
var mobileMetric = 0;
var desktopMetric = 0;

if(!keyword.Mobile) { return 0; }
if(!keyword.DesktopTablet) {
// If we are here, that means there is mobile data, but no desktop data.
// In this case, we want to return a large number to influence this metric more
// We use 3 because the max bid multiplier is 300%
return (3 * total);
}

var metricKey = (METRIC === 'ROAS') ? 'Roas' : 'Rpc';

mobileMetric = keyword.Mobile[metricKey];
desktopMetric = keyword.DesktopTablet[metricKey];

if(mobileMetric == 0) { return 0; }

if(desktopMetric == 0) {
// We use the same reasoning as above
return (3 * total);
}

var weightedRatio = round(mobileMetric/desktopMetric) * total;
return weightedRatio;
}

// Helper function to calculate the total clicks or cost of the keyword
function getTotal(keyword,spendOrClick) {
if(['spend','click'].indexOf(spendOrClick) == -1) {
throw 'getTotal needs spendOrClick to be equal to spend or click. Current value is: ' + spendOrClick;
}

var total = 0;
var metricKey = (spendOrClick === 'spend') ? 'Roas' : 'Rpc';

var mobileMetric = (keyword.Mobile) ? keyword.Mobile[metricKey] : 0;
var desktopMetric = (keyword.DesktopTablet) ? keyword.DesktopTablet[metricKey] : 0;

if(mobileMetric == 0 && desktopMetric == 0) {
//ignore this keyword
return 0;
}

var deviceMetricKey = (spendOrClick === 'spend') ? 'Cost' : 'Clicks';
for(var device in keyword) {
total += keyword[device][deviceMetricKey];
}
return total;
}

// This function sums the results of the click weighted ROAS ratios and calculates
// the final mobile bid adjustment of the campaign.
function getMobileBidAdjustments(entities) {
var mbas = {};
var numerator = 0;
var denominator = 0;

for(var campaignId in entities) {
for(var adGroupId in entities[campaignId]) {
for(var keywordId in entities[campaignId][adGroupId]) {
var keywordData = entities[campaignId][adGroupId][keywordId];
switch(METRIC) {
case 'ROAS' :
numerator += getWeightedRatio(keywordData,'spend');
denominator += getTotal(keywordData,'spend');
break;
case 'RPC' :
numerator += getWeightedRatio(keywordData,'click');
denominator += getTotal(keywordData,'click');
break;
default :
throw 'METRIC needs to be either ROAS, or RPC. Current value is: ' + METRIC;
}
}
if(LEVEL === 'AdGroup') {
Logger.log('Campaign Id: ' + campaignId + ' AdGroupId: ' + adGroupId);
if(!mbas[campaignId]) { mbas[campaignId] = {}; }
mbas[campaignId][adGroupId] = getMba(numerator,denominator);
if(mbas[campaignId][adGroupId] == -1) {
Logger.log('Mobile Bid Adjustment could not be calculated for CampaignId: ' + campaignId + ' and AdGroupId: ' + adGroupId);
delete mbas[campaignId][adGroupId];
}
// Reset the values for the next run
denominator = 0;
numerator = 0;
}
}
if(LEVEL === 'Campaign') {
Logger.log('Campaign Id: ' + campaignId);
mbas[campaignId] = getMba(numerator,denominator);
if(mbas[campaignId] == -1) {
Logger.log('Mobile Bid Adjustment could not be calculated for CampaignId: ' + campaignId);
delete mbas[campaignId];
}
numerator = 0;
denominator = 0;
}
}
return mbas;
}

//This function calculates the Mobile Bid Modifier and trims using MIN and MAX
function getMba(sumOfRatios,total) {
Logger.log('Numerator: ' + sumOfRatios + ' Denominator: ' + total);
var mba = (total != 0) ? round(sumOfRatios/total) : -1;
Logger.log('MBA: ' + (mba==-1) ? 'N/A' : mba + ' (-1 is an error code and the value will be ignored)');
if(mba == -1) {
return mba;
} else {
return (mba < MINIMUM_BID_ADJUSTMENT) ? MINIMUM_BID_ADJUSTMENT :
(mba > MAXIMUM_BID_ADJUSTMENT) ? MAXIMUM_BID_ADJUSTMENT : mba;
}
}

// This function pulls the keyword performance report to get the values needed
// for calculating the ROAS for the keyword.
function getKeywordStats() {
var API_VERSION = { includeZeroImpressions : false };
var query = buildAWQLQuery();
var reportIter = AdWordsApp.report(query, API_VERSION).rows();
var accountMapping = {}; // { campaignId : { adGroupId : { keywordId : { stat : value } } } }
while(reportIter.hasNext()) {
var row = reportIter.next();
//Let's convert all the metrics to floats so we don't have to worry about it later
for(var i in row) {
if(i.indexOf('Id') == -1 && !isNaN(parseFloat(row[i]))) {
row[i] = parseFloat(row[i]);
}
}
var rpc = (row.Clicks != 0) ? round(row.ConversionValue/row.Clicks) : 0;
var roas = (row.AverageCpc != 0) ? round(rpc/row.AverageCpc) : 0;
var cpa = (row.Conversions != 0) ? round(row.Cost/row.Conversions) : 0;
row['Rpc'] = rpc;
row['Roas'] = roas;
row['Cpa'] = cpa;

//Build out the account mapping if needed
if(!accountMapping[row.CampaignId]) {
accountMapping[row.CampaignId] = {};
}
if(!accountMapping[row.CampaignId][row.AdGroupId]) {
accountMapping[row.CampaignId][row.AdGroupId] = {};
}
if(!accountMapping[row.CampaignId][row.AdGroupId][row.Id]) {
accountMapping[row.CampaignId][row.AdGroupId][row.Id] = {};
}
accountMapping[row.CampaignId][row.AdGroupId][row.Id][row.Device.split(' ')[0]] = row;
}
accountMapping = addDesktopTabletEntry(accountMapping);
return accountMapping;
}

// This function will return a valid AWQL query to find keyword performance
function buildAWQLQuery() {
var cols = ['CampaignId','AdGroupId','Id','Device','Clicks','Cost','Conversions','ConversionValue','AverageCpc'];
var report = 'KEYWORDS_PERFORMANCE_REPORT';
return ['select',cols.join(','),'from',report,'during',DATE_RANGE].join(' ');
}

// This function will add an entry into the accountMapping for combining Computers and Tablets
// since the Device targeting is only at that level
function addDesktopTabletEntry(accountMapping) {
for(var campaignId in accountMapping) {
var campaign = accountMapping[campaignId];
for(var adGroupId in campaign) {
var adGroup = campaign[adGroupId];
for(var keywordId in adGroup) {
var devices = adGroup[keywordId];

if(devices.Computers || devices.Tablets) {
accountMapping[campaignId][adGroupId][keywordId]['DesktopTablet'] = combineStats(devices.Computers,devices.Tablets);
}

//Since we combined these, we don't need them anymore
delete accountMapping[campaignId][adGroupId][keywordId]['Computers'];
delete accountMapping[campaignId][adGroupId][keywordId]['Tablets'];
}
}
}
return accountMapping;
}

// This function simply combines the stats of desktop and tablet performance
function combineStats(desktop,tablet) {
if(!desktop) {
desktop = {Cost : 0, Clicks: 0, ConversionValue : 0, Conversions : 0};
}
if(!tablet) {
tablet = {Cost : 0, Clicks: 0, ConversionValue : 0, Conversions : 0};
}
var totalCost = desktop.Cost + tablet.Cost;
var totalClicks = desktop.Clicks + tablet.Clicks;
var totalRevenue = desktop.ConversionValue + tablet.ConversionValue;
var totalConversions = desktop.Conversions + tablet.Conversions;
var averageCpc = (totalClicks != 0) ? round(totalCost/totalClicks) : 0;
var rpc = (totalClicks != 0) ? round(totalRevenue/totalClicks) : 0;
var roas = (averageCpc != 0) ? round(rpc/averageCpc) : 0;
var cpa = (totalConversions != 0) ? round(totalCost/totalConversions) : 0;

return {
Cost : totalCost,
Clicks: totalClicks,
ConversionValue : totalRevenue,
AverageCpc : averageCpc,
Rpc : rpc,
Roas : roas,
Cpa : cpa
};
}

// A helper function to make rounding a little easier
function round(value) {
var decimals = Math.pow(10,DECIMAL_PLACES);
return Math.round(value*decimals)/decimals;
}

// Validate some of the user options just in case
function hasValidOptions() {
var validDates = ['TODAY','YESTERDAY','LAST_7_DAYS','THIS_WEEK_SUN_TODAY',
'THIS_WEEK_MON_TODAY','LAST_WEEK','LAST_14_DAYS',
'LAST_30_DAYS','LAST_BUSINESS_WEEK','LAST_WEEK_SUN_SAT','THIS_MONTH'];
if(DATE_RANGE.indexOf(',') == -1 && validDates.indexOf(DATE_RANGE) == -1) {
throw 'DATE_RANGE is invalid. Current value is: '+DATE_RANGE;
}
if(DECIMAL_PLACES <= 0) {
throw 'You should set DECIMAL_PLACES to be >= 1. You entered: '+DECIMAL_PLACES;
}
if(['Campaign','AdGroup'].indexOf(LEVEL) == -1) {
throw 'LEVEL should be either Campaign or AdGroup. You entered: '+LEVEL;
}
if(['ROAS','RPC'].indexOf(METRIC) == -1) {
throw 'METRIC should be ROAS or RPC. You entered: '+METRIC;
}
if(LEVEL === 'AdGroup' && (!TO || TO.length == 0)) {
throw 'If you set LEVEL to AdGroup, you need to add at least one email.';
}
if(MINIMUM_BID_ADJUSTMENT < 0) {
throw 'MINIMUM_BID_ADJUSTMENT needs to be >= 0. Current value is: '+MINIMUM_BID_ADJUSTMENT;
}
if(MAXIMUM_BID_ADJUSTMENT > 3) {
throw 'MAXIMUM_BID_ADJUSTMENT needs to be <= 3. Current value is: '+MAXIMUM_BID_ADJUSTMENT;
}
}

 

3. Definir e Sugerir Modificador de Lances Mobile – Por Russel Savage. Com esse script você pode ajustar os modificadores de lances para mobile. Tenha maior controle adicionando um ajuste mínimo e um máximo por nível de campanha ou grupo de anúncios.

 

/*********
* Calculate Mobile Bid Adjustments
* Based on the post by Kohki Yamaguchi found here: //goo.gl/iJBCdJ
* Author: Russell Savage
* Date: 2013-08-15
* Verson: v1.1
**********/
// Use this to set the look back window for gathering stats
// You can also use the format YYYYMMDD,YYYYMMDD
var DATE_RANGE = 'LAST_30_DAYS';

// Set to Campaign if you want to calculate at the Campaign level
// Set to AdGroup if you want to calculate at the AdGroup level
var LEVEL = 'AdGroup';
//If you set the LEVEL to AdGroup, the spreadsheet will be mailed to these emails
var TO = ['[email protected]','[email protected]'];

// Set to ROAS to use Return On Ad Spend for the calulcation
// Set to RPC to use Revenue Per Click for the calculation
var METRIC = 'ROAS';

// These will be the min and max values that the mobile bid adjustment could be set to
var MINIMUM_BID_ADJUSTMENT = .5;
var MAXIMUM_BID_ADJUSTMENT = 1;

// Use this to adjust the number of decimal places to keep for calculations
var DECIMAL_PLACES = 3;

function main() {
//Check the options to make sure they are valid
hasValidOptions();

//first, calculate stats for each keyword
var accountMap = getKeywordStats();

//then get the mobileBidAdjustments for the given LEVEL
var mobileBidAdjustments = getMobileBidAdjustments(accountMap);

if(LEVEL === 'Campaign') {
//Apply the calculated modifiers to the campaigns
setCampaignBidModifiers(mobileBidAdjustments);
} else {
//Someday, we'll be able to set the AdGroup modifiers, but until then, here is a spreadsheet
//that you can upload into AdWords Editor
var report = getAdGroupBidModifierReport(mobileBidAdjustments);
sendEmail(report,'adgroup_mobile_modifier_','AdGroup Mobile Modifiers - ','See attached.');
}
}

// This will set the Mobile Bid Modifiers at the Campaign level
function setCampaignBidModifiers(mobileBidAdjustments) {
var campaignIterator = AdWordsApp.campaigns().get();
while(campaignIterator.hasNext()) {
var campaign = campaignIterator.next();
var currentMobileBidAdjustment = campaign.targeting().platforms().mobile().get().next();
if(mobileBidAdjustments[campaign.getId()]) {
Logger.log(campaign.getName() + ' MBA: ' + mobileBidAdjustments[campaign.getId()]);
currentMobileBidAdjustment.setBidModifier(mobileBidAdjustments[campaign.getId()]);
}
}
}

// This function will send an email with the report attached as a
// zipped csv file in case the file is large.
function sendEmail(report,attachmentPrefix,subjectPrefix,body) {
var date_str = Utilities.formatDate(new Date(),AdWordsApp.currentAccount().getTimeZone(),'yyyy-MM-dd');
var blob = Utilities.newBlob(report, 'text/csv', attachmentPrefix+date_str+'.csv')
if(report.length > 1000) {
var zipped_blob = Utilities.zip([blob], attachmentPrefix+date_str+'.zip');
blob = zipped_blob;
}
var options = { attachments: [blob] };
var subject = subjectPrefix + date_str;
for(var i in TO) {
MailApp.sendEmail(TO[i], subject, body, options);
}
}

// This function will build the report for AdGroup Mobile Bid Modifiers
// so that you can upload them into AdWords Editor
function getAdGroupBidModifierReport(accountMap) {
var report = '"' + ['Campaign','Ad Group','Bid Adjustment'].join('","') + '"\n';
var adGroupIterator = AdWordsApp.adGroups().get();
while(adGroupIterator.hasNext()) {
var adGroup = adGroupIterator.next();
var campaignId = adGroup.getCampaign().getId();
var adGroupId = adGroup.getId();
if(accountMap[campaignId] && accountMap[campaignId][adGroupId]) {
var formattedMba = round((accountMap[campaignId][adGroupId] - 1)*100);
report += '"' + [adGroup.getCampaign().getName(),adGroup.getName(),formattedMba].join('","') + '"\n';
Logger.log([adGroup.getCampaign().getName(),adGroup.getName(),formattedMba].join('", "'));
}
}
return report;
}

function getWeightedRatio(keyword,spendOrClick) {
if(['spend','click'].indexOf(spendOrClick) == -1) {
throw 'getWeightedRatio needs spendOrClick to be equal to spend or click. Current value is: ' + spendOrClick;
}

var total = getTotal(keyword,spendOrClick);
var mobileMetric = 0;
var desktopMetric = 0;

if(!keyword.Mobile) { return 0; }
if(!keyword.DesktopTablet) {
// If we are here, that means there is mobile data, but no desktop data.
// In this case, we want to return a large number to influence this metric more
// We use 3 because the max bid multiplier is 300%
return (3 * total);
}

var metricKey = (METRIC === 'ROAS') ? 'Roas' : 'Rpc';

mobileMetric = keyword.Mobile[metricKey];
desktopMetric = keyword.DesktopTablet[metricKey];

if(mobileMetric == 0) { return 0; }

if(desktopMetric == 0) {
// We use the same reasoning as above
return (3 * total);
}

var weightedRatio = round(mobileMetric/desktopMetric) * total;
return weightedRatio;
}

// Helper function to calculate the total clicks or cost of the keyword
function getTotal(keyword,spendOrClick) {
if(['spend','click'].indexOf(spendOrClick) == -1) {
throw 'getTotal needs spendOrClick to be equal to spend or click. Current value is: ' + spendOrClick;
}

var total = 0;
var metricKey = (spendOrClick === 'spend') ? 'Roas' : 'Rpc';

var mobileMetric = (keyword.Mobile) ? keyword.Mobile[metricKey] : 0;
var desktopMetric = (keyword.DesktopTablet) ? keyword.DesktopTablet[metricKey] : 0;

if(mobileMetric == 0 && desktopMetric == 0) {
//ignore this keyword
return 0;
}

var deviceMetricKey = (spendOrClick === 'spend') ? 'Cost' : 'Clicks';
for(var device in keyword) {
total += keyword[device][deviceMetricKey];
}
return total;
}

// This function sums the results of the click weighted ROAS ratios and calculates
// the final mobile bid adjustment of the campaign.
function getMobileBidAdjustments(entities) {
var mbas = {};
var numerator = 0;
var denominator = 0;

for(var campaignId in entities) {
for(var adGroupId in entities[campaignId]) {
for(var keywordId in entities[campaignId][adGroupId]) {
var keywordData = entities[campaignId][adGroupId][keywordId];
switch(METRIC) {
case 'ROAS' :
numerator += getWeightedRatio(keywordData,'spend');
denominator += getTotal(keywordData,'spend');
break;
case 'RPC' :
numerator += getWeightedRatio(keywordData,'click');
denominator += getTotal(keywordData,'click');
break;
default :
throw 'METRIC needs to be either ROAS, or RPC. Current value is: ' + METRIC;
}
}
if(LEVEL === 'AdGroup') {
Logger.log('Campaign Id: ' + campaignId + ' AdGroupId: ' + adGroupId);
if(!mbas[campaignId]) { mbas[campaignId] = {}; }
mbas[campaignId][adGroupId] = getMba(numerator,denominator);
if(mbas[campaignId][adGroupId] == -1) {
Logger.log('Mobile Bid Adjustment could not be calculated for CampaignId: ' + campaignId + ' and AdGroupId: ' + adGroupId);
delete mbas[campaignId][adGroupId];
}
// Reset the values for the next run
denominator = 0;
numerator = 0;
}
}
if(LEVEL === 'Campaign') {
Logger.log('Campaign Id: ' + campaignId);
mbas[campaignId] = getMba(numerator,denominator);
if(mbas[campaignId] == -1) {
Logger.log('Mobile Bid Adjustment could not be calculated for CampaignId: ' + campaignId);
delete mbas[campaignId];
}
numerator = 0;
denominator = 0;
}
}
return mbas;
}

//This function calculates the Mobile Bid Modifier and trims using MIN and MAX
function getMba(sumOfRatios,total) {
Logger.log('Numerator: ' + sumOfRatios + ' Denominator: ' + total);
var mba = (total != 0) ? round(sumOfRatios/total) : -1;
Logger.log('MBA: ' + (mba==-1) ? 'N/A' : mba + ' (-1 is an error code and the value will be ignored)');
if(mba == -1) {
return mba;
} else {
return (mba < MINIMUM_BID_ADJUSTMENT) ? MINIMUM_BID_ADJUSTMENT :
(mba > MAXIMUM_BID_ADJUSTMENT) ? MAXIMUM_BID_ADJUSTMENT : mba;
}
}

// This function pulls the keyword performance report to get the values needed
// for calculating the ROAS for the keyword.
function getKeywordStats() {
var API_VERSION = { includeZeroImpressions : false };
var query = buildAWQLQuery();
var reportIter = AdWordsApp.report(query, API_VERSION).rows();
var accountMapping = {}; // { campaignId : { adGroupId : { keywordId : { stat : value } } } }
while(reportIter.hasNext()) {
var row = reportIter.next();
//Let's convert all the metrics to floats so we don't have to worry about it later
for(var i in row) {
if(i.indexOf('Id') == -1 && !isNaN(parseFloat(row[i]))) {
row[i] = parseFloat(row[i]);
}
}
var rpc = (row.Clicks != 0) ? round(row.ConversionValue/row.Clicks) : 0;
var roas = (row.AverageCpc != 0) ? round(rpc/row.AverageCpc) : 0;
var cpa = (row.Conversions != 0) ? round(row.Cost/row.Conversions) : 0;
row['Rpc'] = rpc;
row['Roas'] = roas;
row['Cpa'] = cpa;

//Build out the account mapping if needed
if(!accountMapping[row.CampaignId]) {
accountMapping[row.CampaignId] = {};
}
if(!accountMapping[row.CampaignId][row.AdGroupId]) {
accountMapping[row.CampaignId][row.AdGroupId] = {};
}
if(!accountMapping[row.CampaignId][row.AdGroupId][row.Id]) {
accountMapping[row.CampaignId][row.AdGroupId][row.Id] = {};
}
accountMapping[row.CampaignId][row.AdGroupId][row.Id][row.Device.split(' ')[0]] = row;
}
accountMapping = addDesktopTabletEntry(accountMapping);
return accountMapping;
}

// This function will return a valid AWQL query to find keyword performance
function buildAWQLQuery() {
var cols = ['CampaignId','AdGroupId','Id','Device','Clicks','Cost','Conversions','ConversionValue','AverageCpc'];
var report = 'KEYWORDS_PERFORMANCE_REPORT';
return ['select',cols.join(','),'from',report,'during',DATE_RANGE].join(' ');
}

// This function will add an entry into the accountMapping for combining Computers and Tablets
// since the Device targeting is only at that level
function addDesktopTabletEntry(accountMapping) {
for(var campaignId in accountMapping) {
var campaign = accountMapping[campaignId];
for(var adGroupId in campaign) {
var adGroup = campaign[adGroupId];
for(var keywordId in adGroup) {
var devices = adGroup[keywordId];

if(devices.Computers || devices.Tablets) {
accountMapping[campaignId][adGroupId][keywordId]['DesktopTablet'] = combineStats(devices.Computers,devices.Tablets);
}

//Since we combined these, we don't need them anymore
delete accountMapping[campaignId][adGroupId][keywordId]['Computers'];
delete accountMapping[campaignId][adGroupId][keywordId]['Tablets'];
}
}
}
return accountMapping;
}

// This function simply combines the stats of desktop and tablet performance
function combineStats(desktop,tablet) {
if(!desktop) {
desktop = {Cost : 0, Clicks: 0, ConversionValue : 0, Conversions : 0};
}
if(!tablet) {
tablet = {Cost : 0, Clicks: 0, ConversionValue : 0, Conversions : 0};
}
var totalCost = desktop.Cost + tablet.Cost;
var totalClicks = desktop.Clicks + tablet.Clicks;
var totalRevenue = desktop.ConversionValue + tablet.ConversionValue;
var totalConversions = desktop.Conversions + tablet.Conversions;
var averageCpc = (totalClicks != 0) ? round(totalCost/totalClicks) : 0;
var rpc = (totalClicks != 0) ? round(totalRevenue/totalClicks) : 0;
var roas = (averageCpc != 0) ? round(rpc/averageCpc) : 0;
var cpa = (totalConversions != 0) ? round(totalCost/totalConversions) : 0;

return {
Cost : totalCost,
Clicks: totalClicks,
ConversionValue : totalRevenue,
AverageCpc : averageCpc,
Rpc : rpc,
Roas : roas,
Cpa : cpa
};
}

// A helper function to make rounding a little easier
function round(value) {
var decimals = Math.pow(10,DECIMAL_PLACES);
return Math.round(value*decimals)/decimals;
}

// Validate some of the user options just in case
function hasValidOptions() {
var validDates = ['TODAY','YESTERDAY','LAST_7_DAYS','THIS_WEEK_SUN_TODAY',
'THIS_WEEK_MON_TODAY','LAST_WEEK','LAST_14_DAYS',
'LAST_30_DAYS','LAST_BUSINESS_WEEK','LAST_WEEK_SUN_SAT','THIS_MONTH'];
if(DATE_RANGE.indexOf(',') == -1 && validDates.indexOf(DATE_RANGE) == -1) {
throw 'DATE_RANGE is invalid. Current value is: '+DATE_RANGE;
}
if(DECIMAL_PLACES <= 0) {
throw 'You should set DECIMAL_PLACES to be >= 1. You entered: '+DECIMAL_PLACES;
}
if(['Campaign','AdGroup'].indexOf(LEVEL) == -1) {
throw 'LEVEL should be either Campaign or AdGroup. You entered: '+LEVEL;
}
if(['ROAS','RPC'].indexOf(METRIC) == -1) {
throw 'METRIC should be ROAS or RPC. You entered: '+METRIC;
}
if(LEVEL === 'AdGroup' && (!TO || TO.length == 0)) {
throw 'If you set LEVEL to AdGroup, you need to add at least one email.';
}
if(MINIMUM_BID_ADJUSTMENT < 0) {
throw 'MINIMUM_BID_ADJUSTMENT needs to be >= 0. Current value is: '+MINIMUM_BID_ADJUSTMENT;
}
if(MAXIMUM_BID_ADJUSTMENT > 3) {
throw 'MAXIMUM_BID_ADJUSTMENT needs to be <= 3. Current value is: '+MAXIMUM_BID_ADJUSTMENT;
}
}