Все услуги
Кинетическая ракета
Москва, ул. Бауманская, 7+7 (495) 792-29-50
Барнаул, ул. Балтийская, 248 800 700-02-07
Оставить заявку
Нажимая на кнопку, вы соглашаетесь с условиями политики конфиденциальности
или напишите нам:

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

Андрей Сахабаев
Head of PPC&CRM

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

Полина

Здравствуйте. В каждом аккаунте 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-чате.

Brand / Icon / Simple / SimpleIcon@3x Created with Sketch.