Guest User

Untitled

a guest
Dec 18th, 2017
68
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 16.85 KB | None | 0 0
  1. /**
  2. * Copying Labels From Keywords To Ads Or Vice-Versa
  3. *
  4. * If a certain percentage of keywords (or ads) are labelled with a particular
  5. * label within an ad group, the script applies the label to all ads (or
  6. * keywords) in that ad group.
  7. *
  8. * Version: 1.0
  9. * Google AdWords Script maintained on brainlabsdigital.com
  10. */
  11.  
  12. //***************************************************************************//
  13. //***************************************************************************//
  14.  
  15. var labelNames = ['Label 1', 'Label 2'];
  16. // Set which labels should be copied.
  17. // For example, ["January Only", "February Only"] would only copy
  18. // the labels 'January Only' and 'February Only'.
  19.  
  20. var copyLabelsFrom = 'Keyword';
  21. // Set which type of entity the labels should be copied from.
  22. // This can take any of two possible values: "Ad" and "Keyword".
  23.  
  24. var copyLabelsTo = 'Ad';
  25. // Set which type of entity the labels should be copied to.
  26. // This can take any of two possible values: "Ad" and "Keyword",
  27. // and it can't have the same value as copyLabelsFrom.
  28.  
  29. var threshold = 0.5;
  30. // The proportion of entities of the type copyLabelsFrom that must be labelled for the
  31. // entities of the type copyLabelsTo to be labelled.
  32. // For instance, if copyLabelsTo is "Keyword" and copyLabelsFrom is "Ad", then 1 means
  33. // the keywords are only labelled if all ads are labelled.
  34. // 0.9 means the keywords are labelled if at least 90% of ads are labelled.
  35. // 0 means the keywords are labelled if at least one ad is labelled.
  36.  
  37. var campaignNameDoesNotContain = [];
  38. // Use this if you want to exclude some campaigns.
  39. // For example ["Display"] would ignore any campaigns with 'Display' in the name,
  40. // while ["Display","Shopping"] would ignore any campaigns with 'Display' or
  41. // 'Shopping' in the name.
  42. // Leave as [] to not exclude any campaigns.
  43.  
  44. var campaignNameContains = [];
  45. // Use this if you only want to look at some campaigns.
  46. // For example ["Brand"] would only look at campaigns with 'Brand' in the name,
  47. // while ["Brand","Generic"] would only look at campaigns with 'Brand' or 'Generic'
  48. // in the name.
  49. // Leave as [] to include all campaigns.
  50.  
  51. var ignorePausedCampaigns = true;
  52. // Set this to true to only look at currently active campaigns.
  53. // Set to false to also include campaigns that are currently paused.
  54.  
  55. var ignorePausedAdGroups = true;
  56. // Set this to true to only look at currently active ad groups.
  57. // Set to false to also include ad groups that are currently paused.
  58.  
  59. var ignorePausedAdsAndKeywords = true;
  60. // Set this to true to only look at currently active keywords and ads.
  61. // Set to false to also include keywords and ads that are currently paused.
  62.  
  63. //***************************************************************************//
  64. //***************************************************************************//
  65.  
  66. var countLabelledEntities = {};
  67. var labelChecks = {};
  68. labelNames.forEach(function(labelName) {
  69. labelChecks[labelName] = false;
  70. });
  71.  
  72. function main()
  73. {
  74. validateEntityTypes(copyLabelsFrom, copyLabelsTo);
  75. validateNumber("threshold", threshold, 0, 1);
  76. validateLabelNames(labelNames);
  77. copyLabels();
  78. checkLabelsAreNotUseless();
  79. }
  80.  
  81. /**
  82. * Works out which proportion of entities are labelled and labels accordingly.
  83. * @return void
  84. */
  85. function copyLabels()
  86. {
  87. var campaignIds = getEntityIds('Campaign', [], []);
  88. var adGroupIds = getEntityIds('AdGroup', campaignIds, []);
  89. Logger.log('Looking at which entities have which labels...');
  90. for (var i = 0; i < adGroupIds.length; i += 10000) {
  91. var batchOfAdGroupIds = adGroupIds.slice(i, i + 10000);
  92. var reportForCopyLabelsFrom = downloadBottomLevelReport(
  93. copyLabelsFrom,
  94. batchOfAdGroupIds
  95. );
  96. var counts = getCounts(reportForCopyLabelsFrom);
  97. updateLabelChecks(counts);
  98. var ratios = getRatios(counts);
  99.  
  100. Logger.log('Labelling ' + copyLabelsTo.toLowerCase() + 's, if appropriate...');
  101. labelEntities(ratios);
  102. logWhatHasBeenLabelled();
  103. }
  104. }
  105.  
  106. /**
  107. * @param Report report
  108. * @return object (keyed by ad group IDs and then by label name)
  109. */
  110. function getCounts(report)
  111. {
  112. var counts = {};
  113. var rows = report.rows();
  114. while (rows.hasNext()) {
  115. var row = rows.next();
  116. var adGroupId = row['AdGroupId'];
  117. if (!(adGroupId in counts)) {
  118. counts[adGroupId] = {};
  119. counts[adGroupId]['Total'] = 0;
  120. }
  121. counts[adGroupId]['Total'] += 1;
  122.  
  123. if (row['Labels'] === "--") {
  124. var labelsThatTheEntityHas = [];
  125. } else {
  126. var labelsThatTheEntityHas = JSON.parse(row['Labels']);
  127. }
  128.  
  129. labelsThatTheEntityHas.forEach(function(entityLabelName) {
  130. if (labelNames.indexOf(entityLabelName) > -1) {
  131. if (!(entityLabelName in counts[adGroupId])) {
  132. counts[adGroupId][entityLabelName] = 0;
  133. }
  134. counts[adGroupId][entityLabelName] += 1;
  135. }
  136. });
  137. }
  138.  
  139. return cleanUpCounts(counts);
  140. }
  141.  
  142. /**
  143. * Removes any values keyed by an ad group ID whose ad group
  144. * does not have any of the relevant labels
  145. * @param object counts
  146. * @return counts
  147. */
  148. function cleanUpCounts(counts)
  149. {
  150. for (var adGroupId in counts) {
  151. var hasSomeLabel = false;
  152. labelNames.forEach(function(labelName) {
  153. if (labelName in counts[adGroupId]) {
  154. hasSomeLabel = true;
  155. }
  156. });
  157. if (!hasSomeLabel) {
  158. delete counts[adGroupId];
  159. }
  160. }
  161. return counts;
  162. }
  163.  
  164. /**
  165. * Calculates ratios from counts
  166. * @param object counts (keyed by ad group IDs and then by label name)
  167. * @return object (keyed in the same way)
  168. */
  169. function getRatios(counts)
  170. {
  171. var ratios = {};
  172. for (var adGroupId in counts) {
  173. ratios[adGroupId] = {};
  174. labelNames.forEach(function(labelName) {
  175. if (labelName in counts[adGroupId]) {
  176. ratios[adGroupId][labelName] = counts[adGroupId][labelName] / counts[adGroupId]['Total'];
  177. }
  178. });
  179. }
  180. return ratios;
  181. }
  182.  
  183. /**
  184. * Breaks everything that needs to be labelled into
  185. * manageable batches and then labels it
  186. * @param object ratios (keyed by ad group IDs and then by label name)
  187. * @return void
  188. */
  189. function labelEntities(ratios)
  190. {
  191. var batchOfAdGroupIds = Object.keys(ratios);
  192. var batchOfIdsToLabel = getEntityIds(
  193. copyLabelsTo,
  194. batchOfAdGroupIds,
  195. []
  196. );
  197. for (var j = 0; j < batchOfIdsToLabel.length; j += 10000) {
  198. var subBatchOfIdsToLabel = batchOfIdsToLabel.slice(j, j + 10000);
  199. labelBatchOfEntities(subBatchOfIdsToLabel, ratios);
  200. }
  201. }
  202.  
  203. /**
  204. * Labels a batch of entities
  205. * @param array batchOfIds
  206. * @param object ratios (keyed by ad group IDs and then by label name)
  207. * @return void
  208. */
  209. function labelBatchOfEntities(batchOfIds, ratios)
  210. {
  211. var selector = getSelector(copyLabelsTo, batchOfIds);
  212. labelNames.forEach(function(labelName) {
  213. selector = selector.withCondition("LabelNames CONTAINS_NONE ['" + labelName + "']");
  214. var iterator = selector.get();
  215. while (iterator.hasNext()) {
  216. var entity = iterator.next();
  217. var adGroupId = entity.getAdGroup().getId();
  218. if (adGroupId in ratios && labelName in ratios[adGroupId]) {
  219. if (ratios[adGroupId][labelName] > threshold) {
  220. entity.applyLabel(labelName);
  221. countLabelledEntities[labelName] += 1;
  222. }
  223. }
  224. }
  225. });
  226. }
  227.  
  228. /**
  229. * Looks at the current state of countLabelledEntities
  230. * and prints a message with this information
  231. * @return void
  232. */
  233. function logWhatHasBeenLabelled()
  234. {
  235. for (var labelName in countLabelledEntities) {
  236. var message = countLabelledEntities[labelName] + " " + copyLabelsTo.toLowerCase() + "s";
  237. message += " have been labelled with the label '" + labelName + "'.";
  238. Logger.log(message);
  239. }
  240. }
  241.  
  242. /**
  243. * Looks through the current counts and updates labelChecks
  244. * if something has been labelled with one of the relevant labels
  245. * @param object counts (keyed by ad group IDs and then by label name)
  246. * @return void
  247. */
  248. function updateLabelChecks(counts)
  249. {
  250. labelNames.forEach(function(labelName) {
  251. for (var adGroupId in counts) {
  252. if (counts[adGroupId][labelName] > 0) {
  253. labelChecks[labelName] = true;
  254. }
  255. }
  256. });
  257. }
  258.  
  259. /**
  260. * Checks, for each label provided, whether there is
  261. * something labelled with it; if not, it logs a warning message
  262. * @return void
  263. */
  264. function checkLabelsAreNotUseless()
  265. {
  266. labelNames.forEach(function(labelName) {
  267. if (!labelChecks[labelName]) {
  268. var message = "Warning: there do not seem to be any " + copyLabelsFrom.toLowerCase() + "s";
  269. message += " labelled with the label name '" + labelName + "'.";
  270. Logger.log(message);
  271. }
  272. });
  273. }
  274.  
  275. /**
  276. * Downloads a report either for ads or for keywords
  277. * @param string entityType (will be passed copyLabelsFrom)
  278. * @param array adGroupIds
  279. * @return Report
  280. */
  281. function downloadBottomLevelReport(entityType, adGroupIds)
  282. {
  283. var whereStatement = "WHERE ";
  284. var idForReport = "Id";
  285. whereStatement += "AdGroupId IN [" + adGroupIds.join(",") + "] AND ";
  286. if (ignorePausedAdsAndKeywords) {
  287. whereStatement += "Status = ENABLED ";
  288. } else {
  289. whereStatement += "Status IN ['ENABLED','PAUSED'] ";
  290. }
  291.  
  292. var query = "SELECT ";
  293. query += idForReport + "," + "AdGroupId,Labels ";
  294. query += "FROM " + getReportType(entityType) + " ";
  295. query += whereStatement;
  296. query += "DURING LAST_30_DAYS";
  297.  
  298. var report = AdWordsApp.report(query);
  299. return report;
  300. }
  301.  
  302. /**
  303. * This function is pretty general. It gets entity IDs with possible filters.
  304. * @param string entityType any of 'Campaign', 'AdGroup', 'Keyword', 'Ad'
  305. * @param array oneLevelUpIds array of IDs to filter by, one level above.
  306. * Pass it an empty array if no filter is required.
  307. * @param array twoLevelsUpIds array of IDs to filter by, two levels above.
  308. * It only makes sense to use this for ads
  309. * and for keywords, and even then only if
  310. * oneLevelUpIds has been passed an empty array.
  311. * Pass it an empty array if no filter is required.
  312. * @return array
  313. * @throws error if entity type is not recognised
  314. * or if no entity IDs pass the filters.
  315. */
  316. function getEntityIds(entityType, oneLevelUpIds, twoLevelsUpIds)
  317. {
  318. var whereStatement = "WHERE ";
  319. var whereStatementsArray = [];
  320. var entityIds = [];
  321.  
  322. switch (entityType) {
  323. case "Campaign":
  324. var idForReport = "CampaignId";
  325. if (ignorePausedCampaigns) {
  326. whereStatement += "CampaignStatus = ENABLED ";
  327. } else {
  328. whereStatement += "CampaignStatus IN ['ENABLED','PAUSED'] ";
  329. }
  330.  
  331. campaignNameDoesNotContain.forEach(function(word) {
  332. whereStatement += "AND CampaignName DOES_NOT_CONTAIN_IGNORE_CASE '" + word.replace(/"/g,'\\\"') + "' ";
  333. });
  334.  
  335. if (campaignNameContains.length === 0) {
  336. whereStatementsArray = [whereStatement];
  337. } else {
  338. campaignNameContains.forEach(function(word) {
  339. whereStatementsArray.push(whereStatement + 'AND CampaignName CONTAINS_IGNORE_CASE "' + word.replace(/"/g,'\\\"') + '" ');
  340. });
  341. }
  342.  
  343. break;
  344.  
  345. case "AdGroup":
  346. var idForReport = "AdGroupId";
  347. if (oneLevelUpIds.length > 0) {
  348. var oneLevelUpName = "Campaign";
  349. whereStatement += oneLevelUpName + "Id IN [" + oneLevelUpIds.join(",") + "] AND ";
  350. }
  351. if (ignorePausedAdGroups) {
  352. whereStatement += "AdGroupStatus = ENABLED ";
  353. } else {
  354. whereStatement += "AdGroupStatus IN ['ENABLED','PAUSED'] ";
  355. }
  356. whereStatementsArray.push(whereStatement);
  357. break;
  358.  
  359. case "Keyword": // Fallthrough
  360. case "Ad":
  361. var idForReport = 'Id';
  362. if (oneLevelUpIds.length > 0) {
  363. var oneLevelUpName = "AdGroup";
  364. whereStatement += oneLevelUpName + "Id IN [" + oneLevelUpIds.join(",") + "] AND ";
  365. }
  366. if (twoLevelsUpIds.length > 0) {
  367. var twoLevelsUpName = "Campaign";
  368. whereStatement += twoLevelsUpName + "Id IN [" + twoLevelsUpIds.join(",") + "] AND ";
  369. }
  370. if (ignorePausedAdsAndKeywords) {
  371. whereStatement += "Status = ENABLED ";
  372. } else {
  373. whereStatement += "Status IN ['ENABLED','PAUSED'] ";
  374. }
  375. whereStatementsArray.push(whereStatement);
  376. break;
  377.  
  378. default:
  379. throw new Error("Type " + entityType + " not recognised");
  380. }
  381.  
  382. whereStatementsArray.forEach(function(statement) {
  383. var query = "SELECT ";
  384. if (entityType === "Keyword" || entityType === "Ad") {
  385. query += idForReport + "," + "AdGroupId ";
  386. } else {
  387. query += idForReport + " ";
  388. }
  389. query += "FROM " + getReportType(entityType) + " ";
  390. query += statement;
  391. query += "DURING LAST_30_DAYS";
  392.  
  393. var report = AdWordsApp.report(query);
  394. var rows = report.rows();
  395. while (rows.hasNext()) {
  396. var row = rows.next();
  397. if (entityType === "Keyword" || entityType === "Ad") {
  398. entityIds.push([row["AdGroupId"], row[idForReport]]);
  399. } else {
  400. entityIds.push(row[idForReport]);
  401. }
  402. }
  403. });
  404.  
  405. if (entityIds.length == 0) {
  406. throw new Error("No " + entityType + "s found with the given settings.");
  407. }
  408.  
  409. if (entityType === "Campaign") {
  410. Logger.log("Number of " + entityType.toLowerCase() + "s found: " + entityIds.length);
  411. }
  412. return entityIds;
  413. }
  414.  
  415. /**
  416. * Returns a selector containing entities with the given IDs
  417. * @param string entityType
  418. * @param array batchOfIds
  419. * @return Selector
  420. * @throws error if entity type is not recognised
  421. */
  422. function getSelector(entityType, batchOfIds)
  423. {
  424. switch (entityType) {
  425. case 'Campaign':
  426. var selector = AdWordsApp.campaigns();
  427. break;
  428. case 'AdGroup':
  429. var selector = AdWordsApp.adGroups();
  430. break;
  431. case 'Ad':
  432. var selector = AdWordsApp.ads();
  433. break;
  434. case 'Keyword':
  435. var selector = AdWordsApp.keywords();
  436. break;
  437. default:
  438. throw new Error("Type '" + entityType + "' not recognised");
  439. }
  440. selector = selector.withIds(batchOfIds);
  441. return selector;
  442. }
  443.  
  444. /**
  445. * Returns the correct name of the report for the given entity
  446. * @param string entityType
  447. * @return string
  448. * @throws error if entity type is not recognised
  449. */
  450. function getReportType(entityType) {
  451. switch(entityType) {
  452. case "Campaign":
  453. return "CAMPAIGN_PERFORMANCE_REPORT";
  454. case "AdGroup":
  455. return "ADGROUP_PERFORMANCE_REPORT";
  456. case "Keyword":
  457. return "KEYWORDS_PERFORMANCE_REPORT";
  458. case "Ad":
  459. return "AD_PERFORMANCE_REPORT";
  460. case "Label":
  461. return "LABEL_REPORT";
  462. default:
  463. throw new Error("Type '" + entityType + "'' not recognised");
  464. }
  465. }
  466.  
  467. /**
  468. * Validates entity types names
  469. * @param string copyLabelsFrom
  470. * @param string copyLabelsTo
  471. * @return void
  472. * @throws error if one of the two entity names is not recognised
  473. * or if they are the same
  474. */
  475. function validateEntityTypes(copyLabelsFrom, copyLabelsTo)
  476. {
  477. copyLabelsFrom = capitaliseCorrectly(copyLabelsFrom);
  478. copyLabelsTo = capitaliseCorrectly(copyLabelsTo);
  479. if (!((copyLabelsFrom === 'Ad' && copyLabelsTo === 'Keyword') ||
  480. (copyLabelsFrom === 'Keyword' && copyLabelsTo === 'Ad'))
  481. ) {
  482. throw new Error("copyLabelsFrom and copyLabelsTo cannot be the same.");
  483. }
  484. }
  485.  
  486. /**
  487. * Makes the entity type name the right capitalisation
  488. * @param string entityType
  489. * @return string
  490. * @throws error if the entity type name is not recognised
  491. */
  492. function capitaliseCorrectly(entityType)
  493. {
  494. var lowerCaseName = entityType.toLowerCase().replace(/ /g,"");
  495. if (lowerCaseName.substr(-1) == "s") {
  496. lowerCaseName = lowerCaseName.slice(0,-1);
  497. }
  498. var correctCapitalisation = {};
  499. correctCapitalisation["keyword"] = "Keyword";
  500. correctCapitalisation["ad"] = "Ad";
  501. if (!(lowerCaseName in correctCapitalisation)) {
  502. throw new Error("Level name '" + entityType + "' not recognised.");
  503. }
  504. return correctCapitalisation[lowerCaseName];
  505. }
  506.  
  507. /**
  508. * Validates a number (to be used for the threshold)
  509. * @param string name
  510. * @param float number
  511. * @param float lowerBound
  512. * @param float upperBound
  513. * @return void
  514. * @throws error if the threshold isn't right
  515. */
  516. function validateNumber(name, number, lowerBound, upperBound)
  517. {
  518. if (isNaN(number)) {
  519. throw new Error(name + " must be a number, '" + number + "' is not.");
  520. }
  521. if (number < lowerBound) {
  522. throw new Error(name + " must be " + lowerBound + " or greater, '" + number + "' is not.");
  523. }
  524. if (number > upperBound) {
  525. throw new Error(name + " must be " + upperBound + " or lower, '" + number + "' is not.");
  526. }
  527. }
  528.  
  529. /**
  530. * Validates the label names
  531. * @param array labelNames
  532. * @return void
  533. * @throws error if no label in the account has one of the given label names
  534. */
  535. function validateLabelNames(labelNames)
  536. {
  537. var labels = AdWordsApp.labels()
  538. .withCondition("Name IN ['" + labelNames.join("','") + "']")
  539. .get();
  540. var existingLabelNames = [];
  541. while (labels.hasNext()) {
  542. var label = labels.next();
  543. existingLabelNames.push(label.getName());
  544. }
  545. labelNames.forEach(function(labelName) {
  546. if (existingLabelNames.indexOf(labelName) > -1) {
  547. countLabelledEntities[labelName] = 0;
  548. } else {
  549. throw new Error("Could not find the label '" + labelName + "'. Please check it is spelt and capitalised correctly.")
  550. }
  551. });
  552. }
Add Comment
Please, Sign In to add comment