Часто ли пользуетесь скриптами и автоматизированными правилами в Гугле — KINETICA
Блог №1 для директоров по performance-маркетингу
контекстная реклама

Часто ли пользуетесь скриптами и автоматизированными правилами в Гугле?

Здравствуйте. Часто ли пользуетесь скриптами и автоматизированными правилами в гугле? На сколько процентов у вас в принципе автоматизированы рекламные кампании?

Полина

0
 Прочитать позже
13 минут  |  время прочтения

Здравствуйте. В каждом аккаунте Google Ads, в зависимости от направления проекта — услуги или e-commerce, мы пользуемся определенным набором скриптов.

Зачастую мы пишем скрипты конкретно для решения определенных задач определенного клиента. Со скриптами проще следить за изменениями в аккаунтах и они экономят время — появляется больше времени на анализ и управление кампаниями.

Автоматизированные правила позволяют экономить время и избегать рутинных ошибок. Эти правила актуальны, когда, например, нужно остановить или включить рекламную кампанию на новогодние праздники. Или остановить ключевые слова и объявления, если у них низкая эффективность — низкий CTR.

Часто ли пользуетесь скриптами и автоматизированными правилами в Гугле?

Приведу несколько скриптов, которыми мы пользуемся.

Проверка ссылок на ошибки «404» и «300»

Этот автоматический скрипт ежедневно пробегает по всем рекламным кампаниям клиентов и проверяет URL объявлений. Если он находит ошибки «404» и «300», вы получите оповещение на почту. Данный скрипт полезен для интернет-магазинов, где товары то появляются, то исчезают.

<script type="text/javascript">
function main() {

    var mail_to = 'здесь пишем e-mail специалиста';
    var mccAccount = AdWordsApp.currentAccount();

    //получаем проекты текущего аккаунта
    var childAccounts = MccApp.accounts().get();

    var email = '';

    //пробегаем по всем проектам аккаунта  
    while (childAccounts.hasNext()) {
        //берем проект
        var childAccount = childAccounts.next();
        //делаем его текущим
        MccApp.select(childAccount);
        var disabled_label = 'disabled-ad';

        //----выполняем логику в рамках данного проекта------
        if (childAccount.getStatsFor("LAST_14_DAYS").getClicks() == 0) continue;

        AdWordsApp.createLabel(disabled_label, "Отключенные объявления в связи с тем, что не нашли цену на сайте", "red");

        var checked_urls = {};

        var campaignIterator = AdWordsApp.campaigns()
            .withCondition('Status = ENABLED')
            .get();

        //пробегаем по всем активным кампаниям,
        while (campaignIterator.hasNext()) {
            var campaign = campaignIterator.next();

            var groupIterator = campaign.adGroups()
                .withCondition('Status = ENABLED')
                .get();

            while (groupIterator.hasNext()) {

                var group = groupIterator.next();
                var adIterator = group.ads()
                    .withCondition('Status = ENABLED')
                    .get();

                //пробегаем по всем группам
                while (adIterator.hasNext()) {
                    var entity = adIterator.next();
                    var url = entity.urls().getFinalUrl();

                    //пропускаем, если урл пустой
                    if (url === null) continue;

                    //если объявление неактивно и нет специального лейбла, то пропускаем
                    if (!entity.isEnabled()) continue;
                    var ad_text = 'Группа: ' + group.getName() + ' Объявление: ' + entity.getId() + ' ' 
					+ (entity.getType() == 'TEXT_AD' ? '"' + entity.getHeadline() 
					+ '"' : (entity.getType() == 'EXPANDED_TEXT_AD' ? '"' + entity.getHeadlinePart1() 
					+ ' ' + entity.getHeadlinePart2() + '"' : ''));

                    //очищаем урл от параметров, меток и т.д.
                    url = clean_url(url);

                    //если урл этого товара мы уже парсили,
                    // то берем данные о цене и наличии из массива данных
                    // чтобы снова не парсить
                    if (checked_urls[url]) {

                        //включили или отключили объявление в зависимости от наличия
                        //или просто оповещаем на имейл (нужное раскомментировать)
                        if (checked_urls[url]) {
                            if (checked_urls[url] === 'error') {
                                //ставим на паузу объявление
                                //entity.pause();
                                //entity.applyLabel(disabled_label);
                                email = email + '\n' + ' Ошибка! ' + childAccount.getName() + ' Кампания: "' 
								+ campaign.getName() + '" ' + ad_text + ' ' + ' Url: ' + url;
                            }
                            else {
                                //иначе включаем и убираем метку
                                //entity.removeLabel(disabled_label);
                                //entity.enable();
                            }
                        }
                    }
                    //если нет, то
                    else {
                        var html, headers, code;
                        //парсим страницу товара
                        try {
                            var parsed = UrlFetchApp.fetch(url);
                            //html = parsed.getContentText();
                            //headers = parsed.getHeaders();
                            code = parsed.getResponseCode();
                        } catch (e) {
                            //отключаем объявление, если не смогли распарсить страницу
                            //или просто оповещаем на имейл (нужное раскомментировать)
                            //entity.pause();
                            //entity.applyLabel(disabled_label);
                            email = email + '\n' + ' Ошибка! ' + childAccount.getName() 
							+ ' Кампания: "' + campaign.getName() + '" ' 
							+ ad_text + ' ' + ' Url: ' + url;
                            checked_urls[url] = 'error';
                        }

                        if (code &gt;= 400) {
                            //ставим на паузу объявление или просто пишем в лог
                            checked_urls[url] = 'error';
                            //entity.pause();
                            //entity.applyLabel(disabled_label);
                        } else {
                            //активизируем объявление или пишем в лог
                            checked_urls[url] = 'ok';
                            //entity.enable();
                            //entity.removeLabel(disabled_label);
                        }
                        if (code &gt;= 300) {
                            //тело письма
                            email = email + '\n' + childAccount.getName() + ' Кампания: "' 
							+ campaign.getName() + '" ' + ad_text + ' ' 
							+ (code &gt;= 400 ? ' ОШИБКА ' : '') + ' Url: ' + url
                        }
                    }
                }
            }
        }

        //---------------------------------------------------
    }

    if (email != '') {
        //отправляем письмо
        MailApp.sendEmail(mail_to, "Оповещение об ошибках в урлах объявлениях AdWords", email);
    }

    //вернулись к MCC аккаунту
    MccApp.select(mccAccount);

}

function clean_url(url) {

    //урл может быть обернут враппером
    url = url.substr(url.lastIndexOf('http'));
    if (decodeURIComponent(url) !== url) {
        url = decodeURIComponent(url);
    }

    //убираем GET параметры
    if (url.indexOf('?') &gt;= 0) {
        url = url.split('?')[0];
    }

    //убираем скобки
    if (url.indexOf('{') &gt;= 0) {
        url = url.replace(/\{[0-9a-zA-Z]+\}/g, '');
    }
    return url;
}
</script>

Составление отчета по ключам и группам

Скрипт заполняет в Google Таблицы показатели по всем кампаниям, группам объявлений и объявлениям:

• число показов;
• количество кликов;
• CTR;
• стоимость;
• показатель качества;
• цена клика.

<script type="text/javascript">
function main() {

    var impressions_sum_total = 0, clicks_sum_total = 0, impressions_proc_sum_total = 0,
        qs_sum_total = 0, qs_e_sum_total = 0, cost_sum_total = 0, ctr_sum_total = 0,
        topcpc_sum_total = 0, firstpagecpc_sum_total = 0, count_total = 0;
    var max = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

    //открываем гуглдок
    var GOOGLE_DOC_URL =
        "самостоятельно создайте пустой google spreadsheet с правом редактирования по ссылке и здесь оставьте ссылку на него
    var spreadsheet = SpreadsheetApp.openByUrl(GOOGLE_DOC_URL);

    //формируем название вкладки из текущей даты вида "2017.01.21"
    var currentDate = new Date();
    var day = currentDate.getDate();
    var month = currentDate.getMonth() + 1;
    var year = currentDate.getFullYear();
    var name = day + "." + (month < 10 ? '0' + month : month) + "." + year;

    //если вкладка с таким именем уже есть, удаляем её
    var cursheet = spreadsheet.getActiveSheet();
    if (cursheet) if (cursheet.getName() == name) spreadsheet.deleteSheet(cursheet);

    //добавялем новую вкладку вида "2017.01.21"
    var newSheet = spreadsheet.insertSheet(name, 0);

    //колонки + их форматирование
    var columnNames = ["Кампания", "Группа объявлений", "Фраза", "Клики", "Показы",
        "Показов %", "QS фразы", "Расширенный QS", "CTR", "Cost", "TopOfPageCpc",
        "FirstPageCpc", "isPaused"];
    var headersRange = newSheet.getRange(1, 1, 1, columnNames.length);
    headersRange.setFontWeight("bold");
    headersRange.setFontSize(12);
    headersRange.setBorder(false, false, true, false, false, false);
    headersRange.setValues([columnNames]);
  
    newSheet.setFrozenRows(1);

    var campaignIterator = AdWordsApp.campaigns()
        .withCondition('Status = ENABLED')
        .get();
    //пробегаемся по кампаниям
    while (campaignIterator.hasNext()) {
        var campaign = campaignIterator.next();
        var count_campaign = 0;
        var keywords = [];

        Logger.log('Кампания: ' + campaign.getName());

        var adGroupIterator = campaign.adGroups()
            .withCondition('Status = ENABLED')
            .get();

        var impressions_campaign_sum = 0, clicks_campaign_sum = 0, impressions_proc_campaign_sum = 0,
            qs_campaign_sum = 0, qs_e_campaign_sum = 0, cost_campaign_sum = 0,
            ctr_campaign_sum = 0, topcpc_campaign_sum = 0, firstpagecpc_campaign_sum = 0;


        //пробегаемся по группам
        while (adGroupIterator.hasNext()) {
            var adGroup = adGroupIterator.next();
            var campaign_name = (adGroup.getCampaign() ? adGroup.getCampaign().getName() : '');

            Logger.log('Группа: ' + adGroup.getName());

            keywords = [];

            var keywordIterator = adGroup.keywords().get();

            //считаем показы кейвордов всей группы за неделю
            // (чтобы потом посчитать % показов для каждого кейворда)
            var all_impressions = 0;
            while (keywordIterator.hasNext()) {
                var keyword = keywordIterator.next();
                var stats = keyword.getStatsFor("LAST_WEEK");
                all_impressions = all_impressions + stats.getImpressions();
            }

            //собираем данные по каждому кейворду группы в массив,
            // дабы после отсортировать и вывести в гуглдок
            var keywordIterator = adGroup.keywords().get();
            var impressions_sum = 0, clicks_sum = 0, impressions_proc_sum = 0, qs_sum = 0,
                qs_e_sum = 0, cost_sum = 0, ctr_sum = 0, topcpc_sum = 0, firstpagecpc_sum = 0, ctr_not_zero_count = 0;
            while (keywordIterator.hasNext()) {
                var keyword = keywordIterator.next();

                //берем статус за последнюю неделю
                var stats = keyword.getStatsFor("LAST_WEEK");

                //если показов не было, то кейворд не берем в гуглдок
                var impressions = stats.getImpressions();
                if (!impressions) continue;

                //берем клики
                var clicks = stats.getClicks();

                //считаем процент показов
                var impressions_proc =
                    Math.round((all_impressions > 0 ? impressions / all_impressions * 100 : 0) * 100) / 100;

                //берем CTR и Cost
                var ctr = Math.round(stats.getCtr() * 1000) / 10;
                var cost = Math.round(stats.getCost());


                //берем QualityScore
                var qs = keyword.getQualityScore();
                //процент QS
                var qs_e = Math.round(impressions_proc * qs);

                //берем MaxCpc
                var maxcpc = keyword.getMaxCpc();

                //берем TopOfPageCpc
                var topcpc = keyword.getTopOfPageCpc();

                //берем FirstPageCpc
                var firstpagecpc = keyword.getFirstPageCpc();

                //берем статус
                var ispaused = keyword.isPaused();
                var keytext = keyword.getText();
                //для фраз, начинающихся на +, добавляем в начало кавычку,
                // иначе гуглдок расценит это как спецсимвол и не отобразит данные в ячейке
                if (keytext[0] == '+') keytext = "'" + keytext;

                //добавляем все эти данные в массив
                keywords.push([campaign_name,
                    adGroup.getName(), keytext, clicks, impressions, impressions_proc, qs, qs_e,
                    ctr, cost, topcpc, firstpagecpc, ispaused]);

                //считаем сумму показателей для вывода ИТОГО,
                // среднего и максимального для каждой колонки, для группы и кампании
                clicks_sum += clicks;
                impressions_sum += impressions;
                impressions_proc_sum += impressions_proc;
                qs_sum += qs;
                qs_e_sum += qs_e;
                ctr_sum += ctr;
                if(ctr > 0) ctr_not_zero_count++;
                cost_sum += cost;
                firstpagecpc_sum += firstpagecpc;
                topcpc_sum += topcpc;

                clicks_campaign_sum += clicks;
                impressions_campaign_sum += impressions;
                impressions_proc_campaign_sum += impressions_proc;
                qs_campaign_sum += qs;
                qs_e_campaign_sum += qs_e;
                ctr_campaign_sum += ctr;
                cost_campaign_sum += cost;
                firstpagecpc_campaign_sum += firstpagecpc;
                topcpc_campaign_sum += topcpc;

                clicks_sum_total += clicks;
                impressions_sum_total += impressions;
                impressions_proc_sum_total += impressions_proc;
                qs_sum_total += qs;
                qs_e_sum_total += qs_e;
                ctr_sum_total += ctr;
                cost_sum_total += cost;
                firstpagecpc_sum_total += firstpagecpc;
                topcpc_sum_total += topcpc;                

                if (clicks > max[4]) max[4] = clicks;
                if (impressions > max[5]) max[5] = impressions;
                if (impressions_proc > max[6]) max[6] = impressions_proc;
                if (qs > max[7]) max[7] = qs;
                if (qs_e > max[8]) max[8] = qs_e;
                if (ctr > max[9]) max[9] = ctr;
                if (cost > max[10]) max[10] = cost;
                if (topcpc > max[11]) max[11] = topcpc;
                if (firstpagecpc > max[12]) max[12] = firstpagecpc;
            }


            //если в группе нашлись кейворды с показами, сортируем их и выводим в гуглдок всю пачку
            var count = keywords.length;
            count_total += count;
            count_campaign += count;
            if (count > 0) {
                Logger.log('Сортируем ключи: ' + keywords.length + ' шт.');
                keywords = usort(keywords, impressionsDesc);
                for (var i in keywords) {
                    Logger.log('Пишу: ' + keywords[i][2]);
                    newSheet.appendRow(keywords[i]);
                }

                //вывод ИТОГО для группы
                newSheet.appendRow(['Итого для группы ' + adGroup.getName()
                + ':', '', '', clicks_sum, Math.round(impressions_sum),
                    Math.round(impressions_proc_sum), Math.round(qs_sum / count),
                                    Math.round(qs_e_sum / count), (ctr_not_zero_count ? Math.round(ctr_sum / ctr_not_zero_count) : 0), Math.round(cost_sum),
                    Math.round(topcpc_sum / count), Math.round(firstpagecpc_sum / count)]);
                newSheet.getRange(newSheet.getLastRow(), 1, 1, newSheet.getLastColumn())
                    .setBackground('#ACDFF7').setFontWeight("bold");
            }

        }

        if (impressions_campaign_sum) {
            //вывод ИТОГО для кампании
            newSheet.appendRow(['Итого для кампании ' + campaign.getName()
            + ':', '', '', clicks_campaign_sum, Math.round(impressions_campaign_sum),
                Math.round(impressions_proc_campaign_sum),
                Math.round(qs_campaign_sum / count_campaign),
                Math.round(qs_e_campaign_sum / count_campaign), Math.round(ctr_campaign_sum / count_campaign),
                Math.round(cost_campaign_sum),
                Math.round(topcpc_campaign_sum / count_campaign),
                Math.round(firstpagecpc_campaign_sum / count_campaign)]);
            newSheet.getRange(newSheet.getLastRow(), 1, 1, newSheet.getLastColumn())
                .setBackground('#5CDFF7').setFontWeight("bold");
            newSheet.appendRow([' ']);

            impressions_campaign_sum = 0, clicks_campaign_sum = 0, impressions_proc_campaign_sum = 0,
                qs_campaign_sum = 0, qs_e_campaign_sum = 0, cost_campaign_sum = 0,
                ctr_campaign_sum = 0, topcpc_campaign_sum = 0, firstpagecpc_campaign_sum = 0;
        }
    }

    //вывод общего ИТОГО
    newSheet.insertRowAfter(1);
    newSheet.appendRow(['Общий итог:', '', '', clicks_sum_total, Math.round(impressions_sum_total),
        Math.round(impressions_proc_sum_total), Math.round(qs_sum_total / count_total),
        Math.round(qs_e_sum_total / count_total), Math.round(ctr_sum_total / count_total),
        Math.round(cost_sum_total), Math.round(topcpc_sum_total / count_total),
        Math.round(firstpagecpc_sum_total / count_total)]);
    newSheet.getRange(newSheet.getLastRow(), 1, 1, newSheet.getLastColumn())
        .setBackground('#A685E0').setFontWeight("bold").moveTo(newSheet.getRange("A2"));
  
    //перекраска колонок
    for (var j = 4; j <= 12; j++) {
        var colors = ['#f8696b', '#faa274', '#ccdd81', '#9acd7e'];
        if (j > 9)  colors = colors.reverse();

        for (var i = 2; i <= newSheet.getLastRow() - 2; i++) {
            var cell = newSheet.getRange(i, j);
            if (cell.getBackground() != '#ffffff') continue;

            for (var part = 1; part < 5; part++) {
                if (cell.getValue() !== '')
                    if ((cell.getValue() < max[j] / 4 * part && cell.getValue() >= max[j] / 4 * (part - 1))
                        || (part == 4 && cell.getValue() == max[j]))
                        cell.setBackground(colors[part - 1]);
            }

        }
    }
  
    for (var i = 2; i <= newSheet.getLastRow() - 2; i++) {
      var cell = newSheet.getRange(i, 13);
      if (cell.getBackground() != '#ffffff') continue;
      if (cell.getValue()) cell.setBackground('#f8696b');
    }

    //автоматический размер колонок
    newSheet.autoResizeColumn(1).autoResizeColumn(2)
        .autoResizeColumn(3).autoResizeColumn(7)
        .autoResizeColumn(8).autoResizeColumn(9)
        .autoResizeColumn(10).autoResizeColumn(11);
}

//функции для сортировки массива
function usort(inputArr, sorter) {

    var valArr = [],
        k = '',
        i = 0,
        strictForIn = false,
        populateArr = {};

    if (typeof sorter === 'string') {
        sorter = this[sorter];
    } else if (Object.prototype.toString.call(sorter) === '[object Array]') {
        sorter = this[sorter[0]][sorter[1]];
    }
    this.php_js = this.php_js || {};
    this.php_js.ini = this.php_js.ini || {};
    strictForIn = this.php_js.ini['phpjs.strictForIn']
    && this.php_js.ini['phpjs.strictForIn'].local_value && this.php_js
        .ini['phpjs.strictForIn'].local_value !== 'off';
    populateArr = strictForIn ? inputArr : populateArr;

    for (k in inputArr) {
        if (inputArr.hasOwnProperty(k)) {
            valArr.push(inputArr[k]);
            if (strictForIn) {
                delete inputArr[k];
            }
        }        
    }
    try {
        valArr.sort(sorter);
    } catch (e) {
        return false;
    }
    for (i = 0; i < valArr.length; i++) {
        populateArr[i] = valArr[i];
    }

    return strictForIn || populateArr;
}

function impressionsDesc(a, b) {
    return b[3] - a[3];
}
</script>

Отчет по поисковым запросам

В данном отчете формируются поисковые запросы, в ответ на которые показываются наши объявления. С помощью этого отчета можно собрать дополнительный список минус-слов.

<script type="text/javascript">
// Минимальное количество показов для работы с "достаточными данными"
var IMPRESSIONS_THRESHOLD = 100;
// Цена за клик (в валюте аккаунта) мы считаем релевантным ключевым словом.
var AVERAGE_CPC_THRESHOLD = 1; // $1
// Порог, который мы используем, чтобы решить, является ли ключевое слово хорошим или плохим.
var CTR_THRESHOLD = 0.5; // 0.5%
// Если CTR выше порогового значения, и наша цена за конверсию не слишком высока,
// ключевое слово станет релевантным.
var COST_PER_CONVERSION_THRESHOLD = 10; // $10
// Одна валютная единица равна миллиону.
var MICRO_AMOUNT_MULTIPLIER = 1000000;

/**
 * Конфигурация, которая будет использоваться для запуска отчетов.
 */
var REPORTING_OPTIONS = {
  // Закомментируйте следующую строку по умолчанию для последней версии отчетов.
  apiVersion: 'v201705'
};

function main() {
  var report = AdWordsApp.report(
      'SELECT Query, Clicks, Cost, Ctr, ConversionRate,' +
      ' CostPerConversion, Conversions, CampaignId, AdGroupId ' +
      ' FROM SEARCH_QUERY_PERFORMANCE_REPORT ' +
      ' WHERE ' +
          ' Conversions > 0' +
          ' AND Impressions > ' + IMPRESSIONS_THRESHOLD +
          ' AND AverageCpc > ' +
           (AVERAGE_CPC_THRESHOLD * MICRO_AMOUNT_MULTIPLIER) +
      ' DURING LAST_7_DAYS', REPORTING_OPTIONS);
  var rows = report.rows();

  var negativeKeywords = {};
  var positiveKeywords = {};
  var allAdGroupIds = {};
  // Выполните поиск по поисковому запросу и решите, стоит ли
  // добавить ключевые слова или игнорировать их.
  while (rows.hasNext()) {
    var row = rows.next();
    if (parseFloat(row['Ctr']) < CTR_THRESHOLD) {
      addToMultiMap(negativeKeywords, row['AdGroupId'], row['Query']);
      allAdGroupIds[row['AdGroupId']] = true;
    } else if (parseFloat(row['CostPerConversion']) <
        COST_PER_CONVERSION_THRESHOLD) {
      addToMultiMap(positiveKeywords, row['AdGroupId'], row['Query']);
      allAdGroupIds[row['AdGroupId']] = true;
    }
  }

  // Скопируйте все adGroupIds из объекта в массив.
  var adGroupIdList = [];
  for (var adGroupId in allAdGroupIds) {
    adGroupIdList.push(adGroupId);
  }

  // Добавьте ключевые слова как отрицательные или положительные в соответствующие группы объявлений.
  var adGroups = AdWordsApp.adGroups().withIds(adGroupIdList).get();
  while (adGroups.hasNext()) {
    var adGroup = adGroups.next();
    if (negativeKeywords[adGroup.getId()]) {
      for (var i = 0; i < negativeKeywords[adGroup.getId()].length; i++) {
        adGroup.createNegativeKeyword(
            '[' + negativeKeywords[adGroup.getId()][i] + ']');
      }
    }
    if (positiveKeywords[adGroup.getId()]) {
      for (var i = 0; i < positiveKeywords[adGroup.getId()].length; i++) {
        var keywordOperation = adGroup.newKeywordBuilder()
            .withText('[' + positiveKeywords[adGroup.getId()][i] + ']')
            .build();
      }
    }
  }
}

function addToMultiMap(map, key, value) {
  if (!map[key]) {
    map[key] = [];
  }
  map[key].push(value);
}
</script>

Выявление аномалий в аккаунте

Скрипт позволяет узнать об аномалиях в рекламном аккаунте и при обнаружении их отправляет информацию специалисту на электронную почту. Скрипт анализирует информацию за текущий день и сравнивает с информацией по этому дню недели. Например, сегодня среда и он сравнивает сегодняшнюю статистику со статистикой всех прошлых сред.

<script type="text/javascript">
// Минимальное количество показов для работы с "достаточными данными"
var IMPRESSIONS_THRESHOLD = 100;
// Цена за клик (в валюте аккаунта) мы считаем релевантным ключевым словом.
var AVERAGE_CPC_THRESHOLD = 1; // $1
// Порог, который мы используем, чтобы решить, является ли ключевое слово хорошим или плохим.
var CTR_THRESHOLD = 0.5; // 0.5%
// Если CTR выше порогового значения, и наша цена за конверсию не слишком высока,
// ключевое слово станет релевантным.
var COST_PER_CONVERSION_THRESHOLD = 10; // $10
// Одна валютная единица равна миллиону.
var MICRO_AMOUNT_MULTIPLIER = 1000000;

/**
 * Конфигурация, которая будет использоваться для запуска отчетов.
 */
var REPORTING_OPTIONS = {
  // Закомментируйте следующую строку по умолчанию для последней версии отчетов.
  apiVersion: 'v201705'
};

function main() {
  var report = AdWordsApp.report(
      'SELECT Query, Clicks, Cost, Ctr, ConversionRate,' +
      ' CostPerConversion, Conversions, CampaignId, AdGroupId ' +
      ' FROM SEARCH_QUERY_PERFORMANCE_REPORT ' +
      ' WHERE ' +
          ' Conversions > 0' +
          ' AND Impressions > ' + IMPRESSIONS_THRESHOLD +
          ' AND AverageCpc > ' +
           (AVERAGE_CPC_THRESHOLD * MICRO_AMOUNT_MULTIPLIER) +
      ' DURING LAST_7_DAYS', REPORTING_OPTIONS);
  var rows = report.rows();

  var negativeKeywords = {};
  var positiveKeywords = {};
  var allAdGroupIds = {};
  // Выполните поиск по поисковому запросу и решите, стоит ли
  // добавить ключевые слова или игнорировать их.
  while (rows.hasNext()) {
    var row = rows.next();
    if (parseFloat(row['Ctr']) < CTR_THRESHOLD) {
      addToMultiMap(negativeKeywords, row['AdGroupId'], row['Query']);
      allAdGroupIds[row['AdGroupId']] = true;
    } else if (parseFloat(row['CostPerConversion']) <
        COST_PER_CONVERSION_THRESHOLD) {
      addToMultiMap(positiveKeywords, row['AdGroupId'], row['Query']);
      allAdGroupIds[row['AdGroupId']] = true;
    }
  }

  // Скопируйте все adGroupIds из объекта в массив.
  var adGroupIdList = [];
  for (var adGroupId in allAdGroupIds) {
    adGroupIdList.push(adGroupId);
  }

  // Добавьте ключевые слова как отрицательные или положительные в соответствующие группы объявлений.
  var adGroups = AdWordsApp.adGroups().withIds(adGroupIdList).get();
  while (adGroups.hasNext()) {
    var adGroup = adGroups.next();
    if (negativeKeywords[adGroup.getId()]) {
      for (var i = 0; i < negativeKeywords[adGroup.getId()].length; i++) {
        adGroup.createNegativeKeyword(
            '[' + negativeKeywords[adGroup.getId()][i] + ']');
      }
    }
    if (positiveKeywords[adGroup.getId()]) {
      for (var i = 0; i < positiveKeywords[adGroup.getId()].length; i++) {
        var keywordOperation = adGroup.newKeywordBuilder()
            .withText('[' + positiveKeywords[adGroup.getId()][i] + ']')
            .build();
      }
    }
  }
}

function addToMultiMap(map, key, value) {
  if (!map[key]) {
    map[key] = [];
  }
  map[key].push(value);
}var SPREADSHEET_URL = 'YOUR_SPREADSHEET_URL';
var DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday',
            'Saturday', 'Sunday'];
/**
 * Конфигурация, которая будет использоваться для запуска отчетов.
 */
var REPORTING_OPTIONS = {
  // Закомментируйте следующую строку по умолчанию для последней версии отчетов.
  apiVersion: 'v201705'
};
function main() {
  Logger.log('Using spreadsheet - %s.', SPREADSHEET_URL);
  var spreadsheet = validateAndGetSpreadsheet(SPREADSHEET_URL);
spreadsheet.setSpreadsheetTimeZone(AdWordsApp.currentAccount().getTimeZone());
  var impressionsThreshold = parseField(spreadsheet.
      getRangeByName('impressions').getValue());
  var clicksThreshold = parseField(spreadsheet.getRangeByName('clicks').
      getValue());
  var conversionsThreshold =
      parseField(spreadsheet.getRangeByName('conversions').getValue());
  var costThreshold = parseField(spreadsheet.getRangeByName('cost').getValue());
  var weeksStr = spreadsheet.getRangeByName('weeks').getValue();
  var weeks = parseInt(weeksStr.substring(0, weeksStr.indexOf(' ')));
  var email = spreadsheet.getRangeByName('email').getValue();
  var now = new Date();
  // Базовая статистика отчетов обычно доступна не более чем за 3 часа
  // задержки.
  var upTo = new Date(now.getTime() - 3 * 3600 * 1000);
  var upToHour = parseInt(getDateStringInTimeZone('h', upTo));

  if (upToHour == 1) {
    // первый запуск за день, скроет существующие оповещения
    spreadsheet.getRangeByName('clicks_alert').clearContent();
    spreadsheet.getRangeByName('impressions_alert').clearContent();
    spreadsheet.getRangeByName('conversions_alert').clearContent();
    spreadsheet.getRangeByName('cost_alert').clearContent();
  }

  var dateRangeToCheck = getDateStringInPast(0, upTo);
  var dateRangeToEnd = getDateStringInPast(1, upTo);
  var dateRangeToStart = getDateStringInPast(1 + weeks * 7, upTo);
  var fields = 'HourOfDay,DayOfWeek,Clicks,Impressions,Conversions,Cost';
  var todayQuery = 'SELECT ' + fields +
      ' FROM ACCOUNT_PERFORMANCE_REPORT DURING ' + dateRangeToCheck + ',' +
      dateRangeToCheck;
  var pastQuery = 'SELECT ' + fields +
      ' FROM ACCOUNT_PERFORMANCE_REPORT WHERE DayOfWeek=' +
      DAYS[getDateStringInTimeZone('u', now)].toUpperCase() +
      ' DURING ' + dateRangeToStart + ',' + dateRangeToEnd;

  var todayStats = getReportStats(todayQuery, upToHour, 1);
  var pastStats = getReportStats(pastQuery, upToHour, weeks);

  var statsExist = true;
  if (typeof todayStats === 'undefined' || typeof pastStats === 'undefined') {
    statsExist = false;
  }

  var alertText = [];
  if (statsExist && impressionsThreshold &&
      todayStats.impressions < pastStats.impressions * impressionsThreshold) {
    var ImpressionsAlert = '    Impressions are too low: ' +
        todayStats.impressions + ' impressions by ' + upToHour +
        ':00, expecting at least ' +
        parseInt(pastStats.impressions * impressionsThreshold);
    writeAlert(spreadsheet, 'impressions_alert', alertText, ImpressionsAlert,
        upToHour);
  }
  if (statsExist && clicksThreshold &&
      todayStats.clicks < pastStats.clicks * clicksThreshold) {
    var clickAlert = '    Clicks are too low: ' + todayStats.clicks +
        ' clicks by ' + upToHour + ':00, expecting at least ' +
        (pastStats.clicks * clicksThreshold).toFixed(1);
    writeAlert(spreadsheet, 'clicks_alert', alertText, clickAlert, upToHour);
  }
  if (statsExist && conversionsThreshold &&
      todayStats.conversions < pastStats.conversions * conversionsThreshold) {
    var conversionsAlert =
        '    Conversions are too low: ' + todayStats.conversions +
        ' conversions by ' + upToHour + ':00, expecting at least ' +
        (pastStats.conversions * conversionsThreshold).toFixed(1);
    writeAlert(
        spreadsheet, 'conversions_alert', alertText, conversionsAlert,
        upToHour);
  }
  if (statsExist && costThreshold &&
      todayStats.cost > pastStats.cost * costThreshold) {
    var costAlert = '    Cost is too high: ' + todayStats.cost + ' ' +
          AdWordsApp.currentAccount().getCurrencyCode() + ' by ' + upToHour +
          ':00, expecting at most ' +
          (pastStats.cost * costThreshold).toFixed(2);
    writeAlert(spreadsheet, 'cost_alert', alertText, costAlert, upToHour);
  }

  if (alertText.length > 0 && email && email.length > 0) {
    MailApp.sendEmail(email,
        'AdWords Account ' + AdWordsApp.currentAccount().getCustomerId() +
        ' misbehaved.',
        'Your account ' + AdWordsApp.currentAccount().getCustomerId() +
        ' is not performing as expected today: \n\n' + alertText.join('\n') +
        '\n\nLog into AdWords and take a look.\n\nAlerts dashboard: ' +
        SPREADSHEET_URL);
  }

  writeDataToSpreadsheet(spreadsheet, now, statsExist, todayStats, pastStats,
      AdWordsApp.currentAccount().getCustomerId());
}

function toFloat(value) {
  value = value.toString().replace(/,/g, '');
  return parseFloat(value);
}

function parseField(value) {
  if (value == 'No alert') {
    return null;
  } else {
    return toFloat(value);
  }
}

/**
 * Скрипт запускает запрос отчета AdWords в течение нескольких недель и возвращает среднее значения для статистики.
 *
 * @param {string} query Форматированный запрос отчета.
 * @param {int} hours Ограничение часа дня для рассмотрения строк отчета.
 * @param {int} week Количество недель для прошлой статистики.
 * @return {Object} Объект, содержащий средние значения для статистики.
 */
function getReportStats(query, hours, weeks) {
  var reportRows = [];
  var report = AdWordsApp.report(query, REPORTING_OPTIONS);
  var rows = report.rows();
  while (rows.hasNext()) {
    reportRows.push(rows.next());
  }
  return accumulateRows(reportRows, hours, weeks);
}

function accumulateRows(rows, hours, weeks) {
  var result = {clicks: 0, impressions: 0, conversions: 0, cost: 0};

  for (var i = 0; i < rows.length; i++) {
    var row = rows[i];
    var hour = row['HourOfDay'];
    if (hour < hours) {
      result = addRow(row, result, 1 / weeks);
    }
  }
  return result;
}

function addRow(row, previous, coefficient) {
  if (!coefficient) {
    coefficient = 1;
  }
  if (row == null) {
    row = {Clicks: 0, Impressions: 0, Conversions: 0, Cost: 0};
  }
  if (!previous) {
    return {
      clicks: parseInt(row['Clicks']) * coefficient,
      impressions: parseInt(row['Impressions']) * coefficient,
      conversions: parseInt(row['Conversions']) * coefficient,
      cost: toFloat(row['Cost']) * coefficient
    };
  } else {
    return {
      clicks: parseInt(row['Clicks']) * coefficient + previous.clicks,
      impressions:
          parseInt(row['Impressions']) * coefficient + previous.impressions,
      conversions:
          parseInt(row['Conversions']) * coefficient + previous.conversions,
      cost: toFloat(row['Cost']) * coefficient + previous.cost
    };
  }
}

/**
 * Следующий скрипт создает отформатированную строку, представляющую дату в прошлом заданной даты.
 *
 * @param {number} numDays Количество дней в прошлом.
 * @param {date} date Объект даты. По умолчанию используется текущая дата.
 * @return {string} Форматированная строка в заданной дате.
 */
function getDateStringInPast(numDays, date) {
  date = date || new Date();
  var MILLIS_PER_DAY = 1000 * 60 * 60 * 24;
  var past = new Date(date.getTime() - numDays * MILLIS_PER_DAY);
  return getDateStringInTimeZone('yyyyMMdd', past);
}


/**
 * Этот скрипт создает отформатированную строку, представляющую данную дату в заданном часовом поясе.
 *
 * @param {string} format Спецификатор формата для создаваемой строки.
 * @param {date} date Объект даты. По умолчанию используется текущая дата.
 * @param {string} timeZone Часовой пояс. По умолчанию используется часовой пояс аккаунта.
 * @return {string} Форматированная строка с указанной датой в заданном часовом поясе.
 */
function getDateStringInTimeZone(format, date, timeZone) {
  date = date || new Date();
  timeZone = timeZone || AdWordsApp.currentAccount().getTimeZone();
  return Utilities.formatDate(date, timeZone, format);
}

/**
* Скрипт проверяет предоставленную таблицу URL-адрес и адрес электронной почты
 * чтобы убедиться, что они настроены правильно. Выдает описательное сообщение об ошибке
 * если проверка не пройдена.
 *
 * @param {string} spreadsheeturl URL-адрес электронной таблицы, которую нужно открыть.
 * @return {Spreadsheet} Сам объект электронной таблицы, извлеченный из URL.
 * @throws {Ошибка} Если URL-адрес электронной почты или электронная почта не заданы
 */
function validateAndGetSpreadsheet(spreadsheeturl) {
  if (spreadsheeturl == 'YOUR_SPREADSHEET_URL') {
    throw new Error('Please specify a valid Spreadsheet URL. You can find' +
        ' a link to a template in the associated guide for this script.');
  }
  var spreadsheet = SpreadsheetApp.openByUrl(spreadsheeturl);
  var email = spreadsheet.getRangeByName('email').getValue();
  if ('foo@example.com' == email) {
    throw new Error('Please either set a custom email address in the' +
        ' spreadsheet, or set the email field in the spreadsheet to blank' +
        ' to send no email.');
  }
  return spreadsheet;
}

/**
 * Скрипт записывает время оповещения в таблицу и отправляет сообщение оповещения на
 * список сообщений.
 *
 * @param {Spreadsheet}, электронная таблица.
 * @param {string} rangeName Именованный диапазон в таблице.
 * @param {Array <string>} alertText Список предупреждений.
 * @param {string} alertMessage Сообщение с предупреждением.
 * @param {int} hour Лимит часа для получения статистики.
 */
function writeAlert(spreadsheet, rangeName, alertText, alertMessage, hour) {
  var range = spreadsheet.getRangeByName(rangeName);
  if (!range.getValue() || range.getValue().length == 0) {
    alertText.push(alertMessage);
    range.setValue('Alerting ' + hour + ':00');
  }
}

/**
 * Скрипт записывает данные в таблицу.
 *
 * @param {Spreadsheet} Таблица.
 * @param {Date} now Дата, соответствующая времени выполнения скрипта.
 * @param {boolean} statsExist Логическое значение, указывающее на наличие статистики.
 * @param {Object} todayStats Статистика за сегодня.
 * @param {Object} pastStats Статистика прошлых периодов, определенных в таблицу.
 * @param {string} accountId Идентификатор учетной записи.
 */
function writeDataToSpreadsheet(spreadsheet, now, statsExist, todayStats,
                                pastStats, accountId) {
  spreadsheet.getRangeByName('date').setValue(now);
  spreadsheet.getRangeByName('account_id').setValue(accountId);
  spreadsheet.getRangeByName('timestamp').setValue(
    getDateStringInTimeZone('E HH:mm:ss z', now));

  if (statsExist) {
    var dataRows = [
      [todayStats.impressions, pastStats.impressions.toFixed(0)],
      [todayStats.clicks, pastStats.clicks.toFixed(1)],
      [todayStats.conversions, pastStats.conversions.toFixed(1)],
      [todayStats.cost, pastStats.cost.toFixed(2)]
    ];
    spreadsheet.getRangeByName('data').setValues(dataRows);
  }
}
</script>

Это не самые популярные скрипты, а лишь те, которые используем мы. С ними проще следить за изменениями в аккаунтах и они экономят время — мы больше анализируем и управляем кампаниями. Еще примеры вы найдете на официальном сайте Google Ads.

Знаете другие полезные скрипты для автоматизации? Поделитесь ими в нашем Telegram-чате.

Приложить файл
Отправляя форму, я принимаю пользовательское соглашение
Был ли материал вам полезен?
Да
Нет
Спасибо
Отправляя форму, я принимаю пользовательское соглашение
Прочитайте еще
Какие форматы рекламы в Фейсбуке подходят для продвижения магазина игрушек?
просмотры3743
репосты 10
уровень Специалист
Как не слить бюджет, используя умные кампании Google Ads?
просмотры2091
репосты 11
уровень Специалист
Кейсы
Как привлечь аудиторию на мероприятие через лид-формы во ВКонтакте и Фейсбуке за 478 рублей
Продвижение фитнес-форума в Сочи
просмотры7949
репосты 8
уровень Специалист
Biz360: продвижение интернет-СМИ на федеральную аудиторию
Связкой таргетированной, контекстной и медийной рекламы