Making good things better is… good. Take campaign-level audiences — they’re good, but wouldn’t meta-campaign-level audiences be even better? Sure.
To reiterate, campaign level audiences are great. You don’t have quite as fine control as with ad group-level audiences (and it’s annoying you can’t have ad group and campaign audiences together!), but they’re much easier to keep track of, because there are a lot fewer of them.
(Plus, there’s a pesky limit of 5 million ad group targeting criteria in an account — which sounds like a lot but is easy to eat up when you’ve got lots of keywords, lots of negatives, a granular structure, duplication for different locations, and then want a handful of audiences in each group.)
But there’s still some slightly tedious work involved in making sure you’ve attached audiences to all campaigns. And not to be rude to humans, but this does leave room for some good old human error (e.g., forgetting to add to new campaigns, for whatever reason).
Obviously, the tech team at Brainlabs (my employer) couldn’t resist the opportunity for some script-based innovation. It’s a bit like the one I shared a few months ago to copy extensions and lists across your account, which was a classic. You set up the audiences (and bid adjustments) you want in one campaign, then the script uses that as a template and copies those audiences to all the other campaigns.
The script won’t remove existing audiences, but if a campaign was already targeting an audience, then the bid modifier may be overwritten. So, when audiences have been copied into a campaign, that campaign is labeled so the script ignores it on future runs — then you can exclude the audiences or change the modifiers however you like. You can rerun the script whenever you feel like it, to make sure any new campaigns have the audiences set up, and it won’t mess up the tweaks you’ve made in the old campaigns.
Sadly, you have to delete any ad group-level audiences before running the script. If there’s a campaign that has ad group audiences, even if they’re paused, then the script can copy campaign-level negative audiences, but not positive ones.
If you want to give it a go, copy the code below into a new AdWords Script in your account. Then change some of the options:
- campaignNameContains is used to filter the campaigns the script looks at: if you only want the script to look at campaigns which have certain words or phrases in the names, put those words or phrases in the square brackets, in double quotes and separated with commas. For example, if campaignNameContains is
["Brand", "Generic"],
then only campaigns with names containing “brand” or “generic” are included. - campaignNameDoesNotContain is the same, but for words or phrases in the names of campaigns you want the script to ignore. For example, if campaignNameDoesNotContain is
["Display", "Competitor"]
, then any campaigns with names containing “display” or “competitor” are ignored.- campaignNameContains and campaignNameDoesNotContain are not case-sensitive.
- Leave them blank,
[]
, to cover all campaigns. - If you need to put a double quote into either, put a backslash before it.
- If ignorePausedCampaigns is true, then the script will only look at currently active campaigns. Set this to false if you want to apply audiences to currently paused campaigns.
- If includeShoppingCampaigns is true, the audiences will be copied to Shopping campaigns, as well as Search campaigns.
- campaignToCopy is the name of the template campaign whose audiences will be copied. This is case-sensitive.
- The template campaign can be paused, but it can’t be removed.
- labelName is the name of the label which will be applied to campaigns once they’ve had the audiences added. This means you can see which campaigns have been covered. Campaigns that already have the label will be ignored; if the script doesn’t cover them all in one run, it can go again until it covers them all.
Remember, you need to delete all ad group-level audiences before running the script! Otherwise you’ll get errors where the audiences couldn’t be added. If you have campaigns where you want to keep the ad group audiences, you may want to get the script to skip over them entirely: you can put their names in campaignNameDoesNotContain, or you could label them with the labelName used to show which campaigns already have audiences.
Notes:
- This doesn’t work on Universal App or Video campaigns.
- Scripts can only run for 30 minutes, which may not be enough time if you’ve got lots of campaigns or lots of audiences. But if the script times out, you can just run it again — it will say in the logs when everything has been covered.
/** | |
* | |
* Campaign Audience Copying | |
* | |
* This script takes the audiences (and audience bid adjustments) applied to one | |
* template campaign and applies them to all other campaigns that match the | |
* filters. Campaigns are then labelled. | |
* | |
* Version: 1.0 | |
* Google AdWords Script maintained on brainlabsdigital.com | |
* | |
**/ | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// | |
//Options | |
var campaignNameDoesNotContain = []; | |
// Use this if you want to exclude some campaigns. | |
// For example ["Display"] would ignore any campaigns with 'Display' in the name, | |
// while ["Display","Competitors"] would ignore any campaigns with 'display' or | |
// 'competitors' in the name. Case insensitive. | |
// Leave as [] to not exclude any campaigns. | |
var campaignNameContains = []; | |
// Use this if you only want to look at some campaigns. | |
// For example ["Brand"] would only look at campaigns with 'Brand' in the name, | |
// while ["Brand","Generic"] would only look at campaigns with 'brand' or 'generic' | |
// in the name. Case insensitive. | |
// Leave as [] to include all campaigns. | |
var ignorePausedCampaigns = true; | |
// Set this to true to only look at currently active campaigns. | |
// Set to false to also include campaigns that are currently paused. | |
var includeShoppingCampaigns = true; | |
// True if you want to copy audiences in Shopping campaigns. | |
// False if you're looking only at Search and Display campaigns. | |
// The template campaign can only be a shopping campaign if this is true. | |
var campaignToCopy = "TEMPLATE CAMPAIGN NAME HERE"; | |
// This is the name of the template campaign which has the desired audiences | |
// already applied. | |
// Audiences shared with this campaign will be shared with the other campaigns. | |
// Case sensitive! | |
var labelName = "Campaign audience applied"; | |
// Once a campaign has had all the audiences added, it will be labelled with this. | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// | |
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~// | |
function main() { | |
// Find or create the campaign label | |
var labels = AdWordsApp.labels().withCondition("Name = '" + labelName + "'").get(); | |
if (!labels.hasNext()) { | |
// If the label does not exist, we create it | |
AdWordsApp.createLabel(labelName); | |
labels = AdWordsApp.labels().withCondition("Name = '" + labelName + "'").get(); | |
} | |
if (AdWordsApp.getExecutionInfo().isPreview() && !labels.hasNext()) { | |
// We can't create labels when previewing scripts, so if this is a preview run | |
// and the label still doesn't exist we use a dummy value for the ID | |
// (as we know nothing can be labelled with the non-existent label anyway) | |
var labelId = 0; | |
} else { | |
var labelId = labels.next().getId(); | |
} | |
// Get the template campaign | |
var templateCampaigns = AdWordsApp.campaigns() | |
.withCondition('CampaignName = "' + campaignToCopy.replace(/"/g,'\\\"') + '"') | |
.withCondition("CampaignStatus IN [ENABLED, PAUSED]") | |
.get(); | |
if (includeShoppingCampaigns && !templateCampaigns.hasNext()) { | |
var templateCampaigns = AdWordsApp.shoppingCampaigns() | |
.withCondition('CampaignName = "' + campaignToCopy.replace(/"/g,'\\\"') + '"') | |
.withCondition("CampaignStatus IN [ENABLED, PAUSED]") | |
.get(); | |
} | |
if (!templateCampaigns.hasNext()) { | |
throw("No template campaign called '" + campaignToCopy + "' found."); | |
} | |
// There should be precisely one campaign in the iterator, because there should be | |
// precisely one template campaign to look at. So we don't use a while loop, we just | |
// look at the first campaign in the iterator. | |
var templateCampaign = templateCampaigns.next(); | |
// Label the template campaign so it's ignored later on | |
if (!templateCampaign.labels().get().hasNext() || !templateCampaign.labels().withIds([labelId]).get().hasNext()) { | |
templateCampaign.applyLabel(labelName); | |
} | |
var targetingSetting = templateCampaign.targeting().getTargetingSetting("USER_INTEREST_AND_LIST"); | |
var audiences = templateCampaign.targeting().audiences().get(); | |
var audiencesToCopy = {}; | |
while (audiences.hasNext()) { | |
var audience = audiences.next(); | |
audiencesToCopy[audience.getAudienceId()] = audience.bidding().getBidModifier(); | |
} | |
var countAudiences = Object.keys(audiencesToCopy).length; | |
Logger.log(countAudiences + " audience(s) found."); | |
var negativeAudiences = templateCampaign.targeting().excludedAudiences().get(); | |
var negativeAudiencesToCopy = {}; | |
while (negativeAudiences.hasNext()) { | |
var audience = negativeAudiences.next(); | |
negativeAudiencesToCopy[audience.getAudienceId()] = "-"; | |
} | |
var countNegativeAudiences = Object.keys(negativeAudiencesToCopy).length; | |
Logger.log(countNegativeAudiences + " negative audience(s) found."); | |
if (countAudiences + countNegativeAudiences == 0) { | |
throw("No audiences found to copy. Please check they are applied to template campaign '" + campaignToCopy + "'."); | |
} | |
// Get all the campaign IDs (based on campaignNameDoesNotContain, campaignNameContains | |
// and ignorePausedCampaigns options). | |
// This ignores labelling - if there are no campaigns it must be because the options | |
// are set incorrectly, so it throws an error. | |
var campaignIds = getCampaignIds(); | |
var campaignCount = 0; | |
// Make an iterator of the campaigns that match the settings and are not labelled | |
var campaignSelectors = [AdWordsApp.campaigns()]; | |
if (includeShoppingCampaigns) { | |
campaignSelectors.push(AdWordsApp.shoppingCampaigns()); | |
} | |
for (var i=0; i<campaignSelectors.length; i++) { | |
var campaigns = campaignSelectors[i] | |
.withCondition("CampaignId IN [" + campaignIds.join(",") + "]") | |
.withCondition("Labels CONTAINS_NONE [" + labelId + "]") | |
.get(); | |
// Go through each campaign and apply the audiences | |
while (campaigns.hasNext()) { | |
var campaign = campaigns.next(); | |
var success = true; | |
campaign.targeting().setTargetingSetting("USER_INTEREST_AND_LIST", targetingSetting); | |
for (var audienceID in audiencesToCopy) { | |
var audienceBuilder = campaign.targeting().newUserListBuilder().withAudienceId(audienceID).withBidModifier(audiencesToCopy[audienceID]).build(); | |
if (!audienceBuilder.isSuccessful()) { | |
success = false; | |
} | |
} | |
for (var audienceID in negativeAudiencesToCopy) { | |
var audienceBuilder = campaign.targeting().newUserListBuilder().withAudienceId(audienceID).exclude(); | |
if (!audienceBuilder.isSuccessful()) { | |
success = false; | |
} | |
} | |
if (success) { | |
campaign.applyLabel(labelName); // Label the campaign if the audiences have been applied | |
} | |
campaignCount++; | |
if (campaignCount%100 == 0) { | |
Logger.log("Applied lists to " + campaignCount + " campaigns so far"); | |
} | |
} | |
} | |
if (campaignCount == 0) { | |
Logger.log(campaignIds.length + " campaigns match the settings, but all were labelled with '" + labelName + "'. This suggests the Audiences have been applied to everything."); | |
} else { | |
Logger.log("Finished. Audiences applied to " + campaignCount + " campaigns."); | |
} | |
} | |
// Get the IDs of campaigns which match the given options | |
function getCampaignIds() { | |
var whereStatement = "WHERE "; | |
var whereStatementsArray = []; | |
var campaignIds = []; | |
if (ignorePausedCampaigns) { | |
whereStatement += "CampaignStatus = ENABLED "; | |
} else { | |
whereStatement += "CampaignStatus IN ['ENABLED','PAUSED'] "; | |
} | |
for (var i=0; i<campaignNameDoesNotContain.length; i++) { | |
whereStatement += "AND CampaignName DOES_NOT_CONTAIN_IGNORE_CASE '" + campaignNameDoesNotContain[i].replace(/"/g,'\\\"') + "' "; | |
} | |
if (campaignNameContains.length == 0) { | |
whereStatementsArray = [whereStatement]; | |
} else { | |
for (var i=0; i<campaignNameContains.length; i++) { | |
whereStatementsArray.push(whereStatement + 'AND CampaignName CONTAINS_IGNORE_CASE "' + campaignNameContains[i].replace(/"/g,'\\\"') + '" '); | |
} | |
} | |
for (var i=0; i<whereStatementsArray.length; i++) { | |
var adTextReport = AdWordsApp.report( | |
"SELECT CampaignId " + | |
"FROM CAMPAIGN_PERFORMANCE_REPORT " + | |
whereStatementsArray[i] + | |
"DURING LAST_30_DAYS"); | |
var rows = adTextReport.rows(); | |
while (rows.hasNext()) { | |
var row = rows.next(); | |
campaignIds.push(row['CampaignId']); | |
} | |
} | |
if (campaignIds.length == 0) { | |
throw("No campaigns found with the given settings."); | |
} | |
return campaignIds; | |
} |
The post Here’s a script that copies audiences to all your campaigns appeared first on Search Engine Land.
from SEO Rank Video Blog http://ift.tt/2pCYmcA
via IFTTT
No comments:
Post a Comment