Это программа-парсер. Вы указываете Telegram-канал, выбираете период — и получаете Excel-табличку со всеми публикациями, просмотрами, реакциями, репостами и вовлечённостью.
Хотите узнать, сколько всего текста опубликовал автор? Пожалуйста. Сколько платных звёздочек получил миллионник за 2024 год? Не вопрос. Хотите сформировать топ постов или сравнить эффективность разных форматов? Всё это — в один клик.
Но эта статья — не просто про парсер. Она про путь: как я, не будучи программистом, сделал этот инструмент с помощью нейросети. Что получилось, какие были затыки и чему я научился в процессе.
В конце — весь код и инструкция, как запустить парсер у себя.
Поехали!
Для начала немножко контекста. Меня зовут Егор Камелев, я проектирую интерфейсы с 2006 года. Работал в агентствах, внутри компаний, на аутсорсе. Основал Проекторат. Специализируюсь на сложных информационных системах. До сих пор в деле.
С чего всё началось
В 2023-24 годах я пытался продвигать свою Книгу нормального фрилансера и написал какое-то невероятное количество контента в Телеграм-канал. И не только в него. Каждую публикацию я стремился выкладывать везде, где она могла подойти по смыслу: Вконтакте, на Дзене, на Пикабу, Виси, Хабре, Ютубе и так далее. Всего набралось больше пятнадцати площадок.
В какие-то моменты я забывал распространять те или иные публикации или выкладывал их не на всех возможных площадках — и потом сидел и долго разбирался, где что опубликовано, а где нет.
Тогда я завёл большую эксельку, в которой стал отмечать где что и когда выкладывал. Это оказалось очень удобно. Потому что как только я находил новую платформу для публикаций (например, узнал о существовании DTF) — мог просто добавить столбец в табличку и потихоньку переносить контент и туда, отмечая плюсиками те посты, которые уже перенёс.
В какой-то момент я решил, что с табличкой работать неудобно, и сделал стартап под названием Посторама. Систему учёта контента. Посторама в базовой версии делает ровно то, что я делал в эксельках: позволяет составлять список постов, сопровождать их тегами и отмечать, когда и на каких платформах они были опубликованы. Разумеется, сразу появились идеи на будущее. Как облегчить авторам работу с большим количеством публикаций на разных платформах. Но для начала это просто система учёта контента. Взгляните на скриншот — и сами всё поймёте. Строки — это публикации, колонки — это площадки.

Разработка стартапа обошлась мне в чуть меньше полутора сотен тысяч рублей и месяц времени. Это без учёта моих собственных работ. Каких? Я сделал интерактивный прототип в Axure, описал его в функциональной спецификации, нашёл разработчиков на бэк и фронт и проследил за тем, чтобы на выходе получилось примерно то, что хотелось.
Бэк сделали на Питоне и Джанго. Фронт — на Реакте. А дальше я начал приглашать в Постораму таких же авторов, как сам. Тех, у кого много публикаций, есть желание распространять их по разным площадкам и стремление к учёту всего этого дела. Народу набралось с гулькин нос. Пара друзей плюс несколько человек из моих подписчиков.
Но руки у меня не опустились, и я стал думать над тем, какие бы ещё функции включить в Постораму, чтобы она стала интереснее более широкой аудитории. Так появился раздел «Инструменты» с тремя заглушками: «Импорт и аналитика постов из Телеграм-канала», «Редактор постов с ИИ» и «Генератор тем для публикаций». Пользователи Посторамы заходили в этот раздел, тыкали по неработающим заглушкам и писали мне в Телегу: «В твоём проекте интересны инструменты, но у меня они чего-то не работают».
Тогда я решил продолжить работать над Посторамой. И выбрал в качестве основной функции «Импорт и аналитику постов из Телеграм-канала». Задумка была такая: можно взять Телеграм-канал, импортировать из него посты, а дальше система подсказала бы, на каких ещё платформах можно попробовать этот контент опубликовать.
Плюс сейчас-то никакого импорта нет, все публикации приходится добавлять ручками поштучно. У меня на скриншоте вы видите 48 свежих публикаций. А представляете, сколько времени я бы добавлял туда тысячу предыдущих?
В общем, дополнил я прототип в Акшуре новыми функциями (импорт постов из Телеграм-канала за период, формирование из него списка, создание перечня импортов и его ограничение ну и прочее по мелочам) и собирался отправлять это дело на оценку разработчикам.
Выглядели основные разделы так:


Но я не был уверен в своём прототипе. А дело в том, что я не знал ничего об ограничениях в работе с API Телеграма и проектировал довольно умозрительно. Я не понимал, как получать данные из закрытых каналов, сколько запросов в секунду можно отправлять, по какому принципу формируются альбомы и группы файлов и так далее и тому подобное.
Как я искал консультанта, который поможет с задачей
Мне нужен был человек с опытом парсинга постов из Телеграм-каналов. Буквально часовая консультация специалиста позволила бы мне сэкономить дни и недели времени. И я принялся его искать.
Все запросы вроде «парсер постов из Телеграма» вели в одно место. К человеку, который сделал парсер постов из Телеграма и написал об этом везде, где мог. Идея его парсера заключается в том, чтобы указывать чужие Телеграм-каналы и автоматически публиковать посты из них в свои. В общем, эдакий завод по воровству контента.
Были ещё несколько поверхностных обучающих статей от образовательных платформ, но они мне никак не помогали. В общем, написал я тому самому автору парсера. И наш диалог оказался настолько эпичным, что я просто без комментариев вставлю его сюда в виде скриншотов:


Особенно порадовал большой палец вверх после отказа.
В итоге поискал я ещё специалистов, да так никого и не нашёл.
Скинул прототип как есть своим разработчикам, они посмотрели на это дело и сделали оценку в пару сотен тысяч рублей и месяц работы.
Я уже потянулся было за кошельком, но затем вспомнил опыт своего предыдущего стартапа и решил, что вижу прекрасную возможность потратить 200к+, но не понимаю, как я их потом верну обратно. Дал по тормозам.
Разработчикам написал, что попробую протестировать гипотезу перед тем как вложусь в разработку. Поищу потенциальных клиентов.
Стал размышлять, как бы это провернуть. Одним из вариантов был такой: пойти в ТГстат, оплатить аккаунт на месяц и воспользоваться функцией экспорта публикаций. Да-да, в ТГстате такая есть. Но, к сожалению, экспортировать можно только за месяц. И чтобы получить перечень постов хотя бы за год, придётся делать двенадцать экспортов, а затем вручную сшивать их в большую табличку.
На сцене появляется ChatGPT. Как я разрабатывал первые версии парсера
И тут меня посетила гениальная идея…
Я включил vpn, открыл в браузере ChatGPT и написал:
Я хотел бы спарсить посты из чужого открытого телеграм-канала с помощью API и сформировать из них xls-табличку. Что для этого нужно сделать?
И получил пошаговый алгоритм. Как получить доступ к Telegram API, установить библиотеку Telethon, как написать скрипт для сбора данных на Питоне, как эти данные отформатировать и как запустить скрипт. ChatGPT мне даже сразу рассказал об ограничениях. Что, мол, неплохо бы ограничить частоту запросов, добавив задержку, и что для работы с закрытыми каналами я должен быть их участником.
Я обрадовался и пошёл по инструкции, задавая уточняющие вопросы. Для начала я спросил, как авторизоваться в Telegram Core (это нужно для получения доступа к API) и получил пошаговую инструкцию. Затем попросил показать код с учётом ограничения количества запросов до одного в секунду. И получил первую версию кода, которая занимала всего 30 строк!
Должен признаться, что Питон к этому моменту уже был установлен на моём компьютере. В начале лета 2024 я прошёл небольшой базовый курс по нему на Степике и в целом освежил знания о синтаксисе. Но даже если бы это было не так, то я всё равно справился бы с задачей, просто задавая немного больше дополнительных вопросов чату.
Мне понравилось, что в конце каждой инструкции нейросеть подбадривала меня фразой: «Если что-то пойдёт не так, напишите, помогу разобраться!».
Я ещё уточнил, как установить недостающие библиотеки (pandas и openpyxl) и запустил скрипт. Тут же получил ошибку. Сообщил о ней чату:
Запустил скрипт и вижу текст: Please enter your phone (or bot token). Что это значит? Где взять токен?
Получил инструкцию, выполнил её (здесь оговорюсь, что глубоко задумался, не стоит ли мне для этих своих экспериментов завести новый аккаунт в Телеграме, чтобы не наломать дров на своём основном, но в итоге, решив, что я правил нарушать не собираюсь, рискнул), но скрипт всё ещё не хотел работать. Снова адресовал вопрос чату:
Запустил скрипт. Получил такую ошибку: ValueError: Excel does not support datetimes with timezones. Please ensure that datetimes are timezone unaware before writing to Excel. Что делать?
Получил ответ. Понравилось, что ответ содержал причину ошибки, исправленную версию кода, перечень внесённых изменений и дополнительное объяснение, почему я столкнулся с таким недоразумением. Ну и снова подбадривающая фраза в конце.


Код заработал, на выходе получилась первая версия таблички с результатами парсинга. Я был счастлив!
Дальше процесс работы выглядел примерно так: я просил внедрить в код какие-то новые функции, тестировал их, получал ошибки, исправлял, двигался дальше.
С каждой новой итерацией код становился всё больше. И в какой-то момент я заметил, что часть функций, содержавшихся в прошлой версии кода, в новой исчезли. Я спросил у чата, что это было, он извинился и вернул пропажу, но во все последующие промпты я уже не забывал добавлять фразу «сохрани всю предыдущую функциональность». Это в целом помогло. Но доверие было утеряно навсегда. Я стал перепроверять объём кода (иногда — хоп! — и пропало 20-30 строк) и по-диагонали пробегался по всем его функциям.
В процессе работы над парсером я столкнулся с рядом интересных задач, о которых не задумывался в процессе создания прототипа.
Например, реакции. Я совсем забыл о том, что существуют платные реакции и их тоже можно учитывать в статистике. А также о том, что есть кастомные. И о том, что стандартные можно условно разделить на позитивные и негативные. В итоге, мой парсер позволяет указать в коде, какие реакции можно считать позитивными, а какие — негативными. Остальные он кидает в «неопределённые» и тоже учитывает их. Платные реакции живут в отдельном столбце. Это прикольно. Например, я могу взять какой-нибудь огромный канал-миллионник и за пару минут узнать, сколько платных звёздочек он всего получил к своим публикациям за весь 2024 год.
Или типы постов. Для моих целей нужно было учитывать только тексты, однако я добавил индикаторы: фото, опрос, альбом и т.п.
С альбомами отдельная история. Я долго думал, как считать статистику по альбомам. Альбомы — это несколько постов, сгруппированных в один. Например, если речь идёт о десяти фотографиях, то это будет десять разных постов, объединённых в альбом. У первой фотографии будет аннотация — это и станет текстом поста.
Вот ТГстат, например, как и сам Телеграм, считает просмотры альбомов как сумму просмотров каждой его составляющей. Таким образом альбом из десяти фотографий с тысячей просмотров, покажет нам десять тысяч просмотров. Что, по моему мнению, совершенно неверно. Поэтому я заложил такую логику: считаем только просмотры первого поста в альбоме. А вот все остальные данные: реакции, репосты, комментарии — суммируем. С учётом того, что чаще всего они сосредоточены всё в том же первом посте.
Чтобы вы лучше понимали, о чём идёт речь, взгляните на скриншот с примером таблички, которую я получаю в результате работы парсера:

Как видите, парсер также считает итоговые и средние значения, указывает период, за который производился парсинг, показывает сколько каких именно реакций получали те или иные посты.
Как парсер становился лучше, благодаря живым клиентам
Но, конечно, первые версии парсера выдавали не так много информации. И это очень показательный момент. Смотрите: я кинул клич в своём канале, что, мол, есть парсер, налетайте. И ко мне обратилось несколько человек. И с каждым новым обращением я сталкивался с какими-нибудь ошибками.
Например, не определялись кастомные реакции (я о них вообще не догадывался в тот момент). Я быстро, в течение получаса, исправлял ошибку — и выдавал улучшенный результат.
В другой раз выяснилось, что парсер не работает с закрытым каналом, даже когда я являюсь его участником. И снова немного работы — и проблема решена.
Затем мне попался канал, почти целиком состоявший из альбомов. И мне пришлось досконально разобраться с тем, как они устроены и как всё организовать, чтобы получаемые данные были правильными и понятными.
То есть я выдавал результат, получал обратную связь и почти мгновенно применял её на практике, улучшая код.
Если бы я это делал не сам, а с помощью разработчиков (без опыта работы с API Телеграма), представляете, во-первых, сколько времени ушло бы на первую версию кода, а, во-вторых, на итерационные доработки с учётом обратной связи от клиентов? В общем, в очередной раз позавидовал разработчикам, которые в состоянии делать проекты для себя без привлечения третьих лиц.
Как я пытался найти аудиторию для этого проекта
Приближался Новый 2025 год. И я задумался над тем, как бы привлечь новых желающих воспользоваться парсером. И решил, так сказать, «показать товар лицом». Одна из задач, которые можно решать с помощью таблички — формировать список топа публикаций за период и подводить некие итоги. А Новый год — как раз хороший повод. Я выбрал пару каналов, которые читаю сам, проанализировал их и подготовил для них посты с итогами года. Одним из них оказался канал Паши Молянова. И он отреагировал на мою инициативу на удивление позитивно:

В публикации сохранилась ссылка на мой канал, поэтому я в итоге получил пару десятков новых интересных подписчиков, а также пару обращений в личку с просьбой спарсить и их каналы.
Обратите внимание: в конце поста я пишу, каким известным произведениям соответствует суммарный объём текстов за год. Получилось интересно! Разумеется, эти данные я также получил с помощью ChatGPT.
В комментариях мои данные сравнили с данными, предоставляемыми ТГстатом, и я уверился в том, что мои всё-таки актуальнее. Отдам должное ТГстату: его аналитика учитывает также все удалённые посты, в том числе и рекламные. Мой парсер, разумеется, такого не умеет, так как получает данные, доступные по API на момент запроса.
На самом деле эту статью я начал писать ещё в конце 2024 года, а дописываю только сейчас. Вот с этого места. За последние полгода многое поменялось в мире нейронок, а у меня появилось много нового опыта: я сейчас поднимаю собственный проект с нуля с помощью вайб-кодинга (хотя по сути не такой он и вайб, я тупо быстро учусь программировать и деплоить, собирая шишки и грабли).
Поэтому историю с парсером продолжаю ретроспективно.
После того, как я спарсил на заказ ещё несколько чужих каналов, я постепенно начал терять интерес к этому направлению. Я понял, что услуга парсинга сама по себе стоит очень недорого — и если не делать из этого автоматизированный сервис — я замучаюсь обрабатывать клиентов с микротранзакциями. А автоматизированный сервис тоже не перспективен — потому что стоимость привлечения клиента будет больше его LTV, а также всегда есть риск того, что через год API Телеграма изменится достаточно сильно, чтобы у меня всё сломалось.
В начале 2025 появился DeepSeek. И с ним тоже есть забавная история. Мой парсер был целиком написан с помощью чата-гпт. И к последним версиям отрабатывал всё менее стабильно. Уж больно долго он собирал и переваривал публикации. В общем, я взял итоговый скрипт и кормил его DeepSeek с комментарием типа «что-то тормозит всё и не совсем верно считает — поправишь?». Как вы догадались, комментарий был, конечно, более развёрнутый и по пунктам. Но суть одна — DeepSeek поправил. Да так, что всё стало летать, а в достоверности данных теперь не стоит сомневаться (я всё ручками перепроверил на паре парсингов).
Мысли и выводы по вайб-кодингу, итоговый код и инструкция
Это был замечательный опыт, который мне подарили технологии. Я бы не смог его получить иным способом (с людьми подобный опыт растягивается на годы для решения тех же задач);
Моё умение работать с контекстом оказалось самым ценным навыком при работе с ChatGPT. Проектируя интерфейсы последние 20 лет, я хорошо разобрался, как вводить в контекст людей (команда, клиенты, пользователи), чтобы мы были с ними на одной волне. С чатом это работало абсолютно так же. Нужно напоминать, возвращать в русло беседы, подтверждать верные предположения и отрицать неверные. Нужно не лениться и писать больше вводных. И формулировать их так, чтобы любой разобрался;
Иногда нейросеть начинает ходить по кругу, не справляясь с решением проблемы. Не нужно ей в этом потакать. Останавливаемся, заново обозначаем вводные, но другими словами. Заново сообщаем, что хотим получить на выходе и почему. Это похоже на то, когда дизайнер начинает костылями как-то спасать своё не очень удачное решение вместо того, чтобы откатиться назад и переделать его с нуля;
Я не могу отказаться писать «пожалуйста», «спасибо» и прочие ненужные слова, которые лишь тратят программные ресурсы. Это прям отдельная тема для философских рассуждений;
За полгода мои навыки общения с другими людьми стали лучше благодаря общению с чатом. Я стал менее токсичен в своих высказываниях. Я получил несколько наглядных примеров, как другой стиль общения начинает располагать других людей к себе, даже если изначально они были настроены против тебя;
Я теперь вижу, что ИИ не может отнять работу вообще ни у кого (даже у тех, чью работу он, казалось бы, может почти полностью автоматизировать). А также понимаю, что результат работы ИИ, с которым я взаимодействую — это результат моей работы. Раньше я в этом сомневался.
Теперь я хочу поделиться с вами получившимся кодом скрипта. Это финальная версия, над которой мы вместе с чатом и обратной связью от клиентов поработали в течение трёх недель. Мой друг фулстэк-программист сказал, что это ужасный код, но мне всё равно, т.к. этот код прекрасно справляется со своей задачей, а до каких-то серьёзных оптимизаций я ещё не дорос:
from telethon.sync import TelegramClient
import pandas as pd
import time
from telethon.tl.types import ReactionEmoji, ReactionPaid, ReactionCustomEmoji, MessageMediaPhoto
from openpyxl import load_workbook
from openpyxl.styles import PatternFill, Font, Alignment
from datetime import datetime
# Конфигурация
api_id = "ХХХХХХХХХ"
api_hash = "ХХХХХХХХХХХХ"
channel_username = "ХХХХХХХХХ"
start_date = datetime(2025, 1, 1)
end_date = datetime(2025, 12, 31, 23, 59, 59)
POSITIVE_EMOJIS = {'?', '❤', '?', '?', '?', '?', '?', '⚡', '❤?', '?', '?', '?', '?', '?', '?', '?', '?'}
NEGATIVE_EMOJIS = {'?', '?', '?', '?'}
def process_reactions(reactions):
"""Обрабатывает реакции и возвращает списки положительных, отрицательных, нейтральных и платных реакций."""
positive = []
negative = []
neutral = []
paid = []
total = 0
if reactions:
for reaction in reactions.results:
if isinstance(reaction.reaction, ReactionEmoji):
emoticon = reaction.reaction.emoticon
if emoticon in POSITIVE_EMOJIS:
positive.append(f"{emoticon} {reaction.count}")
elif emoticon in NEGATIVE_EMOJIS:
negative.append(f"{emoticon} {reaction.count}")
else:
neutral.append(f"{emoticon} {reaction.count}")
elif isinstance(reaction.reaction, ReactionPaid):
paid.append(f"{reaction.count}")
elif isinstance(reaction.reaction, ReactionCustomEmoji):
neutral.append(f"? {reaction.count}")
total = sum(reaction.count for reaction in reactions.results)
return positive, negative, neutral, paid, total
def calculate_engagement_rate(views, reactions, comments, forwards, paid_reactions):
"""Вычисляет Engagement Rate (ER%) с учётом платных реакций."""
if views > 0:
total_engagement = reactions + comments + forwards + paid_reactions
return round(total_engagement / views * 100, 2)
return 0
def save_to_excel(df, filename, channel_username, start_date, end_date):
"""Сохраняет DataFrame в Excel-файл и добавляет строку с информацией."""
# Перемещаем строки «Итого» и «Среднее» в начало таблицы
summary_row = df[df['Дата'] == 'Итого']
average_row = df[df['Дата'] == 'Среднее']
df = df[~df['Дата'].isin(['Итого', 'Среднее'])] # Убираем строки «Итого» и «Среднее» из основного DataFrame
df = pd.concat([summary_row, average_row, df], ignore_index=True) # Вставляем их в начало
df.to_excel(filename, index=False)
# Загружаем книгу и лист
wb = load_workbook(filename)
ws = wb.active
# Добавляем строку с информацией в начало
info_text = f"Данные из канала {channel_username} (https://t.me/{channel_username}) за период с {start_date.strftime('%Y-%m-%d %H:%M:%S')} по {end_date.strftime('%Y-%m-%d %H:%M:%S')}"
ws.insert_rows(1) # Вставляем строку в начало
ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=len(df.columns)) # Объединяем ячейки
ws.cell(row=1, column=1, value=info_text) # Записываем текст
# Форматируем строку
fill = PatternFill(start_color="D9E1F2", end_color="D9E1F2", fill_type="solid") # Светло-голубой цвет
font = Font(bold=True, size=12) # Жирный шрифт, размер 12
alignment = Alignment(horizontal="center", vertical="center") # Выравнивание по центру
for cell in ws[1]: # Применяем стили к первой строке
cell.fill = fill
cell.font = font
cell.alignment = alignment
# Задаём ширину столбцов
column_widths = {
'A': 10, # Ширина столбца A (например, "Тип")
'B': 20, # Ширина столбца B (например, "Дата")
'C': 30, # Ширина столбца C (например, "Текст")
'D': 8, # Ширина столбца D (например, "Длина")
'E': 15, # Ширина столбца E (например, "Ссылка")
'F': 13, # Ширина столбца F (например, "Просмотры")
'G': 11, # Ширина столбца G (например, "Реакции")
'H': 17, # Ширина столбца H (например, "Позитивные")
'I': 11, # Ширина столбца I (например, "Всего (+)")
'J': 17, # Ширина столбца J (например, "Негативные")
'K': 11, # Ширина столбца K (например, "Всего (-)")
'L': 20, # Ширина столбца L (например, "Неопределённые")
'M': 11, # Ширина столбца M (например, "Всего")
'N': 13, # Ширина столбца N (например, "Платные")
'O': 14, # Ширина столбца O (например, "Комменты")
'P': 15, # Ширина столбца P (например, "Пересылки")
'Q': 7, # Ширина столбца Q (например, "ER%")
}
# Применяем ширину столбцов
for col, width in column_widths.items():
ws.column_dimensions[col].width = width
# Закрашиваем первую строку (заголовки)
header_fill = PatternFill(start_color="F2F2F2", end_color="F2F2F2", fill_type="solid") # Серый цвет
for cell in ws[2]: # Вторая строка (после добавленной строки с информацией)
cell.fill = header_fill
# Закрашиваем итоговые строки
summary_fill = PatternFill(start_color="66FF66", end_color="66FF66", fill_type="solid") # Зелёный цвет
average_fill = PatternFill(start_color="66FF66", end_color="66FF66", fill_type="solid") # Зелёный цвет
# Итоговая строка (теперь она на третьей строке)
for cell in ws[3]: # Третья строка
cell.fill = summary_fill
# Строка со средними значениями (теперь она на четвёртой строке)
for cell in ws[4]: # Четвёртая строка
cell.fill = average_fill
# Замораживаем первые четыре строки (информация, заголовки, итог, среднее)
ws.freeze_panes = 'A5' # Теперь закрепляем строки с информацией, заголовками, итогом и средним
# Сохраняем книгу
wb.save(filename)
def main():
"""Основная функция для сбора и обработки данных."""
with TelegramClient('session_name', api_id, api_hash) as client:
messages = []
albums = {}
for message in client.iter_messages(channel_username):
message_date_naive = message.date.replace(tzinfo=None)
# Фильтр по дате
if message_date_naive < start_date:
break
if message_date_naive > end_date:
continue
# Обработка альбомов
grouped_id = getattr(message, 'grouped_id', None)
if grouped_id:
if grouped_id not in albums:
albums[grouped_id] = []
albums[grouped_id].append(message)
else:
# Обработка одиночных сообщений
post_type = 'Опрос' if message.poll else 'Текст' if message.text else 'Фото' if isinstance(message.media, MessageMediaPhoto) else 'Другое'
message_link = f"https://t.me/{channel_username}/{message.id}" if channel_username else 'Нет ссылки'
positive, negative, neutral, paid, total_reactions = process_reactions(message.reactions)
views = message.views if message.views else 0
comments = message.replies.replies if message.replies else 0
forwards = message.forwards if message.forwards else 0
# Подсчёт платных реакций
paid_reactions = sum(int(p) for p in paid)
er_percentage = calculate_engagement_rate(views, total_reactions, comments, forwards, paid_reactions)
messages.append({
'Тип': post_type,
'Дата': message_date_naive,
'Текст': message.text or '',
'Длина': len(message.text or ''),
'Ссылка': message_link,
'Просмотры': views,
'Реакции': total_reactions,
'Позитивные': ' '.join(positive),
'Всего (+)': sum(int(r.split(' ')[1]) for r in positive),
'Негативные': ' '.join(negative),
'Всего (-)': sum(int(r.split(' ')[1]) for r in negative),
'Неопределённые': ' '.join(neutral),
'Всего': sum(int(r.split(' ')[1]) for r in neutral),
'Платные': ' '.join(paid),
'Комменты': comments,
'Пересылки': forwards,
'ER%': er_percentage
})
# Задержка для соблюдения ограничений API
time.sleep(0.1)
# Обработка альбомов
for grouped_id, album_messages in albums.items():
first_message = album_messages[0]
views = first_message.views if first_message.views else 0
# Суммируем реакции из всех сообщений в альбоме
positive = []
negative = []
neutral = []
paid = []
total_reactions = 0
paid_reactions_total = 0 # Общее количество платных реакций в альбоме
for message in album_messages:
p, n, neu, p_paid, total = process_reactions(message.reactions)
positive.extend(p)
negative.extend(n)
neutral.extend(neu)
paid.extend(p_paid)
total_reactions += total
paid_reactions_total += sum(int(p) for p in p_paid) # Суммируем платные реакции
comments = sum(m.replies.replies if m.replies else 0 for m in album_messages)
forwards = sum(m.forwards if m.forwards else 0 for m in album_messages)
er_percentage = calculate_engagement_rate(views, total_reactions, comments, forwards, paid_reactions_total)
messages.append({
'Тип': 'Альбом',
'Дата': first_message.date.replace(tzinfo=None),
'Текст': ' '.join(m.text or '' for m in album_messages),
'Длина': sum(len(m.text or '') for m in album_messages),
'Ссылка': f"https://t.me/{channel_username}/{first_message.id}" if channel_username else 'Нет ссылки',
'Просмотры': views,
'Реакции': total_reactions,
'Позитивные': ' '.join(positive),
'Всего (+)': sum(int(r.split(' ')[1]) for r in positive),
'Негативные': ' '.join(negative),
'Всего (-)': sum(int(r.split(' ')[1]) for r in negative),
'Неопределённые': ' '.join(neutral),
'Всего': sum(int(r.split(' ')[1]) for r in neutral),
'Платные': ' '.join(paid),
'Комменты': comments,
'Пересылки': forwards,
'ER%': er_percentage
})
# Создаём DataFrame
df = pd.DataFrame(messages)
df = df.sort_values(by='Дата', ascending=False).reset_index(drop=True)
# Убедимся, что числовые столбцы имеют правильный тип данных
numeric_columns = ['Просмотры', 'Реакции', 'Всего (+)', 'Всего (-)', 'Всего', 'Платные', 'Комменты', 'Пересылки', 'ER%']
for col in numeric_columns:
df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0)
# Подсчёт итоговых значений
summary = {
'Тип': '', # Пустое значение для столбца «Тип»
'Дата': 'Итого',
'Текст': f'Количество постов: {len(df)}', # Исправлено: добавляем текст
'Длина': df['Длина'].sum(),
'Ссылка': '',
'Просмотры': df['Просмотры'].sum(),
'Реакции': df['Реакции'].sum(),
'Позитивные': '',
'Всего (+)': df['Всего (+)'].sum(),
'Негативные': '',
'Всего (-)': df['Всего (-)'].sum(),
'Неопределённые': '',
'Всего': df['Всего'].sum(),
'Платные': df['Платные'].sum(),
'Комменты': df['Комменты'].sum(),
'Пересылки': df['Пересылки'].sum(),
'ER%': ''
}
# Подсчёт средних значений
average = {
'Тип': '', # Пустое значение для столбца «Тип»
'Дата': 'В среднем на пост',
'Текст': '',
'Длина': round(df[df['Длина'] > 0]['Длина'].mean(), 2),
'Ссылка': '',
'Просмотры': round(df['Просмотры'].mean(), 2),
'Реакции': round(df['Реакции'].mean(), 2),
'Позитивные': '',
'Всего (+)': round(df['Всего (+)'].mean(), 2),
'Негативные': '',
'Всего (-)': round(df['Всего (-)'].mean(), 2),
'Неопределённые': '',
'Всего': round(df['Всего'].mean(), 2),
'Платные': round(df['Платные'].mean(), 2),
'Комменты': round(df['Комменты'].mean(), 2),
'Пересылки': round(df['Пересылки'].mean(), 2),
'ER%': round(df['ER%'].mean(), 2)
}
# Получаем порядок столбцов из основного DataFrame
columns_order = df.columns
# Создаём DataFrame для «Итого» и «Среднее» с сохранением порядка столбцов
summary_df = pd.DataFrame([summary], columns=columns_order)
average_df = pd.DataFrame([average], columns=columns_order)
# Добавляем итоговые и средние значения в начало DataFrame
df = pd.concat([summary_df, average_df, df], ignore_index=True)
# Сохранение в Excel
save_to_excel(df, 'ХХХХХХХХХ.xlsx', channel_username, start_date, end_date)
if __name__ == "__main__":
main()
Всего 300 строк кода (а начиналось всё с 30).
Несколько слов для тех, кто попытается с этим разобраться.
Это скрипт на Питоне. Его можно запустить у себя на компе через терминал. Если что, я сам использую VS Code в качестве IDE. Разумеется, предварительно придётся установить Питон и пару библиотек. ChatGPT поможет вам с этим, если попросите;
Для работы со скриптом вам понадобится завести себе аккаунт разработчика в Телеграме. Тоже спросите, как это делать, у чата. От аккаунта вам понадобятся данные: api_id и api_hash;
В самом верху скрипта (9 строка) есть раздел #Конфигурация. Именно туда надо вставлять айди, хэш, название Телеграм-канала (без всяких ссылок и слэшей, только его уникальную адресную часть), указать даты для начала и конца парсинга. Можно также настроить, какие именно иконки парсер будет считать позитивными, негативными или нейтральными;
В самом конце скрипта (297 строка) — настройки сохранения в эксель. Нужно будет подставить название файла вместо иксов.
Если бы я прямо сейчас, прочитав эту статью, не знал, что делать и как запустить такой скрипт, я бы пошёл в ChatGPT (или DeepSeek) и написал такой промпт: «Хочу запустить этот скрипт на своём компе, чтобы спарсить данные с такого-то Телеграм-канала. У меня нет знаний в программировании, нет IDE, не установлен Питон и необходимые библиотеки к нему, нет аккаунта разработчика в Телеграме. С учётом этого, помоги мне запустить скрипт». В тот же промпт я бы запульнул весь код — и дальше действовал бы по инструкции от нейросети.
Ещё важный момент. Код, которым я с вами делюсь, работает только с открытыми каналами. Кодом для работы с закрытыми каналами я не хочу так открыто делиться, потому что все мы прекрасно понимаем, для чего его сразу начнут использовать (ведь каналы не так просто закрывают от широкой публики).
Буду рад вашим вопросам в комментариях.
Полезные ссылки:
Мой Телеграм-канал о проектировании интерфейсов и работе на фрилансе;
Моя книга нормального фрилансера (ребята, она бесплатная);
Посторама (система учёта контента, с которой я начал рассказ в статье). Нас тут полтора пользователя, которые достаточно системно подходят к контенту, чтобы этот сервис был действительно полезен. Если вы тоже из таких — стучитесь. Открытой регистрации нет. И это тоже бесплатно.
Комментарии (4)
Grav
18.07.2025 11:50Я вот сделал итоги прошлого года для своего канала UX Notes. С помощью этого скрипта — за пару минут. За год вышло 134 поста. Я бы запарился самостоятельно выписывать их параметры в табличку, чтобы посчитать количество просмотров, реакций, пересылок и так далее, найти самый просматриваемый, залайканный, закомментированный пост. Что-то похожее делают сервисы типа TGStat, но у них своя логика построения годовых итогов, свои разрезы статистики и кое-каких параметров (вроде количества символов в публикациях) просто нет
definitelyRealPerson
Автор явно переоценивает удобство своего инструмента. Зачем заморачиваться с анализом постов, если проще самому посидеть и подумать головой? Нейросети — не серебряная пуля, а инструмент, требующий понимания и контроля. Лучше тратить время на творчество, чем пытаться автоматизировать очевидное.
Ralory
Ну речь скорее о сборе статистики