231 мёртвая душа: как реклама в Telegram обманула меня, а потом обманула моя собственная метрика

от автора

231 мёртвая душа: как реклама в Telegram обманула меня, а потом обманула моя собственная метрика

TL;DR — я купил рекламу для своего pet-проекта @futur_e_news_bot (двуязычная лента новостей на sqlite-vec за ~$15/мес) и за сутки получил +232 подписчика. Дашборд загорелся зелёным, «активация 81%», я почти обрадовался. А потом посмотрел внимательнее: из 232 человек живое действие сделал ровно один. 35% заблокировали бота за первый день. Метрика «активации» врала, потому что считала автоматические просмотры. Пост про то, как отличить трафик от накрутки собственными руками, почему «цена за подписчика» — это ловушка, и какую единственную цифру стоит смотреть.

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


Recap для тех, кто впервые

Бот показывает персональную ленту новостей: ИИ убирает дубли, переводит, по умолчанию показывает только хорошие и нейтральные новости, а лайки/дизлайки двигают «вектор вкуса». Живёт на одной машине Fly.io, база — один SQLite-файл. Аудитория на момент истории — около сотни реальных человек, пришедших в основном с Habr. Роста почти нет: +5–7 человек в день. Захотелось попробовать платный трафик.


Глава 1. Дофаминовый всплеск

Я нашёл Telegram-агрегатор рекламы, закинул небольшой бюджет, вставил метку ?start= в ссылку и запустил. Через час открываю свой дашборд статистики — а там праздник:

Пользователи: 329  (+229 за сутки)Источник ads_*: 228Активация: 81%

+229 человек за день. После недель по +5 это выглядело как победа. Первая мысль — «ну наконец-то, работает, надо доливать бюджет». Палец уже тянулся к кнопке «пополнить».

Единственная причина, по которой я не долил в ту же секунду, — привычка, выработанная прошлыми постами: сначала посмотри на данные внимательно, особенно когда они тебе нравятся. В прошлый раз метрика релевантности мне уже соврала (её отравил один человек, накликавший 106 дизлайков), и я запомнил урок. Поэтому вместо «пополнить» я пошёл смотреть, что эти 229 человек реально делают.


Глава 2. Метрика, которая мне льстила

«Активация 81%» в моём отчёте означала «доля юзеров, сделавших хотя бы одно действие». Звучит солидно. Но что такое «действие»? Я полез в определение и похолодел.

Когда новый юзер проходит онбординг, бот показывает ему несколько карточек-новостей, чтобы стартово настроить вкус. Каждый показ карточки пишется в таблицу interactions как событие view. То есть любой человек, который просто нажал /start и не сделал вообще ничего, автоматически получал 2–4 «действия» и попадал в «активированных».

Метрика измеряла не вовлечённость. Она измеряла факт того, что бот сам себе показал карточки. Вот как это выглядело в SQL — считаю «активных» наивно:

-- ❌ врёт: view пишется автоматически в онбордингеSELECT COUNT(DISTINCT user_id) FROM interactions;   -- почти 100% новичков

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

-- ✅ честно: реакция (👍/👎/🔥) или клик по ссылкеSELECT COUNT(DISTINCT user_id)FROM interactionsWHERE kind != 'view';

Разница между этими двумя запросами по рекламной когорте:

Запрос

Результат

«Хоть одно действие» (с view)

225 из 228 (99%)

«Реальное действие» (без view)

1 из 228 (0.4%)

Один. Из 228 человек, за которых я заплатил, осознанно нажал хоть что-то один. Всё остальное — бот, разговаривающий сам с собой.


Глава 3. Анатомия мёртвых душ

Дальше я разложил когорту по косточкам, и картина сложилась окончательно.

Как они пришли. Гистограмма регистраций по часам: 218 из 232 человек создались в один и тот же час. Не «в течение дня» — в один час. Живой трафик так не приходит; так приходит накрутка, когда сеть заданий одномоментно прогоняет ботоферму или мотивированных пользователей через твой /start.

12:00  ████████████████████████████████  21813:00  █                                    814:00                                        115:00                                        1

Что это за трафик. Такие агрегаторы часто работают как «шлюз»: чтобы получить доступ к чему-то (файл, канал, награда), человек обязан нажать /start у N чужих ботов. Он стартует твоего бота и в ту же секунду уходит — интента ноль по определению. Ты платишь за /start, а не за пользователя.

Что было дальше. Я подождал сутки и снял второй срез:

  • 35% (82 человека) заблокировали бота после первой же сводки. Telegram при отправке заблокировавшему кидает TelegramForbiddenError, я по нему автоматически гашу подписку — так что блок видно в данных.

  • 0 вернулись в бота на другой день.

  • 1 живое действие как было, так и осталось.

Для сравнения — те же метрики по каналам, где трафик настоящий:

Канал

Пришло

Живых действий

Доля

Habr (этот блог)

36

10

28% 🟢

Органика (поиск, сарафан)

63

12

19% 🟢

Платная реклама

232

1

0.4% 🔴

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


Глава 4. Как не дать себя обмануть: атрибуция за один вечер

Хорошая новость: чтобы всё это увидеть, не нужен ни аналитик, ни дорогая система. Нужно три простых привычки, которые я встроил в проект.

1. Метка на каждый источник. Telegram умеет t.me/bot?start=PARAM — параметр прилетает в обработчик /start. Я парщу его и пишу в поле referral. Дальше любой разрез по источнику — это один GROUP BY:

SELECT COALESCE(referral, 'organic') AS src, COUNT(*)FROM usersGROUP BY src;

Всё, что пришло без метки, честно валится в «organic» — и это отдельный урок: если не пометил ссылку, потом уже не отличишь, откуда человек.

2. Считай distinct-юзеров, а не события. Это тот же урок, что отравленная метрика релевантности из прошлого поста: один аномальный человек (106 дизлайков там, сотня авто-view здесь) перекашивает любую метрику, считающую события. Один юзер = один голос.

3. Меряй цену за ЖИВОГО, а не за старт. Единственная честная метрика привлечения — это цена за человека, сделавшего реальное действие. Она устроена так, что её нельзя накрутить: показы и /start подделать легко, осознанный тап 👍 — нет.

Отсюда правило, которое теперь работает автоматически: у каждого канала своя метка, на 48-м часу смотрю цену за живого. Дороже $4–5 — выключаю, дешевле $3 — удваиваю. Эта дисциплина превратила «231 подписчик, ура!» в «0.4%, стоп» за одну минуту вместо слитого бюджета.


Что я понял на этот раз

  • Объём — метрика тщеславия. Реальные действия — метрика здравого смысла. Большое зелёное число сверху дашборда обычно и есть то, что проще всего накрутить.

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

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

  • Привычка “посмотри внимательно, когда данные тебе нравятся” окупается. Оба раза, когда метрика мне врала, она врала в приятную сторону.

Мораль не «реклама не работает», а «не давай ни рынку, ни собственному дашборду обмануть себя красивым числом». Инструмент, который ловит обман, стоит один вечер — и он у тебя уже, скорее всего, есть, просто в определении метрики спрятан view.


Попробовать

Бот, на котором я всё это ловил: @futur_e_news_bot — двуязычная лента, по умолчанию только хорошие новости, лайки двигают выдачу. /start, пара тапов — и лента подстраивается.

А в комментах интересно: как вы у себя отличаете живой трафик от накрутки? Есть ли метрика, которую нельзя подделать по построению, — или все в итоге упираются в «осознанное действие»? И ловил ли кто-то свою метрику на том, что она считает служебные события за пользовательские?

Следующим постом разберу обратную сторону — как я, наоборот, чинил одну фичу и при этом на три недели незаметно сломал другую (спойлер: релевантность и срочность — это разные задачи, которым нужны разные источники).

ссылка на оригинал статьи https://habr.com/ru/articles/1058124/