ПУЛЬС - анализ телеграм канала своими руками

А чем вы анализируете свой канал (лучше в @P_U_L_S_bot)

Набрел в одном канале @ссылка, на такую простую идею, и решил отработать её. В первую очередь мою, больную до автоматизации всего, душонку тригернуло отсутствие автоматизации (я абсолютно понимаю что непрограммистам не до этого), во вторую очередь душное нутро всегда хотело делать хоть какую аналитику, а не вот эти ваши прокси гпт боты или витрины магазинов.

А еще и это главное, захотелось что нибудь создать под ключ и МГНОВЕННО, типо за день! Пускай не очень нужное, пускай MVP, но слово ВАРИАБЛ - капсом. Плюс - есть подозрение что в аудитории моего другого бота @ВЖУХ аудитория состоит из админов каналов в какой то степени, вдруг кого то заценит.

Очень хотелось сделать ПО опираясь только на функционал ботов, но к сожалению в данном случае это невозможно (зато возможно кое что другое неочевидное по сканированию каналов, об этом как нибудь расскажу в контексте другого готовящегося бота).

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

from pyrogram import Client api_id, api_hash = # получаем на https://my.telegram.org/auth app = Client("my_account", # любое имя - пирограм так назовет файл этой сессии api_id, api_hash) # далее в выводе консоли у вас спросят логин\код\пароль, никому не сообщайте их кроме пирограма.

Потом любой ценой но бесплатно получаем идентификатор нужного канала (никнейм тоже канает). И просто запрашиваем историю сообщений, но лучше с ограничителем:

async for message in app.get_chat_history("@y_bals", #ник канала или айди типо -12354235 limit=50): # если это рашатудей, без такого лимита вы получите итератор на 500_000 элементов... # тут пошла обработка print(message.id)

Вот и собсна казалось бы всё! Можно заполнять табличку -

  • просмотры = message.views
  • репосты = message.forwards
  • реакции = message.reactions

".. а где коменты.. а реакции как делить на лайки\дизлайки.. а еще посты с несколькими картинками такие странные.. " - короче есть нюансы. Пойдем по порядку

1. Коменты

В тг коменты существуют не очень интуитивно, потому что тг это не соц.сеть. Это месенжер. И всё тут строится вокруг чатов. По этому коменты к вашим постам это не более чем обычные сообщения в обычном групповом чате, который вы привязываете к каналу если захотите.

самый обычный канал в тг - переходим в чат коментов
самый обычный канал в тг - переходим в чат коментов

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

там про ферму-деревню речь
там про ферму-деревню речь

И уже ответы на него считаются на стороне клиентского приложения для отображения счетчика коментов под постом.

хорошая конверсия между прочим
хорошая конверсия между прочим

Вот и получается, что стату по коментам надо искать в совсем другой отдельной сущносте - чате. К счастью он очень просто находится - нужно получить обьект основного канала, у него будет специальное поле: linked_chat - если на канале доступны коменты, это поле будет содержать еще один обьект чата, но уже группового. Если коментов нет, в поле будет None

chat = await app.get_chat("@y_bals") if chat.linked_chat: print("этот админ не боится критики")

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

comments_count = await app.get_discussion_replies_count(message.chat.id, message.id)

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

chat = await app.get_chat("@y_bals") async for message in app.get_chat_history("@y_bals", #ник канала или айди типо -12354235 limit=500_000): # окей, грузим rt_russ if chat.linked_chat: # спрашиваем коменты у апи, только когда чат-обсуждения существует. comments_count = await app.get_discussion_replies_count(message.chat.id, message.id) else: comments_count = 0

2. Типы реакций.

Как же узнать, сколько лайков набрал пост, а сколько колунов.

никак

По крайней мере, то что телеграм у себя в системе знает какие реакты позитивные\негативные, нам никак не поможет - доступа к этому функционалу нет. Но интернет-гпт предложат вам несколько NLP либ которые помогут примерно узнавать настроение каждой реакции. Либо составлять собственную карту, как сделал я:

def classify_emoji(emoji_char): POSITIVE_EMOJI = {"😀", "😃", "😄", "😁", "😆", "😊", "😇", "😍", "🥰", "😘", "😋", "🥳", "🎉", "👍", "💖", "💕", "💗", "🌟", "☀️", "❤️",'❤', '🤣', '🍌', '👏', '🍾', '🔥', '💘', "💋", "🕊", "🏆", '🙏', "🤝", "❤‍🔥", "👌", '💯', "🤩", "😎" # "🦄" '🤓' } NEGATIVE_EMOJI = {"😞", "😟", "😔", "😕", "😖", "😫", "😩", "😡", "💩", "💊", "😠", "👎", "💔", "🤬", "🤯", "😤", "🤢", "🖕", "🤮", "😐", "🤨", '🥱', '🥴', '🤡', '🙉', "😭", "😢", "😱", "😨", "😰", } if emoji_char in POSITIVE_EMOJI: return LIKE elif emoji_char in NEGATIVE_EMOJI: return DISLIKE return NEUTRAL

Это конечно субьективно, можно спроить по поводу плаксивых реактов, а также 🦄. Но на общий случай и так сойдет

наш маскот
наш маскот

Смотрим сами реакты:

if message.reactions: for reaction in message.reactions.reactions: print(reaction.emoji) f_total_reaction += reaction.count

Далее опять нюанс. Код выше убьется лбом об первый кастомный реакт. Кастомные реакты не выражаются в UTF-8 байтах как обычные. По этому их не получится применить здесь например - движок сайта знает только UTF-8 стандартные эмодзи. У кастомных эмодзи и реактов в тг свои длинные айди. И в обьекте реакта (reaction) из кода выше поле .emoji будет None, зато в поле .custom_emoji_id будет чтото типо 5400149298513978905.

Класифицировать кастомные эмодзи по настроению - отдельное хобби. Я просто сделал словарь, и привожу их все к общепринятым аналогам:

# этот словарь необходимо расширять эмпирическим путем CUSTOM_EMOJI = { 5372886001465170842: "😍", 5400149298513978905: "😰" }
# Считаем реакции по типам q_total_reactions = {LIKE: 0, DISLIKE: 0, NEUTRAL: 0} if message.reactions: for reaction in message.reactions.reactions: f_total_reaction += reaction.count # далее либо получаем UTF-8 символ реакта, либо UTF-8 аналог кастома. q_emojy = reaction.emoji or CUSTOM_EMOJI.get(reaction.custom_emoji_id, "🤷‍♂️") # classify_emoji вернет LIKE или DISLIKE или NEUTRAL emoji_type = classify_emoji(q_emojy) q_total_reactions[emoji_type] += 1

таким образом по этому посту мы в словаре q_total_reactions получили количество лайков\дизлайков и непонятных реактов.

3. Галереи медиа

и видео галереи такие же
и видео галереи такие же

Этот нюанс вызван тем, что телеграм настолько простой на уровне нулей и единиц, что у одного поста\сообщения может быть только одно медиа - фото\видео\аудио. Если вы видите в ГУИе пост с несколькими фотками - это иллюзия. И метод get_chat_history, чей результат мы щас разбираем, эту иллюзию не поддерживает: на каждую галерею (пост с несколькими фотками) вы получите несколько обьектов message. Общее у них будет только поле message.media_group_id, по которому ГУИевые клиенты их и обьединяют в один визуальный пост. Но каждое из них это отдельное самодостаточное сообщение - со своим счетчиком просмотров (значения реально разные у каждого), репостов итп.

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

f_all_messages = [] async for message in app.get_chat_history("@y_bals", #ник канала или айди типо -12354235 limit=50): # если это рашатудей, без такого лимита вы получите итератор на 500_000 элементов... f_all_messages.append(message) f_last_mediagroup = None f_last_media_msg = None i = 0 while i < len(f_all_messages): message = f_all_messages[i] i += 1 # вычисляем участников галерей. if message.media_group_id and (f_last_mediagroup is None or f_last_mediagroup == message.media_group_id): # message будет равен f_last_media_msg только если мы шаг цикла вернули назад. До тех пор - нейтрализуем неугодного участника. if message != f_last_media_msg: f_last_mediagroup = message.media_group_id f_last_media_msg = message continue else: # message оказался равен f_last_media_msg, мы вернулись на последнего участника f_last_media_msg = None f_last_mediagroup = None else: # цикл вышел за пределы галереи - предыдущее сообщение было последним - возвращаем цикл на него. if f_last_media_msg: i -= 2 # Возвращаемся на 1 элемент назад continue # тут начинаем снимать показания статистики с обьекта message.

Ну а дальше делов то, плюнуть и растереть (на плечо): формируем из всех данных эксель табличку, наваливаем форматирования чучут, считаем ER по каждому посту. А еще заводим бота, пару хэндлеров на прием ссылки канала в разных форматах, бот вызывает эти методы сканирования, получает табличку, отправляет пользователю (возможно за деньги).
Кстати я всё сделал: @P_U_L_S_bot Но за два дня(

2
1 комментарий