Лучший опыт

Create keywords from search queries using a script

Create keywords from search queries using a script

Any specialist working with Google Ads is familiar with the process of analyzing search phrases. If you do not use a broad match type, and keep your negative keywords up to date, then there is quite a bit of garbage, but there are many opportunities for segmentation and more precise targeting.

However, with a large amount of data, this work becomes monotonous and can often be ignored. In order not to occupy my head with such a routine, I automated the process using a script.

The script collects data from Google Ads and its associated view in Universal Analytics.

This is the config:

function CONFIG() {
    return {
        // ID the GA profile that the ad account is associated with
        gaProfileId: '1234567890',
      
        // The label with which the script marks the created keywords
        scriptLabel: 'Keyword Builder',

        // Specify the number of days to select
        // If we want to use data about conversions or profitability, then as a value 
        // you should specify a number greater than the conversion window. 
        customDaysInDateRange: 30,

         // Specify how many days from today we shift the selection.
         // Necessary in order not to take those days when statistics are not actual
         // If we want to use data about conversions or profitability, then as a value
         // you should specify a number equal to days in the conversion window.
        customDateRangeShift: 0,
      
        // Added match types (BROAD, PHRASE, EXACT). Leave only the ones you want on the list.
        targetMatchType: [
            'BROAD',
            'PHRASE',
            'EXACT'
        ],
    }
}

Select campaigns that are match for analysis:

function main() {

    ensureAccountLabels(); // Checking and creating labels
    
    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); // creating keywords
        }
    }
}

In them, we select ad groups with sufficient statistics:

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('-----------------------------------------------------------------------------------------');
    }
}

And already in the an groups themselves we generate reports, on the basis of which new phrases will be created. We join reports on search queries from Ads and Analytics and check for conflicts with negative keywords.

function buildNewKeywords(ad_group_id, campaign_id) {

    var allNegativeKeywordsList = getNegativeKeywordForAdGroup(), 
        // Negative keywords collected from all levels (group, campaign, lists)
        google_ads_queries = getSearchQweries(), 
        // search queries according to Google Ads
        google_analytics_queries = getGaReport(AdsApp.currentAccount().getCustomerId().replace(/\-/gm, ''), CONFIG().gaProfileId); 
        // search queries based on Google Analytics data

    var full_queries_list = google_ads_queries.concat(google_analytics_queries);
    full_queries_list = unique(full_queries_list).sort();
  
    addingKeywords(full_queries_list); // Add new keywords

    function addingKeywords(arr) {
        var adGroupIterator = AdsApp.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) { // check for intersection with negative keywords
                    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()) { // Get results
                            keyword.pause();
                            keyword.applyLabel(customDateRange('now').toString());
                            keyword.applyLabel(CONFIG().scriptLabel);
                            Logger.log('Added: ' + new_key.toString());
                        } else {
                            var errors = keywordOperation.getErrors(); 
                            // Error correction
                        }
                    }
                }
            }
        }
    }

    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() { // Get negative keywords from the group
        var fullNegativeKeywordsList = [];

        var adGroupIterator = AdsApp.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) { // Get negative keywords from the campaign
            var campaignNegativeKeywordsList = [];
            var campaignIterator = AdsApp.campaigns()
                .withCondition('CampaignId = ' + campaign_id)
                .get();
            if (campaignIterator.hasNext()) {
                var campaign = campaignIterator.next();
                var negativeKeywordListSelector = campaign.negativeKeywordLists() // Getting negative keywords from lists
                    .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) { // this is some ancient piece, I won't touch it
        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, '')