Коригування ставок рекламних кампаній за часом доби в Google AdWords

Багато рекламодавців стикаються з тим, що рекламні кампанії працюють на протязі доби, і, відповідно, витрачають бюджет цілодобово, при цьому конверсії (продажі, дзвінки) зазвичай припадають на певні години, тоді як в інший час доби спостерігається повна їх відсутність. Можна використовувати вбудований  розклад AdWords, але існує обмеження, на кількість тимчасових інтервалів в розкладі (не більше 6 інтервалів в день).

расписание показа объявлений google adwords

Вирішення цього питання розробила команда BrainLabs.
Все що потрібно – це скрипт, таблиця в Google Sheets і дані з Вашої системи аналітики.

Етап перший – підготовка даних з Google Analytics.

Для прикладу використовуємо дані з по eCommerce проекту, конкретно – кількість транзакцій, з прив’язкою до днів тижня (вибірка для прикладу бралася за 3 місяці).
Створюємо користувальницький звіт, де в метриках вибираємо коефіцієнт транзакцій (Ecommerce Conversion Rate) сесії (щоб уникнути порожніх рядків і пропущених значень в подальшому), в параметрах – дні тижня. Додаємо фільтр по каналу AdWords.
фильтр по каналу AdWords

В отриманому звіті, як вторинний параметр вибираємо «Годинник» і експортуємо в Excel. З даних в вивантаженні, створюємо зведену таблицю, в якій рядки – це годинник, стовпці – дні тижня, значення – коефіцієнт транзакцій. Для візуальної наочності можна використовувати умовне форматування.
расширенное расписание google adwords

При такому поданні стає видно, коли рекламні кампанії контекстної реклами ефективні, а коли – ні.
Наступний крок – перетворимо дані в таблиці. Для цього – копіюємо зведену таблицю на новий лист і дублюємо там-же. В клітинках значень однієї з таблиць, на Ваш розсуд, вираховуємо відхилення коефіцієнта конверсії в заданий час щодо середнього значення для цього часу доби (вираховується при створенні зведеної таблиці). Для наочності використовуємо умовне форматування.
расширенное расписание google adwords

Наступний крок – використання скрипта і розкладу від BrainLabs.
Суть роботи скрипта полягає в наступному:

  1. В Google таблицях створюється розклад аналогічний вивантаженню наведеному вище.
  2. Для завдання коригувань можемо використовувати отриману вище таблицю. Варто враховувати, що максимально допустимий коефіцієнт коригування для AdWords: 900% на підвищення (+ 300% для мобільних) і -100% на зниження, тобто якщо ми вкажемо коригування -100% – реклама буде зупинена. При вказівці коригування 300%, при обраної нами ставки за клік в 1 $, після застосування розкладу, максимальна ставка за клік буде 3 $.

расширенное расписание google adwords3. Коефіцієнти, звичайно, варто підправити під свою РК. Підсумкова таблиця буде мати такий вигляд:

расширенное расписание google adwords4. Додаємо сам скрипт, наведений нижче, до облікового запису AdWords.

  1. /*
  2. *
  3. * Advanced ad scheduling
  4. *
  5. * This script will apply ad schedules to campaigns or shopping campaigns and set the
  6. * ad schedule bid modifier and mobile bid modifier at each hour according to
  7. * multiplier timetables in a Google sheet.
  8. *
  9. * Version: 2.0
  10. * brainlabsdigital.com
  11. *
  12. */
  13. function main() {
  14. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
  15. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
  16. //Options
  17. //The Google sheet to use
  18. //The default value is the example sheet linked to in the article
  19. var spreadsheetUrl = “https://docs.google.com/a/brainlabsdigital.com/spreadsheets/d/1JDGBPs2qyGdHd94BRZw9lE9JFtoTaB2AmlL7xcmLx2g/edit#gid=0”;
  20. //Shopping or regular campaigns
  21. //Use true if you want to run script on shopping campaigns (not regular campaigns).
  22. //Use false for regular campaigns.
  23. var shoppingCampaigns = false;
  24. //Use true if you want to set mobile bid adjustments as well as ad schedules.
  25. //Use false to just set ad schedules.
  26. var runMobileBids = true;
  27. //Optional parameters for filtering campaign names. The matching is case insensitive.
  28. //Select which campaigns to exclude e.g [“foo”, “bar”] will ignore all campaigns
  29. //whose name contains ‘foo’ or ‘bar’. Leave blank [] to not exclude any campaigns.
  30. var excludeCampaignNameContains = [];
  31. //Select which campaigns to include e.g [“foo”, “bar”] will include only campaigns
  32. //whose name contains ‘foo’ or ‘bar’. Leave blank [] to include all campaigns.
  33. var includeCampaignNameContains = [];
  34. //When you want to stop running the ad scheduling for good, set the lastRun
  35. //variable to true to remove all ad schedules and mobile bid modifiers.
  36. var lastRun = false;
  37. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
  38. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
  39. //Initialise for use later.
  40. var weekDays = [“MONDAY”, “TUESDAY”, “WEDNESDAY”, “THURSDAY”, “FRIDAY”, “SATURDAY”, “SUNDAY”];
  41. var adScheduleCodes = [];
  42. var campaignIds = [];
  43. //Retrieving up hourly data
  44. var scheduleRange = “B2:H25”;
  45. var accountName = AdWordsApp.currentAccount().getName();
  46. var spreadsheet = SpreadsheetApp.openByUrl(spreadsheetUrl);
  47. var sheets = spreadsheet.getSheets();
  48. var timeZone = AdWordsApp.currentAccount().getTimeZone();
  49. var date = new Date();
  50. var dayOfWeek = parseInt(Utilities.formatDate(date, timeZone, “uu”), 10) – 1;
  51. var hour = parseInt(Utilities.formatDate(date, timeZone, “HH”), 10);
  52. var sheet = sheets[0];
  53. var data = sheet.getRange(scheduleRange).getValues();
  54. //This hour’s bid multiplier.
  55. var thisHourMultiplier = data[hour][dayOfWeek];
  56. var lastHourCell = “I2”;
  57. getRange(lastHourCell).setValue(thisHourMultiplier);
  58. //The next few hours’ multipliers
  59. var timesAndModifiers = [];
  60. for (var h=0; h<4; h++) {
  61. var newHour = (hour + h)%24;
  62. if (hour + h > 23) {
  63. var newDay = (dayOfWeek + 1)%7;
  64. } else {
  65. var newDay = dayOfWeek;
  66. }
  67. var bidModifier = data[newHour][newDay];
  68. if (isNaN(bidModifier) || (bidModifier < -0.9 && bidModifier > -1) || bidModifier > 9) {
  69. log(“Bid modifier ‘” + bidModifier + “‘ for ” + weekDays[newDay] + ” ” + newHour + ” is not valid.”);
  70. push([newHour, weekDays[newDay], 0]);
  71. } else if (bidModifier != -1 && bidModifier.length != 0) {
  72. push([newHour, weekDays[newDay], bidModifier]);
  73. }
  74. }
  75. if (timesAndModifiers.length === 0){
  76. // If there are no modifiers then the campaigns should not be running for the next few hours.
  77. // If we just removed all schedules, then ads would start running (with no modifier)
  78. // so an arbitrary ad schedule is created (scheduled for yesterday so it does not affect anything).
  79. push([0,weekDays[(dayOfWeek+6)%7],0.0]);
  80. }
  81. //Pull a list of all relevant campaign IDs in the account.
  82. var campaignSelector = ConstructIterator(shoppingCampaigns)
  83. for(var i = 0; i < excludeCampaignNameContains.length; i++){
  84. campaignSelector = campaignSelector.withCondition(‘Name DOES_NOT_CONTAIN_IGNORE_CASE “‘ + excludeCampaignNameContains[i] + ‘”‘);
  85. }
  86. var campaignIterator = campaignSelector.get();
  87. while(campaignIterator.hasNext()){
  88. var campaign = campaignIterator.next();
  89. var campaignName = campaign.getName();
  90. var includeCampaign = false;
  91. if(includeCampaignNameContains.length === 0){
  92. includeCampaign = true;
  93. }
  94. for(var i = 0; i < includeCampaignNameContains.length; i++){
  95. var index = campaignName.toLowerCase().indexOf(includeCampaignNameContains[i].toLowerCase());
  96. if(index !== -1){
  97. includeCampaign = true;
  98. break;
  99. }
  100. }
  101. if(includeCampaign){
  102. var campaignId = campaign.getId();
  103. push(campaignId);
  104. }
  105. }
  106. //Return if there are no campaigns.
  107. if(campaignIds.length === 0){
  108. log(“There are no campaigns matching your criteria.”);
  109. return;
  110. }
  111. //Remove all ad scheduling and mobile bid adjustments for the last run.
  112. if(lastRun){
  113. checkAndRemoveAdSchedules(campaignIds, []);
  114. ModifyMobileBidAdjustment(campaignIds, 0);
  115. return;
  116. }
  117. // Change the mobile bid adjustment
  118. if(runMobileBids){
  119. if (sheets.length < 2) {
  120. log(“Mobile ad schedule sheet was not found in the Google spreadsheet.”);
  121. } else {
  122. var sheet = sheets[1];
  123. var data = sheet.getRange(scheduleRange).getValues();
  124. var thisHourMultiplier_Mobile = data[hour][dayOfWeek];
  125. if (thisHourMultiplier_Mobile.length === 0) {
  126. thisHourMultiplier_Mobile = -1;
  127. }
  128. if (isNaN(thisHourMultiplier_Mobile) || (thisHourMultiplier_Mobile < -0.9 && thisHourMultiplier_Mobile > -1) || thisHourMultiplier_Mobile > 3) {
  129. log(“Mobile bid modifier ‘” + thisHourMultiplier_Mobile + “‘ for ” + weekDays[dayOfWeek] + ” ” + hour + ” is not valid.”);
  130. thisHourMultiplier_Mobile = 0;
  131. }
  132. if(thisHourMultiplier === “”){
  133. var totalMultiplier = “”;
  134. }
  135. else if(thisHourMultiplier == -1 || thisHourMultiplier_Mobile == -1){
  136. var totalMultiplier = -1;
  137. }
  138. else{
  139. var totalMultiplier = (1+thisHourMultiplier_Mobile)*(1+thisHourMultiplier) -1;
  140. }
  141. getRange(“I2”).setValue(thisHourMultiplier_Mobile);
  142. getRange(“T2”).setValue(totalMultiplier);
  143. ModifyMobileBidAdjustment(campaignIds, thisHourMultiplier_Mobile);
  144. }
  145. }
  146. // Check the existing ad schedules, removing those no longer necessary
  147. var existingSchedules = checkAndRemoveAdSchedules(campaignIds, timesAndModifiers);
  148. // Add in the new ad schedules
  149. AddHourlyAdSchedules(campaignIds, timesAndModifiers, existingSchedules, shoppingCampaigns);
  150. }
  151. /**
  152. * Function to add ad schedules for the campaigns with the given IDs, unless the schedules are
  153. * referenced in the existingSchedules array. The scheduling will be added as a hour long periods
  154. * as specified in the passed parameter array and will be given the specified bid modifier.
  155. *
  156. * @param array campaignIds array of campaign IDs to add ad schedules to
  157. * @param array timesAndModifiers the array of [hour, day, bid modifier] for which to add ad scheduling
  158. * @param array existingSchedules array of strings identifying already existing schedules.
  159. * @param bool shoppingCampaigns using shopping campaigns?
  160. * @return void
  161. */
  162. function AddHourlyAdSchedules(campaignIds, timesAndModifiers, existingSchedules, shoppingCampaigns){
  163. // times = [[hour,day],[hour,day]]
  164. var campaignIterator = ConstructIterator(shoppingCampaigns)
  165. .withIds(campaignIds)
  166. .get();
  167. while(campaignIterator.hasNext()){
  168. var campaign = campaignIterator.next();
  169. for(var i = 0; i < timesAndModifiers.length; i++){
  170. if (existingSchedules.indexOf(
  171. timesAndModifiers[i][0] + “|” + (timesAndModifiers[i][0]+1) + “|” + timesAndModifiers[i][1]
  172. + “|” + Utilities.formatString(“%.2f”,(timesAndModifiers[i][2]+1)) + “|” + campaign.getId())
  173. > -1) {
  174. continue;
  175. }
  176. addAdSchedule({
  177. dayOfWeek: timesAndModifiers[i][1],
  178. startHour: timesAndModifiers[i][0],
  179. startMinute: 0,
  180. endHour: timesAndModifiers[i][0]+1,
  181. endMinute: 0,
  182. bidModifier: 1+timesAndModifiers[i][2]
  183. });
  184. }
  185. }
  186. }
  187. /**
  188. * Function to remove ad schedules from all campaigns referenced in the passed array
  189. * which do not correspond to schedules specified in the passed timesAndModifiers array.
  190. *
  191. * @param array campaignIds array of campaign IDs to remove ad scheduling from
  192. * @param array timesAndModifiers array of [hour, day, bid modifier] of the wanted schedules
  193. * @return array existingWantedSchedules array of strings identifying the existing undeleted schedules
  194. */
  195. function checkAndRemoveAdSchedules(campaignIds, timesAndModifiers) {
  196. var adScheduleIds = [];
  197. var report = AdWordsApp.report(
  198. ‘SELECT CampaignId, Id ‘ +
  199. ‘FROM CAMPAIGN_AD_SCHEDULE_TARGET_REPORT ‘ +
  200. ‘WHERE CampaignId IN [“‘ + campaignIds.join(‘”,”‘) + ‘”]’
  201. );
  202. var rows = report.rows();
  203. while(rows.hasNext()){
  204. var row = rows.next();
  205. var adScheduleId = row[‘Id’];
  206. var campaignId = row[‘CampaignId’];
  207. push([campaignId,adScheduleId]);
  208. }
  209. var chunkedArray = [];
  210. var chunkSize = 10000;
  211. for(var i = 0; i < adScheduleIds.length; i += chunkSize){
  212. push(adScheduleIds.slice(i, i + chunkSize));
  213. }
  214. var wantedSchedules = [];
  215. var existingWantedSchedules = [];
  216. for (var j=0; j<timesAndModifiers.length; j++) {
  217. push(timesAndModifiers[j][0] + “|” + (timesAndModifiers[j][0]+1) + “|” + timesAndModifiers[j][1] + “|” + Utilities.formatString(“%.2f”,timesAndModifiers[j][2]+1));
  218. }
  219. for(var i = 0; i < chunkedArray.length; i++){
  220. var unwantedSchedules = [];
  221. var adScheduleIterator = AdWordsApp.targeting()
  222. .adSchedules()
  223. .withIds(chunkedArray[i])
  224. .get();
  225. while (adScheduleIterator.hasNext()) {
  226. var adSchedule = adScheduleIterator.next();
  227. var key = adSchedule.getStartHour() + “|” + adSchedule.getEndHour() + “|” + adSchedule.getDayOfWeek() + “|” + Utilities.formatString(“%.2f”,adSchedule.getBidModifier());
  228. if (wantedSchedules.indexOf(key) > -1) {
  229. push(key + “|” + adSchedule.getCampaign().getId());
  230. } else {
  231. push(adSchedule);
  232. }
  233. }
  234. for(var j = 0; j < unwantedSchedules.length; j++){
  235. unwantedSchedules[j].remove();
  236. }
  237. }
  238. return existingWantedSchedules;
  239. }
  240. /**
  241. * Function to construct an iterator for shopping campaigns or regular campaigns.
  242. *
  243. * @param bool shoppingCampaigns Using shopping campaigns?
  244. * @return AdWords iterator Returns the corresponding AdWords iterator
  245. */
  246. function ConstructIterator(shoppingCampaigns){
  247. if(shoppingCampaigns === true){
  248. return AdWordsApp.shoppingCampaigns();
  249. }
  250. else{
  251. return AdWordsApp.campaigns();
  252. }
  253. }
  254. /**
  255. * Function to set a mobile bid modifier for a set of campaigns
  256. *
  257. * @param array campaignIds An array of the campaign IDs to be affected
  258. * @param Float bidModifier The multiplicative mobile bid modifier
  259. * @return void
  260. */
  261. function ModifyMobileBidAdjustment(campaignIds, bidModifier){
  262. var platformIds = [];
  263. for(var i = 0; i < campaignIds.length; i++){
  264. push([campaignIds[i],30001]);
  265. }
  266. var platformIterator = AdWordsApp.targeting()
  267. .platforms()
  268. .withIds(platformIds)
  269. .get();
  270. while (platformIterator.hasNext()) {
  271. var platform = platformIterator.next();
  272. setBidModifier(1+bidModifier);
  273. }
  274. }

Що потрібно знати користувачеві про скрипт?

Рядок 21 – вказуємо посилання на нашу Google таблицю з розкладом.

Рядок 26 – вказуємо, чи буде розклад працювати для товарних кампаній або ж для пошукових.

Рядок 30 – використовуємо додатковий розклад для мобільних пристроїв (другий лист в прикладі).

Рядок 35 – вказуємо кампанії для яких не плануємо використовувати скрипт.

Рядок 39 – вказуємо кампанії для яких плануємо використовувати розклад.

Рядок 43 – коли слід зупинити роботу скрипта розкладу.

Важливо: Для всіх кампаній, в яких буде використовуватися скрипт – будуть видалені всі розклади показу.

  • Якщо у нас в розкладі задана є порожня клітинка – це сприймається, як коригування -100%.
  • Скрипт використовує часовий пояс вашого облікового запису AdWords.
  • Максимальне коригування ставки + 900% (для мобільних обмеження на + 300%), мінімальне – -100%.

У статті використані матеріали з ресурсів searchengineland.com і liraltd.com.