5 минут чтения
2 июля 2020 г.
Семантический анализ мнений о поправках к Конституции на основе данных ВКонтакте
Сегодня поработаем с открытыми данными из ВКонтакте и получим семантическую оценку на популярное и актуальное событие — поправки к Конституции Российской Федерации.
Обзор методов API
Воспользуемся методом newsfeed.search: он позволяет получить до тысячи последних постов из новостной ленты по ключевому слову. В результате приходит много полей: среди них идентификаторы записи и пользователя или сообщества, текст поста, количество лайков, комментарии, приложения, геопозиция и прочее. Нас интересуют только идентификаторы и текст.
Для аналитики пригодится расширенная информация об авторе поста: его город, пол и возраст можно получить методом users.get, причём в запросе будем отправлять сразу до тысячи пользователей.
Создаём таблицы в Clickhouse
Данные нужно будет где-то хранить, в качестве СУБД подойдёт Clickhouse. Создадим две таблицы: для постов и для пользователей. В первой будем хранить идентификаторы и текст поста, во второй — данные о пользователе: его id, пол, возраст и город. Движок ReplacingMergeTree() будет удалять дубликаты.
Мы уже писали о том, как установить Clickhouse на бесплатную машину AWS, создавать в нём внешние словари и материализованные представления
CREATE TABLE vk_posts(
post_id UInt64,
post_date DateTime,
owner_id UInt64,
from_id UInt64,
text String
) ENGINE ReplacingMergeTree()
ORDER BY post_date
CREATE TABLE vk_users(
user_id UInt64,
user_sex Nullable(UInt8),
user_city String,
user_age Nullable(UInt16)
) ENGINE ReplacingMergeTree()
ORDER BY user_id
Сбор постов через API ВКонтакте
Перейдём к написанию скрипта. Импортируем библиотеки и задаём несколько константных значений:
from clickhouse_driver import Client
from datetime import datetime
import requests
import pandas as pd
import time
token = ‘your_token’
version = 5.103
client = Client(host=’ec1-23-456-789-1011.us-east-2.compute.amazonaws.com’, user=’default’, password=», port=’9000′, database=’default’)
data_list = []
start_from = 0
query_string = ‘конституция’
В материале «Собираем данные по рекламным кампаниям ВКонтакте» подробно описан процесс получения токена пользователя для VK API
Опишем функцию get_and_insert_info_by_user — она будет принимать список идентификаторов пользователей, получать расширенную информацию о них и отправлять в таблицу vk_users. Так как параметр user_ids метода принимает список как строку, переводим структуру в тип str и отсекаем квадратные скобки. Многие пользователи скрывают пол, возраст или город — в таком случае вставляет Nullable значения. Для получения возраста берём текущий год и вычитаем год из даты рождения, если он представлен — проверку делаем регулярным выражением по четырём цифрам.
Функция get_and_insert_info_by_user
def get_and_insert_info_by_user(users):
try:
r = requests.get(‘https://api.vk.com/method/users.get’, params={
‘access_token’:token,
‘v’:version,
‘user_ids’:str(users)[1:-2],
‘fields’:’sex, city, bdate’
}).json()[‘response’]
for user in r:
user_list = []
user_list.append(user[‘id’])
if client.execute(f»SELECT count(1) FROM vk_users where user_id={user[‘id’]}»)[0][0] == 0:
print(user[‘id’])
try:
user_list.append(user[‘sex’])
except Exception:
user_list.append(‘cast(Null as Nullable(UInt8))’)
try:
user_list.append(user[‘city’][‘title’])
except Exception:
user_list.append(»)
try:
now = datetime.now()
year = item.split(‘.’)[-1]
if re.match(r’\d\d\d\d’, year):
age = now.year — int(year)
user_list.append(age)
except Exception:
user_list.append(‘cast(Null as Nullable(UInt16))’)
user_insert_tuple = tuple(user_list)
client.execute(f’INSERT INTO vk_users VALUES {user_insert_tuple}’)
except KeyError:
pass
Наш скрипт будет работать в вечном цикле, чтобы постоянно добирать новые данные, ведь мы можем получать только тысячу последних. Метод newsfeed.search за раз возвращает двести постов, так что нужно вызывать его пять раз подряд и собирать все ответы.
Цикл сбора новых постов
while True:
for i in range(5):
r = requests.get(‘https://api.vk.com/method/newsfeed.search’, params={
‘access_token’:token,
‘v’:version,
‘q’:query_string,
‘count’:200,
‘start_from’: start_from
})
data_list.append(r.json()[‘response’])
try:
start_from = r.json()[‘response’][‘next_from’]
except KeyError:
pass
Полученные в ответе данные можно распарсить. В ВКонтакте у пользователей id всегда положительный, а у сообществ идёт со знаком минус. Чтобы получить данные только от пользователей, будем собирать только те, где from_id больше нуля. Следующая проверка — на отсутствие текста в посте, такие нам тоже не нужны. Наконец, будем собирать данные только если таких ещё нет — для этого обращаемся к таблице vk_posts по текущему id. В конце приостановим скрипт на 180 секунд, чтобы дождаться новых постов и не столкнуться с ограничениями по запросам VK API.
Занесение новых данных в Clickhouse
user_ids = []
for data in data_list:
for data_item in data[‘items’]:
if data_item[‘from_id’] > 0:
post_list = []
if not data_item[‘text’]:
continue
if client.execute(f»SELECT count(1) FROM vk_posts WHERE post_id={data_item[‘id’]} AND from_id={data_item[‘from_id’]}»)[0][0] == 0:
user_ids.append(data_item[‘from_id’])
date = datetime.fromtimestamp(data_item[‘date’])
date = datetime.strftime(date, ‘%Y-%m-%d %H:%M:%S’)
post_list.append(date)
post_list.append(data_item[‘id’])
post_list.append(data_item[‘owner_id’])
post_list.append(data_item[‘from_id’])
post_list.append(data_item[‘text’].replace(«‘»,»»).replace(‘»‘,»).replace(«\n»,»»))
post_list.append(query_string)
post_tuple = tuple(post_list)
print(post_list)
try:
client.execute(f’INSERT INTO vk_posts VALUES {post_tuple}’)
except Exception as E:
print(‘!!!!! try to insert into vk_post but got’, E)
try:
get_and_insert_info_by_user(user_ids)
except Exception as E:
print(«Try to insert user list:», user_ids, «but got:», E)
time.sleep(180)
Анализ постов через Dostoevsky
Этот скрипт мы оставили работать на неделю: за это время он набрал почти 20000 постов из ВКонтакте, в которых упоминается ключевое слово «конституция». Напишем второй скрипт — для аналитики и визуализации данных. Для начала соберём данные из таблицы, сформируем DataFrame и для каждого поста получим значения тональности: насколько он положителен, отрицателен и нейтрален. Для оценки тональности текста будем использовать библиотеку Dostoevsky.
from dostoevsky.tokenization import RegexTokenizer
from dostoevsky.models import FastTextSocialNetworkModel
from clickhouse_driver import Client
import pandas as pd
client = Client(host=’ec1-23-456-789-1011.us-east-2.compute.amazonaws.com’, user=’default’, password=», port=’9000′, database=’default’)
Простым запросом содержимое всей таблицы с постами занесём в переменную vk_posts. Пройдём все посты, выберем те посты, где есть текст помимо пробелов и положим их в DataFrame.
vk_posts = client.execute(‘SELECT * FROM vk_posts’)
list_of_posts = []
list_of_ids = []
for post in vk_posts:
if str(post[-2]).replace(» «, «»):
list_of_posts.append(str(post[-2]).replace(«\n»,»»))
list_of_ids.append(int(post[2]))
df_posts = pd.DataFrame()
df_posts[‘post’] = list_of_posts
df_posts[‘id’] = list_of_ids
Обходим моделью весь список постов с текстом и получаем к оценку тональности для каждой записи.
tokenizer = RegexTokenizer()
model = FastTextSocialNetworkModel(tokenizer=tokenizer)
sentiment_list = []
results = model.predict(list_of_posts, k=2)
for sentiment in results:
sentiment_list.append(sentiment)
Для каждой строки в DataFrame заведём ещё три колонки: насколько запись положительна, отрицательна и нейтральна. В случае, если по одному из трёх параметров ничего не вернулось, будем заносить ноль.
neutral_list = []
negative_list = []
positive_list = []
speech_list = []
skip_list = []
for sentiment in sentiment_list:
neutral = sentiment.get(‘neutral’)
negative = sentiment.get(‘negative’)
positive = sentiment.get(‘positive’)
if neutral is None:
neutral_list.append(0)
else:
neutral_list.append(sentiment.get(‘neutral’))
if negative is None:
negative_list.append(0)
else:
negative_list.append(sentiment.get(‘negative’))
if positive is None:
positive_list.append(0)
else:
positive_list.append(sentiment.get(‘positive’))
df_posts[‘neutral’] = neutral_list
df_posts[‘negative’] = negative_list
df_posts[‘positive’] = positive_list
Посмотрим, как выглядит наш DataFrame теперь:
Можем посмотреть примеры самых негативных постов:
df_posts[df_posts.negative > 0.9]
Нашей таблице не хватает данных об авторах постов. Возьмём их из таблицы vk_users и сольём обе таблицы по полю «id».
vk_users = client.execute(‘SELECT * FROM vk_users’)
vk_user_ids_list = []
vk_user_sex_list = []
vk_user_city_list = []
vk_user_age_list = []
for user in vk_users:
vk_user_ids_list.append(user[0])
vk_user_sex_list.append(user[1])
vk_user_city_list.append(user[2])
vk_user_age_list.append(user[3])
df_users = pd.DataFrame()
df_users[‘id’] = vk_user_ids_list
df_users[‘sex’] = vk_user_sex_list
df_users[‘city’] = vk_user_city_list
df_users[‘age’] = vk_user_age_list
df = df_posts.merge(df_users, on=’id’)
Теперь таблица выглядит так:
Анализируем графики от plotly
В материале «Как построить красивый waterfall chart в Python?» мы уже строили графики библиотекой plotly
Для начала посчитаем процентное соотношение постов с положительной, отрицательной и нейтральной тональностью: пройдём все три столбца и подсчитаем для каждого случая строки, отличные от нуля. Затем проделаем то же самое для разных возрастных категорий и половых принадлежностей.
Из графика следует, что 46% постов по запросу «конституция» за последнюю неделю имеют негативный окрас. Другие 52% высказываются нейтрально. Чуть позже узнаем, насколько мнения в интернете совпадают с официальными результатами голосования.
Заметно, что доля положительных постов среди мужской аудитории составляет 2%, среди женской — вдвое больше, 4%. Впрочем, негативных постов в обоих группах практически поровну: 47% среди мужской и 44% среди женской.
Наконец, оценка постов по возрастным группам: больше всего доля позитивного текста наблюдается в группе 18 — 25 лет, это 3%. Меньше всего позитивных постов в группе до 18 лет, но это может происходить и в связи с тем, что многие пользователи моложе 18 лет предпочитают скрывать возраст, и точные данные по такой группе получить не удастся. Негативных постов во всех группах кроме 18 — 25 поровну: 46%.
Заметно, что на всех трёх графиках данные распределены приблизительно одинаково. Это говорит о том, что за последнюю неделю практически половина всех постов по ключевому слову «конституция» в новостной ленте ВКонтакте имела негативный окрас.
[ Рекомендации ]
Читайте также
[ Связаться ]
Давайте раскроем потенциал вашего бизнеса вместе
Заполните форму на бесплатную консультацию