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';
Разница между этими двумя запросами по рекламной когорте:
|
Запрос |
Результат |
|---|---|
|
«Хоть одно действие» (с |
225 из 228 (99%) |
|
«Реальное действие» (без |
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/