Сквозная аналитика своими силами: Метрика, PostgreSQL и Битрикс24

Сквозная аналитика своими силами: Метрика, PostgreSQL и Битрикс24

Обновлено 3 сентября 2026 года.

Сквозную аналитику можно собрать из Яндекс Метрики, выгрузок рекламных расходов, CRM и PostgreSQL. Такая система связывает наблюдаемые касания с обращениями, сделками и финансовыми результатами по выбранным правилам. Она помогает сравнивать каналы, но не гарантирует, что удалось восстановить все действия человека или доказать причинный эффект рекламы.

Ниже — порядок самостоятельной сборки без отдельной подписки на специализированный сервис сквозной аналитики. Бесплатность компонентов не означает нулевую стоимость: остаются сервер, разработка, сопровождение и условия доступа к CRM.

1. Проверьте доступы и границы проекта

Для начала нужны права на счётчик Метрики, доступ к расходам рекламных кабинетов, возможность выгружать CRM и источник оплат или учётной выручки. PostgreSQL хранит данные; Python или другой инструмент выполняет загрузки; BI показывает согласованные показатели.

В облачном Битрикс24 обычные локальные вебхуки и приложения требуют подписки BitrixGPT + Маркетплейс. Исключение для одного crm.automation.trigger не подходит для выгрузки CRM. В коробочной версии входящие вебхуки освобождены от требования такой подписки. Эти условия описаны в официальном разъяснении. До разработки проверьте свой тариф, регион и способ подключения.

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

2. Что связывает визит и CRM

Идентификатор Назначение Ограничение
ClientID Метрики Связать данные браузера с обращением Не является постоянным идентификатором человека или клиента компании
ID обращения Отличить две отправки формы и повторную доставку одной заявки Должен сохраняться при повторной попытке передачи
ID лида или сделки Сопоставить объект CRM Нужны также портал и тип сущности; номера разных сущностей могут совпасть
ID клиента Объединять покупки для когорт и LTV Требует согласованного правила для контактов, компаний и дублей
Yclid Связь с конкретным рекламным кликом Директа в поддерживаемом сценарии Не заменяет идентификатор всех визитов клиента

Один человек может иметь несколько ClientID, а одним браузером могут пользоваться разные люди. Удаление cookie, блокировка счётчика или обращение по телефону создают пробелы. Такие обращения нужно сохранить в отчёте с понятной причиной отсутствия связи.

Если на сайте есть авторизация, можно передавать собственный UserID через setUserID. Идентификатор учётной записи тоже требует правил сопоставления; он не восстанавливает автоматически всю прежнюю историю человека.

3. Передайте ClientID и параметры обращения в CRM

Используйте документированный getClientID. Он возвращает строку. Не переводите её в JavaScript Number, Excel-число или дробный тип: длинный идентификатор может потерять точность. Не извлекайте дату первого посещения из его цифр — это не контракт API.

Ниже вспомогательная функция для собственной формы сайта. Счётчик и его стандартная очередь ym должны быть установлены заранее. Функция ждёт ответ ограниченное время и возвращает null, если счётчик недоступен; обработчик формы должен продолжить отправку самой заявки.

function getMetrikaClientId(counterId, timeoutMs = 1500) {
  return new Promise((resolve) => {
    let settled = false;
    const finish = (value) => {
      if (settled) return;
      settled = true;
      clearTimeout(timer);
      resolve(value);
    };
    const timer = setTimeout(() => finish(null), timeoutMs);
    if (typeof window.ym !== 'function') {
      finish(null);
      return;
    }
    try {
      window.ym(counterId, 'getClientID', (clientId) => {
        finish(typeof clientId === 'string' && clientId.length > 0
          ? clientId : null);
      });
    } catch {
      finish(null);
    }
  });
}

В обработчике собственной формы результат await getMetrikaClientId(номерСчётчика) добавляют в данные обращения перед отправкой на свой сервер. Сервер сохраняет обращение и передаёт его в CRM. Это не готовый обработчик отправки формы: к существующей логике нужно подключить получение идентификатора и обработать повторные попытки.

Для встроенной CRM-формы Битрикс24 используйте поддерживаемые механизмы передачи дополнительных полей именно этой формы. Обычный поиск hidden input в родительской странице не гарантирует доступ к форме внутри другого документа или iframe.

Вместе с обращением полезно сохранить время, ID формы, страницу и имеющиеся UTM-параметры. Если сохраняете первое и последнее касание на сайте, заведите разные поля и правила обновления: очередной прямой заход не должен незаметно перезаписать историю. Утраченную UTM-метку не всегда удастся восстановить из Метрики.

Секретный URL вебхука Битрикс24 должен оставаться на сервере. Ключ, помещённый в браузерный JavaScript, посетитель сможет получить. Значения формы сервер проверяет; повтор одной заявки обрабатывает по её ID, чтобы не создать второй лид.

4. Выгрузите визиты через Logs API

API отчётов предназначен для статистики по группировкам, а Logs API — для неагрегированных данных. API управления решает другие задачи, включая настройки и организацию выгрузок. Для собственного журнала визитов используйте Logs API и его список полей, а не случайно смешанные поля разных интерфейсов.

В текущей документации указаны ограничения: в одном запросе период не более года; данные за текущий день недоступны; список fields — до 3000 символов; базовая квота подготовленных лог-файлов — 10 ГБ. Эта квота не является ежедневным лимитом чтения. Ограничение длины периода не означает, что вся история старше 180 дней удалена. Доступность конкретного периода проверяют запросом evaluate.

Недавние визиты могут дополняться. Поэтому загрузка «один раз за вчера» недостаточна для неизменяемого архива: нужен повторный захват недавнего периода и обновление записей по ключу. Размер такого окна выбирают по задержкам и контрольным сверкам.

Поля для первого пилота

Поле Logs API Что сохранить
ym:s:counterID Счётчик
ym:s:visitID ID визита; по документации уникален в пределах одного года
ym:s:clientID ID браузера
ym:s:date, ym:s:dateTime Дата и время визита; dateTime задан в часовом поясе счётчика
ym:s:lastTrafficSource Источник по модели «последний переход»
ym:s:lastUTMSource, ym:s:lastUTMMedium, ym:s:lastUTMCampaign UTM в выбранной модели атрибуции
ym:s:goalsID Массив достигнутых целей, если он нужен задаче

Сверяйте названия и типы с каталогом полей визитов. Например, используется dateTime, а не visitDateTime. В актуальном каталоге dateTimeUTC описан как UTC+3, поэтому одно название поля не даёт права интерпретировать его как UTC+0. Массивы целей и покупок требуют отдельного разбора.

Читай также:  Шесть утилит BI Data: телефоны, HTTP, JWT, XML, URL и регистр

У полей источников есть параметр атрибуции. В примере используется last, чтобы затем применять своё правило выбора визита. Согласно документации параметризации, с 25 июня 2026 года часть старых моделей заменяется аналогами: первый и последний значимый переход — кросс-девайс вариантами, последние переходы из Директа — автоматической атрибуцией. Название старого поля не гарантирует прежнюю семантику.

Последовательность запросов

Базовый адрес в текущей документации: https://api-metrika.yandex.net/management/v1/counter/{counterId}. Токен передают в HTTP-заголовке Authorization: OAuth TOKEN, где TOKEN заменяют своим токеном, как указано в правилах авторизации. Его не нужно помещать в строку URL или журналировать.

Шаг Метод относительно базового адреса Что проверить
Оценка GET /logrequests/evaluate source=visits, date1, date2, fields; log_request_evaluation.possible
Создание POST /logrequests Те же параметры; сохранить log_request.request_id
Ожидание GET /logrequest/{requestId} Статус log_request.status; конечный срок ожидания и обработка ошибок
Скачивание GET /logrequest/{requestId}/part/{partNumber}/download При processed скачать все номера из parts, а не только part/0
Освобождение квоты POST /logrequest/{requestId}/clean Сначала подтвердить сохранность и полноту полученных файлов

Справочники: evaluate, создание, статус, скачивание части, очистка.

Загрузчик должен проверять HTTP-ответы, таймауты и состояние запроса. canceled, processing_failed и очищенный запрос нельзя бесконечно ждать как готовящийся. Сохраняйте request_id, параметры, номера частей и число строк, чтобы продолжить после сбоя. Повтор создания запроса после сетевого таймаута требует проверки: сервер мог уже принять предыдущий запрос.

Полученные TSV сначала загружают в промежуточный слой. Проверяют заголовки, порядок колонок, экранирование, массивы, пустые значения и преобразования типов. Слепой COPY в заранее созданную таблицу или pandas с автоматическим выбором числовых типов может исказить идентификаторы либо перепутать столбцы.

5. Разделите сущности в PostgreSQL

Набор Одна запись Ключ и важные поля
Визиты Один визит Счётчик + год визита + visitID; ClientID, время, источник, UTM
Обращения и лиды Один объект CRM Портал + тип сущности + внешний ID; время, ClientID, статус
Сделки Одна сделка Связи с лидом и клиентом, стадия, валюта, сумма CRM
Оплаты и возвраты Одна финансовая операция ID операции, сделка, дата, сумма, валюта и вид операции
Расходы День и рекламный разрез Кабинет, кампания, валюта, выбранный состав затрат и НДС
Атрибуция Выбранная связь обращения с визитом Объект CRM, ключ визита, модель, окно, версия расчёта

ClientID удобно хранить как text. Для UInt64 visitID можно использовать numeric(20,0) или проверенную строку: обычный signed bigint PostgreSQL не покрывает весь диапазон UInt64. Год ключа вычисляйте из даты визита в согласованном часовом поясе счётчика, а не случайно из даты загрузки.

Время приводят к однозначному моменту с учётом исходного пояса; для такого момента в PostgreSQL подходит timestamptz. Отдельно сохраняют бизнес-дату, если по ней считаются дневные отчёты. Суммы хранят с валютой и достаточной десятичной точностью.

ID из CRM должен остаться внешним идентификатором. Новая автоматически выданная последовательность в PostgreSQL не заменяет его. Повторная загрузка должна обновить ту же сущность, а не создать её копию.

6. Получите новые и изменённые объекты CRM

Для выгрузки используйте поддерживаемые методы, например crm.item.list. Тип сущности, поля, фильтры и пагинация задаются по документации. Страница содержит до 50 элементов; нужно обрабатывать продолжение next. Коды пользовательских полей получают у своего портала, а не принимают UF_CRM_CLIENT_ID из примера за существующее поле.

Лид, конвертированный в сделку, и успешная сделка — разные состояния разных сущностей. OPPORTUNITY — сумма в карточке, а не подтверждённая оплата. Для отчёта о деньгах нужны платежи и возвраты; для учётной выручки — соответствующий источник и правила признания.

Загрузка только объектов с новым ID пропустит изменение старого лида, закрытие сделки и возврат. Нужны фильтр по времени изменения с перекрытием периода, обновление по ключу и периодическая сверка удалений и объединений. Текущий статус не восстанавливает сам по себе всю историю стадий.

Входящий вебхук используется вашим загрузчиком для вызовов REST. Исходящий вебхук отправляет событие из Битрикс24 вашему обработчику; для получения полной карточки обычно нужен дополнительный запрос. Это разные направления, описанные в документации вебхуков. События полезны для оперативности, но контрольная сверка нужна и при событийной схеме.

7. Выберите модель атрибуции до расчёта сумм

Правило «соединить все визиты и лиды по ClientID» размножает строки. Если у лида три визита, его сумма появится три раза. COUNT(DISTINCT lead_id) исправит число лидов, но не завышенный SUM.

Для первого пилота можно выбрать первое наблюдаемое касание в течение 30 дней до создания лида. Это наше учебное правило, а не универсальная рекомендация и не точное воспроизведение first-click Метрики. Оно не означает «первый раз, когда человек узнал о компании»: более ранняя история может быть за пределами окна или отсутствовать.

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

WITH visits(counter_id, visit_year, visit_id, client_id, visited_at, source) AS (
  VALUES
    (7, 2026, 1001::numeric, 'A', TIMESTAMPTZ '2026-08-10 10:00+03', 'yandex'),
    (7, 2026, 1002, 'A', TIMESTAMPTZ '2026-08-20 10:00+03', 'organic'),
    (7, 2026, 1003, 'A', TIMESTAMPTZ '2026-09-02 10:00+03', 'email'),
    (7, 2026, 1004, 'B', TIMESTAMPTZ '2026-09-01 09:00+03', 'direct'),
    (7, 2026, 1005, 'B', TIMESTAMPTZ '2026-09-01 09:00+03', 'email'),
    (8, 2026,    1, 'A', TIMESTAMPTZ '2026-08-05 10:00+03', 'other_counter')
), leads(counter_id, lead_id, client_id, created_at) AS (
  VALUES
    (7, 1, 'A', TIMESTAMPTZ '2026-09-01 10:00+03'),
    (7, 2, 'B', TIMESTAMPTZ '2026-09-01 10:00+03'),
    (7, 3, NULL, TIMESTAMPTZ '2026-09-01 10:00+03'),
    (7, 4, 'C', TIMESTAMPTZ '2026-09-01 10:00+03'),
    (7, 5, 'A', TIMESTAMPTZ '2026-10-05 10:00+03')
)
SELECT l.lead_id, v.visit_id, v.source
FROM leads AS l
LEFT JOIN LATERAL (
  SELECT v.visit_id, v.source
  FROM visits AS v
  WHERE v.counter_id = l.counter_id
    AND v.client_id = l.client_id
    AND v.visited_at >= l.created_at - INTERVAL '30 days'
    AND v.visited_at <= l.created_at
  ORDER BY v.visited_at, v.visit_year, v.visit_id
  LIMIT 1
) AS v ON TRUE
ORDER BY l.lead_id;

Результат: лид 1 связан с визитом 1001 и yandex; лид 2 — с 1004 и direct; лиды 3, 4 и 5 остаются без найденного визита. Проверены отсутствие ClientID, отсутствие совпадений, другой счётчик, будущий визит, одинаковое время и выход за 30-дневное окно. В рабочем проекте дополнительно нужны уникальные ключи, нормализация источников и индексы под выбранные запросы.

Читай также:  Open source и коммерческие BI: DataLens, Superset, Power BI, Qlik и Tableau

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

Пустая UTM не равна отсутствию визита: органический или прямой переход может быть корректно определён без меток. Различайте «визит найден, источник direct», «источник не классифицирован» и «связь с визитом не найдена».

8. Добавьте расходы и финансовый результат

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

В API Директа суммы могут возвращаться в денежных микроединицах; режим задаёт returnMoneyInMicros. НДС и состав затрат нужно согласовать с финансовым отчётом. См. денежные значения API Директа. Расход по кампании не сообщает фактическую стоимость каждого отдельного визита.

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

Пример расчёта

Учебные данные одной согласованной когорты: реклама — 20 000 ₽, лиды — 40, новые покупатели — 10, выручка — 80 000 ₽, затраты на выполнение этих продаж — 50 000 ₽. Маржинальный доход до рекламы — 30 000 ₽. Предположим, дополнительно на подготовку маркетинговой кампании потрачено 5 000 ₽ вне рекламного кабинета; других затрат на привлечение по условию примера нет.

Показатель Расчёт Результат
CPL по рекламным расходам 20 000 / 40 500 ₽
Полный CAC при заданном составе затрат (20 000 + 5 000) / 10 2 500 ₽
ROAS 80 000 / 20 000 4, или 400%
ДРР 20 000 / 80 000 × 100% 25%
Окупаемость рекламы по маржинальному доходу (30 000 − 20 000) / 20 000 × 100% 50%
ROMI с дополнительными 5 000 ₽ в расходах (30 000 − 25 000) / 25 000 × 100% 20%

Для формулы (результат − затраты) / затраты точка окупаемости — 0%, а не 100%. Отношение выручки к рекламным расходам — ROAS; оно не учитывает себестоимость и само по себе не доказывает прибыльность. В названии показателя указывайте состав затрат и способ определения результата.

Атрибутированный доход не равен дополнительному доходу, вызванному рекламой. Для причинной оценки нужны эксперимент или другой обоснованный метод. Низкий CPL тоже не является самостоятельной причиной увеличивать бюджет: важно качество обращений, продажи и маржа.

Для LTV объединяйте операции по бизнес-клиенту, а не по ClientID браузера. Сумма продаж за три месяца — наблюдаемая выручка за три месяца, а не автоматически пожизненная ценность. При сравнении когорт задайте одинаковый возраст, состав маржи и правила учёта возвратов.

9. Выведите качество данных рядом с показателями

  • Время последней успешной загрузки каждого источника и возраст данных.
  • Число обращений, доля с ClientID и доля с найденным визитом — отдельно.
  • Число дублей, пропусков и ошибок преобразования.
  • Сверка расходов с рекламным кабинетом и финансовых операций с учётным источником.
  • Выбранная атрибуция, окно и временная логика отчёта.
  • Каналы без продаж, продажи без атрибуции и расходы без сопоставленного канала.

В Power BI можно подключить PostgreSQL через штатный коннектор; в Superset — через соответствующий драйвер и создать SQL-датасеты в SQL Lab. Размещение дашборда, права пользователей, лицензии BI и кеширование оцениваются отдельно от построения базы.

Не требуйте нулевой доли несопоставленных обращений любой ценой: это может стимулировать неверное объединение клиентов. Полезнее знать причины пропусков и изменение доли по формам и каналам. Совпадение всех отчётов до единицы тоже не гарантировано из-за разных определений, периодов и атрибуции.

10. Подготовьте регулярную эксплуатацию

У загрузок должен быть владелец, журнал результатов, ограничение повторов, уведомление об ошибке и проверенный способ восстановления. Храните исходные выгрузки в рамках выбранного срока, чтобы объяснить изменения отчёта. Права загрузчика, BI и администратора разделяйте по выполняемым задачам.

ClientID следует считать техническим идентификатором для сопоставления, а не гарантией полной анонимности связанных данных. Ограничьте доступ и состав выгрузки, не помещайте телефон, email или секреты в UTM, URL и логи. Сбор и хранение должны соответствовать принятому в компании порядку работы с данными.

Если затем понадобится передавать офлайн-конверсии в Метрику, используйте отдельный сценарий импорта. Он поддерживает не только ClientID: в документации предусмотрены также UserID, Yclid и PurchaseId. При передаче проверяются точные имена полей, цель, время события и статус обработки. Этот импорт не заменяет собственную сверку CRM и финансов.

Самостоятельная сквозная аналитика полезна, когда команда может поддерживать связи, загрузки и определения показателей. Её результат — воспроизводимый отчёт с известными ограничениями, а не обещание полной видимости пути клиента или гарантированного снижения CPL.