CoderCastrov logo
CoderCastrov
Машинное обучение

Парсинг твитов и анализ социальных настроений

Парсинг твитов и анализ социальных настроений
просмотров
9 мин чтение
#Машинное обучение

Обработка естественного языка (Natural Language Processing, NLP) - это действительно интересная и широкая область искусственного интеллекта. Здесь я собираюсь использовать ее для обработки текстовых записей и провести краткий курс по парсингу и анализу настроений. Для парсинга я использовал Selenium и tweepy, а для анализа настроений - классы и методы NLTK и модель наивного Байеса. Я постарался охватить большую часть шагов, которые следует выполнить при работе с текстовыми данными, и уверяю вас, что это будет стоящим занятием.

Итак, что такое парсинг и анализ настроений?

Парсинг - это процесс получения небольших фрагментов чего-либо. В нашем случае это веб-парсинг, поэтому мы получаем фрагменты информации, доступной на веб-сайте.

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

Люди обычно предпочитают использовать социальные медиа для выражения своих мнений или взглядов, это может быть Facebook, Twitter, Quora или любой другой блог-сайт. В этом руководстве я собираюсь рассмотреть Twitter в качестве источника информации, а темой, которую я хотел бы выбрать, является «ИИ и глубокое обучение». Однако код, который я буду делить, будет полностью общим, поэтому вы также можете выбрать любую другую интересную тему.

Из заголовка и описания вы уже, наверное, поняли, что текстовые данные, необходимые для проведения анализа настроений, нужно получить с Twitter. Ниже приведены основные операции, которые я собираюсь выполнить:

  1. Парсинг твитов
  2. Определение настроений
  3. Предварительная обработка текста
  4. Извлечение признаков
  5. Построение модели

Если вы уже выполняли парсинг на 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

Редактировать описание

www.kaggle.com

В этом посте я предполагаю, что у вас уже есть базовые знания об обработке текста с использованием NLTK. Если у вас их нет, то я рекомендую ознакомиться с моим ниже примером, который содержит объяснение всех основных операций с текстом и подробное объяснение извлечения признаков с использованием мешка слов и TF-IDF.

Предобработка текста и извлечение признаков | Kaggle

Редактировать описание

www.kaggle.com

Пожалуйста, оставьте комментарий, если вам нужно более подробное объяснение чего-либо. Все предложения и отзывы всегда приветствуются.

Спасибо за чтение, счастливого обучения ;)