Google AdWords: как отобрать хорошие места размещения при помощи скриптов
Google Display Network (GDN) – одна из двух рекламных сетей в Google AdWords, позволяющая показывать рекламные объявления на большом количестве различных сайтов. Хоть этот инструмент и более «пассивен», чем реклама Google в поиске, он охватывает значительно большую аудиторию.
Проблема GDN
«Темной стороной» контекстной рекламы в Google Display Network является то, что ее настройка требует много времени на отбор качественных площадок. Ведь эффективной реклама будет только в том случае, если объявления будут показываться на подходящих площадках. А фильтрация тысяч площадок – это настоящая проблема для специалиста по контекстной рекламе.
Хорошая новость в том, что Google AdWords дает возможность управлять вашими рекламными кампаниями при помощи скриптов. Так что существует возможность автоматического отбора качественных площадок.
Мы решили разобраться с этим вопросом и разработать эффективное решение. За основу взяли подходы Алексея Ярошенка и Derek Martin и разработали свою методологию работы скрипта для «чистки» площадок в Google Display Network.
Решение
Для понимания, как работает наше решение в целом, разберем вначале его базовые этапы.
Начальные условия
Наш скрипт обращается к отчету по площадкам AdWords за определенный период времени и отбирает места размещения, удовлетворяющие различным условиям:
- затраты на площадку составляют более X USD и нет конверсий;
- стоимость конверсии с площадки больше X USD;
- количество показов площадки больше X, CTR меньше Y и нет конверсий;
- количество показов площадки больше X, CTR больше Y и нет конверсий.
Перечисленные выше условия могут меняться вручную в конфигурационном файле Google Spreadsheet.
Нежелательные домены
Следующая проверка отсеивает площадки без конверсий, в домене которых присутствуют нежелательные слова, и заносит их в black list.

Рис. 1. Пример Google Spreadsheet со словами, по которым отсеиваются домены
Список исключений
Отобранные скриптом площадки заносятся в лист исключений на уровне «Shared library» в аккаунте AdWords.

Рис. 2. Пример списков исключений в «Shared library»

Рис. 3. Пример отчета работы скрипта в Google Spreadsheet
Уведомления о работе скрипта по email
После выполнения скрипта результаты его работы высылаются на email, указанный в конфигурационном файле. В письме содержится сводная статистика по обработанным площадкам и ссылка на документ с полным отчетом.
Пошаговое решение
1. Определение рабочего Google Spreadsheet и имя проекта.
var config = { SPREADSHEET_URL:Link to the Google Spreadsheet', PROJECT_NAME:'Name of the project' }
2. Определение списка, в который будут заноситься «плохие», отфильтрованные площадки.
function getConfigData(spreadsheet) { var excludeDomainSheet = spreadsheet.getSheetByName('exclude_domain'), values = exclude DomainSheet.getSheetValues(1,1,excludeDomainSheet.getLastRow(),1); config.exclude = []; if (typeof values == "object") { for (i=0; i < values.length; i++) { config.exclude.push(values[i][0]); } } Logger.log(config.exclude); }
3. Определение списка для площадок, по которым не будет вноситься статистика.
var except DomainSheet = spreadsheet.getSheetByName('except_domain'), values = except DomainSheet.getSheetValues(1,1,exceptDomainSheet.getLastRow(),1); config.except = []; if(typeof values == "object") { for (i=0; i < values.length; i++) { config.except.push(values[i][0]); } } Logger.log (config.except)
4. Определение условий отбора площадок:
- название списка,
- контактный email,
- отчетный период,
- максимальная стоимость клика,
- максимальная стоимость конверсии,
- минимальное количество показов,
- максимальный CTR.
var configSheet = spreadsheet.getSheetByName('config'); config.email = configSheet.getRange(1,2,1,1).getValues(); config.timeperiod = configSheet.getRange(2,2,1,1).getValue(); config.listCost = configSheet.getRange(3,2,1,1).getValue(); config.list2ConversionCost = configSheet.getRange(4,2,1,1).getValue(); config.list3Impressions = configSheet.getRange(5,2,1,1).getValue(); config.list3Ctr = configSheet.getRange(6,2,1,1).getValue(); config.list4Impressions = configSheet.getRange(7,2,1,1).getValue(); config.list4Ctr = configSheet.getRange(8,2,1,1).getValue()
5. Обновление названия документа с учетом даты запуска скрипта.
function main() { var spreadsheet = SpreadsheetApp.openByUrl(config.SPREADSHEET_URL); var curDate = Utilities.formatDate(new Date(), "GMT+3", "yyyy-MMMM-dd"); getConfigData(spreadsheet); spreadsheet.setName("GDN Report " + config.PROJECT_NAME + " " + curDate); }
6. Генерация отчета.
var body = "[h2]Google Display Network – Report on underperforming placements[/h2]"; body += "[h3]Placements that spent more than " + config.listCost + " USD and did not bring conversions:[/h3]"; body += "[ul]"; var list = runHightCostAndNoConvertingReport(); varrows = []; for (i=0; i < list.length; i++) { body += "[li]" + list[i].placement + ' - ' + list[i].cost + ' USD ' + "[/li]"; } addPlacementList('list1',list,spreadsheet); body += "[/ul]"; body += "[h3]Placements with the conversion cost more than " + config.list2ConversionCost + " USD:[/h3]; body += "[ul]"; var list2 = runHighCostOfConversionsReport(); for (i=0; i < list2.length; i++) { body += "[li]" + list2[i].placement + ' - ' + list2[i].cost + ' USD ' + ' - The cost of conversion - ' + list2[i].costperconversion + ' USD ' + "[/li]"; } addPlacementList('list2',list2,spreadsheet); body += "[/ul]"; body += "[h3]Placements with more than " + config.list3Impressions + " impressions and CTR less than " + config.list3Ctr + "%:[/h3] "; body += "[ul]"; var list3 = runBadCtrNoConversionsReport(); body += "[li]The number of exceptions - " + list3.length + "[/li]"; addPlacementList('list3',list3,spreadsheet); body += "[/ul]"; body += "[h3]Placements with more than " + config.list4Impressions + " impressions and CTR more than " + config.list4Ctr + "%:[/h3] " ; body += "[ul]"; var list4 = runHighCtrReport(); body += "[li]The number of exceptions - " + list4.length + "[/li]"; addPlacementList('list4',list4,spreadsheet); body += "[/ul]"; body += "[h3]The placements, which domain name contains the unwanted word:[/h3] " ; body += "[ul]"; var list5 = gamePlacements(); body += "[li] The number of exceptions - " + list5.length + "[/li]"; addPlacementList('list5',list5,spreadsheet); body += "[/ul]"; body += "[a href='"+spreadsheet.getUrl()+"']Link to the report Google Spreadsheet[/a]";
7. Отправка email с результатами.
if(config.email.length){ MailApp.sendEmail(config.email,'Display Network Alerts - ' + config.PROJECT_NAME + " - " + curDate, body, {htmlBody: body}); }
8. Использование функций для добавления площадок в исключения.
function addPlacementList(nameList,list,spreadsheet) { var rows = [], sheet, range, listSharedExcludedPlacementIterator; sheet = spreadsheet.getSheetByName(nameList); sheet.clear(); range = sheet.getRange(1, 1, 1, 7).setValues([['Exclusion URL','Impressions','Clicks','CTR','Cost','Conversions','Cost Per Conversion']]); range.setBackground("yellow"); listSharedExcludedPlacementIterator = AdWordsApp.excludedPlacementLists() .withCondition("Name CONTAINS '" + nameList + "'").get(); while (listSharedExcludedPlacementIterator.hasNext()) { listSharedExcludedPlacement = listSharedExcludedPlacementIterator.next(); } for (i=0; i < list.length; i++) { listSharedExcludedPlacement.addExcludedPlacement(list[i].placement); rows.push([list[i].placement, list[i].impressions, list[i].clicks, list[i].clicks / list[i].impressions * 100 + "%", list[i].cost, list[i].conversions, list[i].costperconversion]) } if(rows.length) sheet.getRange(2, 1, rows.length, 7).setValues(rows).sort({column: 2, ascending: false}); } function runHightCostAndNoConvertingReport() { list = []; var periodString = ''; if(config.timeperiod) { periodString = 'DURING ' + config.timeperiod; Logger.log(periodString); } else { Logger.log('DURING ALL TIME'); } }
9. Определение площадок, которые потратили более X USD и не принесли конверсий.
var report = AdWordsApp.report( 'SELECT Domain, Clicks, Impressions, CostPerConversion, Conversions, Cost ' + 'FROM AUTOMATIC_PLACEMENTS_PERFORMANCE_REPORT ' + 'WHERE Cost > ' + config.listCost * 1000000 + " " + 'AND Conversions < 1 ' + periodString); var rows = report.rows(); while (rows.hasNext()) { var row = rows.next(); var anonymous = row['Domain'].match(new RegExp(config.except.join('|').replace(/\./g,'\\.'),'g')); if (anonymous == null) { var placementDetail = new placementObject(row['Domain'], row['Clicks'], row['Impressions'], row['CostPerConversion'],row['Conversions'], row['Cost']); list.push(placementDetail); } } function runHighCostOfConversionsReport() { list = []; var periodString = ''; if(config.timeperiod) { periodString = 'DURING ' + config.timeperiod; Logger.log(periodString); } else { Logger.log('DURING ALL TIME'); } }
10. Определение площадок с конверсиями, которые стоят более X USD.
var report = AdWordsApp.report( 'SELECT Domain, Clicks, Impressions, CostPerConversion, Conversions, Cost ' + 'FROM AUTOMATIC_PLACEMENTS_PERFORMANCE_REPORT ' + 'WHERE CostPerConversion > ' + config.list2ConversionCost * 1000000 + " " + 'AND Conversions > 1 ' + periodString); var rows = report.rows(); while (rows.hasNext()) { var row = rows.next(); var anonymous = row['Domain'].match(new RegExp(config.except.join('|').replace(/\./g,'\\.'),'g')); if (anonymous == null) { var placementDetail = new placementObject(row['Domain'], row['Clicks'], row['Impressions'], row['CostPerConversion'], row['Conversions'], row['Cost']); list.push(placementDetail); } } function runBadCtrNoConversionsReport() { list = []; var periodString = ''; if(config.timeperiod) { periodString = 'DURING ' + config.timeperiod; Logger.log(periodString); } else { Logger.log('DURING ALL TIME'); } }
11. Определение площадок без конверсий с более чем X показами CTR менее Y %.
var report = AdWordsApp.report( 'SELECT Domain, Clicks, Impressions, CostPerConversion, Conversions, Cost ' + 'FROM AUTOMATIC_PLACEMENTS_PERFORMANCE_REPORT ' + 'WHERE Impressions > ' + config.list3Impressions + " " + 'AND Ctr < ' + config.list3Ctr * 0.01 + " " + 'AND Conversions < 1 ' + periodString); var rows = report.rows(); while (rows.hasNext()) { var row = rows.next(); var anonymous = row['Domain'].match(new RegExp(config.except.join('|').replace(/\./g,'\\.'),'g')); if (anonymous == null) { var placementDetail = new placementObject(row['Domain'], row['Clicks'], row['Impressions'], row['CostPerConversion'], row['Conversions'], row['Cost']); list.push(placementDetail); } } function runHighCtrReport() { list = []; var periodString = ''; if(config.timeperiod) { periodString = 'DURING ' + config.timeperiod; Logger.log(periodString); } else { Logger.log('DURING ALL TIME'); } }
12. Определение площадок без конверсий с более чем X показами и CTR большим, чем Y %.
var report = AdWordsApp.report( 'SELECT Domain, Clicks, Impressions, CostPerConversion, Conversions, Cost ' + 'FROM AUTOMATIC_PLACEMENTS_PERFORMANCE_REPORT ' + 'WHERE Impressions > ' + config.list4Impressions + " " + 'AND Ctr > ' + config.list4Ctr * 0.01 + " " + 'AND Conversions < 1 ' + periodString); var rows = report.rows(); while (rows.hasNext()) { var row = rows.next(); var anonymous = row['Domain'].match(new RegExp(config.except.join('|').replace(/\./g,'\\.'),'g')); if (anonymous == null) { var placementDetail = new placementObject(row['Domain'], row['Clicks'],row['Impressions'], row['CostPerConversion'], row['Conversions'], row['Cost']); list.push(placementDetail); } } function gamePlacements() { list = []; var periodString = ''; if(config.timeperiod) { periodString = 'DURING ' + config.timeperiod; Logger.log(periodString); } else { Logger.log('DURING ALL TIME'); } }
13. Добавление в исключения площадок без конверсий, в домене которых содержатся нежелательные слова.
var report = AdWordsApp.report( 'SELECT Domain, Clicks, Impressions, CostPerConversion, Conversions, Cost ' + 'FROM AUTOMATIC_PLACEMENTS_PERFORMANCE_REPORT ' + 'WHERE Conversions < 1 ' + periodString); var rows = report.rows(); while (rows.hasNext()) { var row = rows.next(); var anonymous = row['Domain'].match(new RegExp(config.except.join('|').replace(/\./g,'\\.'),'g')); if (anonymous == null) { var placement = row['Domain']; var clicks = row['Clicks']; var impressions = row['Impressions']; var costperconversion = row['CostPerConversion'] var conversions = row['Conversions']; var cost = row['Cost']; var placementDetail = new placementObject(placement, clicks, impressions, costperconversion, conversions, cost); if (containsAny(placement.toString(), config.exclude)) { var placementDetail = new placementObject(placement, clicks, impressions, costperconversion, conversions, cost); list.push(placementDetail); } } } function containsAny(str, substrings) { for (var i = 0; i != substrings.length; i++) { var substring = substrings[i]; if (str.indexOf(substring) != - 1) { return substring; } } return null; } function placementObject(placement, clicks, impressions, costperconversion, conversions, cost) { this.placement = placement; this.clicks = clicks; this.impressions = impressions; this.costperconversion = costperconversion; this.conversions = conversions; this.cost = cost; }
Результаты
Мы тестировали работу скрипта, используя следующие критерии отбора площадок:
Содержание письма:
Размещения, которые потратили более 300 USD и не принесли конверсий: 0
Размещения со стоимостью конверсии более чем 500 USD: 0
Размещения с более чем 100 показами и CTR, менее 0.05%: 3
Размещения с более чем 50 показами и CTR более 8%: 7
Размещения, содержащие в домене нежелательные слова: 382
Заключительные комментарии
При использовании нашего решения нужно помнить о следующих моментах:
- «Отсеивающие» параметры можно и нужно прописывать с учетом особенностей вашего проекта.
- Вы можете настроить автоматический запуск скрипта, например, еженедельно.
- Если ваш аккаунт достаточно старый, мы рекомендуем проверить площадки за весь период его работы. Чтобы сделать это, просто оставьте поле с отчетным периодом пустым.
Есть о чем рассказать? Тогда присылайте свои материалы Марине Ибушевой
-
Феликс Неботов больше года назадВадим, здравствуйте.
Чем отличается от этого скрипта?
gist.github.com/derekmartinla/06e51b8a4298b8bbb8ff -
Гость больше года назадВадим, спасибо за отличный скрипт!
Скажите, пожалуйста, а можно привязать выполнение скрипта к определенной кампании? -
Гость больше года назадСпасибо за полезную информацию. Можете ли поделится шаблоном файла настроек и содержимым скрипта, сложенным в один файл?




