UTM-метки: что это, как сделать и не сломать отчёт по каналам
UTM-метки простыми словами: что писать в utm_source, utm_medium и utm_campaign, правила именования, примеры ссылок для Директа и рассылок и разбор меток в SQL.
Содержание статьи
В конце месяца маркетолог просит у аналитика простую цифру: сколько регистраций принесла летняя кампания в Директе. В отчёте по источникам у Яндекса четыре строки: yandex / cpc, Yandex / CPC, ya / cpc и yandex%20 / cpc, а ещё один переход из той же кампании записан как источник cpc с каналом yandex. Ответ зависит от того, кто и как сложит эти строки. UTM-метки нужны, чтобы такой вопрос решался одной группировкой. Ниже — что писать в каждый из пяти параметров, как договориться об именах, готовые ссылки для Директа, рассылок и партнёров и как разобрать метки в SQL, чтобы одна заглавная буква не делила канал на части.
Коротко
- UTM-метки — параметры в конце ссылки (
?utm_source=…&utm_medium=…), по которым система аналитики понимает, откуда пришёл посетитель. Страница от них не меняется. - Стандартных параметров пять:
utm_source(откуда),utm_medium(тип трафика),utm_campaign(кампания),utm_content(объявление или ссылка),utm_term(ключевая фраза). Первые три ставятся всегда. - Значения чувствительны к регистру: и Google Analytics, и Яндекс Метрика считают
Yandexиyandexразными источниками. Пишите строчной латиницей, слова соединяйте подчёркиванием. - Для аналитика метка — ключ для
GROUP BY. Без общего словаря значений отчёт по каналам приходится чинить руками каждый месяц. - В данных продукта метку сохраняют при первом заходе и не перезаписывают; в учебной базе «Маяк» это готовый столбец
users.channel. - Перед запуском кампании кликните по размеченной ссылке и проверьте, что параметры дожили до адресной строки после всех перенаправлений.
Что такое UTM-метки и зачем они аналитику
UTM-метка — это пара «параметр=значение» в адресе ссылки после знака вопроса: https://mayak.example/?utm_source=yandex&utm_medium=cpc&utm_campaign=summer_sale. Открывается та же страница, что и без хвоста, но счётчик на сайте — Яндекс Метрика, Google Analytics или ваш собственный сборщик событий — читает параметры и записывает визит с этими значениями. Название восходит к Urchin — системе веб-аналитики, которую Google купил в 2005 году и превратил в Google Analytics.
Зачем метки, если счётчик и так видит переходы с поиска и других сайтов? Без меток источник определяется по рефереру — адресу страницы, откуда пришёл человек. Реферер не скажет, какая из трёх кампаний в Директе сработала, не отличит две кнопки в одном письме, а у переходов из почтовых программ и мессенджеров его часто нет совсем — такие визиты оседают в прямых заходах.
Для аналитика метка — ключ, по которому потом группируют регистрации, оплаты и расходы. Стоимость привлечения клиента, доля рекламных расходов, удержание по каналам — все эти расчёты начинаются с group by source, medium. Если ключ грязный, ошибка расходится по всем производным метрикам и при этом не бросается в глаза: строки отчёта просто разъезжаются по разным значениям.
Пять параметров: что писать в utm_source, utm_medium, utm_campaign, utm_content и utm_term
Названия и смысл параметров одинаковы в Google Analytics и Яндекс Метрике. Справка Google Analytics советует всегда ставить source, medium и campaign; справка Директа называет обязательными те же три, а content и term — необязательными. Метрика формально мягче: utm_source в ней обязателен для рекламных систем и соцсетей, utm_medium — для рассылок, мессенджеров и QR-кодов. Для аналитика правило проще: три первых параметра ставятся всегда, иначе в отчёте появляются пустые значения — (not set) в терминах Google Analytics.
| Параметр | На какой вопрос отвечает | Что писать | Примеры |
|---|---|---|---|
utm_source | Откуда пришёл человек | площадка или отправитель: рекламная система, соцсеть, рассылка, партнёр | yandex, google, vk, newsletter, partner_blog |
utm_medium | Какой это тип трафика | значение из короткого словаря каналов | cpc, email, paid_social, partner, qr |
utm_campaign | Какая кампания | акция, продукт или промокод; одно имя на всю кампанию во всех источниках | summer_sale, august_digest, pro_launch_202609 |
utm_content | Какое объявление или какая ссылка | вариант креатива или место ссылки, чтобы различить две кнопки в одном письме | ad_101, button_top, video_15s |
utm_term | По какой фразе показали объявление | ключевая фраза платного поиска; в Директе подставляется автоматически | {keyword}, report_tool |
Чем utm_source отличается от utm_medium
Это самая частая путаница в разметке. Source — конкретное место: yandex, vk, newsletter. Medium — категория, к которой это место относится: cpc, email, paid_social. Проверка простая: если завтра появится вторая рекламная система, у неё будет другой source, но тот же medium.
Отсюда и роль в отчётах. Отчёт по каналам строится по medium, отчёт по площадкам внутри канала — по source, сравнение кампаний — по campaign. Перепутанные параметры не просто дают неверную подпись: визит уходит в чужой канал, и в итоговой таблице его уже не найти.
В Google Analytics есть и дополнительные параметры, например utm_id — идентификатор кампании для загрузки расходов. Если вы не загружаете расходы в Google Analytics, хватает пяти стандартных.
Как называть значения: правила разметки для всей команды
Значения в метках чувствительны к регистру. Справка Google Analytics приводит в пример google и Google как два разных источника, справка Метрики — vkontakte и Vkontakte. Обе советуют нижний регистр, справка Директа добавляет латиницу и разделители _ или -. Кириллица в адресе кодируется в UTF-8: слово «обзор» превращается в %D0%BE%D0%B1%D0%B7%D0%BE%D1%80, ссылка удлиняется, а в выгрузке остаётся нечитаемая строка.
Правила стоит записать в одном месте — в таблице, из которой маркетологи собирают ссылки, — а не держать в голове у одного человека. Минимальный набор такой:
| Правило | Как писать | Как не писать | Почему |
|---|---|---|---|
| Регистр | yandex, summer_sale | Yandex, Summer_Sale | системы аналитики считают их разными значениями |
| Разделитель слов | один на всю команду: summer_sale | summer sale, summer-sale вперемешку с summer_sale | пробел кодируется в %20 или +, смешение разделителей раскалывает строку |
| Алфавит | латиница по одному правилу транслитерации: obzor | обзор | кириллица кодируется и в выгрузке становится нечитаемой |
| Пробелы по краям | yandex | yandex (скопировано из таблицы) | невидимый хвост превращается в yandex%20 |
| Даты в названии | pro_launch_202609 | запуск 01.09 | формат ггггмм сортируется и не содержит точек и пробелов |
| Значения medium | только из словаря | cpc, CPC, ppc, context для одного канала | по medium строится отчёт по каналам |
cpc— оплата за клик в поисковой и контекстной рекламе: Директ, Google Ads.paid_social— платная реклама в соцсетях.email— письма и рассылки, включая триггерные.partner— размещения у партнёров и блогеров по договорённости.referral— реферальная программа «приведи друга».banner— медийные размещения с оплатой за показы.qr— офлайн: листовки, упаковка, стенды на конференциях.
Словарь medium и стандартные каналы GA4
Словарь выше — пример для небольшого продукта, а не стандарт: важнее, чтобы он был один и его соблюдали. Если отчёты смотрят в GA4, сверьте словарь с его стандартной группировкой каналов, иначе часть трафика уйдёт в «прочее». Письмо попадает в канал Email, только если source или medium равен email, e-mail, e_mail или e mail. Платный поиск требует источник из списка поисковых систем и medium, подходящий под шаблон ^(.*cp.*|ppc|retargeting|paid.*)$.
Тонкость: группировка каналов в GA4 регистр не различает, а сами значения source и medium в отчётах — различают. Поэтому Email попадёт в канал Email, но в таблице по источникам будет отдельной строкой рядом с email. Нормализовать значения всё равно придётся.
Как сделать UTM-метку: примеры готовых ссылок
Метку собирают вручную, в генераторе ссылок (у Google есть Campaign URL Builder) или в общей таблице, где формула склеивает адрес из выпадающих списков. Таблица надёжнее: словарь medium зашит в список, и опечатке негде появиться.
Синтаксис короткий. Первый параметр идёт после ?, остальные — через &. Если в адресе уже есть параметр, например ?ref=1, метки добавляют через &, второй знак вопроса не нужен. Якорь # ставится в самом конце, после меток: всё, что после решётки, браузер на сервер не передаёт.
# Директ, поисковая кампания: значения в фигурных скобках подставит Директ
https://mayak.example/?utm_source=yandex&utm_medium=cpc&utm_campaign={campaign_id}&utm_content={ad_id}&utm_term={keyword}
# Рассылка: две кнопки в одном письме различает utm_content
https://mayak.example/pricing?utm_source=newsletter&utm_medium=email&utm_campaign=august_digest&utm_content=button_top
https://mayak.example/pricing?utm_source=newsletter&utm_medium=email&utm_campaign=august_digest&utm_content=button_footer
# Реклама во VK
https://mayak.example/?utm_source=vk&utm_medium=paid_social&utm_campaign=summer_sale&utm_content=video_15s
# Обзор у партнёра
https://mayak.example/?utm_source=partner_blog&utm_medium=partner&utm_campaign=obzor_202608
# Листовка на конференции, ссылка зашита в QR-код
https://mayak.example/?utm_source=conf_leaflet&utm_medium=qr&utm_campaign=analytics_conf_202609
# В адресе уже есть параметр, якорь стоит последним
https://mayak.example/?ref=1&utm_source=newsletter&utm_medium=email&utm_campaign=august_digest#pricingUTM-метки для Яндекс Директа: динамические параметры
В Директе значения не обязательно вписывать руками. Справка Директа описывает динамические параметры — шаблоны в фигурных скобках, вместо которых при клике подставляются настоящие значения. В первой ссылке из примеров {campaign_id} станет номером кампании, {ad_id} — номером объявления, {keyword} — фразой, по которой его показали.
Номер кампании в utm_campaign надёжнее названия. Название переименуют посреди месяца, и одна кампания разъедется на две строки, а номер останется прежним. Названия подтягивают из выгрузки Директа по номеру. Если нужна читаемость прямо в Метрике, есть {campaign_name_lat}, но риск переименования остаётся.
Две особенности из справки Директа прямо касаются отчётов. Первая: при клике utm_source=yandex иногда сокращается до utm_source=ya, поэтому в словаре нормализации нужна строка ya → yandex. Вторая: кириллица в ссылке кодируется, и если адрес вырастет больше 4096 байт, значения передадутся только для меток yclid и openstat. Сам Директ передаёт yclid — идентификатор клика для Метрики; UTM-метки нужны продуктовой базе, CRM и системам, которые yclid не читают.
| Параметр | Что подставится |
|---|---|
{campaign_id} | номер кампании |
{campaign_name_lat} | название кампании транслитом; {campaign_name} — как есть |
{gbid} | номер группы объявлений |
{ad_id} | номер объявления |
{keyword} | ключевая фраза, по которой показано объявление |
{source_type} | search на поиске, context в сетях |
{source} | площадка в сетях; на поиске — none |
{device_type} | desktop, mobile или tablet |
{region_name} | регион показа |
Как метка попадает в данные продукта
Метрика хранит метку у визита и, по справке, берёт её из первого просмотра в визите. Продуктовой базе нужно другое — канал у пользователя, чтобы считать по нему регистрации, оплаты и удержание. Обычная схема: при первом заходе параметры из адреса сохраняются в cookie или локальном хранилище браузера, при регистрации записываются в строку пользователя и дальше не меняются. Это первое касание — канал, который впервые привёл человека, а не тот, с которого он пришёл перед оплатой. Как выбор правила меняет итоговые цифры по каналам, разобрано в статье о моделях атрибуции.
В учебной базе «Маяк» такой столбец уже есть — users.channel. Сырых адресов в ней нет, поэтому разбор показан на учебной вставке: двенадцать адресов страниц входа, какими их видит сборщик событий. В них собраны типичные ошибки: заглавные буквы, сокращение ya, пробел в конце значения, письмо без utm_medium, перепутанные source и medium, кириллица в названии кампании.
Для извлечения подходит split_part — он одинаково работает в DuckDB и PostgreSQL. Сначала из адреса вырезается строка запроса: всё между ? и #. Её обрамляют амперсандами, чтобы искать &utm_source=, а не просто utm_source=: иначе совпал бы и параметр с именем вроде xutm_source. Если параметра нет, split_part возвращает пустую строку, и nullif превращает её в NULL. Вариант на регулярных выражениях и различия функций между движками — в статье про регулярные выражения в SQL, сам split_part — в статье про строковые функции.
| visit_id | utm_source | utm_medium | utm_campaign | utm_content | utm_term |
|---|---|---|---|---|---|
| 1 | yandex | cpc | summer_sale | ad_101 | report_tool |
| 2 | yandex | cpc | summer_sale | ad_102 | NULL |
| 3 | Yandex | CPC | Summer_Sale | NULL | NULL |
| 4 | ya | cpc | summer_sale | NULL | NULL |
| 5 | yandex%20 | cpc | summer_sale | NULL | NULL |
| 6 | newsletter | august_digest | NULL | NULL | |
| 7 | newsletter | NULL | august_digest | NULL | NULL |
| 8 | cpc | yandex | summer_sale | NULL | NULL |
| 9 | vk | paid_social | summer_sale | NULL | NULL |
| 10 | partner_blog | partner | %D0%BE%D0%B1%D0%B7%D0%BE%D1%80 | NULL | NULL |
| 11 | NULL | NULL | NULL | NULL | NULL |
| 12 | newsletter | august_digest | button_top | NULL |
with raw_visits(visit_id, landing_url) as (
values
(1, 'https://mayak.example/?utm_source=yandex&utm_medium=cpc&utm_campaign=summer_sale&utm_content=ad_101&utm_term=report_tool'),
(2, 'https://mayak.example/pricing?utm_source=yandex&utm_medium=cpc&utm_campaign=summer_sale&utm_content=ad_102'),
(3, 'https://mayak.example/?utm_source=Yandex&utm_medium=CPC&utm_campaign=Summer_Sale'),
(4, 'https://mayak.example/?utm_source=ya&utm_medium=cpc&utm_campaign=summer_sale'),
(5, 'https://mayak.example/?utm_source=yandex%20&utm_medium=cpc&utm_campaign=summer_sale'),
(6, 'https://mayak.example/?utm_source=newsletter&utm_medium=email&utm_campaign=august_digest'),
(7, 'https://mayak.example/?utm_source=newsletter&utm_campaign=august_digest'),
(8, 'https://mayak.example/?utm_source=cpc&utm_medium=yandex&utm_campaign=summer_sale'),
(9, 'https://mayak.example/?utm_source=vk&utm_medium=paid_social&utm_campaign=summer_sale#pricing'),
(10, 'https://mayak.example/?utm_source=partner_blog&utm_medium=partner&utm_campaign=%D0%BE%D0%B1%D0%B7%D0%BE%D1%80'),
(11, 'https://mayak.example/blog/sql-regexp/'),
(12, 'https://mayak.example/?utm_source=newsletter&utm_medium=email&utm_campaign=august_digest&utm_content=button_top')
),
query_strings as (
-- всё между «?» и «#», обрамлённое амперсандами
select visit_id,
'&' || split_part(split_part(landing_url, '#', 1), '?', 2) || '&' as qs
from raw_visits
)
select visit_id,
nullif(split_part(split_part(qs, '&utm_source=', 2), '&', 1), '') as utm_source,
nullif(split_part(split_part(qs, '&utm_medium=', 2), '&', 1), '') as utm_medium,
nullif(split_part(split_part(qs, '&utm_campaign=', 2), '&', 1), '') as utm_campaign,
nullif(split_part(split_part(qs, '&utm_content=', 2), '&', 1), '') as utm_content,
nullif(split_part(split_part(qs, '&utm_term=', 2), '&', 1), '') as utm_term
from query_strings
order by visit_id;Как одна заглавная буква делит канал на несколько строк
Теперь сгруппируем те же визиты по source и medium — так, как это делает любой отчёт по источникам. Пустые значения подписаны (not set), как в Google Analytics.
| utm_source | utm_medium | visits |
|---|---|---|
| newsletter | 2 | |
| yandex | cpc | 2 |
| (not set) | (not set) | 1 |
| Yandex | CPC | 1 |
| cpc | yandex | 1 |
| newsletter | (not set) | 1 |
| partner_blog | partner | 1 |
| vk | paid_social | 1 |
| ya | cpc | 1 |
| yandex%20 | cpc | 1 |
with raw_visits(visit_id, landing_url) as (
values
(1, 'https://mayak.example/?utm_source=yandex&utm_medium=cpc&utm_campaign=summer_sale&utm_content=ad_101&utm_term=report_tool'),
(2, 'https://mayak.example/pricing?utm_source=yandex&utm_medium=cpc&utm_campaign=summer_sale&utm_content=ad_102'),
(3, 'https://mayak.example/?utm_source=Yandex&utm_medium=CPC&utm_campaign=Summer_Sale'),
(4, 'https://mayak.example/?utm_source=ya&utm_medium=cpc&utm_campaign=summer_sale'),
(5, 'https://mayak.example/?utm_source=yandex%20&utm_medium=cpc&utm_campaign=summer_sale'),
(6, 'https://mayak.example/?utm_source=newsletter&utm_medium=email&utm_campaign=august_digest'),
(7, 'https://mayak.example/?utm_source=newsletter&utm_campaign=august_digest'),
(8, 'https://mayak.example/?utm_source=cpc&utm_medium=yandex&utm_campaign=summer_sale'),
(9, 'https://mayak.example/?utm_source=vk&utm_medium=paid_social&utm_campaign=summer_sale#pricing'),
(10, 'https://mayak.example/?utm_source=partner_blog&utm_medium=partner&utm_campaign=%D0%BE%D0%B1%D0%B7%D0%BE%D1%80'),
(11, 'https://mayak.example/blog/sql-regexp/'),
(12, 'https://mayak.example/?utm_source=newsletter&utm_medium=email&utm_campaign=august_digest&utm_content=button_top')
),
query_strings as (
select visit_id,
'&' || split_part(split_part(landing_url, '#', 1), '?', 2) || '&' as qs
from raw_visits
),
tags as (
select visit_id,
nullif(split_part(split_part(qs, '&utm_source=', 2), '&', 1), '') as utm_source,
nullif(split_part(split_part(qs, '&utm_medium=', 2), '&', 1), '') as utm_medium
from query_strings
)
select coalesce(utm_source, '(not set)') as utm_source,
coalesce(utm_medium, '(not set)') as utm_medium,
count(*) as visits
from tags
group by 1, 2
order by visits desc, utm_source, utm_medium;Что видно в сыром отчёте
Пять переходов из одной кампании в Директе разошлись по четырём строкам: yandex / cpc — два, а Yandex / CPC, ya / cpc и yandex%20 / cpc — по одному. Кто посмотрит только на верхнюю строку, увидит два визита вместо пяти и занизит платный поиск в 2,5 раза.
Восьмой визит хуже: перепутанные параметры дали источник cpc и канал yandex. В отчёте по каналам он окажется в отдельной строке, которую никто не свяжет с Директом. Седьмой — письмо без utm_medium — выпадает из канала email. При этом общее число визитов верное, и сверка «итог совпал с Метрикой» ошибку не ловит. Похожие ловушки, когда метрика честно посчитана, но врёт из-за данных, собраны в статье о том, почему метрика обманывает.
Как нормализовать метки и свести их к каналам
Чистка идёт в три шага. Первый — механический: replace убирает закодированный пробел %20, trim — обычные пробелы по краям, lower приводит к нижнему регистру. Второй — словарь: известные синонимы сводятся к одному значению, ya становится yandex. Третий — правило канала: из пары source и medium получается одно значение для отчёта.
Важно, куда попадает то, что правила не узнали. В запросе ниже это отдельная строка «не распознано» с номерами визитов, а не тихое «прочее». Письмо без medium и визит с перепутанными параметрами видны сразу, и их можно исправить в источнике — в шаблоне рассылки и в настройках объявления. Отнести utm_source=newsletter к email без medium запрос может, но такое решение стоит записать в словарь явно, а не прятать внутрь case.
| channel | visits | visit_ids |
|---|---|---|
| paid_search | 5 | 1, 2, 3, 4, 5 |
| 2 | 6, 12 | |
| не распознано | 2 | 7, 8 |
| paid_social | 1 | 9 |
| partner | 1 | 10 |
| без меток | 1 | 11 |
with raw_visits(visit_id, landing_url) as (
values
(1, 'https://mayak.example/?utm_source=yandex&utm_medium=cpc&utm_campaign=summer_sale&utm_content=ad_101&utm_term=report_tool'),
(2, 'https://mayak.example/pricing?utm_source=yandex&utm_medium=cpc&utm_campaign=summer_sale&utm_content=ad_102'),
(3, 'https://mayak.example/?utm_source=Yandex&utm_medium=CPC&utm_campaign=Summer_Sale'),
(4, 'https://mayak.example/?utm_source=ya&utm_medium=cpc&utm_campaign=summer_sale'),
(5, 'https://mayak.example/?utm_source=yandex%20&utm_medium=cpc&utm_campaign=summer_sale'),
(6, 'https://mayak.example/?utm_source=newsletter&utm_medium=email&utm_campaign=august_digest'),
(7, 'https://mayak.example/?utm_source=newsletter&utm_campaign=august_digest'),
(8, 'https://mayak.example/?utm_source=cpc&utm_medium=yandex&utm_campaign=summer_sale'),
(9, 'https://mayak.example/?utm_source=vk&utm_medium=paid_social&utm_campaign=summer_sale#pricing'),
(10, 'https://mayak.example/?utm_source=partner_blog&utm_medium=partner&utm_campaign=%D0%BE%D0%B1%D0%B7%D0%BE%D1%80'),
(11, 'https://mayak.example/blog/sql-regexp/'),
(12, 'https://mayak.example/?utm_source=newsletter&utm_medium=email&utm_campaign=august_digest&utm_content=button_top')
),
query_strings as (
select visit_id,
'&' || split_part(split_part(landing_url, '#', 1), '?', 2) || '&' as qs
from raw_visits
),
tags as (
select visit_id,
nullif(split_part(split_part(qs, '&utm_source=', 2), '&', 1), '') as utm_source,
nullif(split_part(split_part(qs, '&utm_medium=', 2), '&', 1), '') as utm_medium
from query_strings
),
clean as (
-- шаг 1: закодированный пробел, пробелы по краям, регистр
select visit_id,
lower(trim(replace(utm_source, '%20', ' '))) as source,
lower(trim(replace(utm_medium, '%20', ' '))) as medium
from tags
),
dictionary as (
-- шаг 2: синонимы из словаря
select visit_id,
case when source = 'ya' then 'yandex' else source end as source,
medium
from clean
)
-- шаг 3: правило канала
select case
when source is null and medium is null then 'без меток'
when medium = 'cpc' and source in ('yandex', 'google') then 'paid_search'
when medium = 'email' then 'email'
when medium = 'paid_social' then 'paid_social'
when medium = 'partner' then 'partner'
else 'не распознано'
end as channel,
count(*) as visits,
string_agg(cast(visit_id as varchar), ', ' order by visit_id) as visit_ids
from dictionary
group by 1
order by visits desc, channel;Что остаётся после чистки и как то же сделать в Python
Платный поиск собрал все пять своих визитов. Без меток остался один заход на статью блога: это органика или прямой заход, и различить их можно только по рефереру, которого в адресе нет. Десятый визит попал в партнёров, но название кампании так и осталось закодированным: split_part ничего не декодирует, а в PostgreSQL встроенной функции для этого нет (в DuckDB есть url_decode). Кириллицу проще не пускать в метки, чем чинить потом в базе.
В Python разбор адреса короче: urllib.parse сам делит строку запроса на параметры и декодирует кириллицу. Тот же словарь даёт те же шесть групп, а кампания десятого визита читается как «обзор». Учтите: parse_qs превращает + в пробел, а SQL-разбор выше — нет.
from collections import Counter
from urllib.parse import urlsplit, parse_qs
urls = [
'https://mayak.example/?utm_source=yandex&utm_medium=cpc&utm_campaign=summer_sale&utm_content=ad_101&utm_term=report_tool',
'https://mayak.example/pricing?utm_source=yandex&utm_medium=cpc&utm_campaign=summer_sale&utm_content=ad_102',
'https://mayak.example/?utm_source=Yandex&utm_medium=CPC&utm_campaign=Summer_Sale',
'https://mayak.example/?utm_source=ya&utm_medium=cpc&utm_campaign=summer_sale',
'https://mayak.example/?utm_source=yandex%20&utm_medium=cpc&utm_campaign=summer_sale',
'https://mayak.example/?utm_source=newsletter&utm_medium=email&utm_campaign=august_digest',
'https://mayak.example/?utm_source=newsletter&utm_campaign=august_digest',
'https://mayak.example/?utm_source=cpc&utm_medium=yandex&utm_campaign=summer_sale',
'https://mayak.example/?utm_source=vk&utm_medium=paid_social&utm_campaign=summer_sale#pricing',
'https://mayak.example/?utm_source=partner_blog&utm_medium=partner&utm_campaign=%D0%BE%D0%B1%D0%B7%D0%BE%D1%80',
'https://mayak.example/blog/sql-regexp/',
'https://mayak.example/?utm_source=newsletter&utm_medium=email&utm_campaign=august_digest&utm_content=button_top',
]
def tag(params, name):
# parse_qs декодирует %20 и кириллицу; пустое значение — None
value = params.get(name, [''])[0].strip().lower()
return value or None
def channel(source, medium):
source = 'yandex' if source == 'ya' else source
if source is None and medium is None:
return 'без меток'
if medium == 'cpc' and source in ('yandex', 'google'):
return 'paid_search'
if medium in ('email', 'paid_social', 'partner'):
return medium
return 'не распознано'
counts = Counter()
for url in urls:
params = parse_qs(urlsplit(url).query)
counts[channel(tag(params, 'utm_source'), tag(params, 'utm_medium'))] += 1
print(counts.most_common())
print(tag(parse_qs(urlsplit(urls[9]).query), 'utm_campaign'))
# [('paid_search', 5), ('email', 2), ('не распознано', 2), ('paid_social', 1), ('partner', 1), ('без меток', 1)]
# обзорКаналы в учебной базе: как выглядит чистый результат
В таблице users «Маяка» разметка уже сведена к четырём каналам — так выглядит результат, когда словарь соблюдается. Перед расчётом по каналам проверьте это запросом: сколько различных значений, нет ли заглавных букв и пробелов, когда каждое значение появилось впервые.
| channel | users | share_pct | first_signup | is_clean |
|---|---|---|---|---|
| organic | 1747 | 37.9 | 2026-06-01 | true |
| referral | 1037 | 22.5 | 2026-06-01 | true |
| paid_search | 1015 | 22.0 | 2026-07-01 | true |
| partner | 814 | 17.6 | 2026-06-01 | true |
select channel,
count(*) as users,
round(cast(100.0 * count(*) / sum(count(*)) over () as numeric(18, 6)), 1) as share_pct,
min(signup_date) as first_signup,
channel = lower(trim(channel)) as is_clean
from users
group by channel
order by users desc;Как читать распределение каналов
Значений ровно четыре, все в нижнем регистре и без пробелов. Органика даёт 37,9% регистраций, рекомендации — 22,5%, платный поиск — 22,0%, партнёры — 17,6%. Столбец first_signup показывает то, что легко пропустить: платный поиск появился только 1 июля, остальные каналы — с 1 июня. Доля за все три месяца его занижает: с 1 июля на платный поиск приходится 1 015 регистраций из 3 571, то есть 28,4%.
Отсюда правило: каналы сравнивают на одинаковом окне, а не за всё время. Как довести такое сравнение от регистраций до выручки и порога цены привлечения, показано в разборе оценки рекламного канала в SQL.
Частые ошибки в UTM-разметке
Метки на внутренних ссылках сайта. Баннер на главной ведёт на страницу тарифов по ссылке с utm_source=main_banner. Счётчик видит в адресе новую метку и может принять клик за новый переход: в зависимости от системы он начнёт новый визит или станет последним касанием перед покупкой. Сборщик событий, который пишет «последнюю увиденную метку», перезапишет канал пользователя, и регистрации, которые привёл Директ, уйдут на ваш собственный баннер. Внутренние переходы отмечают событием клика или отдельным параметром, имя которого не начинается с utm_.
Разный регистр и синонимы. Yandex, yandex и ya — три источника для любой системы.
Пробелы и кириллица. Значение, скопированное из таблицы с пробелом, превращается в %20 или +. Кириллица кодируется в UTF-8, удлиняет ссылку и в выгрузке остаётся нечитаемой, а у Директа при длине адреса больше 4096 байт UTM-значения не передаются вовсе.
Метка теряется при перенаправлении. Ссылка ведёт на адрес с http:// или без www, сервер перенаправляет на основной адрес и отбрасывает строку запроса. Сокращатели ссылок и промежуточные страницы приложений иногда делают то же. Визит записывается как прямой заход. Для JavaScript-перенаправлений Метрика предлагает передавать исходный адрес в параметре utm_referrer. Проверка одна: кликнуть по готовой ссылке и посмотреть адресную строку после загрузки.
**Рассылки без utm_medium=email.** Письмо с utm_source=newsletter без medium не попадёт в канал email ни в вашем словаре, ни в группировке GA4, а в Метрике medium для рассылок обязателен. Без меток вовсе переход из почтовой программы часто выглядит как прямой заход.
Путаница source и medium. utm_source=cpc&utm_medium=yandex или utm_medium=yandex_direct — визит уходит из канала платного поиска. Правило из раздела выше: source — место, medium — категория из словаря.
Как проверять разметку: перед запуском и раз в неделю
Разметка ломается постепенно: новый подрядчик, новая площадка, срочная рассылка, собранная вручную.
- Перед запуском кликнуть по каждой ссылке, убедиться, что после всех перенаправлений параметры на месте, и найти свой визит в отчёте «Метки UTM» Метрики.
- Собирать ссылки только из общей таблицы со словарём medium. Ссылка, набранная руками в письме, — главный источник хвостов.
- Раз в неделю выводить все пары source и medium, не попавшие в словарь, с числом визитов. Новое значение — либо опечатка, которую чинят в ссылке, либо новый источник, который вносят в словарь.
- Хранить сырые значения меток рядом с нормализованными. Если правило словаря поменяется, канал можно пересчитать задним числом.
Частые вопросы
Что такое UTM-метки простыми словами?
Это подписи в конце ссылки, по которым система аналитики понимает, из какой рекламы, письма или публикации пришёл человек. На содержание страницы они не влияют.
Какие UTM-метки обязательны?
Справка Google Analytics и справка Директа называют обязательными utm_source, utm_medium и utm_campaign. Метрика требует их выборочно, в зависимости от типа источника, но для чистого отчёта по каналам три параметра нужны всегда.
Нужны ли UTM-метки в Директе, если Метрика понимает yclid?
Для отчётов внутри Метрики хватает yclid. Метки нужны всему остальному: продуктовой базе, CRM, сквозной аналитике и другим счётчикам, которые yclid не читают. Динамические параметры заполняют их автоматически.
Можно ли писать значения меток по-русски?
Технически можно: кириллица закодируется в UTF-8. Но ссылка станет длинной, значения в выгрузке — нечитаемыми, а справка Директа советует латиницу. Выберите одно правило транслитерации и держитесь его.
Не повредят ли метки поисковому продвижению?
Адреса с метками — копии одной страницы. Чтобы поисковики не считали их отдельными страницами, укажите канонический адрес (rel=canonical), а для Яндекса — директиву Clean-param в robots.txt.
Итог
UTM-метка нужна не для красоты ссылки, а чтобы через месяц трафик и деньги группировались по каналам без ручной склейки. Для этого хватает пяти параметров, словаря значений medium, нижнего регистра и латиницы, правила первого касания в данных продукта и еженедельной проверки новых значений. Всё, что не попало в словарь, выводится отдельной строкой и чинится в источнике.
Дальше метки превращаются в метрики: стоимость привлечения, удержание и выручку по каналам. Считать их на базе продукта, от регистраций до оплат, учат в Симуляторе SQL-аналитика.
Материалы по теме

Когортный анализ: что это, как построить и читать таблицу
Когортный анализ простыми словами: как построить когортную таблицу в SQL, pandas и Excel, читать её по строкам, столбцам и диагоналям и что делать с незрелыми когортами.

Продуктовый аналитик: кто это и чем занимается на примере недели
Неделя продуктового аналитика на одном продукте: пять вопросов команды с SQL-запросами, ловушками и решениями, а после — навыки, метрики и отличия от соседних ролей.

ИИ в работе аналитика: где нейросеть помогает, а где врёт
Как аналитику работать с нейросетями: какие задачи ИИ ускоряет, где выдумывает цифры, как ставить задачу и проверять ответ. С результатами нашего эксперимента.