Last active
August 25, 2020 11:14
-
-
Save alewolf/b37fbd2679afae7abac390090c76c63c to your computer and use it in GitHub Desktop.
Generic Keywords Remover
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Title: Generic Keywords Remover | |
* Descritpion: Remove generic keywords from branded search campaigns | |
* Author: Wolf+Bär Agency, Aleksandar Vucenovic | |
* Website: https://wolfundbaer.ch | |
* License: GNU GPLv3 | |
* Version: 0.2 | |
* URL: https://gist.github.com/alewolf/b37fbd2679afae7abac390090c76c63c | |
* URL: | |
*/ | |
/********* START Description ************************************************ | |
* | |
* Removes all generic keywords for a campaign by detecting generic keywords | |
* and adding them as exact match negative keywords to that campaign. | |
* | |
* A single campaign currently can only contain a max. of 10'000 negative | |
* keywords. | |
* A shared negeative keyword list can only contain a max. of 5'000 | |
* negative keywords. | |
* | |
* Therefore we need to limit adding negative keywords as much as possible. | |
* | |
* The otpimal tactics are: | |
* | |
* A) Create a shared negative keyword list which you add to all branded | |
* campaigns. Maintain the list manually and regularly until it reaches | |
* approx. 5'000 negative keywords. | |
* B) Automatically add new negative generic keywords to the branded | |
* campaigns by running this script. It will add negative generic | |
* keywords in the intervals you choose. Make sure that you set the | |
* appropriate lookback window in the settings below. Also, you have to | |
* specifiy which campaigns this script should process. | |
* C) Limit addition of negative longtail search terms to a maximum of words. | |
* A good threshold is somewhere around four to six words. | |
* You can choose your limit in the settings below. | |
* | |
********** END Description **************************************************/ | |
/********* START Settings **************************************************/ | |
// The name of the shared negative keyword list with your brand terms | |
var NEG_LIST_BRAND = 'LIST_NAME'; | |
// The maximim word limit. If the search term is over the limit it will not | |
// be added to the negative list. | |
var MAX_WORD_COUNT_THRESHOLD = 5; | |
// The maximim of how many negative keywords can be added to a campaign | |
// before the scripts automatically stops execution and sends | |
// an email alert. | |
var MAX_NEG_KEYWORDS_COUNT = 9500; | |
// A list of all branded campaigns where you want to exclude generic | |
// search terms. (format this as a JavaScript array) | |
var ARRAY_OF_BRANDED_KEYWORDS_CAMPAIGNS = [ | |
'CAMPAIGN_NAME', | |
]; | |
// The timeframe which is used to select the search terms of the past. | |
// Use the Google Ads Script documentation to choose other timeframes. | |
// Make sure to run the script in a frequency that matches the | |
// lookback window. | |
var LOOKBACK_WINDOW = 'YESTERDAY'; | |
// A list of comma separated emails that will receive an alert if anything | |
// goes wrong. | |
// One of the emails MUST belong to the account that authorizes | |
// the script to run. | |
var ALERT_EMAIL = 'EMAIL_ADDRESSES'; | |
/********* END Settings ***************************************************/ | |
/********* START Global Variables *****************************************/ | |
// var timeZone = AdWordsApp.currentAccount().getTimeZone(); | |
/********* END Global Variables *******************************************/ | |
function main() { | |
var accountId = AdsApp.currentAccount().getCustomerId(); | |
var accountName = AdsApp.currentAccount().getName(); | |
Logger.log('account id: ' + accountName); | |
// Only continue if the negative brand list exists, | |
// otherwise stop the execution and send an alert | |
doesListExist(accountId, accountName, NEG_LIST_BRAND); | |
// load brand keywords into array | |
var brand_negative_keywords = AdsApp.negativeKeywordLists() | |
.withCondition('Name = "' + NEG_LIST_BRAND + '"') | |
.withCondition("Status = ACTIVE") | |
.get() | |
.next() | |
.negativeKeywords() | |
.get(); | |
var brand_keyword_array = []; | |
while(brand_negative_keywords.hasNext()){ | |
var brand_keyword = brand_negative_keywords.next(); | |
brand_keyword_array.push(brand_keyword.getText()); | |
} | |
// Clean up matching operators and duplicates | |
brand_keyword_array = removeMatchOperators(brand_keyword_array); | |
Logger.log('brand_keyword_array: ' + brand_keyword_array.toString()); | |
// Get the branded campaings names and remove generic keywords | |
ARRAY_OF_BRANDED_KEYWORDS_CAMPAIGNS.forEach(function(campaign_name){ | |
Logger.log('campaign name: ' + campaign_name); | |
var campaign_type = getCampaignTypeByCampaignName(campaign_name); | |
var campaign_neg_keyword_number; | |
if (campaign_type == 'Search') { | |
campaign_neg_keyword_number = AdsApp | |
.campaigns() | |
.withCondition('Name = "' + campaign_name + '"') | |
.get() | |
.next() | |
.negativeKeywords() | |
.get() | |
.totalNumEntities(); | |
} else if (campaign_type == 'Shopping') { | |
campaign_neg_keyword_number = AdsApp | |
.shoppingCampaigns() | |
.withCondition('Name = "' + campaign_name + '"') | |
.get() | |
.next() | |
.negativeKeywords() | |
.get() | |
.totalNumEntities(); | |
} | |
// Abort and send alert if the campaign contains already more | |
// than the specified maximum negative keywords | |
if(campaign_neg_keyword_number > MAX_NEG_KEYWORDS_COUNT){ | |
Logger.log('The negative keyword list of the campaign ' + campaign_name + ' has become to large'); | |
negKeywordListTooLargeAlert(accountId, accountName, campaign_name); | |
throw new Error('stopped execution of script'); | |
} | |
// Get the search terms of the set time frame | |
var report = AdsApp.report( | |
"SELECT Query" + | |
" FROM SEARCH_QUERY_PERFORMANCE_REPORT " + | |
" WHERE CampaignName = '" + campaign_name + "'" + | |
" DURING " + LOOKBACK_WINDOW | |
); | |
var rows = report.rows(); | |
while(rows.hasNext()) | |
{ | |
var row = rows.next(); | |
var query_text = row['Query']; | |
// Add the search terms as negatives to the campaign if they don't contain a brand keyword | |
// and if they are shorter or equal as the set word limit. | |
if (! new RegExp(brand_keyword_array.join("|")).test(query_text) && keywordShorterThan(query_text)) | |
{ | |
// Logger.log(query_text); | |
if (campaign_type == 'Search'){ | |
AdsApp | |
.campaigns() | |
.withCondition('Name = "' + campaign_name + '"') | |
.get() | |
.next() | |
.createNegativeKeyword('[' + query_text + ']'); | |
} else if (campaign_type == 'Shopping'){ | |
AdsApp | |
.shoppingCampaigns() | |
.withCondition('Name = "' + campaign_name + '"') | |
.get() | |
.next() | |
.createNegativeKeyword('[' + query_text + ']'); | |
} | |
} | |
} | |
}); | |
Logger.log('finished without errors'); | |
} // end main | |
function getCampaignTypeByCampaignName(campaign_name){ | |
var report = AdWordsApp.report( | |
"SELECT CampaignName, AdvertisingChannelType " + | |
"FROM CAMPAIGN_PERFORMANCE_REPORT " + | |
"WHERE CampaignName = '" + campaign_name + "'"); | |
var row = report.rows().next(); | |
return row['AdvertisingChannelType']; | |
} // end getCampaignTypeByCampaignName() | |
function keywordShorterThan(query_text){ | |
if(query_text.split(' ').length <= MAX_WORD_COUNT_THRESHOLD ){ | |
return true; | |
} else { | |
return false; | |
} | |
} | |
function doesListExist(accountId, accountName, list_name){ | |
if(! AdsApp.negativeKeywordLists() | |
.withCondition('Name = "' + list_name + '"') | |
.withCondition("Status = ACTIVE") | |
.get().hasNext()){ | |
Logger.log(list_name + ' doesn\'t exists'); | |
listAlert(accountId, accountName, list_name); | |
throw new Error('stopped execution of script'); | |
} | |
} // end doesListExist() | |
function negKeywordListTooLargeAlert(accountId, accountName, campaign_name){ | |
var subject = 'negative keyword list of campaign ' + campaign_name + ' has become too large'; | |
var str1 = 'WARNING: The negative keyword list for the campaign ' + campaign_name + ' has become too large !' + '\n<br><br>'; | |
var str2 = 'Account ID: ' + accountId + '\n<br>'; | |
var str3 = 'Account Name: ' + accountName + '\n<br>'; | |
var str4 = 'Campaign Name: ' + campaign_name + '\n<br>'; | |
var body = str1.concat(str2, str3, str4); | |
sendEmail(subject, body); | |
} // end emailAlert() | |
function listAlert(accountId, accountName, list_name){ | |
var subject = 'negative shared keyword list missing'; | |
var str1 = 'WARNING: shared negative keyword list missing' + '\n<br><br>'; | |
var str2 = 'Account ID: ' + accountId + '\n<br>'; | |
var str3 = 'Account Name: ' + accountName + '\n<br>'; | |
var str4 = 'List Name: ' + list_name + '\n<br>'; | |
var body = str1.concat(str2, str3, str4); | |
sendEmail(subject, body); | |
} // end emailAlert() | |
function sendEmail(subject, body){ | |
MailApp.sendEmail({ | |
to: ALERT_EMAIL, | |
subject: subject, | |
htmlBody: body, | |
noReply: true, | |
}); | |
Logger.log('alert email sent'); | |
} // end sendEmail() | |
function removeMatchOperators(keyword_array){ | |
keyword_array.forEach(function(part, index){ | |
this[index] = this[index].replace(/[\[\]"+]/g, ''); | |
// Logger.log('TEST: ' + this[index]); | |
}, keyword_array); | |
var keyword_array_unique = keyword_array.filter(function(item, pos, self) { | |
return self.indexOf(item) == pos; | |
}); | |
return keyword_array_unique; | |
} // end removeMatchOperators |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment