Last active
June 17, 2024 18:23
-
-
Save rfharmon/8d0614e1fe8c3ea573d41ef3d0713012 to your computer and use it in GitHub Desktop.
This MCC script will find ads in your accounts that are ready for optimization (based on CTR, Conversion Rate, or Cost/Conv). It will send the winning ad copy to a spreadsheet where you can create variations. Then you can run a separate script to post those variations.
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
| // This MCC script will find ads in your accounts that are ready for optimization. | |
| // It will pause lower performing ads, and send the copy from the higher performing | |
| // ad to a spreadsheet. | |
| // | |
| // There, you can tweak the copy or URLs. Then you can run the "Post Ad" script to post the | |
| // variations to your accounts. The script will also find ad groups with only one ad and send | |
| // that ad to the spreadsheet so that you can create a variation of it. | |
| // | |
| // In the spreadsheet, the "Notes" column will say "Better" (if the ad was better than the other) | |
| // "Close" (if it was too close to call), or "Only" (if there was only one ad in the ad group). | |
| // | |
| // Make a copy of the template found at the URL found at: | |
| // https://docs.google.com/spreadsheets/d/1KZRRyvfArzUUwX8oISkj2yo8yj0hWgo9uxnN-gynUzQ/edit?usp=sharing | |
| // Paste your URL below. | |
| var SPREADSHEET_URL = "[INSERT-URL-HERE]"; | |
| var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL); | |
| var sheet = spreadsheet.getSheetByName("Optimize"); | |
| // If you want to restrict the script to one account, enter the account name below. | |
| var SINGLE_ACCOUNT = ""; | |
| // If you want to restrict the script to accounts with a specific label, uncomment | |
| // the line below and the following line in the first variable of the main() function: | |
| // .withCondition("LabelNames CONTAINS '" + LABEL_CONTAINS + "' ") | |
| var LABEL_CONTAINS = ""; | |
| // What metric do you want to compare to determine the winner? | |
| // Choose "CTR", "Conv Rate", or "Cost/Conv". | |
| var COMPARE = "Conv Rate"; | |
| // How big should the difference in CTR be before an ad is selected for optimization? | |
| var EFFECT_SIZE = 0.02; | |
| // How many impressions should an ad have before it is selected for optimization? | |
| var MINIMUM_SAMPLE_SIZE = 1000; | |
| // If the total impressions of two ads averages out to the following number without | |
| // either surpassing the other by the threshold determined above, the better performing | |
| // ad will be selected for optimization. | |
| var TOO_CLOSE = 3000; | |
| var accountName = ""; | |
| var i = firstEmptyRow(); | |
| function firstEmptyRow() { | |
| var column = sheet.getRange('A:A'); | |
| var values = column.getValues(); | |
| var ct = 0; | |
| while (values[ct] && values[ct][0] !== "") { | |
| ct++; | |
| } | |
| return (ct + 1); | |
| } | |
| function main() { | |
| var getAccount = MccApp.accounts() | |
| // .withCondition("LabelNames CONTAINS '" + LABEL_CONTAINS + "' ") | |
| .withCondition("Name CONTAINS_IGNORE_CASE '" + SINGLE_ACCOUNT + "'") | |
| .get(); | |
| while (getAccount.hasNext()) { | |
| var account = getAccount.next(); | |
| MccApp.select(account); | |
| accountName = account.getName(); | |
| Logger.log("Account: " + accountName); | |
| var adGroupIterator = AdWordsApp | |
| .adGroups() | |
| .withCondition('Status="ENABLED"') | |
| .withCondition('AdNetworkType1 IN [SEARCH]') | |
| .withCondition('CampaignStatus = "ENABLED"') | |
| .withCondition('CampaignStatus != "REMOVED"') | |
| .get(); | |
| while (adGroupIterator.hasNext()) { | |
| var adGroup = adGroupIterator.next(); | |
| var campaignName = adGroup.getCampaign().getName(); | |
| var campaignId = adGroup.getCampaign().getId(); | |
| var adGroupName = adGroup.getName(); | |
| var adGroupId = adGroup.getId(); | |
| Logger.log("[ Campaign: " + campaignName + " ] [ Ad Group: " + adGroupName + " ]"); | |
| var adIterator = AdWordsApp | |
| .ads() | |
| .withCondition('Status="ENABLED"') | |
| .withCondition('Type = "EXPANDED_TEXT_AD"') | |
| .withCondition('AdGroupId = "' + adGroupId + '"') | |
| .get(); | |
| var adCount = adIterator.totalNumEntities(); | |
| var adSet = []; | |
| while (adIterator.hasNext()) { | |
| var ad = adIterator.next(); | |
| adSet.push(ad); | |
| } | |
| if (adCount == 1) { | |
| sendToSpreadsheet(ad, "Only", campaignId, adGroupId); | |
| } | |
| if (adCount == 2) { | |
| Logger.log("There are two ads."); | |
| var adOne = adSet[0]; | |
| var adTwo = adSet[1]; | |
| getBetterPerformingAd(adOne, adTwo, campaignId, adGroupId) | |
| } | |
| } | |
| } | |
| } | |
| function getBetterPerformingAd(firstAd, secondAd, campaignId, adGroupId) { | |
| Logger.log("Looking for ads that are ready for optimization."); | |
| var adSet = [firstAd, secondAd]; | |
| var adSetContainer = []; | |
| for (ad in adSet) { | |
| var stats = adSet[ad].getStatsFor("ALL_TIME"); | |
| var adId = adSet[ad].getId(); | |
| var results; | |
| if (COMPARE == "CTR") { | |
| results = stats.getCtr(); | |
| } else if (COMPARE == "Conv Rate") { | |
| results = stats.getConversionRate(); | |
| } else if (COMPARE == "Cost/Conv") { | |
| var cost = stats.getCost(); | |
| var conversions = stats.getConversions(); | |
| results = cost / conversions; | |
| } | |
| var imps = stats.getImpressions(); | |
| var adSets = { | |
| theId: adId, | |
| results: results, | |
| imps: imps, | |
| theAdGroupId: adGroupId | |
| }; | |
| adSetContainer.push(adSets); | |
| if (COMPARE == "Cost/Conv") { | |
| adSetContainer.sort(function(a, b) { | |
| return b.results - a.results; | |
| }); | |
| } else { | |
| adSetContainer.sort(function(a, b) { | |
| return a.results - b.results; | |
| }); | |
| } | |
| } | |
| var adOne = adSetContainer[0]; | |
| var adTwo = adSetContainer[1]; | |
| var betterOrClose = ""; | |
| var resultsDiff = adTwo.results - adOne.results; | |
| var adImps = (adOne.imps + adTwo.imps) / 2; | |
| Logger.log("resultsDiff: " + resultsDiff + " | adImps: " + adImps); | |
| var betterAd = resultsDiff >= EFFECT_SIZE && adImps >= MINIMUM_SAMPLE_SIZE; | |
| var tooClose = resultsDiff < EFFECT_SIZE && adImps >= TOO_CLOSE; | |
| Logger.log("betterAd: " + betterAd + " | tooClose: " + tooClose); | |
| if (betterAd === true || tooClose === true) { | |
| Logger.log("Ads for optimization found."); | |
| if (betterAd === true) { | |
| betterOrClose = "Better"; | |
| Logger.log("This ad is better."); | |
| } | |
| if (tooClose === true) { | |
| betterOrClose = "Close"; | |
| Logger.log("This one is too close to call."); | |
| } | |
| var idLow = adSetContainer[0].theId; | |
| var idHigh = adSetContainer[1].theId; | |
| var agIdLow = adSetContainer[0].theAdGroupId; | |
| var agIdHigh = adSetContainer[1].theAdGroupId; | |
| var adToPause = AdWordsApp.ads().withIds([ | |
| [agIdLow, idLow] | |
| ]).get().next(); | |
| adToPause.pause(); | |
| var idAdHighIter = AdWordsApp.ads().withIds([ | |
| [agIdHigh, idHigh] | |
| ]).get().next(); | |
| sendToSpreadsheet(idAdHighIter, betterOrClose, campaignId, agIdLow, idLow); | |
| } | |
| } | |
| function sendToSpreadsheet(adToCopy, betterOrNo, campaignId, agId, adToPause) { | |
| Logger.log("Sending to spreadsheet."); | |
| var campaignCopy = adToCopy.getCampaign().getName(); | |
| var adGroupCopy = adToCopy.getAdGroup().getName(); | |
| var head1Copy = adToCopy.getHeadlinePart1(); | |
| var head2Copy = adToCopy.getHeadlinePart2(); | |
| var descCopy = adToCopy.getDescription(); | |
| var path1Copy = adToCopy.getPath1(); | |
| var path2Copy = adToCopy.getPath2(); | |
| var finalUrlCopy = adToCopy.urls().getFinalUrl(); | |
| sheet.getRange(i, 1).setValue(accountName); | |
| sheet.getRange(i, 2).setValue(campaignCopy); | |
| sheet.getRange(i, 3).setValue(adGroupCopy); | |
| sheet.getRange(i, 4).setValue(head1Copy); | |
| sheet.getRange(i, 5).setValue(head2Copy); | |
| sheet.getRange(i, 6).setValue(descCopy); | |
| sheet.getRange(i, 7).setValue(path1Copy); | |
| sheet.getRange(i, 8).setValue(path2Copy); | |
| sheet.getRange(i, 9).setValue(finalUrlCopy); | |
| sheet.getRange(i, 10).setValue(betterOrNo); | |
| sheet.getRange(i, 11).setValue("No"); | |
| sheet.getRange(i, 12).setValue(campaignId); | |
| sheet.getRange(i, 13).setValue(agId); | |
| i++; | |
| } |
Author
I made an update. Thanks for bringing this to my attention! @yaangjieyu
Hey again @rfharmon, you are super quick bro, really appreciate that!
I'm still getting this error though:
20:53:41.295 Document 1P5bQVkCWNhzYeF2rF7mGQ62GU-3E9Yq_YxWBzaxBjww is missing (perhaps it was deleted?) (line 16)
Author
That's a problem with your spreadsheet. Did you make a copy of the template and then enter that link in the script?
Hey @rharmon. Yup I used a copy for the link. It could run the first time
Hey @rfharmon, pardon me asking again. Based on the link you shared, do you reckon its related to access you give on the spreadsheet?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi rfharmon,
Thanks for building this out.
Small error encountered: "The label General does not exist. (line 187)" - Does this mean I would have to create that label for every account in my MCC? Or will there be a workaround for this.
Cheers