[Из песочницы] Рациональная автоматизация кампании в Google AdWords

Предыстория Все началось со слов «А сделай-ка xml выгрузку для AdWords», и тут понеслось. Как ни странно, но именно эта задача была выполнена довольно быстро, но дальше было интереснее. Как оказалось, в AdWords появилась возможность писать скрипты (javascript) по автоматизации процесса ведения кампании и было бы все хорошо, если бы не лимиты по времени исполнения и xml. Да-да, именно xml. Я не знаю, почему всем так запал в душу этот формат, но мне он никогда не нравился. С 95% задачи я справился и, откровенно говоря, удовольствия я от этого не получил да и оставалось еще 5% задачи. Именно эти 5% я бросил уже не на xml, a на json и вот тут стало весело.Больше конкретики Давайте конкретизируем о чем вообще идет речь. Есть интернет магазин с ~25 000 наименований. Маркетологу нужна выгрузка, чтоб загнать это все в кампанию: создать группы обьявлений, сами обьявления, ключи и т.д. Как выяснилось дальше, то не важно какой формат входящих данных (xml/json), по этому я выбрал тот, что мне больше по душе — json. { elems: [ { id: 555233, n: «Agent Provocateur Maitresse», p: 346, u: «http://site.ua/555233.html», v: «Agent Provocateur», c: «Женская парфюмерия» }, { id: 559675, n: «Angel Schlesser Essential for Men», p: 191, u: «http://site.ua/559675.html», v: «Angel Schlesser», c: «Мужская парфюмерия» } ]} И вот имеем N элементов со структурой id, n (Name), p (Price), u (Url), v (Vendor), с (Category), во всяком случае это именно те данные, что нужны были мне. Приступим к автоматизации?

Скрипт 1. Создание групп обьявлений // Получаем google-таблицу для записи итератора добавления групп обьявлений var doc = SpreadsheetApp.openByUrl ('https://docs.google.com/spreadsheets/d/15_W4y3GpivCjuNRWPN8HKy27MjtUeW2NTThAAPPXdkc/edit#gid=0'); // Выбираем по имени лист var sheet = doc.getSheetByName ('parfums'); // Ячейка, в которую будет записан текущий итератор этого скрипта var i_cell = sheet.getRange ('B2'); var date_cell = sheet.getRange ('B3');

function main () { var i_cell_val = (! i_cell.getValue ()) ? 0: i_cell.getValue (); // Получаем JSON var json = JSON.parse (UrlFetchApp.fetch ('http://site.ua/adwords.json').getContentText ()); // Получаем зараннее созданную нами кампанию var tmp = AdWordsApp.campaigns ().withCondition ('Name = «ббб»').get (); var unloaded = json.elems; var export_l = unloaded.length; if (is_exported ()) { Logger.log ('Already exported'); return; } if (tmp.hasNext ()) { var campaign = tmp.next (); } else { Logger.log ('Company not found'); return; } for (i= i_cell_val; i<=export_l-1; i++) { el = unloaded[i]; var tmp = campaign.adGroups().withCondition('Name CONTAINS "__ID-' + el.id +'"').get(); if (tmp.hasNext()) { var tmp_g = tmp.next(); tmp_g.enable(); } else { var adGroupName = el.c + '_' + el.v + '_' + el.n + '__ID-' + el.id; addAdGroup(adGroupName, campaign); } i_cell.getValue(); i_cell.setValue(i); if (i == export_l-1) { date_cell.setValue(Utilities.formatDate(new Date(), "GMT+3", "d.M.yyyy")); i_cell.setValue(0); } } }

function addAdGroup (adGroupName, ci) { var adGroup = ci.newAdGroupBuilder (); adGroup = adGroup.withName (adGroupName).withStatus («ENABLED»).withKeywordMaxCpc (1).create (); }

function is_exported () { var exp_date = Number (Utilities.formatDate (new Date (date_cell.getValue ()), «GMT+3», «dd»)); var today = Utilities.formatDate (new Date (), «GMT+3», «dd HH»).split (' '); if (Number (today[1]) < 6) return true; if ( (exp_date < Number(today[0])) || date_cell.getValue() == 'NaN') return false; else return true; } Этот скрипт будет козой для понимания следующих. Кто пробежался глазами по скрипту, явно задался вопросами «Зачем? Зачем тут гугл док? Что вообще за бред?». Рассказываю. Как бы я не любил Google, но, увы, эти автоматизационные скрипты выполняются крайне долго, а расписание настраивается крайне туго, вот и присутствие костылей.

Зачем же нам google-док? Spreadsheet в гугл-док будет для нас хранилищем, для доступа к которому есть описанное и поддерживаемое API. Туда мы будем писать данные, по которым скрипты будут понимать, нужно ли еще что-то делать или стоит обрываться.Табличка будет примерно такой:

image

Ячейка B2 — сюда у нас записывается текущий итератор элементов в цикле. Нулю он равен тогда, когда все в текущий день выгружено, так же должно быть равно текущей дате значение в ячейке B3, совокупность этих равенств будет значить, что на текущей день выгружены все элементы. Для чего это нужно? Для того, чтоб можно было поставить скрипт по расписанию на каждый час и после полного выполнения он просто выключался с сообщением «Все выгружено».

Что за функция is_exported? Эта функция присутствует в каждом скрипте и будет проверять, нужно ли гнать все данные по новой.Конкретно в моем случае, она выглядит так: function is_exported () { var exp_date = Number (Utilities.formatDate (new Date (date_cell.getValue ()), «GMT+3», «dd»)); var today = Utilities.formatDate (new Date (), «GMT+3», «dd HH»).split (' '); if (Number (today[1]) < 6) return true; if ( (exp_date < Number(today[0])) || date_cell.getValue() == 'NaN') return false; else return true; } Копируя это себе, не забывайте о паре важных моментов. Во-первых, поставьте свой часовой пояс, у меня стоит GMT+3, во-вторых, поставьте if (Number(today[1]) < 6) сюда, вместо 6, свое значения «ЧАСОВ», после которого скрипт будет выполняться. У меня стоит 6 часов, потому что, примерно, к тому времени будет готова вся выгрузка.

Скрипт 2. Создание текстов обьявлений в группах // Получаем google-таблицу для записи итератора добавления групп обьявлений var doc = SpreadsheetApp.openByUrl ('https://docs.google.com/spreadsheets/d/15_W4y3GpivCjuNRWPN8HKy27MjtUeW2NTThAAPPXdkc/edit#gid=0'); var sheet = doc.getSheetByName ('parfums'); // Ячейка, в которую будет записан текущий итератор этого скрипта var i_cell = sheet.getRange ('C2'); var date_cell = sheet.getRange ('C3');

function main () { var i_cell_val = ((! i_cell.getValue ()) ? 0: i_cell.getValue ()); // Получаем JSON var json = JSON.parse (UrlFetchApp.fetch ('http://site.ua/adwords.json').getContentText ()); // Получаем зараннее созданную нами кампанию var tmp = AdWordsApp.campaigns ().withCondition ('Name = «ббб»').get (); var unloaded = json.elems; var export_l = unloaded.length; if (is_exported ()) { Logger.log ('Already exported'); return; } if (tmp.hasNext ()) { var campaign = tmp.next (); } else { Logger.log ('Company not found'); return; } for (i= i_cell_val; i<=export_l-1; i++) { el = unloaded[i]; var tmp_g = campaign.adGroups().withCondition('Name CONTAINS "__ID-' + el.id +'"').get(); if (tmp_g.hasNext()) { var adGroup = tmp_g.next(); var lb = adGroup.labels().withCondition('Name = "with_text"').get(); //Ищем лейбл в данной ADG if (!lb.hasNext()) { //Если в текущей ADG нету обьявления, создадим его adGroup.createTextAd('{KeyWord:Купить парфюмерию}', 'Покупай духи за {param1: ' + el.p + '} грн', 'Бесплатная курьерская доставка!', 'parfums.ua/' + el.v.replace(/ /g, '_'), el.u); adGroup.applyLabel('with_text'); } } else { Logger.log("Группа объявлений '" + el.id + "' не найдена."); } i_cell.getValue(); i_cell.setValue(i); if (i == export_l-1) { //Если выгрузка закончена date_cell.setValue(Utilities.formatDate(new Date(), "GMT+3", "d.M.yyyy")) i_cell.setValue(0); } } }

function is_exported () { var exp_date = Number (Utilities.formatDate (new Date (date_cell.getValue ()), «GMT+3», «dd»)); var today = Utilities.formatDate (new Date (), «GMT+3», «dd HH»).split (' '); if (Number (today[1]) < 8) return true; if ( (exp_date < Number(today[0])) || date_cell.getValue() == 'NaN') return false; else return true; } В этом скрипте, я думаю, всем все понятно, но один моментв се же обьяснить стоит — использование ярлыков. Зачем? Да просто потому, что я не нашел, как проверить, существует ли в группе обьявление. Вот и все, если метку в группе нашли — значит существует, т.к. ярлык мы присваиваем только после добавления текста. Все просто.

Скрипт 3. Создание ключевых слов // Получаем google-таблицу для записи итератора добавления групп обьявлений var doc = SpreadsheetApp.openByUrl ('https://docs.google.com/spreadsheets/d/15_W4y3GpivCjuNRWPN8HKy27MjtUeW2NTThAAPPXdkc/edit#gid=0'); var sheet = doc.getSheetByName ('parfums'); // Ячейка, в которую будет записан текущий итератор этого скрипта var i_cell = sheet.getRange ('D2'); var date_cell = sheet.getRange ('D3'); var flag_cell = sheet.getRange ('D4');

function main () { var i_cell_val = ((! i_cell.getValue ()) ? 0: i_cell.getValue ()); // Получаем JSON var json = JSON.parse (UrlFetchApp.fetch ('http://site.ua/adwords.json').getContentText ()); // Получаем зараннее созданную нами кампанию var tmp = AdWordsApp.campaigns ().withCondition ('Name = «ббб»').get (); var unloaded = json.elems; var export_l = unloaded.length; if (is_exported ()) { Logger.log ('Already exported'); return; } if (tmp.hasNext ()) { var campaign = tmp.next (); } else { Logger.log ('Company not found'); return; } var flag_v = (! flag_cell.getValue ()) ? 1: flag_cell.getValue (); for (i= i_cell_val; i<=export_l-1; i++) { el = unloaded[i]; var tmp_g = campaign.adGroups().withCondition('Name CONTAINS "__ID-' + el.id +'"').get(); if (tmp_g.hasNext()) { var adGroup = tmp_g.next(); var key = el.n; var tmp_key = AdWordsApp.keywords().withCondition('Text = "' + key + '"').get(); //Ищем существует ли уже такой ключ if (!tmp.hasNext()) { adGroup.createKeyword(key); } key = '[' + el.n + ']'; tmp = AdWordsApp.keywords().withCondition('Text = "' + key + '"').get(); if (!tmp.hasNext()) { adGroup.createKeyword(key); } } else { flag_v = 0; Logger.log("Группа объявлений '" + el.id + "' не найдена."); } i_cell.getValue(); i_cell.setValue(i); flag_cell.setValue(flag_v); if (i == export_l-1) { //Если выгрузка закончена if (Number(flag_v)) //Если добавили обьявления во все ADG date_cell.setValue(Utilities.formatDate(new Date(), "GMT+3", "d.M.yyyy")); i_cell.setValue(0); } } }

function is_exported () { var exp_date = Number (Utilities.formatDate (new Date (date_cell.getValue ()), «GMT+3», «dd»)); var today = Utilities.formatDate (new Date (), «GMT+3», «dd HH»).split (' '); if (Number (today[1]) < 8) return true; if ( (exp_date < Number(today[0])) || date_cell.getValue() == 'NaN') return false; else return true; } Тут тоже все просто. Есть выгрузка, по ID вытягивает группу, в группе создаем ключи. Занавес! Но и тут не без мелочей. Здесь появилась переменная flag_v. Если она равна нулю, то работа цикла не закрывается. Это сделано для того, чтоб избежать рассинхрона, в случае, если группы обьявлений еще не были созданы. Так, же поменяйте «ЧАСОВОЙ» параметр в функции is_exported, поставьте на час или 2 позже.

Скрипт 4. Обновление параметров // Получаем google-таблицу для записи итератора добавления групп обьявлений var doc = SpreadsheetApp.openByUrl ('https://docs.google.com/spreadsheets/d/15_W4y3GpivCjuNRWPN8HKy27MjtUeW2NTThAAPPXdkc/edit#gid=0'); var sheet = doc.getSheetByName ('parfums'); // Ячейка, в которую будет записан текущий итератор этого скрипта var i_cell = sheet.getRange ('E2'); var date_cell = sheet.getRange ('E3'); var flag_cell = sheet.getRange ('E4');

function main () { var i_cell_val = ((! i_cell.getValue ()) ? 0: i_cell.getValue ()); // Получаем JSON var json = JSON.parse (UrlFetchApp.fetch ('http://site.ua/adwords.json').getContentText ()); // Получаем зараннее созданную нами кампанию var tmp = AdWordsApp.campaigns ().withCondition ('Name = «ббб»').get (); var unloaded = json.elems; var export_l = unloaded.length; if (is_exported ()) { Logger.log ('Already exported'); return; } if (tmp.hasNext ()) { var campaign = tmp.next (); } else { Logger.log ('Company not found'); return; } var flag_v = (! flag_cell.getValue ()) ? 1: flag_cell.getValue (); for (i= i_cell_val; i<=export_l-1; i++) { el = unloaded[i]; var tmp_g = campaign.adGroups().withCondition('Name CONTAINS "__ID-' + el.id +'"').get(); if (tmp_g.hasNext()) { var adGroup = tmp.next(); var keywordIter = adGroup.keywords().get(); while (keywordIter.hasNext()) { var keyword = keywordIter.next(); keyword.setAdParam(1, el.p); } } else { flag_v = 0; Logger.log("Группа объявлений '" + el.id + "' не найдена."); } i_cell.getValue(); i_cell.setValue(i); flag_cell.setValue(flag_v); if (i == export_l-1) { //Если выгрузка закончена if (Number(flag_v)) //Если добавили обьявления во все ADG date_cell.setValue(Utilities.formatDate(new Date(), "GMT+3", "d.M.yyyy")); i_cell.setValue(0); } } }

function is_exported () { var exp_date = Number (Utilities.formatDate (new Date (date_cell.getValue ()), «GMT+3», «dd»)); var today = Utilities.formatDate (new Date (), «GMT+3», «dd HH»).split (' '); if (Number (today[1]) < 6) return true; if ( (exp_date < Number(today[0])) || date_cell.getValue() == 'NaN') return false; else return true; } Тут все еще проще. Получили группу, получили итератор ключей, пробежались по всем ключевым словам, обновили параметр 1 (цена) во всех ключах. Готово.

Скрипт 5. Обновление статуса групп function main () { var json_ids = JSON.parse (UrlFetchApp.fetch ('http://site.ua/adwords.json').getContentText ()).ids; var tmp = AdWordsApp.campaigns ().withCondition ('Name = «ббб»').get (); if (tmp.hasNext ()) { var campaign = tmp.next (); var tmp = campaign.adGroups ().get (); } else { Logger.log ('Company not found'); } while (tmp.hasNext ()) { group = tmp.next (); name = group.getName (); id = /__ID-(\d+)$/.exec (name)[1]; if (json_ids.indexOf (id) == -1) { group.pause (); } } } Это самый крутой и быстрый скрипт. В нем мы циклом пробегаемся по всем группам обьявлений, регуляркой вытягиваем ID товара, проверяем есть ли он в выгрузке и, если нету, ставим группу на паузу, чтоб экономить денежку. Если не включать никаких логгеров, скрипт пробегает по ~3000 обьявлений за ~20 минут. Да, забыл упомянуть, в выгрузке вы должны иметь секцию с массивом всех участвующих в этом процессе ID’шников. Можете сделать для этого отдельный json, можете засунуть в тот же, что и предыдущих скриптах участвует — на вкус и цвет.

Пара интересных моментов i_cell.getValue (); i_cell.setValue (i); Это сделано для того, чтоб в живом режиме обновлялись данные в таблице Формируя группу обьявлений, делайте это так, чтоб можно было из нее вытащить ID Продумайте расписание исполнения скриптов, чтоб не было накладок на «несозданные» элементы Последний скрипт можно поставить на исполнение каждые 15 минут, следовательно, если у вас живой магазин и высокая динамика в изменении наличия товара, вы будете экономить деньги. Остальные скрипты можно ставить на исполнение каждый час Это все только коза для общего понимания Простите за простыни кода, там многое повторяется, но иначе можно очень запутаться Главный источник: developers.google.com/adwords/scripts/docs/reference/indexПосмотреть, как будет выглядеть док можно здесь: docs.google.com/spreadsheets/d/15_W4y3GpivCjuNRWPN8HKy27MjtUeW2NTThAAPPXdkc/edit? usp=sharing

Меньше вам ручной работы и больше экономии разумной. Всем спасибо за внимание.

© Habrahabr.ru