Парсинг твитов и анализ социальных настроений
Обработка естественного языка (Natural Language Processing, NLP) - это действительно интересная и широкая область искусственного интеллекта. Здесь я собираюсь использовать ее для обработки текстовых записей и провести краткий курс по парсингу и анализу настроений. Для парсинга я использовал Selenium и tweepy, а для анализа настроений - классы и методы NLTK и модель наивного Байеса. Я постарался охватить большую часть шагов, которые следует выполнить при работе с текстовыми данными, и уверяю вас, что это будет стоящим занятием.
Итак, что такое парсинг и анализ настроений?
Парсинг - это процесс получения небольших фрагментов чего-либо. В нашем случае это веб-парсинг, поэтому мы получаем фрагменты информации, доступной на веб-сайте.
Анализ настроений - это процесс анализа мнений или взглядов людей на любую тему. Темой может быть что угодно: продукт, фильм, политический или социальный вопрос, технология, событие или какая-то тенденция.
Люди обычно предпочитают использовать социальные медиа для выражения своих мнений или взглядов, это может быть Facebook, Twitter, Quora или любой другой блог-сайт. В этом руководстве я собираюсь рассмотреть Twitter в качестве источника информации, а темой, которую я хотел бы выбрать, является «ИИ и глубокое обучение». Однако код, который я буду делить, будет полностью общим, поэтому вы также можете выбрать любую другую интересную тему.
Из заголовка и описания вы уже, наверное, поняли, что текстовые данные, необходимые для проведения анализа настроений, нужно получить с Twitter. Ниже приведены основные операции, которые я собираюсь выполнить:
- Парсинг твитов
- Определение настроений
- Предварительная обработка текста
- Извлечение признаков
- Построение модели
Если вы уже выполняли парсинг на Python, то, наверное, использовали "Requests" и "Beautiful Soup"; для тех, кто не слышал об этом раньше, Request - это библиотека Python для отправки HTTP-запросов, а Beautiful Soup - это парсер HTML для анализа DOM и получения нужной информации из него. Но мы не можем использовать эти библиотеки для парсинга твитов из Twitter из-за динамической и постепенной генерации твитов. Теперь у нас остается два варианта:
а). Selenium
б). Библиотека Python tweepy
Я покажу вам реализацию обоих, кстати, Selenium - это инструмент для имитации браузера, обычно используемый для тестирования веб-страниц, а tweepy, как я уже упоминал, это библиотека Python, которая предоставляет доступ к различным API Twitter.
а). Парсинг с использованием Selenium:
Предполагается, что вы уже импортировали numpy и pandas. Ниже приведен класс SeleniumClient, который будет выполнять парсинг:
from selenium import webdriver
from selenium.webdriver.common.keys import Keysclass **SeleniumClient**(object):
def __init__(self):
#_Метод инициализации._
self.chrome_options = webdriver.ChromeOptions()
self.chrome_options.add_argument('--headless')
self.chrome_options.add_argument('--no-sandbox')
self.chrome_options.add_argument('--disable-setuid-sandbox')
_# вам нужно указать путь к chromdriver в вашей системе_
self.browser = webdriver.Chrome('D:/chromedriver_win32/chromedriver', options=self.chrome_options)
self.base_url = 'https://twitter.com/search?q='
def get_tweets(self, query):
_''' _
_ Функция для получения твитов. _
_ '''_
try:
self.browser.get(self.base_url+query)
time.sleep(2)
body = self.browser.find_element_by_tag_name('body')
for _ **in** range(3000):
body.send_keys(Keys.PAGE_DOWN)
time.sleep(0.3)
timeline = self.browser.find_element_by_id('timeline')
tweet_nodes = timeline.find_elements_by_css_selector('.tweet-text')
return pd.DataFrame({'tweets': [tweet_node.text for tweet_node **in** tweet_nodes]})
except:
print("Selenium - Произошла ошибка при получении твитов.")
В коде выше вам нужно указать путь к драйверу браузера webdriver или вы можете просто установить переменную среды и не передавать параметры внутри webdriver.Chrome().
Вы можете использовать этот класс:
_selenium_client = SeleniumClient()_
_tweets_df = selenium_client.get_tweets('ИИ и глубокое обучение')_
В tweets_df вы получите фрейм данных, содержащий все полученные твиты.
б). Получение твитов с использованием tweepy:
Мы можем создать класс TwitterClient:
import tweepy
from tweepy import OAuthHandlerclass **TwitterClient**(object):
def __init__(self):
# Учетные данные доступа consumer_key = 'XXXX'
consumer_secret = 'XXXX'
access_token = 'XXXX'
access_token_secret = 'XXXX'try:
_# Объект OAuthHandler _
auth = OAuthHandler(consumer_key, consumer_secret)
_# установка токена доступа и секрета _
auth.set_access_token(access_token, access_token_secret)
_# создание объекта tweepy API для получения твитов _
self.api = tweepy.API(auth, wait_on_rate_limit=True, wait_on_rate_limit_notify=True)
except tweepy.TweepError as e:
print(f"Ошибка: Аутентификация в Twitter не удалась - **\n**{str(e)}")
# _Функция для получения твитов_
def get_tweets(self, query, maxTweets = 1000):
_# пустой список для хранения обработанных твитов _
tweets = []
sinceId = None
max_id = -1
tweetCount = 0
tweetsPerQry = 100
while tweetCount < maxTweets:
try:
if (max_id <= 0):
if (**not** sinceId):
new_tweets = self.api.search(q=query, count=tweetsPerQry)
else:
new_tweets = self.api.search(q=query, count=tweetsPerQry,
since_id=sinceId)
else:
if (**not** sinceId):
new_tweets = self.api.search(q=query, count=tweetsPerQry,
max_id=str(max_id - 1))
else:
new_tweets = self.api.search(q=query, count=tweetsPerQry,
max_id=str(max_id - 1),
since_id=sinceId)
if **not** new_tweets:
print("Больше твитов не найдено")
break
for tweet **in** new_tweets:
parsed_tweet = {}
parsed_tweet['tweets'] = tweet.text
_# добавление обработанного твита в список твитов _
if tweet.retweet_count > 0:
_# если твит имеет ретвиты, убедитесь, что он добавлен только один раз _
if parsed_tweet **not** **in** tweets:
tweets.append(parsed_tweet)
else:
tweets.append(parsed_tweet)
tweetCount += len(new_tweets)
print("Загружено **{0}** твитов".format(tweetCount))
max_id = new_tweets[-1].id
except tweepy.TweepError as e:
print("Ошибка Tweepy: " + str(e))
break
return pd.DataFrame(tweets)
В вышеуказанном коде нам нужны "Учетные данные доступа" для выполнения вызовов API. Их можно получить в консоли разработчика Twitter, вам просто нужно зарегистрировать свое приложение и предоставить все необходимые причины для получения доступа. Этот вызов можно использовать так же, как мы использовали SeleniumClient. В ответ мы получим фрейм данных, содержащий все полученные твиты.
Какой из них следует использовать?
Да, это очевидный вопрос. Ответ - tweepy, потому что он работает быстрее и надежнее. Однако, если у вас нет учетных данных доступа к API Twitter и вы не хотите ждать одобрения Twitter, то вы можете использовать SeleniumClient. Всегда полезно знать различные подходы к выполнению любой задачи.
2. Определение типа настроения
Таким образом, тип настроения - это общая реакция, она может быть положительной, отрицательной или нейтральной. В нашем случае мы собираемся рассматривать только положительные (включая нейтральные) и отрицательные.
Вопрос: Зачем нам определять тип настроения?
Потому что в конечном итоге мы будем обучать модель, которая должна быть способна классифицировать отрицательные и положительные настроения в твитах. Для этой классификации мы будем использовать некоторую модель обучения с учителем, поэтому нам нужно иметь целевую переменную. Тип настроения будет нашей целевой переменной.
Я выделил два способа определения:
а. С использованием SentimentIntensityAnalyzer из NLTK (будем называть SIA) б. С использованием TextBlob
import nltk
from nltk.sentiment.vader import SentimentIntensityAnalyzer
from textblob import TextBlobdef fetch_sentiment_using_SIA(text):
sid = SentimentIntensityAnalyzer()
polarity_scores = sid.polarity_scores(text)
if polarity_scores['neg'] > polarity_scores['pos']:
return 'отрицательное'
else:
return 'положительное'
def fetch_sentiment_using_textblob(text):
analysis = TextBlob(text)
_# установка настроения _
if analysis.sentiment.polarity >= 0:
return 'положительное'
else:
return 'отрицательное'
Мы можем выбрать любой из них, я лично предпочитаю TextBlob, он дает лучшую категоризацию.
3. Предварительная обработка текста
Текст, полученный из твитов, не достаточно чистый для использования в обучении модели, поэтому его нужно предварительно обработать. Мы, возможно, не сможем полностью очистить его, но должны постараться сделать его максимально чистым.
а. Удаление «@имен»:
Все «@любоеназвание» не имеют значения, так как они не несут никакого смысла.
def remove_pattern(text, pattern_regex):
r = re.findall(pattern_regex, text)
for i **in** r:
text = re.sub(i, '', text)
return text_# Мы сохраняем очищенные твиты в новом столбце с именем 'tidy_tweets'_
tweets_df['tidy_tweets'] = np.vectorize(remove_pattern)(tweets_df['tweets'], "@[\w]*: | *RT*")
б. Удаление ссылок (http | https)
Ссылки в тексте бесполезны, потому что они не несут никакой полезной информации.
cleaned_tweets = []
for index, row **in** tweets_df.iterrows():
_# Здесь мы фильтруем все слова, содержащие ссылку_
words_without_links = [word for word **in** row.tidy_tweets.split() if 'http' **not** **in** word]
cleaned_tweets.append(' '.join(words_without_links))
tweets_df['tidy_tweets'] = cleaned_tweets
в. Удаление дублирующихся строк
У нас может быть дубликаты твитов в нашем фрейме данных, это нужно учесть:
tweets_df.drop_duplicates(subset=['tidy_tweets'], keep=False)
г. Удаление знаков препинания, чисел и специальных символов
tweets_df['absolute_tidy_tweets'] = tweets_df['tidy_tweets'].str.replace("[^а-яА-Яa-zA-Z# ]", "")
Этот шаг не следует выполнять, если мы также хотим провести анализ настроений по ключевым фразам, так как в предложении должен присутствовать семантический смысл. Поэтому здесь мы создадим дополнительный столбец "absolute_tidy_tweets", который будет содержать абсолютно чистые слова, которые можно дальше использовать для анализа настроений по ключевым словам.
д. Удаление стоп-слов
Стоп-слова - это слова, которые используются только для правильного формирования предложений. Они не несут никакой информации. Поэтому их нужно удалить, чтобы сделать наши текстовые записи более чистыми.
from nltk.corpus import stopwords
nltk.download('stopwords')stopwords_set = set(stopwords.words("russian"))
cleaned_tweets = []
for index, row **in** tweets_df.iterrows():
_# фильтрация всех стоп-слов _
words_without_stopwords = [word for word **in** row.absolute_tidy_tweets.split() if **not** word **in** stopwords_set]
_# создание списка кортежей, содержащих стоп-слова (список) и тип настроения _
cleaned_tweets.append(' '.join(words_without_stopwords))
tweets_df['absolute_tidy_tweets'] = cleaned_tweets
е. Токенизация и лемматизация:
from nltk.stem import WordNetLemmatizer# Токенизация
tokenized_tweet = tweets_df['absolute_tidy_tweets'].apply(lambda x: x.split())
# Поиск леммы для каждого слова
word_lemmatizer = WordNetLemmatizer()
tokenized_tweet = tokenized_tweet.apply(lambda x: [word_lemmatizer.lemmatize(i) for i **in** x])
# объединение слов в предложения (откуда они взялись)
for i, tokens **in** enumerate(tokenized_tweet):
tokenized_tweet[i] = ' '.join(tokens)
tweets_df['absolute_tidy_tweets'] = tokenized_tweet
4. Извлечение признаков
Нам нужно преобразовать текстовое представление в виде числовых признаков. У нас есть две популярные техники для выполнения извлечения признаков:
Мы будем использовать извлеченные признаки из обоих по очереди для выполнения анализа настроений и сравним результат в конце.
Проверьте мой ниже приведенный ноутбук, чтобы правильно понять интуицию методов извлечения признаков с примерами: [https://www.kaggle.com/amar09/text-pre-processing-and-feature-extraction](http://Мы должны преобразовать текстовое представление в числовые признаки. У нас есть две популярные техники для выполнения извлечения признаков: Мешок слов (простая векторизация) TF-IDF (Term Frequency - Inverse Document Frequency) Мы будем использовать извлеченные признаки из обоих по очереди для выполнения анализа настроений и сравним результат в конце. Проверьте мой нижеуказанный ноутбук, чтобы правильно понять интуицию этих методов: https://www.kaggle.com/amar09/text-pre-processing-and-feature-extraction)
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer_# Признаки мешка слов_
bow_word_vectorizer = CountVectorizer(max_df=0.90, min_df=2, stop_words='russian')
_# матрица признаков мешка слов_
bow_word_feature = bow_word_vectorizer.fit_transform(tweets_df['absolute_tidy_tweets'])
_# Признаки TF-IDF_
tfidf_word_vectorizer = TfidfVectorizer(max_df=0.90, min_df=2, stop_words='russian')
_# матрица признаков TF-IDF_
tfidf_word_feature = tfidf_word_vectorizer.fit_transform(tweets_df['absolute_tidy_tweets'])
5. Построение модели
Давайте сначала сопоставим целевую переменную с 1.
target_variable = tweets_df['sentiment'].apply(lambda x: 0 if x=='отрицательное' else 1 )
Мы собираемся использовать модель наивного Байеса для классификации настроений, потому что я также пробовал SVM, логистическую регрессию и дерево решений, но лучшие результаты получил только с использованием наивного Байеса.
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import train_test_split
from sklearn.metrics import f1_scoredef naive_model(X_train, X_test, y_train, y_test):
naive_classifier = GaussianNB()
naive_classifier.fit(X_train.toarray(), y_train)
_# прогнозы для тестового набора_
predictions = naive_classifier.predict(X_test.toarray())
_# вычисление F1-меры_
print(f'F1-мера - {f1_score(y_test, predictions)}')
Обучение для признаков, извлеченных с помощью Bag of Words:
X_train, X_test, y_train, y_test = train_test_split(bow_word_feature, target_variable, test_size=0.3, random_state=870)
naive_model(X_train, X_test, y_train, y_test)
Это дает F1-меру - 0.9387254901960784
Теперь обучимся для признаков, извлеченных из TF-IDF:
X_train, X_test, y_train, y_test = train_test_split(tfidf_word_feature, target_variable, test_size=0.3, random_state=870)
naive_model(X_train, X_test, y_train, y_test)
Я получил F1-меру - 0.9400244798041616
Признаки TF-IDF явно дают лучший результат.
**Вывод: **Здесь для анализа настроений мы использовали только «ключевые слова», мы также можем использовать «ключевые фразы». Есть много других шагов, которые мы должны выполнить, чтобы узнать о них подробнее, ознакомьтесь с моим полным ноутбуком.
Анализ тональности скрапнутых твитов | Kaggle
Редактировать описание
В этом посте я предполагаю, что у вас уже есть базовые знания об обработке текста с использованием NLTK. Если у вас их нет, то я рекомендую ознакомиться с моим ниже примером, который содержит объяснение всех основных операций с текстом и подробное объяснение извлечения признаков с использованием мешка слов и TF-IDF.
Предобработка текста и извлечение признаков | Kaggle
Редактировать описание
Пожалуйста, оставьте комментарий, если вам нужно более подробное объяснение чего-либо. Все предложения и отзывы всегда приветствуются.
Спасибо за чтение, счастливого обучения ;)