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

Основано на лабораторке 2022 года (мне слишком нравился предмет, так что пора применить знания). Профессиональные аналитики: лучше закройте глаза.
Сбор текстовых данных
Сбор текстовых данных является важным этапом в процессе анализа и обработки естественного языка. Эти данные могут быть получены из различных источников, таких как социальные сети, блоги, новостные статьи, отзывы пользователей и многие другие.
Парсинг
Для парсинга текстов постов из Telegram-канала в Google Colab с использованием Python, использовала библиотеку Telethon
. Она позволяет взаимодействовать с Telegram API как обычный пользователь, что дает возможность читать сообщения из каналов (в том числе приватных, если ваш аккаунт имеет к ним доступ).
Для работы с Telegram API вам потребуются api_id
и api_hash
.
-
Перейдите на сайт my.telegram.org.
-
Войдите в свой аккаунт Telegram.
-
Нажмите «API development tools».
-
Создайте новое приложение (любое название и описание подойдут).
-
Вы получите
App api_id
иApp api_hash
. Скопируйте их.
Код для подключения к Google Drive и Telegram
import os from google.colab import drive drive.mount('/content/drive') session_path = '/content/drive/MyDrive/telethon_sessions/' os.makedirs(session_path, exist_ok=True) session_name = 'my_telegram_session' session_file_path = os.path.join(session_path, session_name) print(f"Файл сессии будет сохранен по пути: {session_file_path}.session")
!pip install telethon from telethon.sync import TelegramClient from telethon.tl.types import Channel # !!! ЗАМЕНИТЕ НА ВАШИ API ID и API Hash !!! api_id = 0 api_hash = '' client = TelegramClient(session_file_path, api_id, api_hash) try: await client.start() except Exception as e: if not client.is_user_authorized(): phone_number = input('Введите ваш номер телефона (с кодом страны, например +79XXXXXXXXX): ') await client.start(phone=phone_number) print(f"Авторизация успешна как: {(await client.get_me()).first_name} {(await client.get_me()).last_name}") # client.disconnect() # Отключение после парсинга
Так как постов много и каждый раз устанавливать сессию затратно, то запишем результаты парсинга в файл и сохраним на диске. Также можно поставить лимит на количество читаемых постов. Таким образом, у нас получится файл с данными для дальнейшего анализа.
import json async def get_channel_posts_text(channel_identifier): posts_text = [] try: entity = await client.get_entity(channel_identifier) if not isinstance(entity, (Channel)): return [] async for message in client.iter_messages(entity, limit=None): if message.text: posts_text.append(message.text) print(f"Всего получено {len(posts_text)} текстовых постов из канала '{entity.title}'.") return posts_text except Exception as e: return [] channel_username = '@m0rtymerr_channel' parsed_posts = await get_channel_posts_text(channel_username) if parsed_posts: safe_channel_name = channel_username.replace('@', '') output_file_name = f"{safe_channel_name}_posts.json" output_file_path = os.path.join(session_path, output_file_name) with open(output_file_path, 'w', encoding='utf-8') as f: json.dump(parsed_posts, f, ensure_ascii=False, indent=4)
Добавлю код для чтение из файла:
Код
!pip install numpy==1.26.4 'pandas<1.8' # ПЕРЕЗАПУСТИТЬ import pandas as pd import re import numpy as np from matplotlib import pyplot as plt channel_username = '@m0rtymerr_channel' safe_channel_name = channel_username.replace('@', '') output_file_name = f"{safe_channel_name}_posts.json" output_file_path = os.path.join(session_path, output_file_name) df=pd.read_json(output_file_path) df.rename(columns={0:'text'}, inplace = True ) df.head()
Предобработка собранных данных
Предобработка данных — это ключевой этап, который помогает подготовить текстовую информацию к дальнейшему анализу. Этот процесс включает несколько шагов, таких как:
-
приведение к нижнему регистру
-
удаление эмодзи, цифр, знаков препинания
-
удаление стоп-слов
-
для анализа русскоязычного сообщества можно удалить англицизмы
Отдельно стоит отметить токенизацию и лемматизацию. Вместе они помогают структурировать и упрощать данные, что является необходимым для успешного выполнения множества задач в области обработки естественного языка, таких как анализ тональности, тематическое моделирование, машинный перевод и многие другие.
Токенизация — это процесс разделения текста на отдельные элементы, называемые токенами. Токены могут быть словами, фразами или символами, в зависимости от задачи анализа. Этот шаг необходим для дальнейшей обработки текста и позволяет работать с отдельными единицами информации.
Лемматизация — это процесс приведения слов к их базовой форме (лемме). Например, слова «бегу», «бежит» и «бегал» будут приведены к лемме «бежать». Лемматизация помогает уменьшить количество уникальных слов в тексте и улучшает качество анализа, так как разные формы одного и того же слова будут рассматриваться как одно и то же слово.

Код для предобработки данных
Нижний регистр
df["tokens"] = df.text.apply(str.lower)
Удаление эмоджи
!pip install emoji import emoji df['tokens'] = df['tokens'].apply(lambda s: emoji.replace_emoji(s, ''))
Удаление цифр
df['tokens'] = df['tokens'].apply(lambda s: re.sub(r"\d+", "", s, flags=re.UNICODE))
Токенизация
!pip install razdel from razdel import tokenize def get_tokens(sentence): return [_.text.strip() for _ in tokenize(sentence)] df["tokens"] = df.tokens.apply(get_tokens)
Удаление пунктуации
import string df["tokens"] = df.tokens.apply(lambda row: [token for token in row if token not in string.punctuation + string.digits + '...'+'—'+'»'+'«'+"–" + '**' + '⁃' + '=)'+'•'+'-']) df["tokens"] = df["tokens"].apply(lambda row: [token for token in row if not re.match(r'^-+$', token)])
Удаление английских слов
df['tokens'] = df['tokens'].apply( lambda row: [ token for token in row if token == "it" or not re.search(r"[a-zA-Z]+", token)])
Лемматизация
!pip install pymorphy3 pymorphy3-dicts-ru pymorphy2 import pymorphy2 from tqdm.notebook import tqdm # ну... бывает def pymorphy2_311_hotfix(): from inspect import getfullargspec from pymorphy2.units.base import BaseAnalyzerUnit def _get_param_names_311(klass): if klass.__init__ is object.__init__: return [] args = getfullargspec(klass.__init__).args return sorted(args[1:]) setattr(BaseAnalyzerUnit, '_get_param_names', _get_param_names_311) pymorphy2_311_hotfix() analyzer = pymorphy2.MorphAnalyzer() df["tokens"] = tqdm(df.tokens.apply(lambda row: [analyzer.parse(token)[0].normal_form for token in row if token]))
Удаление стоп слов
import nltk from nltk.corpus import stopwords nltk.download('stopwords') nltk.download('punkt') additional_stops=["это", "который", "наш", "мочь", "год","такой", "мы", "свой", "один", "другой", "человек", "всё", "все", "весь", "очень", "каждый", "день", "её", "ваш", "ваше", "день", "самый", "ещё","также", "нужно","например", "вещь", "хороший", 'новый', "спасибо", 'твой', 'любой','что-то','че','какой-то','какой-то', 'привет', 'час', 'месяц','неделя','сегодня'] om_stops=['сообщество', 'ом', 'антон','назаров'] stops = list(string.ascii_lowercase) + list('абвгдеёжзийклмнопрстуфхцчшщъыьэюя') + stopwords.words("russian") + om_stops + additional_stops df["tokens"] = df.tokens.apply(lambda row: [token for token in row if token not in stops])
Для дальнейшего использования
all_words = [] for doc in df.tokens.tolist(): all_words.extend(doc)
Тематическое моделирование
Тематическое моделирование — это важный подход в NLP, который позволяет выявлять скрытые структуры в больших объемах текстовых данных. С его помощью можно автоматически идентифицировать темы, которые присутствуют в текстах, что особенно полезно для анализа больших коллекций документов, таких как статьи, блоги, отзывы и другие источники информации.
Одним из наиболее распространенных методов тематического моделирования является латентное размещение Дирихле (LDA). Этот алгоритм основывается на предположении, что каждый документ может быть представлен как смесь нескольких тем, а каждая тема характеризуется распределением слов. LDA позволяет исследователям и аналитикам не только находить основные темы в текстах, но и оценивать их значимость и взаимосвязи.
Также можно использование метрики согласованности для оценки качества обнаруженных тем. Будем варьировать количество тем и анализировать, как это влияет на согласованность модели.

Если согласованность повышается или понижается с увеличением количества тем, это может свидетельствовать о наличии корреляции между этими двумя переменными. Например, это может означать, что с увеличением количества тем становится сложнее поддерживать согласованность между ними. Здесь же корреляция не сильно заметна.
Полный код с построением графика
import gensim.corpora as corpora from gensim.models import LdaMulticore, CoherenceModel from tqdm import tqdm import warnings import matplotlib.pyplot as plt id2word = corpora.Dictionary(df.tokens.tolist()) texts = df.tokens.tolist() corpus = [id2word.doc2bow(text) for text in texts] warnings.filterwarnings("ignore") def compute_coherence_values(dictionary, corpus, texts, limit, start=2, step=3): coherence_values = [] model_list = [] for num_topics in tqdm(range(start, limit, step)): model=LdaMulticore(corpus=corpus,id2word=dictionary, num_topics=num_topics) model_list.append(model) coherencemodel = CoherenceModel(model=model, texts=texts, dictionary=dictionary, coherence='c_v') coherence_values.append(coherencemodel.get_coherence()) return model_list, coherence_values model_list, coherence_values = compute_coherence_values(dictionary=id2word, corpus=corpus, texts=texts, start=2, limit=20, step=1) limit=20; start=2; step=1; x = range(start, limit, step) plt.figure(facecolor='black') ax = plt.gca() ax.set_facecolor('black') plt.plot(x, coherence_values, color='#DEB040') plt.xlabel("Количество тем", color='white') plt.ylabel("Согласованность", color='white') plt.tick_params(labelcolor='white') plt.grid(color='white', linestyle='--', linewidth=0.5) plt.title("График согласованности", color='white') plt.show()
Когда определились с количеством тем, обучаем модель LDA на нашем корпусе текстов и затем используем pyLDAvis для подготовки интерактивной визуализации тем. Эта визуализация позволяет исследовать темы, их ключевые слова и взаимосвязи между ними, что значительно упрощает интерпретацию результатов тематического моделирования.
lda_model = LdaMulticore(corpus=corpus, id2word=id2word, num_topics=n_topics) pyLDAvis.enable_notebook() LDAvis_prepared = pyLDAvis.gensim.prepare(lda_model, corpus, id2word) LDAvis_prepared

На первый взгляд очень много одинаковых слов в темах, но на интерактивном графике (тут только картинка), можно посмотреть отдельно на слова и узнать в каких темах они чаще используются. Можно исследовать, как темы связаны друг с другом. Есть ли темы, которые часто появляются вместе?
Потыкав на графике я видела темы собеседований, менторов, накрутку, выход роликов, интервью, всякое странное о жизни. Но в самом большом кружке очень много пересечений. Точный анализ не совсем получился.
Полный код с построением графика
!pip install matplotlib pyLDAvis==2.1.2 from gensim.models import LdaMulticore, CoherenceModel import pyLDAvis.gensim import pickle import pyLDAvis import os n_topics= 10 lda_model = LdaMulticore(corpus=corpus, id2word=id2word, num_topics=n_topics) pyLDAvis.enable_notebook() LDAvis_prepared = pyLDAvis.gensim.prepare(lda_model, corpus, id2word) LDAvis_prepared
Анализ тональности текста
Анализ тональности — это процесс определения эмоциональной окраски текста, который позволяет классифицировать его как положительный, отрицательный или нейтральный. В последние годы этот метод стал особенно актуален в условиях быстрого роста объемов текстовых данных, генерируемых пользователями в социальных сетях, отзывах, новостях и других источниках.
С помощью анализа тональности компании и исследователи могут извлекать ценные инсайты о чувствах потребителей, что способствует более эффективному принятию решений. Например, бренды могут отслеживать общественное мнение о своих продуктах, выявлять проблемы на ранних стадиях и адаптировать свои маркетинговые стратегии.
В 2022 году мы в универе использовали библиотеку dostoevsky. Однако в связи с изменениями в поддержке и доступности библиотеки, я решила попробовать библиотеку Transformers
от Hugging Face.
from transformers import pipeline sentiment_pipeline = pipeline("sentiment-analysis", model="blanchefort/rubert-base-cased-sentiment", tokenizer="blanchefort/rubert-base-cased-sentiment") sentiment_pipeline('Эта статья прекрасна!')

Можно построить график с подсчетом результатов. Нейтральных сообщений обычно гораздо больше, так что лучше посмотреть график без них. Отчетливо видим, что негативных сообщений больше позитивных.

Тут я решила проследить динамику изменения количества негативных\позитивных сообщений. Суммировала по 100 штук. Видно, что есть периоды, где наблюдаются скачки. Но сейчас агрессивный маркетинг количество отрицательных постов уменьшается.
Полный код с построением графика
from transformers import pipeline sentiment_pipeline = pipeline("sentiment-analysis", model="blanchefort/rubert-base-cased-sentiment", tokenizer="blanchefort/rubert-base-cased-sentiment") tokens = df.tokens.tolist() messages = [" ".join(inner_list) for inner_list in tokens] sentiment = [item['label'] for item in sentiment_pipeline(messages)] vis = pd.DataFrame(pd.Series(sentiment).value_counts()).reset_index() vis.columns = ['sentiment', 'counts'] vis=vis.sort_values(by="counts") vis_res=vis.drop([0]) # убираем нейтральные сообщения colors = ["#DEB040", "yellow"] plt.figure(facecolor='black') ax = plt.gca() ax.set_facecolor('black') plt.tick_params(labelcolor='white') plt.grid(color='white', linestyle='--', linewidth=0.5) plt.title("Анализ тональности (кол-во сообщений)", color='white') plt.barh(y=vis_res.sentiment, width=vis_res.counts, color=colors) plt.show()
Для второго графика
from matplotlib.ticker import FuncFormatter positive_counts = [] negative_counts = [] step = 100 for i in range(0, len(sent), step): inner_list = sent[i:i + step] positive_count = inner_list.count('POSITIVE')/len(inner_list)*100 negative_count = inner_list.count('NEGATIVE')/len(inner_list)*100 positive_counts.append(positive_count) negative_counts.append(negative_count) indices = np.arange(len(sent) / step) plt.figure(facecolor='black') ax = plt.gca() ax.set_facecolor('black') plt.tick_params(labelcolor='white') plt.title("Анализ тональности (подсчет по 100 сообщений)", color='white') ax.set_xticklabels([]) ax.yaxis.set_major_formatter(FuncFormatter(lambda x, pos: f'{int(x)}%')) plt.plot(indices, positive_counts, label='Positive', color='#DEB040') plt.plot(indices, negative_counts, label='Negative', color='yellow') plt.xticks(indices) plt.legend(facecolor='black', labelcolor='white') plt.show()
Частотный анализ
По своей сути, частотный анализ — это процесс подсчета количества вхождений (частоты) отдельных слов, фраз или символов в заданном текстовом корпусе. Этот, казалось бы, простой метод открывает двери для глубокого понимания структуры и содержания текста. Он позволяет быстро выявить ключевые слова, доминирующие темы, а также необычные или повторяющиеся паттерны, которые могут быть неочевидны при поверхностном чтении.
Частотный анализ служит отправной точкой для множества прикладных задач: от определения наиболее релевантных терминов для SEO-оптимизации до лингвистических исследований.
Для начала можно просто посмотреть самые популярные слова:
res = pd.DataFrame(all_words, columns=["terms"]) vis = pd.DataFrame(res.terms.value_counts()).reset_index() vis.columns = ['terms', 'counts'] vis=vis.sort_values(by="counts")[-10:] colors = ["#DEB040" for _ in range(9)]+["yellow"] plt.figure(facecolor='black') ax = plt.gca() ax.set_facecolor('black') plt.tick_params(labelcolor='white') plt.barh(y=vis.terms, width=vis.counts, color=colors) plt.grid(color='white', linestyle='--', linewidth=0.5) plt.title("Частотный анализ", color='white') plt.show()

Работа, вопрос, опыт, it, стая — то, о чем говорят в сообществе. Также нас учили делать акценты на графиках, так что можно выделить первое слово особенно ярко.
Также можно построить облака слов — это визуальное представление текстовых данных, где частота слов в тексте отображается их размером: чем чаще слово встречается, тем крупнее оно отображается. Несмотря на кажущуюся простоту, облака слов делают по нескольким причинам:
-
быстрый обзор и понимание основной темы
-
визуализация и привлекательность
-
доступность и простота для неспециалистов
-
сравнение текстов
-
инструмент для предварительного анализа
-
вовлечение аудитории

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

Код для облаков слов
!pip install wordcloud from collections import Counter from wordcloud import WordCloud from PIL import Image import requests from io import BytesIO import numpy as np import matplotlib.pyplot as plt words = dict(Counter(all_words)) url='/content/wolf.png' if os.path.exists(url): image_pil = Image.open(url) cloud_mask = np.array(image_pil) wc = WordCloud(background_color="black", max_words=200, mask=cloud_mask, colormap="Wistia_r") wc.generate_from_frequencies(words) plt.figure(figsize=(10, 10)) plt.imshow(wc, interpolation="bilinear") plt.axis("off") plt.show()
Облако слов по частям речи
def get_words(functors_pos): morph=pymorphy2.MorphAnalyzer() words=[] for word in all_words: if morph.parse(word)[0].tag.POS in functors_pos: words.append(word) words = dict(Counter(words)) return words noun = get_words({'NOUN'}) adjf = get_words({'ADJF', 'ADJS'}) verb = get_words({'VERB', 'INFN'}) url = "https://www.pinclipart.com/picdir/middle/560-5604427_money-bag-clip-art-png-download.png" response = requests.get(url) cloud_mask = np.array(Image.open(BytesIO(response.content))) wc = WordCloud(background_color="black", max_words=200, mask=cloud_mask, colormap="Wistia_r") wc.generate_from_frequencies(words) wc.generate_from_frequencies(verb) fig, axs = plt.subplots(nrows= 1 , ncols= 3, figsize=(20, 20)) axs[0].imshow(wc, interpolation="bilinear") axs[0].axis("off") wc.generate_from_frequencies(adjf) axs[1].imshow(wc, interpolation="bilinear") axs[1].axis("off") wc.generate_from_frequencies(noun) axs[2].imshow(wc, interpolation="bilinear") axs[2].axis("off")
Граф
Использование графов для анализа текстовых данных позволяет не только визуализировать связи между словами, но и выявлять скрытые паттерны в текстах. Такой подход может быть полезен в различных областях, включая лингвистику, маркетинг и социальные исследования.
В данном примере использовала библиотеку NetworkX
для создания графа, где узлы представляют собой слова, а рёбра — связи между ними. Работать с биграммами (парами соседних слов) из текстов. Будем считать, что между двумя словами есть связь, если они встретились в одном тексте. Будем использовать все части речи, но опять же, можно построить граф только с существительными, например.

В центре видим слова, которые уже видели в частотном анализе. Очень много связей у слов собес — собеседование, работать — работуа. Также есть ветки посвященные качеству видео, подписке. Много мелочи около слова вопрос. Отдельные темы выделенные уходящими ветками: истории успеха, зарплатный рост, комментарии, уровни подписок.
Код для построения графа
!pip install networkx">=2.5" from collections import Counter import networkx as nx import sys import matplotlib.pyplot as plt import matplotlib pairs = [] for doc in df.tokens.tolist(): if doc: b = list((nltk.bigrams(doc))) if b: pairs.extend(b) pairs = [tuple(sorted(pair)) for pair in pairs] word_pairs = dict(Counter(pairs)) word_pairs = [(pair[0], pair[1], val) for pair, val in word_pairs.items() if val > 5] G = nx.Graph() edges = word_pairs G.add_weighted_edges_from(edges) # Удаляем узлы с степенью менее 2 remove = [node for node, degree in dict(G.degree()).items() if degree < 2] G.remove_nodes_from(remove) # Удаляем рёбра, которые соединяют удаленные узлы remove_edge = [pair for pair in G.edges() if pair[0] in remove and pair[1] in remove] G.remove_edges_from(remove_edge) # Удаляем петли remove = [pair for pair in G.edges() if pair[0] == pair[1]] G.remove_edges_from(remove) node_sizes = [deg*50 for node, deg in dict(G.degree()).items()] # Строим график plt.figure(figsize=(40,40)) pos = nx.layout.spring_layout(G) edges, weights = zip(*nx.get_edge_attributes(G,'weight').items()) for key in pos: x, y = pos[key] plt.text(x,y,key, ha="center", va="center", fontsize=6) nx.draw(G, pos, node_color='#DEB040', edgelist=list(G.edges()), edge_color=range(len(G.edges())), width=1.0, with_labels=False, edge_cmap=plt.cm.Blues, node_size=node_sizes) plt.show()
Граф с узлами цвета тем
def get_color(node): color_map = { 9: '#526DC0', 8: '#FFFACD', 7: '#FFB6C1', 6: '#98FF98', 5: '#FFDAB9', 4: '#E6E6FA', 3: '#FFE5B4', 2: '#F5F5DC', 1: '#D3D3D3', 0: '#ADD8E6' } for topic_index, color in color_map.items(): if node in topics[topic_index][1]: return color return '#FFFFF0' topics=lda_model.print_topics(num_words=100) color_map = [get_color(node) for node in G] plt.figure(figsize=(30,30)) edges, weights = zip(*nx.get_edge_attributes(G,'weight').items()) nx.draw(G, pos, edgelist=list(G.edges()), edge_color=range(len(G.edges())), width=1.0, with_labels=False, edge_cmap=plt.cm.Blues, node_size=node_sizes, node_color=color_map) for key in pos: x, y = pos[key] plt.text(x,y,key, ha="center", va="center", fontsize=6) plt.show()
Конец
P.S.
Я не упоминала ОМ всуе
ссылка на оригинал статьи https://habr.com/ru/articles/917344/
Добавить комментарий