Настраиваем пользовательские цели на основе поведения посетителей сайта

Настраиваем пользовательские цели на основе поведения посетителей сайта

Всем привет 👋

Прошел почти месяц с момента публикации моего проекта по автоматической оптимизации рекламных кампаний в Яндекс Директ — вот ссылка на пост.

К сожалению, пост не получил достаточного отклика, чтобы я начал записывать подробную инструкцию по настройке, но всё ещё в силе: как только он наберёт 100 лайков — я обязательно подготовлю и опубликую полноценное руководство.

А пока что — немного новостей и полезностей.

Сейчас я работаю над новым проектом, который опубликую здесь, как только доведу его до ума. Он полностью решит проблему с обучаемостью рекламных кампаний — раз и навсегда.

Но уже сегодня хочу поделиться простым скриптом, который также помогает справляться с этой проблемой. Это временное решение — менее гибкое, чем то, над которым я сейчас работаю, но оно вполне рабочее и может быть полезно прямо сейчас.

Что делает скрипт?

Он создаёт 3 цели, которые автоматически заполняются в зависимости от того, насколько "качественным" оказался пользователь, зашедший на сайт. Затем эти цели можно использовать в качестве конверсий для оптимизации в Яндекс Директ — это позволит системе эффективнее обучаться и не останавливать обучение рекламной кампании.

Скрипт не является полностью моей разработкой, некоторую часть я позаимствовал у одного специалиста. Ссылочку на автора исходного скрипта оставлю здесь:

Итак, давайте сначала разберем принцип работы скрипта и каким проектам он будет полезен.

Принцип работы

Принцип работы максимально простой: скрипт отслеживает поведение пользователя на сайте и по завершению сессии определяет, к какому из трёх сегментов он относится:

  • Качественный пользователь (low) - низкая заинтересованность
  • Качественный пользователь (medium) - средняя заинтересованность
  • Качественный пользователь (high) - высокая заинтересованность.

Распределение по сегментам происходит на основе заранее заданных правил. Скрипт оценивает следующие параметры:

var SEGMENT_RULES = { 'quality_user_high': { minTimeOnSiteMs: 300000, minPageViews: 3, minScrollDepthPercentage: 30, minScrollPauses: 3 }, 'quality_user_medium': { minTimeOnSiteMs: 60000, minPageViews: 2, minScrollDepthPercentage: 30, minScrollPauses: 2 }, 'quality_user_low': { minTimeOnSiteMs: 0, minPageViews: 0, minScrollDepthPercentage: 0, minScrollPauses: 0 } }
  • minTimeOnSiteMs - минимальное количество времени, который должен провести на сайте (в миллисекундах)
  • minPageViews - минимальное количество страниц, которое пользователь должен просмотреть во время сессии.
  • minScrollDepthPercentage - минимальный процент прокрутки страницы
  • minScrollPauses — минимальное количество пауз при прокрутке (скролл-стопов)

Отдельно стоит отметить: скрипт умеет определять, является ли посетитель сайта ботом. Если пользователь распознан как бот — никакие цели ему не присваиваются.

Для каких проектов будет полезно?

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

  • Высокая конкуренция
  • дорогой CPL (стоимость лида)
  • Недельный бюджет, который не позволяет стабильно получать хотя бы 10 конверсий в неделю — это минимальный объём, необходимый для обучения рекламных кампаний в Яндекс Директе.

Инструкция по установке

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

  • Скрипт — сам код, который будет анализировать поведение и отправлять цели в Метрику
  • Доступ к Яндекс Тег Менеджеру — если нет возможности напрямую редактировать HTML-код сайта
  • Доступ к Яндекс Метрике — минимальный уровень доступа: Аналитика, чтобы была возможность создать цели для скрипта.

Ниже опубликован сам скрипт:

<script> (function() { console.log('[SegTracker] Скрипт инициализирован.') var DAILY_COUNTER_STORAGE_KEY = 'quality_user_daily_counter' var MAX_GOAL_COUNT_PER_DAY = 1 var SESSION_END_INACTIVITY_THRESHOLD = 180000 var MIN_SESSION_DURATION_FOR_GOAL_MS = 30000 var SEGMENT_RULES = { 'quality_user_high': { minTimeOnSiteMs: 300000, minPageViews: 3, minScrollDepthPercentage: 30, minScrollPauses: 3 }, 'quality_user_medium': { minTimeOnSiteMs: 60000, minPageViews: 2, minScrollDepthPercentage: 30, minScrollPauses: 2 }, 'quality_user_low': { minTimeOnSiteMs: 0, minPageViews: 0, minScrollDepthPercentage: 0, minScrollPauses: 0 } } var SCROLL_PAUSE_DURATION = 3000 var SEGMENT_CHECK_INTERVAL_MS = 5000; // New: Check segment every 5 seconds var STORAGE_KEYS = { startTime: 'q_ses_start', active: 'q_ses_active', scrollPauses: 'q_scroll_pauses', scrollDepth: 'q_scroll_depth', pageViews: 'q_page_views', sent: 'q_goal_sent', segment: 'q_current_segment', daily: function(goal) { return DAILY_COUNTER_STORAGE_KEY + '_' + goal } } var startTime = parseInt(sessionStorage.getItem(STORAGE_KEYS.startTime)) || Date.now() sessionStorage.setItem(STORAGE_KEYS.startTime, startTime) var scrollPauses = parseInt(sessionStorage.getItem(STORAGE_KEYS.scrollPauses)) || 0 var maxScroll = parseInt(sessionStorage.getItem(STORAGE_KEYS.scrollDepth)) || 0 var pageViews = parseInt(sessionStorage.getItem(STORAGE_KEYS.pageViews)) || 1 var goalSent = sessionStorage.getItem(STORAGE_KEYS.sent) === 'true' function getScrollPercent() { var height = document.documentElement.scrollHeight - window.innerHeight return height > 0 ? Math.min(100, (maxScroll / height) * 100) : 0 } function isWithinDailyLimit(goal) { return (parseInt(localStorage.getItem(STORAGE_KEYS.daily(goal))) || 0) < MAX_GOAL_COUNT_PER_DAY } function incrementDailyCounter(goal) { var key = STORAGE_KEYS.daily(goal) var current = parseInt(localStorage.getItem(key)) || 0 localStorage.setItem(key, current + 1) } function determineSegment() { var time = Date.now() - startTime var scroll = getScrollPercent() // Дополнительное логирование для отладки console.log('[SegTracker] Проверка сегмента: Время на сайте (мс): ' + time + ', Просмотры страниц: ' + pageViews + ', Прокрутка (%): ' + scroll.toFixed(2) + ' (макс. пикселей: ' + maxScroll + '), Паузы скролла: ' + scrollPauses); var keys = [] for (var name in SEGMENT_RULES) { if (!SEGMENT_RULES.hasOwnProperty(name)) continue keys.push(name) } keys.sort(function(a, b) { return SEGMENT_RULES[b].minTimeOnSiteMs - SEGMENT_RULES[a].minTimeOnSiteMs }) for (var i = 0; i < keys.length; i++) { var k = keys[i] var rules = SEGMENT_RULES[k] if ( time >= rules.minTimeOnSiteMs && pageViews >= rules.minPageViews && scroll >= rules.minScrollDepthPercentage && scrollPauses >= rules.minScrollPauses ) { console.log('[SegTracker] Текущий сегмент: ' + k) return k } } return null } function sendFinalGoal() { if (goalSent) return var segment = sessionStorage.getItem(STORAGE_KEYS.segment) || determineSegment() if (!segment || !isWithinDailyLimit(segment)) return var time = Date.now() - startTime if (segment !== 'quality_user_low' && time < MIN_SESSION_DURATION_FOR_GOAL_MS) { console.log('[SegTracker] Недостаточно времени на сайте для цели: ' + time + ' мс') return } ym(НОМЕР_ВАШЕГО_СЧЕТЧИКА, 'reachGoal', segment) incrementDailyCounter(segment) sessionStorage.setItem(STORAGE_KEYS.sent, 'true') console.log('[SegTracker] Отправлена цель: ' + segment) } var scrollTimer window.addEventListener('scroll', function() { var y = window.scrollY maxScroll = Math.max(maxScroll, y) sessionStorage.setItem(STORAGE_KEYS.scrollDepth, maxScroll.toString()) clearTimeout(scrollTimer) scrollTimer = setTimeout(function() { scrollPauses++ sessionStorage.setItem(STORAGE_KEYS.scrollPauses, scrollPauses.toString()) console.log('[SegTracker] Засчитана пауза прокрутки. Всего: ' + scrollPauses + '. Текущая макс. прокрутка (px): ' + maxScroll) // Дополнительное логирование }, SCROLL_PAUSE_DURATION) }) var inactivityTimer function resetInactivityTimer() { clearTimeout(inactivityTimer) inactivityTimer = setTimeout(function() { console.log('[SegTracker] Неактивность. Попытка отправки цели.') sendFinalGoal() }, SESSION_END_INACTIVITY_THRESHOLD) } var events = ['mousemove', 'keydown', 'click', 'touchstart'] for (var i = 0; i < events.length; i++) { document.addEventListener(events[i], resetInactivityTimer, { passive: true }) } document.addEventListener('visibilitychange', function() { if (document.hidden) { console.log('[SegTracker] Страница скрыта. Ожидание завершения сессии...') inactivityTimer = setTimeout(function() { sendFinalGoal() }, SESSION_END_INACTIVITY_THRESHOLD) } else { console.log('[SegTracker] Страница снова видима.') resetInactivityTimer() } }) window.addEventListener('beforeunload', function() { if (sessionStorage.getItem('q_internal_nav') === 'true') { sessionStorage.removeItem('q_internal_nav') console.log('[SegTracker] Внутренний переход. Цель не отправляется.') return } console.log('[SegTracker] Выход со страницы. Отправка цели...') sendFinalGoal() }) document.addEventListener('click', function(e) { var a = e.target.closest('a') if (a && a.href && a.origin === location.origin && a.pathname !== location.pathname) { sessionStorage.setItem('q_internal_nav', 'true') console.log('[SegTracker] Обнаружен внутренний переход по ссылке: ' + a.href) } }) // New function to check and update segment periodically function checkAndUpdateSegment() { var best = determineSegment() var currentStoredSegment = sessionStorage.getItem(STORAGE_KEYS.segment) if (best && best !== currentStoredSegment) { sessionStorage.setItem(STORAGE_KEYS.segment, best) console.log('[SegTracker] Обнаружен и сохранен новый сегмент: ' + best + '. Предыдущий сегмент: ' + (currentStoredSegment || 'не определен')) } // No else if for 'best' being the same, to reduce console spam from periodic checks } window.addEventListener('load', function() { var prev = sessionStorage.getItem('q_prev_url') var curr = location.href if (prev && prev !== curr) { pageViews++ console.log('[SegTracker] Переход на новую страницу. pageViews: ' + pageViews) } else if (!prev) { console.log('[SegTracker] Первая страница в сессии.') } else { console.log('[SegTracker] Перезагрузка текущей страницы.') } sessionStorage.setItem(STORAGE_KEYS.pageViews, pageViews.toString()) sessionStorage.setItem('q_prev_url', curr) // Initial segment check on page load checkAndUpdateSegment(); }) window.sendFinalGoal = sendFinalGoal resetInactivityTimer() // New: Start periodic segment check setInterval(checkAndUpdateSegment, SEGMENT_CHECK_INTERVAL_MS); })() </script>

Найдите следующую строчку

ym(НОМЕР_ВАШЕГО_СЧЕТЧИКА, 'reachGoal', segment)

Замените "НОМЕР_ВАШЕГО_СЧЕТЧИКА" на актуальный номер счетчика яндекс метрики, перед тем, как установить скрипт на сайт.

Ниже — ключевые параметры скрипта, которые вы можете (и должны) настроить под особенности вашего сайта:

var MAX_GOAL_COUNT_PER_DAY = 1

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

var SESSION_END_INACTIVITY_THRESHOLD = 180000

Порог бездействия для завершения сессии (в миллисекундах). Если пользователь не проявлял активности более 3 минут (180 000 мс), сессия считается завершённой — скрипт оценивает поведение и отправляет цель.

var MIN_SESSION_DURATION_FOR_GOAL_MS = 10000

Минимальная продолжительность сессии, чтобы засчитать цель. Если пользователь пробыл на сайте меньше 10 секунд — поведение считается недостаточным для сегментации, и цель не отправляется.

var SCROLL_PAUSE_DURATION = 3000

Длительность паузы между скроллами (в миллисекундах). Используется для оценки "вдумчивого" чтения. Если пользователь останавливается во время скролла хотя бы на 3 секунды — засчитывается пауза.

var SEGMENT_CHECK_INTERVAL_MS = 5000

Интервал проверки сегментации (в миллисекундах). Скрипт каждые 5 секунд анализирует активность пользователя, чтобы определить, достиг ли он нужного уровня вовлечённости.

Условия (правила) для распределения пользователя в тот или иной сегмент описаны выше.

Создаем цель в Яндекс Метрике

Теперь, когда вы отредактировали скрипт, нужно создать соответствующие цели в интерфейсе Яндекс Метрики.

Переходим в нужный счётчик → раздел «Цели» → «Добавить цель», и создаём 3 цели типа "JavaScript-событие" с идентификаторами:

  • quality_user_low
  • quality_user_medium
  • quality_user_high

Ниже записал видео как пример создания цели quality_user_low

Установка скрипта с помощью Яндекс Тег Менеджера

Если у вас нет доступа к HTML-коду сайта, вы можете установить скрипт через Яндекс Тег Менеджер (ЯТМ). Это удобно, быстро и не требует участия разработчиков.

Чтобы начать работу с ЯТМ, нужно сначала убедиться, что:

  • Тег Менеджер активирован — это можно сделать в настройках счётчика Яндекс Метрики. 👉 Важно: активировать Тег Менеджер может только владелец счётчика.
  • У вас есть доступ к контейнеру — для полноценной работы (просмотр, редактирование и добавление скриптов) необходим доступ с правами:
  • «Просмотр контейнера»
  • «Редактирование контейнера»
  • «Создание контейнера»

Внутри интерфейса ЯТМ перейдите в раздел «Теги» и нажмите на кнопку «Добавить тег», откроется окно создания тега со следующим содержимым:

  • Название — придумайте название тегу, оно может быть произвольным, например «Качественный пользователь»
  • Шаблон тега — в выпадающем списке выберите «Пользовательский HTML» и вставьте подготовленный скрипт внутрь появившегося окна
  • Триггеры — пока оставьте пустым

Сохраните ваш тег.

Перейдите в раздел «Триггеры» и нажмите на кнопку «Добавить триггер», откроется окно создания тега со следующим содержимым:

  • Название — придумайте название тегу, оно может быть произвольным, например «Качественный пользователь»
  • Тип триггера — в выпадающем списке выберите «Просмотр страницы»
  • Условия активации триггера — все события

Сохраните ваш триггер.

После создания триггера вернитесь в раздел «Теги», откройте ранее созданный тег и выберите нужный триггер в соответствующем поле. В конечном счете у вас должно быть примерно вот так:

Настраиваем пользовательские цели на основе поведения посетителей сайта

Не забудьте опубликовать изменения, без этого скрипт не заработает на сайте.

Проверка работоспособности

Теперь, когда вы опубликовали тег в интерфейсе ЯТМ осталось проверить его работоспособность. Для этого перейдите на сайт, откройте режим разработчика и перейдите во вкладку «Console». Вы должны увидеть, что скрипт начал исполняться, соответствующие сообщения вы должны увидеть здесь.

Настраиваем пользовательские цели на основе поведения посетителей сайта
[SegTracker] Скрипт инициализирован. [SegTracker] Первая страница в сессии. [SegTracker] Проверка сегмента: Время на сайте (мс): 100, Просмотры страниц: 1, Прокрутка (%): 0.00 (макс. пикселей: 0), Паузы скролла: 0 [SegTracker] Текущий сегмент: quality_user_low [SegTracker] Обнаружен и сохранен новый сегмент: quality_user_low. Предыдущий сегмент: не определен [SegTracker] Проверка сегмента: Время на сайте (мс): 5002, Просмотры страниц: 1, Прокрутка (%): 0.00 (макс. пикселей: 0), Паузы скролла: 0

Эти сообщения, которые выводятся в консоли говорят о том, что скрипт запустился и начал свою работу.

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

Откройте сайт с параметром отладки, например:

www.washsaite.ru?_ym_debug=2

Параллельно откройте инструменты разработчика (F12) → вкладка Console. Выполните какие-либо действия на сайте (пролистывания, переходы, задержитесь на странице), и наблюдайте, что выводится в консоль — скрипт покажет, какой сегмент был назначен.

Когда увидите сообщение о присвоении сегмента (например, Обнаружен и сохранен новый сегмент: quality_user_low. ), вручную отправьте срабатывание цели, выполнив в консоли команду:

sendFinalGoal();

После того как отправите команду в консоль, откройте вкладку «Events» в дебаггере Метрики — вы должны увидеть, что цель, соответствующая вашему сегменту, сработала. Это значит, что скрипт работает корректно и цели успешно передаются в Метрику.

Теперь, когда стало понятно, что скрипт работает корректно, можно переходить к следующему шагу — добавлением соответствующих целей в настройках рекламных кампаний. Это позволит алгоритму Яндекса получать больше конверсий и поддерживать обучаемость алгоритмов даже в условиях небольшого бюджета или высокой стоимости лида.

✌ Надеюсь этот скрипт поможет в оптимизации ваших рекламных кампаний. Если будут вопросы, замечания, предложения — пишите, всегда открыт для всех.

Ну а если вам нужна настройка рекламных кампаний, аудит или консультация, можете написать мне в телеграм 😊

3
6 комментариев