Делаю в одиночку приложение, которое собирает публикации из подобранных Telegram‑каналов по темам, убирает дубли и шум с помощью AI‑классификации и превращает результат в короткий аудио‑дайджест. Ниже — не обзор архитектуры целиком, а разбор конкретных мест, где план и реальность разошлись, и что пришлось чинить.
Грабля 1. RSS — не от Telegram, а через чужой мост со своим невидимым лимитом
Прямого доступа к Telegram API для чтения произвольных публичных каналов нет — это API для ботов, а не для мониторинга чужих каналов. Решение — бесплатный сторонний сервис, который превращает публичный канал в RSS‑ленту по URL. У него неофициальный лимит — по опыту, около 13–15 запросов подряд в течение нескольких минут, дальше сервис отвечает 429 и восстанавливается дольше, чем указано в официальной документации (заявлено 15 запросов/мин, фактическое время простоя больше).
Это не разовая проблема, а параметр архитектуры: именно этот лимит определяет паузу между темами внутри пайплайна (75 секунд) и, соответственно, нижнюю границу частоты полного цикла сбора по всем темам (около 40–45 минут на 12 тем). Внешняя зависимость без SLA и без официальной документации стала жёстким ограничением скорости всего продукта.
Грабля 2. Экономика количества запусков, а не только денег
У сервиса автоматизации, который использую, на базовом тарифе — лимит запусков в месяц (2500) и лимит одновременных запусков (5). Раньше у каждой из 12 тем было собственное расписание — 12 независимых точек входа. При учащении расписания (например, до ежечасного) это моментально умножает число засчитываемых запусков в 12 раз и выбивает тариф.
Решение — единая точка входа («Оркестратор»), которая по расписанию вызывает все 12 тем последовательно изнутри себя: внутренние вызовы в лимит не засчитываются, считается только сам факт срабатывания Оркестратора. Это не архитектурная эстетика, а прямое следствие тарифной модели — при другой схеме оплаты решение, вероятно, было бы другим.
Грабля 3. Дедуп, который не дедуплицировал вообще
В пайплайне были два разных идентификатора для одной и той же новости:
— external_id — детерминированный хэш от ссылки на пост и даты. Работает корректно: одна и та же статья из разных источников даёт одинаковый external_id, и бэкенд по нему корректно схлопывает дубли на приёме.
— cluster_id — хэш от точного текста описания (первые 200 символов), задуманный для кросс‑канальной кластеризации «разные каналы написали об одном и том же своими словами».
Проверка на 1003 записях за 14 дней показала 1002 уникальных cluster_id — то есть механизм не «работал слабо», а не работал практически никогда: разные каналы почти всегда описывают одно событие разными словами, и хэш от точного текста этого не ловит в принципе. Позже (после стоп‑листа служебных слов и добавления стемминга — «Россия»/«России», «дроны»/«беспилотники» и тому подобное) отдельный экспериментальный механизм на сравнении заголовков (а не заголовка с резюме, как было изначально по ошибке) дал точность около 89% на выборке — но это уже другой механизм, работающий параллельно и пока не подключённый к реальной ленте.
Грабля 4. Платишь за озвучку дважды — и не сразу это видно
Многие каналы относятся сразу к нескольким темам (например, один канал деловых изданий — сразу к «Банкам» и «Финансам»). Каждая тема — при старой архитектуре — отдельный прогон пайплайна с собственным вызовом синтеза речи. Дедупликация по external_id происходит на бэкенде после того, как аудио уже сгенерировано и оплачено — то есть бэкенд корректно схлопывает дубль на экране пользователя, но деньги на повторную озвучку уже потрачены.
Прямой подсчёт по 14 дням: 2275 фактических вызовов синтеза речи на 1003 уникальных атома — множитель ×2.27, то есть около 56% озвучки было избыточным. В деньгах, по тарифу на тот момент, это ориентировочно 1300 ₽/мес экономии при устранении дубля (оценка снизу — реальный текст для озвучки длиннее текста для отображения, потому что числа проговариваются словами).
Фикс — кэш «этот атом уже озвучен в другой теме за последние 48 часов» перед вызовом синтеза. Не сработал бы без побочного эффекта от другого исправления: пока темы стартовали по независимым расписаниям с разбросом в минуту, соседние прогоны почти всегда работали параллельно (замеры показывали пересечение по времени в 1.5–2.3 минуты), и кэш систематически промахивался — тема, стартовавшая на минуту позже, читала кэш раньше, чем предыдущая успевала туда записать. Когда для починки другого инцидента прогоны сделали строго последовательными через единый Оркестратор, кэш начал работать как задумано — без единой правки самого кэша.
Грабля 5. Ложно‑зелёный статус: расписание чаще — конвейер встал почти полностью
Учащение цикла сбора с 4 раз в сутки до раза в час привело не к постепенному росту расходов, как ожидалось, а к почти полной остановке сбора материалов с первого же часа новой частоты. Причина — бесплатная дневная квота классификатора (лимит 500 запросов/сутки на модель) была исчерпана в первый же час: при 4 прогонах в сутки квоты хватало неделями, при 24 — она кончилась почти сразу.
Хуже другое: сам Оркестратор при этом продолжал отчитываться «success» на всех прогонах. Ошибка ловится на уровне отдельной темы (осознанное решение — чтобы сбой одной темы не останавливал остальные), и должна была уйти в отдельный лог с email‑алертом, но алерт оказался выключен ещё с более раннего инцидента и не был включён обратно. Без ручной проверки данных полный отказ сбора остался бы незамеченным.
Что это меняет в архитектуре дальше
Классификатор был заменён с одной LLM на другую по стоимости (перешли на более дешёвую модель при выросшем объёме материала — прежняя осталась в графе отключённым резервом на случай сбоя новой). Готовится следующий шаг: не по одному вызову модели на материал, а батчами (около 15 материалов за вызов), с гейтами (по дате, по уже виденному external_id, по кросс‑канальному совпадению) до обращения к модели, а не после — потому что при текущей схеме до 19 вызовов из 20 уходят либо на «это не подходящий материал», либо на повторную обработку того, что уже разбирали час назад.
Честно о масштабе
Проект в ранней бете, соло‑разработка. По данным прямых запросов к базе за август: 4733 открытий приложения, 1037 прослушиваний, ежедневное число уникальных слушателей почти всегда 1–6 (максимум 11 за день). Подтверждённых внешних регистраций с реальной активностью на начало сентября — 0 (8 из 9 учётных записей в базе — сам фаундер, технический соавтор и один сотрудник платформы автоматизации, тестировавший продукт). При этом среди тех немногих внешних слушателей, что были, доля дослушивания стабильно держится в диапазоне 60–80% по неделям — то есть там, где до контента реально доходят, он не производит впечатление низкого качества. Узкое место — не контент и не техника, а количество людей, которые вообще узнали о продукте.
KivApple
t.me парсить легко и просто самому и там либо нет лимитов, либо они очень щедрые. Просто сохраните несколько страниц t.me/s/username разных каналов и попросите ИИ агента написать парсер. Он справиться. Хорошо справиться.
Для избранных каналов где хочется риалтайм - юзербот. Телеграм учётки покупаются по 50 рублей за штуку (вам пофиг из какой они страны и каким образом зарегистрированы). До 500 каналов на учетку, подписываться где-то на 50 каналов в сутки можно с одной учетки (потом будет flood wait, да и эти 50 подписок надо сделать с задержкой). Учетки иногда будут отлетать, но гораздо реже, чем у спаммеров (так как вы всего лишь каждому каналу накрутили +1 подписчика, но на вас нет жалоб), так что надо заложить бюджет на покупку новых.
Кстати, обычные бот учетки можно логинить по токену через mtproto и получать гораздо больше, чем по bot api. Например, можно получать профиль любого чата, канала, юзера по юзернейму (resolve username, где-то 150-200 в сутки на бот токен, потом flood wait, если запомнить access hash, то в следующий раз можно уже с гораздо щедрыми лимитами смотреть обновления через get full user и аналог для канала). Возможно, публичную историю тоже можно получать, не проверял. В таком случае получается мультипликатор 20 бот токенов из одной купленной учетки, что вам точно хватит.
Если нормально делать, то так. На моем semagram.io мне хватило скрапить t.me + получать инфу о ботах (команды, mau, описание) через resolve username + get full user через mtproto на бот токенах.