Любому специалисту работающему с Google Ads знаком процесс анализа поисковых фраз. Если вы не используете широкий тип соответствия, и следите за актуальностью минус-слов, то мусора там довольно мало, а вот возможностей для сегментации и более точного таргетинга — много.

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

Скрипт собирает данные из Google Ads и связанного с ним представления в Universal Analytics.

Это конфиг:

function CONFIG() {
    return {
        // ID профиля GA с которым связан рекламный аккаунт
        gaProfileId: '1234567890',

        // Ярлык которым скрипт помечает созданные слова
        scriptLabel: 'Keyword Builder',

        // Указываем количество дней для выборки
        // Если хотим использовать данные о конверсиях или доходности, то в качестве значения
        // следует указывать число большее чем окно конверсии.
        customDaysInDateRange: 30,

        // Указываем на сколько дней от сегодняшнего мы сдвигаем выборку.
        // Нужно для того чтобы не брать те дни когда запаздывает статистика.
        // Если хотим использовать данные о конверсиях или доходности, то в качестве значения
        // следует указывать число равное дням в окне конверсии.
        customDateRangeShift: 0,
        // Добавляемые типы соответствий (BROAD, PHRASE, EXACT). Оставьте в списке только нужные
        targetMatchType: [
            'BROAD',
            'PHRASE',
            'EXACT'
        ],
    }
}

Выбираем кампании которые подходят для анализа:

function main() {

    ensureAccountLabels(); // Проверяем и создаем ярлыки
    var campaignQuery = 'SELECT ' +
        'campaign.name ' +
        'FROM campaign ' +
        'WHERE campaign.advertising_channel_type = "SEARCH" ' +
        'AND campaign.status != "REMOVED" ' +
        'AND metrics.impressions > ' + CONFIG().customDaysInDateRange + ' ' +
        'AND segments.date BETWEEN "' + customDateRange('from') + '" AND "' + customDateRange('to') + '"';
    var campaignResult = AdsApp.search(campaignQuery, {
        apiVersion: 'v8'
    });
    while (campaignResult.hasNext()) {
        var campaign_row = campaignResult.next(),
            campaign_name = campaign_row.campaign.name;
        if (campaign_row) {
            adGroupReport(campaign_name); // Создаем ключи
        }
    }
}

В них выбираем группы, с достаточной статистикой:

function adGroupReport(campaign_name) {
    var adGroupSelector = AdsApp.adGroups()
        .withCondition('Impressions > ' + CONFIG().customDaysInDateRange)
        .withCondition('CampaignName = "' + campaign_name + '"')
        .withCondition('Status != REMOVED')
        .forDateRange('LAST_30_DAYS')
        .orderBy('Cost DESC');
    var adGroupIterator = adGroupSelector.get();
    while (adGroupIterator.hasNext()) {
        var ad_group = adGroupIterator.next(),
            ad_group_id = ad_group.getId(),
            ad_group_name = ad_group.getName(),
            campaign_name = ad_group.getCampaign().getName(),
            campaign_id = ad_group.getCampaign().getId();
        Logger.log('Campaign: ' + campaign_name + ', Ad Group: ' + ad_group_name);
        buildNewKeywords(ad_group_id, campaign_id);
        Logger.log('-----------------------------------------------------------------------------------------');
    }
}

И уже в самих группах формируем отчеты, на основе которых и буду создаваться новые фразы. Отчеты по поисковым запросам из Ads и Analytics склеиваем и проверяем на конфликты с минус-словами.

function buildNewKeywords(ad_group_id, campaign_id) {

    var allNegativeKeywordsList = getNegativeKeywordForAdGroup(),
        // Минус-слова собранные со всех уровней (группа, кампания, списки)
        google_ads_queries = getSearchQweries(),
        // поисковые запросы по данным Google Ads
        google_analytics_queries = getGaReport(AdWordsApp.currentAccount().getCustomerId().replace(/\-/gm, ''), CONFIG().gaProfileId);
        // поисковые запросы по данным Google Aanalytics

    var full_queries_list = google_ads_queries.concat(google_analytics_queries);
    full_queries_list = unique(full_queries_list).sort();

    addingKeywords(full_queries_list); // Добавляем новые ключевые слова

    function addingKeywords(arr) {
        var adGroupIterator = AdWordsApp.adGroups()
            .withCondition('CampaignId = ' + campaign_id)
            .withCondition('AdGroupId = ' + ad_group_id)
            .get();
        while (adGroupIterator.hasNext()) {
            var adGroup = adGroupIterator.next();
            for (var k = 0; k < arr.length; k++) {
                if (checkNegativeKeywords(arr[k]) != false) { // проверяем пересечение с минус-словами
                    var match_types = CONFIG().targetMatchType;
                    for (var m = 0; m < match_types.length; m++) {
                        if (match_types[m] == 'BROAD') {
                            var new_key = arr[k];
                        }
                        if (match_types[m] == 'PHRASE') {
                            var new_key = '"' + arr[k] + '"';
                        }
                        if (match_types[m] == 'EXACT') {
                            var new_key = '[' + arr[k] + ']';
                        }
                        var keywordOperation = adGroup.newKeywordBuilder()
                            .withText(new_key)
                            .build();
                        if (keywordOperation.isSuccessful()) { // Получение результатов.
                            keyword.pause();
                            keyword.applyLabel(customDateRange('now').toString());
                            keyword.applyLabel(CONFIG().scriptLabel);
                            Logger.log('Добавили: ' + new_key.toString());
                        } else {
                            var errors = keywordOperation.getErrors();
                            // Исправление ошибок
                        }
                    }
                }
            }
        }
    }

    function getSearchQweries() {
        var report = [];
        var search_term_query = 'SELECT ' +
            'search_term_view.search_term, ' +
            'metrics.impressions ' +
            'FROM search_term_view ' +
            'WHERE search_term_view.status NOT IN ("ADDED", "ADDED_EXCLUDED", "EXCLUDED") ' +
            'AND campaign.id = ' + campaign_id + ' ' +
            'AND ad_group.id = ' + ad_group_id + ' ' +
            'AND metrics.impressions >= ' + CONFIG().customDaysInDateRange + ' ' +
            'AND segments.date BETWEEN "' + customDateRange('from') + '" AND "' + customDateRange('to') + '"';
        var search_term_result = AdsApp.search(search_term_query, {
            apiVersion: 'v8'
        });
        while (search_term_result.hasNext()) {
            var search_term_row = search_term_result.next();
            var search_term = search_term_row.searchTermView.searchTerm.toLowerCase().trim();
            var impressions = search_term_row.metrics.impressions;
            if (search_term.split(' ').length <= 7) {
                report.push(search_term);
            }
        }
        report = unique(report).sort();
        return report;
    }

    function getGaReport(id, profile_id) {
        var report = [];
        var today = new Date(),
            start_date = new Date(today.getTime() - (CONFIG().customDaysInDateRange + CONFIG().customDateRangeShift) * 24 * 60 * 60 * 1000),
            end_date = new Date(today.getTime() - CONFIG().customDateRangeShift * 24 * 60 * 60 * 1000),
            start_formatted_date = Utilities.formatDate(start_date, 'UTC', 'yyyy-MM-dd'),
            end_formatted_date = Utilities.formatDate(end_date, 'UTC', 'yyyy-MM-dd');
        var table_id = 'ga:' + profile_id;
        var metric = 'ga:impressions';
        var options = {
            'samplingLevel': 'HIGHER_PRECISION',
            'dimensions': 'ga:keyword,ga:adMatchedQuery',
            'sort': '-ga:impressions',
            'filters': 'ga:adwordsCustomerID==' + id + ';ga:adKeywordMatchType!=Exact;ga:impressions>' + CONFIG().customDaysInDateRange + ';ga:adwordsAdGroupID==' + ad_group_id + ';ga:adwordsCampaignID==' + campaign_id,
            'max-results': 10000
        };
        var ga_report = Analytics.Data.Ga.get(table_id, start_formatted_date, end_formatted_date, metric, options);
        if (ga_report.rows) {
            for (var i = 0; i < ga_report.rows.length; i++) {
                var ga_row = ga_report.rows[i];
                var keyword = ga_row[0].replace(/\+/gm, '').toLowerCase().trim(),
                    ad_matched_query = ga_row[1].toLowerCase().trim();
                if (keyword != ad_matched_query) {
                    if (ad_matched_query.split(' ').length <= 7) {
                        report.push(ad_matched_query);
                    }
                }
            }
        } else {
            Logger.log('No rows returned.');
        }
        report = unique(report).sort();
        return report;
    }

    function getNegativeKeywordForAdGroup() { // Получаем минус-слова из группы
        var fullNegativeKeywordsList = [];

        var adGroupIterator = AdWordsApp.adGroups()
            .withCondition('CampaignId = ' + campaign_id)
            .withCondition('AdGroupId = ' + ad_group_id)
            .get();
        if (adGroupIterator.hasNext()) {
            var adGroup = adGroupIterator.next();
            var adGroupNegativeKeywordIterator = adGroup.negativeKeywords()
                .get();
            while (adGroupNegativeKeywordIterator.hasNext()) {
                var adGroupNegativeKeyword = adGroupNegativeKeywordIterator.next();
                fullNegativeKeywordsList[fullNegativeKeywordsList.length] = adGroupNegativeKeyword.getText();
            }
        }
        var negativesListFromCampaign = getCampaignNegatives(campaign_id);
        fullNegativeKeywordsList = fullNegativeKeywordsList.concat(fullNegativeKeywordsList, negativesListFromCampaign).sort();

        return fullNegativeKeywordsList;

        function getCampaignNegatives(campaign_id) { // Получаем минус-слова из кампании
            var campaignNegativeKeywordsList = [];
            var campaignIterator = AdWordsApp.campaigns()
                .withCondition('CampaignId = ' + campaign_id)
                .get();
            if (campaignIterator.hasNext()) {
                var campaign = campaignIterator.next();
                var negativeKeywordListSelector = campaign.negativeKeywordLists() // Получаем минус-слова из списков
                    .withCondition('Status = ACTIVE');
                var negativeKeywordListIterator = negativeKeywordListSelector.get();
                while (negativeKeywordListIterator.hasNext()) {
                    var negativeKeywordList = negativeKeywordListIterator.next();
                    var sharedNegativeKeywordIterator = negativeKeywordList.negativeKeywords().get();
                    var sharedNegativeKeywords = [];
                    while (sharedNegativeKeywordIterator.hasNext()) {
                        var negativeKeywordFromList = sharedNegativeKeywordIterator.next();
                        sharedNegativeKeywords[sharedNegativeKeywords.length] = negativeKeywordFromList.getText();
                    }
                    campaignNegativeKeywordsList = campaignNegativeKeywordsList.concat(campaignNegativeKeywordsList, sharedNegativeKeywords);
                }
                var campaignNegativeKeywordIterator = campaign.negativeKeywords().get();
                while (campaignNegativeKeywordIterator.hasNext()) {
                    var campaignNegativeKeyword = campaignNegativeKeywordIterator.next();
                    campaignNegativeKeywordsList[campaignNegativeKeywordsList.length] = campaignNegativeKeyword.getText();
                }
            }
            campaignNegativeKeywordsList = campaignNegativeKeywordsList.sort();
            return campaignNegativeKeywordsList;
        }
    }

    function checkNegativeKeywords(keywordForCheck) { // это какой-то древний кусок, не буду его трогать
        function checkingNegativeKeywords() {
            var result = true;
            function checkResult(check) {
                if (check != true) {
                    result = false;
                }
            }
            allNegativeKeywordsList.forEach(
                function (negativeKeyword) {
                    var negativeWord = negativeKeyword.toString().toLowerCase();
                    var clearedNegativeKeyword = negativeWord.replace(/\+/g, '').replace(/\"/g, '');
                    if ((keywordForCheck.indexOf('[') != -1) || (keywordForCheck.indexOf('"') != -1)) { // минус-фраза с точным или фразовым соответствием
                        if (negativeWord == keywordForCheck) {
                            checkResult(false);
                            // Logger.log('(1) Фраза: ' + keywordForCheck + ' Минус-фраза: ' + negativeWord);
                        } else if (negativeWord.indexOf('[') == -1) {
                            if (clearedNegativeKeyword.indexOf(' ') != -1) {
                                if (keywordForCheck.indexOf(clearedNegativeKeyword) != -1) {
                                    // очищеная минус-фраза есть в ключевой фразе, но минус-фраза не в точном и не в широком соответствии
                                    checkResult(false);
                                    // Logger.log('(2) Фраза: ' + keywordForCheck + ' Минус-фраза: ' + negativeWord);
                                }
                            } else {
                                var words = [];
                                words = keywordForCheck.toLowerCase().replace(/\+/g, '').replace(/\"/g, '').replace(/\[/g, '').replace(/\]/g, '').split(' '); // разбиваем ключевую фразу на слова
                                // Logger.log(words);
                                words.forEach(
                                    function (word) {
                                        if (negativeWord == word) { // проверяем совпадение минус-фразы(слова), со словами в ключевой фразе
                                            checkResult(false);
                                            // Logger.log('(3) Фраза: ' + keywordForCheck + ' Минус-фраза: ' + negativeWord);
                                        }
                                    }
                                );
                            }
                        }
                    } else { // минус-фраза с широким соответствием
                        if (negativeWord.indexOf(' ') != -1) {
                            var negativeWords = [];
                            negativeWords = negativeWord.replace(/\+/g, '').replace(/\"/g, '').replace(/\[/g, '').replace(/\]/g, '').split(' ');
                            var words = [];
                            words = keywordForCheck.toLowerCase().replace(/\+/g, '').replace(/\"/g, '').replace(/\[/g, '').replace(/\]/g, '').split(' '); // разбиваем ключевую фразу на слова
                            // Logger.log(words);
                            Array.prototype.diff = function (a) {
                                return this.filter(function (i) {
                                    return !(a.indexOf(i) > -1);
                                });
                            };
                            var diffWords = negativeWords.diff(words);
                            if (diffWords.length == 0) {
                                checkResult(false);
                                // Logger.log('(4) Фраза: ' + keywordForCheck + ' Минус-фраза: ' + negativeWord);
                            }
                        } else {
                            var words = [];
                            words = keywordForCheck.toLowerCase().replace(/\+/g, '').replace(/\"/g, '').replace(/\[/g, '').replace(/\]/g, '').split(' '); // разбиваем ключевую фразу на слова
                            // Logger.log(words);
                            words.forEach(
                                function (word) {
                                    if (negativeWord == word) { // проверяем совпадение минус-фразы(слова), со словами в ключевой фразе
                                        checkResult(false);
                                        // Logger.log('(5) Фраза: ' + keywordForCheck + ' Минус-фраза: ' + negativeWord);
                                    }
                                }
                            );
                        }
                    }
                }
            );
            return result;
        }
        return checkingNegativeKeywords();
    }
}

Добавленные слова скрипт помечает своим ярлыком - Keyword Builder, а также ярлыком с текущей датой. Это помогает понимать, кто и когда этот ключ добавил. Ключи добавляются на паузе, так как все происходящее в рекламной кампании — ответственность специалиста, он должен их отсмотреть и включить собственноручно.

Реклама

Ещё интересное


Добавить комментарий