Skip to content

Instantly share code, notes, and snippets.

@alewolf
Last active August 25, 2020 11:14
Show Gist options
  • Save alewolf/b37fbd2679afae7abac390090c76c63c to your computer and use it in GitHub Desktop.
Save alewolf/b37fbd2679afae7abac390090c76c63c to your computer and use it in GitHub Desktop.
Generic Keywords Remover
/**
* 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