Оптимізація PMAX кампаній в гугл рекламі зменшуємо частку показів на площадках youtube

Проблема: PMAX пішов у перенавчання і втратив конверсії

Нещодавно одна з моїх PMAX-кампаній раптово почала показувати дивну поведінку. За аналітикою, понад 90% показів припадало на відеорекламу — YouTube, відео-прероли, відео-фід. На перший погляд — нічого поганого: відео має високу охоплюваність.

Але ось проблема: кількість конверсій впала з 4–5 на день до 1–2, а вартість конверсії (CPA) збільшилась майже втричі.

Це не просто “тимчасовий спад”. Це сигнал: кампанія пішла в перенавчання і втратила фокус. Google Ads почав “експериментувати” з форматами, і ці експерименти не працюють на бізнес.

Діагностика: де показується реклама?

Перший крок — зрозуміти, де саме показується реклама. У PMAX є кілька плейсментів:

  • Search (пошук)
  • Shopping (товарні оголошення)
  • Display (банери)
  • YouTube (відео)
  • Discovery
  • Gmail

Але алгоритм іноді “зациклюється” на одному каналі — як у моєму випадку на YouTube.

Як перевіряти де показується?

Безкоштовний спосіб:
скрипт Mike Rhodes V28 або новий сервіс https://8020agent.com/

// make a copy of this template sheet first & copy your URL into the ss variable on line 5. template: https://docs.google.com/spreadsheets/d/1aGOBOLNUjEIwwlYuS0D3fcIimRnzGdLSJxVd9rHOQ0E/copy

function main() {
let ss = SpreadsheetApp.openByUrl(‘вставити_сюди_ваше_посилання_на_файл’);
let zombieDays = 366 // how many days data do you want to use to identify zombie (0 click) products?
let prodDays = 181 // how many days data do you want to use to identify ‘long range’ products?

 

// don’t change any code below this line ——————————————————————————————————————————————————————————————————————————————

 

// define query elements. wrap with spaces for safety
let impr = ‘ metrics.impressions ‘;
let clicks = ‘ metrics.clicks ‘;
let cost = ‘ metrics.cost_micros ‘;
let conv = ‘ metrics.conversions ‘;
let value = ‘ metrics.conversions_value ‘;
let allConv = ‘ metrics.all_conversions ‘;
let allValue = ‘ metrics.all_conversions_value ‘;
let views = ‘ metrics.video_views ‘;
let cpv = ‘ metrics.average_cpv ‘;
let segDate = ‘ segments.date ‘;
let prodTitle = ‘ segments.product_title ‘;
let prodID = ‘ segments.product_item_id ‘;
let prodC0 = ‘ segments.product_custom_attribute0 ‘;
let prodC1 = ‘ segments.product_custom_attribute1 ‘;
let prodC2 = ‘ segments.product_custom_attribute2 ‘;
let prodC3 = ‘ segments.product_custom_attribute3 ‘;
let prodC4 = ‘ segments.product_custom_attribute4 ‘;
let campName = ‘ campaign.name ‘;
let chType = ‘ campaign.advertising_channel_type ‘;
let adgName = ‘ ad_group.name ‘;
let adStatus = ‘ ad_group_ad.status ‘;
let adPerf = ‘ ad_group_ad_asset_view.performance_label ‘;
let adType = ‘ ad_group_ad_asset_view.field_type ‘;
let aIdAsset = ‘ asset.resource_name ‘;
let aId = ‘ asset.id ‘;
let assetType = ‘ asset.type ‘;
let aFinalUrl = ‘ asset.final_urls ‘;
let assetName = ‘ asset.name ‘;
let assetText = ‘ asset.text_asset.text ‘;
let assetSource = ‘ asset.source ‘ ;
let adUrl = ‘ asset.image_asset.full_size.url ‘;
let ytTitle = ‘ asset.youtube_video_asset.youtube_video_title ‘;
let ytId = ‘ asset.youtube_video_asset.youtube_video_id ‘;
let agId = ‘ asset_group.id ‘;
let assetFtype = ‘ asset_group_asset.field_type ‘;
let adPmaxPerf = ‘ asset_group_asset.performance_label ‘;
let agStrength = ‘ asset_group.ad_strength ‘;
let agStatus = ‘ asset_group.status ‘;
let asgName = ‘ asset_group.name ‘;
let lgType = ‘ asset_group_listing_group_filter.type ‘;
let aIdCamp = ‘ segments.asset_interaction_target.asset ‘;
let interAsset = ‘ segments.asset_interaction_target.interaction_on_this_asset ‘;
let pMaxOnly = ‘ AND campaign.advertising_channel_type = “PERFORMANCE_MAX” ‘;
let searchOnly = ‘ AND campaign.advertising_channel_type = “SEARCH” ‘;
let agFilter = ‘ AND asset_group_listing_group_filter.type != “SUBDIVISION” ‘;
let adgEnabled = ‘ AND ad_group.status = “ENABLED” AND campaign.status = “ENABLED” AND ad_group_ad.status = “ENABLED” ‘;
let asgEnabled = ‘ asset_group.status = “ENABLED” AND campaign.status = “ENABLED” ‘;
let notInter = ‘ AND segments.asset_interaction_target.interaction_on_this_asset != “TRUE” ‘;
let inter = ‘ AND segments.asset_interaction_target.interaction_on_this_asset = “TRUE” ‘;
let date07 = ‘ segments.date DURING LAST_7_DAYS ‘;
let date30 = ‘ segments.date DURING LAST_30_DAYS ‘;
let order = ‘ ORDER BY campaign.name ‘;
let orderImpr = ‘ ORDER BY metrics.impressions DESC ‘;

// Date stuff
let MILLIS_PER_DAY = 1000 * 60 * 60 * 24;
let now = new Date();
let from = new Date(now.getTime() – zombieDays * MILLIS_PER_DAY); // xx days in the past – default 366
let prod180 = new Date(now.getTime() – prodDays * MILLIS_PER_DAY); // xx days in the past – default 181
let to = new Date(now.getTime() – 1 * MILLIS_PER_DAY); // yesterday
let timeZone = AdsApp.currentAccount().getTimeZone();
let zombieRange = ‘ segments.date BETWEEN “‘ + Utilities.formatDate(from, timeZone, ‘yyyy-MM-dd’) + ‘” AND “‘ + Utilities.formatDate(to, timeZone, ‘yyyy-MM-dd’) + ‘”‘
let prodDate = ‘ segments.date BETWEEN “‘ + Utilities.formatDate(prod180, timeZone, ‘yyyy-MM-dd’) + ‘” AND “‘ + Utilities.formatDate(to, timeZone, ‘yyyy-MM-dd’) + ‘”‘

// build queries
let cd = [segDate, campName, cost, conv, value, views, cpv, impr, clicks, chType] // campaign by day
let campQuery = ‘SELECT ‘ + cd.join(‘,’) +
‘ FROM campaign ‘ +
‘ WHERE ‘ + date30 + pMaxOnly + order ;

let dv = [segDate, campName, aIdCamp, cost, conv, value, views, cpv, impr, chType, interAsset] // inter by day
let dvQuery = ‘SELECT ‘ + dv.join(‘,’) +
‘ FROM campaign ‘ +
‘ WHERE ‘ + date30 + pMaxOnly + notInter + order ;

let p = [campName, prodTitle, cost, conv, value, impr, chType,prodID,prodC0,prodC1,prodC2,prodC3,prodC4] // product totals
let pQuery = ‘SELECT ‘ + p.join(‘,’) +
‘ FROM shopping_performance_view ‘ +
‘ WHERE ‘ + date30 + pMaxOnly + order ;
let p180Query = ‘SELECT ‘ + p.join(‘,’) +
‘ FROM shopping_performance_view ‘ +
‘ WHERE ‘ + prodDate + pMaxOnly + order ;

let ag = [segDate, campName, asgName, agStrength, agStatus, lgType, impr, clicks, cost, conv, value] // asset group by day
let agQuery = ‘SELECT ‘ + ag.join(‘,’) +
‘ FROM asset_group_product_group_view ‘ +
‘ WHERE ‘ + date30 + agFilter ;

let assets = [aId, aFinalUrl, assetSource, assetType, ytTitle, ytId, assetText, aIdAsset, assetName] // names, IDs, URLs for all ad assets in account
let assetQuery = ‘SELECT ‘ + assets.join(‘,’) +
‘ FROM asset ‘ ;

let ads = [campName, asgName, agId, aIdAsset, assetFtype, adPmaxPerf, agStrength, agStatus, assetSource] // pMax ads
let adsQuery = ‘SELECT ‘ + ads.join(‘,’) +
‘ FROM asset_group_asset ‘ ;

let zombies = [prodID, clicks, impr, prodTitle] // zombie (0click) products – last xx days, set xx days at top of script
let zQuery = ‘SELECT ‘ + zombies.join(‘,’) +
‘ FROM shopping_performance_view ‘ +
‘ WHERE metrics.clicks < 1 AND ‘ + zombieRange + orderImpr ;

// call report function to pull data & push it to the named tabs in the sheet
runReport(campQuery, ss.getSheetByName(‘r_camp’));
runReport(dvQuery, ss.getSheetByName(‘r_dv’));
runReport(pQuery, ss.getSheetByName(‘r_prod_t’));
runReport(p180Query, ss.getSheetByName(‘r_prod_t_180’));
runReport(agQuery, ss.getSheetByName(‘r_ag’));
runReport(assetQuery, ss.getSheetByName(‘r_allads’));
runReport(adsQuery, ss.getSheetByName(‘r_ads’));
runReport(zQuery, ss.getSheetByName(‘zombies’));

} // end main

 

// query & export report data to named sheet
function runReport(q,sh) {
const report = AdsApp.report(q);
report.exportToSheet(sh);
}

Рішення: фільтрація нерелевантних каналів

Оскільки це відео-плейсменти, я вирішив вручну виключити нерелевантні YouTube-канали, які споживають бюджет, але не конвертують.

Крок 1: Аналіз списку плейсментів

  • Завантажив список усіх сайтів і каналів, де показувалась реклама.
  • У Google Таблицях відфільтрував:
    • Відео-ресурси.
    • Назви зі словами: новини, новости, игры, games, релігія, політика.
    • Плейсменти з більш ніж 5 показами (щоб відсіяти шум).

Крок 2: Ручне виключення

На жаль, Google Ads надає тільки відео по замовчуванню.А скрипту котрий перетворював би посилання на відео у посилання каналу – в мене не знайшлось (на момент запису відео).

UPDATE: Розібрався з скриптом, щоб перелік відео переробляти в канали з котрих ці відео.

.

Що я робив:

  1. Клікав по кожному відео в звіті.
  2. Копіював посилання на канал.
  3. Переходив у Tools → Content Suitability → Excluded Placements.
  4. Додавав канал до списку виключень.

Так я виключив десятки нерелевантних каналів, які показували рекламу у контексті новин, політики чи ігор.

Додаткові заходи

Крім виключення плейсментів, я вжив ще кількох заходів:

1. Зменшення бюджету на 20%

Це допомогло дати сигнал алгоритмам, що потрібно зменшувати витрати на неефективні канали.

2. Виключення за темами

У налаштуваннях Display та YouTube додав обмеження:

  • ❌ Новини
  • ❌ Політика
  • ❌ Релігія
  • ✅ Залишив тільки сімейні теми та нейтральний контент.

Результат: повернення конверсій і зниження CPA

Через 5–7 днів після змін я побачив:

  • Покази на відео знизилися з 90% до 30–40%.
  • Конверсії повернулися на попередній рівень — 4–5 на день.
  • Вартість конверсії знизилася до нормального рівня.

Після цього я повернув зменшений бюджет — і кампанія почала стабільно працювати.

Висновки: що варто робити, якщо PMAX пішов у перенавчання

  1. Не ігноруйте зміни у розподілі плейсментів. Якщо один канал займає більше 70–80%, це тривога.
  2. Аналізуйте плейсменти регулярно. Особливо після оновлення кампанії чи зміни цілей.
  3. Використовуйте виключення плейсментів. Це сильний інструмент контролю над тим, де показується ваша реклама.
  4. Не бійтеся тимчасово зменшувати бюджет. Це допомагає уникнути втрат під час оптимізації.
  5. Фільтруйте за темами. Навіть у PMAX можна задавати обмеження, щоб уникнути небажаного контексту.

Leave a Reply

Your email address will not be published. Required fields are marked *