Поговорим о поисковых запросах в PMax
Если вы работаете в сфере PPC, то вы точно понимаете ценность релевантных ключевых слов для эффективной контекстной рекламы.
Ключевые слова – самый мощный сигнал умысла пользователя. Они отлично подходят для таргетинга. А еще они важны для унитаргетинга – исключение нерелевантных запросов.
Добавление отрицательных ключевых слов – один из наиболее эффективных методов сокращения расходов на рекламу и повышения доходности кампаний.
Однако компании Google Performance Max значительно усложняют возможность исключать неэффективные поисковые запросы, которые только тратят бюджет, не принося результатов. Этот тип кампаний более автоматизирован и многое не позволяет делать по сравнению с обычными поисковыми кампаниями.
Интерфейс Google Ads предоставляет очень мало информации о его работе. Даже базовые отчеты, такие как клики и конверсии по запросам, долгое время были недоступны.
Но больше не нужно мириться с этим! Как решение – автоматизированный скрипт.
Благодаря API Google Ads мы можем получить больше данных и использовать их в свою пользу. Представьте: каждую неделю вы получаете email-уведомление с Google Таблицей, где собраны все поисковые запросы, получившие значительное количество кликов, но никакой конверсии.
Готовы исключить их из кампаний и остановить утечку бюджета?
Этот скрипт уже готов к использованию – абсолютно бесплатно!
Автоматизированный скрипт для отслеживания неэффективных поисковых запросов в PMax
- Анализирует запросы в ваших кампаниях Performance Max.
- Фиксирует неэффективные запросы (без конверсий) в таблице Google.
- Отправляет вам email-уведомление, если обнаружены такие запросы.
Результат: список запросов, которые можно немедленно исключить для оптимизации ваших кампаний.
Готовы попробовать?
Инструкция по настройке
1. Скопируйте и установите скрипт в свой аккаунт Google Ads.
Вам не нужны навыки программирования – это просто копировать-вставить.
2. Создайте новую Таблицу Google.
Лайфхак для Chrome: в строке браузера введите sheets.new – и новая таблица откроется мгновенно.
3. Добавьте ссылку на эту таблицу в скрипт (строка 35).
4. Укажите свой email-адрес в скрипте (строка 36).
5. Добавьте имя аккаунта Google Ads в тему письма (строка 41).
6. Настройте переменные:
LOOKBACK_WINDOW – период анализа данных (строка 43).
MIN_CLICKS – минимальное количество кликов для анализа (строка 44).
CONVERSION_THRESHOLD – порог конверсий для исключения (строка 45).
7. Авторизируйте, проверьте работу скрипта и запланируйте его запуск.
Рекомендуемое время запуска: еженедельно, в понедельник в 06:00 утра.
| * PMax Non-Converting Search Term Alerts |
| * @author: Nils Rooijmans (c) |
| * 1. Create a new Google Sheet (tip for chrome users: simply type ‘sheets.new’ in the address bar) |
| * 2. Add the complete URL of the spreadsheet to the SPREADSHEET_URL below (line 35) |
| * 3. Add your email address to the script (line 36) |
| * 4. Add the name of your Google Ads account to the subject of emails (line 41) |
| * 5. Set the values for LOOKBACK_WINDOW, MIN_CLICKS and CONVERSION_THRESHOLD variables (lines 43,44,45) |
| * 6. Authorize and Preview |
| * 7. Schedule to run weekly (I prefer Mondays at 6AM) |
| * — log gaql errors in sheet |
| * — add ignore list in sheet for search terms to ignore |
| * — add option to clear sheet after each run (right now all new alerts will be added on top of sheet) |
| /*** [REQUIRED] ADD YOUR SETTINGS HERE ***/ |
| var SPREADSHEET_URL = «»; // insert a new blank spreadsheet url between the quotes, be sure to add the complete url of the spreadsheet |
| var EMAIL_ADDRESSES = «»; // insert email addresses of people that want to get the alerts between the quotes, seperate multipe email addresses by a comma |
| /*** [OPTIONAL] YOU MIGHT WANT TO CHANGE SOME CONFIGURATIONS HERE ***/ |
| var EMAIL_SUBJECT = «[GAds Script][account name] — PMax Alert — You’ve got {nr_of_alerts} Non-Converting Search Terms»; // subject of emails, you might want to include your account name here. Don’t replace the {nr_of_alerts} part. |
| var LOOKBACK_WINDOW = 90; // number of days before today, for which search term data is analysed |
| var MIN_CLICKS = 100; // ignore search terms with less clicks during the lookback window |
| var CONVERSION_THRESHOLD = 0.5; // alert when search term has had less conversions than the threshold |
| /*** DO NOT CHANGE ANYTHING BELOW THIS LINE ***/ |
| console.log(`Let’s get rolling…`); |
| var sheet = prepareOutputSheet(); |
| var campaignIds = getCampaignIds(); |
| if (campaignIds.length == 0) { |
| console.log(`The account currently has zero Performance Max campaigns that are enabled. We’re done here.`); |
| console.log(`The account currently has ${campaignIds.length} Performance Max campaigns that are enabled`); |
| var startDate = getDate(LOOKBACK_WINDOW); |
| var endDate = getDate(1); |
| var searchTermAlerts = processCampaigns(campaignIds, startDate, endDate); |
| if (searchTermAlerts.length == 0) { |
| console.log(`The account has zero PMax search terms alerts. We’re done here.`); |
| var nrOfsearchTermAlerts = searchTermAlerts.length; |
| console.log(`The account has ${nrOfsearchTermAlerts} search terms alerts for Performance Max campaigns that are enabled`); |
| addOutputToSheet(searchTermAlerts, sheet); |
| sendEmail(nrOfsearchTermAlerts); |
| console.log(`\nWe’re done. Here’s the report: ${SPREADSHEET_URL}`); |
| function processCampaigns(campaignIds, startDate, endDate) { |
| var searchTermAlerts = []; |
| for (var i=0; i<campaignIds.length; i++) { |
| var campaignId = campaignIds[i]; |
| var campaignSearchTermAlerts = getCampaignSearchTermAlerts(campaignId, startDate, endDate); |
| if (campaignSearchTermAlerts.length == 0) { |
| console.log(`Campaign id ${campaignId} has zero search terms alerts.`); |
| console.log(`Campaign id ${campaignId} has ${campaignSearchTermAlerts.length} search terms alerts.`); |
| for (var j=0; j<campaignSearchTermAlerts.length; j++) { |
| searchTermAlerts.push(campaignSearchTermAlerts[j]); |
| function getCampaignSearchTermAlerts(campaignId, startDate, endDate) { |
| console.log(`\n— Processing campaign id: ${campaignId} —`); |
| var searchTermAlerts = []; |
| var campaignSearchTermInsightCategories = getCampaignSearchTermInsightCategories(campaignId, startDate, endDate); |
| for (var i=0; i<campaignSearchTermInsightCategories.length; i++) { |
| var campaignSearchTermInsight = campaignSearchTermInsightCategories[i]; |
| var campaignSearchTermInsightTerms = getCampaignSearchTermInsightTerms(campaignSearchTermInsight, startDate, endDate); |
| for (var j=0; j<campaignSearchTermInsightTerms.length; j++) { |
| var campaignSearchTermInsightTerm = campaignSearchTermInsightTerms[j]; |
| if(isAlert(campaignSearchTermInsightTerm)) { |
| campaignSearchTermInsight.campaignName, |
| campaignSearchTermInsight.searchTermInsightCategory, |
| campaignSearchTermInsightTerm.searchTerm, |
| campaignSearchTermInsightTerm.impressions, |
| campaignSearchTermInsightTerm.clicks, |
| campaignSearchTermInsightTerm.conversions, |
| campaignSearchTermInsightTerm.conversionsValue |
| var secondsRemaining = AdsApp.getExecutionInfo().getRemainingTime(); |
| //console.log(`*** We’ve got ${secondsRemaining} secs left on this run.`); |
| if (secondsRemaining<180) { |
| `### This Google Ads script ran out of time and only had ${secondsRemaining} sec left to generate the report. We’ve quit fetching search term data and prepared the report with the alerts if there are any.\n`+ |
| `—> To process all search terms please consider upgrading to the paid version of this script, or increase MIN_CLICKS and decrease LOOKBACK_WINDOW in the settings of the script.`; |
| console.log(timeOutWarning); |
| var emailSubject = EMAIL_SUBJECT.replace(`You’ve got {nr_of_alerts} Non-Converting Search Terms` , `Non-Converting Search Terms Alert script ran out of time.`); |
| emailSubject = emailSubject.replace(`[GAds Script]`,`[WARNING]`); |
| MailApp.sendEmail(EMAIL_ADDRESSES, emailSubject, timeOutWarning); |
| function isAlert(campaignSearchTermInsightTerm) { |
| return (campaignSearchTermInsightTerm.clicks>MIN_CLICKS && campaignSearchTermInsightTerm.conversions<CONVERSION_THRESHOLD); |
| function getCampaignSearchTermInsightTerms(campaignSearchTermInsight, startDate, endDate) { |
| var campaignSearchTermInsightTerms = []; |
| var searchTermInsightCategoryId = campaignSearchTermInsight.searchTermInsightCategoryId; |
| var campaignId = campaignSearchTermInsight.campaignId; |
| var startTime = new Date().getTime(); |
| console.log(` Fetching search term data for search term insight category : ${campaignSearchTermInsight.searchTermInsightCategory} at precisely ${startTime} `); |
| segments.search_subcategory, |
| campaign_search_term_insight.id, |
| metrics.conversions_value |
| campaign_search_term_insight |
| segments.date BETWEEN ${startDate} AND ${endDate} |
| AND campaign_search_term_insight.campaign_id = ${campaignId} |
| AND campaign_search_term_insight.id = «${searchTermInsightCategoryId}» |
| //console.log(«gaqlQuery: «+gaqlQuery); |
| var results = AdsApp.search(gaqlQuery); |
| while (results.hasNext()) { |
| var result = results.next(); |
| var searchSubcat = result.segments.searchSubcategory; |
| var searchTerm = result.segments.searchTerm; |
| var searchTermInsightCategoryId = result.campaignSearchTermInsight.id; |
| var impressions = result.metrics.impressions; |
| var clicks = result.metrics.clicks; |
| var conversions = result.metrics.conversions; |
| var conversionsValue = result.metrics.conversionsValue; |
| campaignSearchTermInsightTerms.push( |
| searchTermInsightCategoryId: searchTermInsightCategoryId, |
| impressions: impressions, |
| conversions: conversions, |
| conversionsValue: conversionsValue |
| console.log(`### ERROR fetching search term data for campaign_search_term_insight ${campaignSearchTermInsight.searchTermInsightCategory} with id: ${searchTermInsightCategoryId}, error code = ${e}`); |
| var endTime = new Date().getTime(); |
| var duration = (endTime — startTime) / 1000; |
| console.log(` Finished fetching search term data for search term insight category : ${campaignSearchTermInsight.searchTermInsightCategory} at precisely ${endTime} —> it took ${duration} secs`); |
| console.log(`### GODDAMN that last query took forever! ${duration} seconds !!! Let’s hope the next one will go quicker.`); |
| // TODO: log slow queries in sheets |
| return removeDuplicates(campaignSearchTermInsightTerms, searchTermInsightCategoryId, searchTerm); |
| function getCampaignSearchTermInsightCategories(campaignId, startDate, endDate) { |
| var campaignSearchTermInsightCategories = []; |
| console.log(`Fetching search term insight category data for campaign : ${campaignId}`); |
| campaign_search_term_insight.category_label, |
| campaign_search_term_insight.id, |
| campaign_search_term_insight |
| segments.date BETWEEN ${startDate} AND ${endDate} |
| AND campaign_search_term_insight.campaign_id = ${campaignId} |
| AND metrics.clicks >= ${MIN_CLICKS} |
| //console.log(«gaqlQuery: «+gaqlQuery); |
| var results = AdsApp.search(gaqlQuery); |
| while (results.hasNext()) { |
| var result = results.next(); |
| var campaignName = result.campaign.name; |
| var searchTermInsightCategory = result.campaignSearchTermInsight.categoryLabel; |
| var searchTermInsightCategoryId = result.campaignSearchTermInsight.id; |
| var clicks = result.metrics.clicks; |
| campaignSearchTermInsightCategories.push( |
| searchTermInsightCategoryId: searchTermInsightCategoryId, |
| campaignName: campaignName, |
| searchTermInsightCategory: searchTermInsightCategory, |
| console.log(`### ERROR fetching search term insight category data for campaign : ${campaignId}, error code = ${e}`); |
| return campaignSearchTermInsightCategories; |
| // returns an array with campaign ids of all enabled performance max campaigns |
| function getCampaignIds() { |
| var gaqlQuery = «SELECT campaign.id FROM campaign WHERE campaign.advertising_channel_type = ‘PERFORMANCE_MAX’ AND campaign.status = ‘ENABLED'»; |
| var results = AdsApp.search(gaqlQuery); |
| while (results.hasNext()) { |
| var result = results.next(); |
| var campaignId = result.campaign.id; |
| campaignIds.push(campaignId); |
| return Utilities.formatDate(new Date(Date.now() — days * 86400000), AdsApp.currentAccount().getTimeZone(), «yyyyMMdd»); |
| function removeDuplicates(arr, prop1, prop2) { |
| const unique = new Map(); |
| const uniqueKey = `${item[prop1]}_${item[prop2]}`; // Creating a unique key |
| if (!unique.has(uniqueKey)) { |
| unique.set(uniqueKey, item); // Store item if unique |
| return Array.from(unique.values()); // Convert back to array |
| // prepare the report sheet |
| function prepareOutputSheet() { |
| var spreadsheet = SpreadsheetApp.openByUrl(SPREADSHEET_URL); |
| Logger.log(«Cannot open new reporting spreadsheet») ; |
| var sheet = spreadsheet.getActiveSheet(); |
| Logger.log(«Cannot open new reporting sheet») ; |
| sheet.setColumnWidth(1, 100); |
| sheet.setColumnWidth(2, 300); |
| sheet.setColumnWidth(3, 300); |
| sheet.setColumnWidth(4, 300); |
| sheet.setColumnWidth(5, 100); |
| sheet.setColumnWidth(6, 100); |
| sheet.setColumnWidth(7, 100); |
| sheet.setColumnWidth(8, 100); |
| addHeaderToOutputSheet(sheet); |
| function addHeaderToOutputSheet(sheet) { |
| console.log(`### There was an issue opening the header sheet. Please download the latest version of this script at https://nilsrooijmans.com\n${e}`); |
| throw `### There was an issue opening the header sheet. Please download the latest version of this script at https://nilsrooijmans.com\n${e}`; |
| var headerRange = headerSheet.getRange(1, 1, 2, headerSheet.getLastColumn()); |
| var headerData = headerRange.getValues(); |
| console.log(«Adding header to the output sheet»); |
| headerData[0][4] = `Current lookback window: ${LOOKBACK_WINDOW} DAYS`; |
| var range=sheet.getRange(1,1,2,headerData[1].length); |
| range.setValues(headerData) |
| range.setFontWeight(«bold»); |
| range = sheet.getRange(1,1,1,1); |
| range.setFontColor(‘#007BFF’) |
| // add alerts to report sheet |
| function addOutputToSheet(output, sheet) { |
| if (!(output.length > 0)) return; // nothing to add to sheet |
| var numberOfRows=sheet.getLastRow() ; |
| sheet.insertRowsBefore(3, output.length); // add empty rows below header row |
| var range=sheet.getRange(startRow, 1, output.length, output[0].length) ; |
| range.setValues(output) ; |
| console.log(«Number of rows added to output sheet: «+output.length+»\n\n»); |
| function sendEmail(number) { |
| «\nNumber of non-converting search terms in your PMax campaigns: » + number + «\n» + |
| «See details: «+ SPREADSHEET_URL+ «\n—\n\n»+ |
| «For more FREE Google Ads Scripts to improve your results and make your working day feel like a breeze, visit https://nilsrooijmans.com \n» + |
| «This email is generated by a copy of the free Google Ads Script — PMax Non-Converting Search Term Alerts, (C) Nils Rooijmans \n» + |
| var emailSubject = EMAIL_SUBJECT.replace(‘{nr_of_alerts}’ , number); |
| MailApp.sendEmail(EMAIL_ADDRESSES, emailSubject, emailBody); |
| Logger.log(«Sending mail»); |
Коментарів 0
Релевантные статьи
Релевантные статьи
ВЫ ГОТОВЫ УЛУЧШИТЬ СВОЙ ПРОЕКТ ВМЕСТЕ С #UAATEAM?
Мы будем рады обсудить с вами основные цели и задачи по вашему проекту. Вы можете запланировать встречу с одним из наших менеджеров
Напишите нам
Open->
to new