Загружаем модули и библиотеки, необходимые для работы:
import requests
import time
import pandas as pd
Для начала давайте посмотрим на документацию API и посмотрим, как к нему формировать запросы: https://dev.vk.com/api/api-requests.
В прошлый раз по инструкции мы получили доступ к API, вспомним шаги.
# вводим id своего приложения
# и проходим по ссылке с этим id
app_id = input("Enter your client id: ")
url = f"https://oauth.vk.com/authorize?client_id={app_id}&display=page&redirect_uri=http://oauth.vk.com/blank.html&scope=all&response_type=token"
print(url)
Enter your client id: 7435771 https://oauth.vk.com/authorize?client_id=7435771&display=page&redirect_uri=http://oauth.vk.com/blank.html&scope=all&response_type=token
# копируем токен доступа
token = input("Enter your token here: ")
Enter your token here: 613f507e544b28900b1da6ed753eb62dc1cd2a4c43a24b84ea3c4e094f9b7693b7d9bc78affb46d147e21
На этом практическом занятии мы будем выгружать посты из сообщества Цитатник ВШЭ. Сохраним в переменные версию API, ссылку для метода работы со стеной сообщества и название сообщества:
v = "5.131"
main_wall = "https://api.vk.com/method/wall.get"
domain = "hseteachers"
Функция get()
из библиотеки requests
умеет подставлять в запрос необходимые параметры и объединять их с помощью ?
и &
. Сохраним необходимые параметры в виде словаря:
params_wall = {"access_token" : token,
"domain" : domain,
"count" : 100,
"v" : v}
А теперь сформируем запрос и выгрузим результаты в формате JSON – в Python данные в таком формате будут представлены в виде словаря:
req_wall = requests.get(main_wall, params = params_wall)
json_wall = req_wall.json()
# json_wall
Извлечём из этого большого словаря элемент, который хранит непосредственно результаты – список из маленьких словарей с информацией о постах (1 словарь = 1 пост):
items_wall = json_wall['response']['items']
Посмотрим на один элемент такого списка:
i = items_wall[0]
i
{'id': 36498, 'from_id': -63442801, 'owner_id': -63442801, 'date': 1648034700, 'marked_as_ads': 0, 'post_type': 'post', 'text': 'Прошу прощения за эту пытку, я старался как мог...\n\n#Автономов_ВШЭ #Микроэкономика', 'post_source': {'type': 'vk'}, 'comments': {'can_post': 1, 'count': 0}, 'likes': {'can_like': 1, 'count': 145, 'user_likes': 0, 'can_publish': 1}, 'reposts': {'count': 23, 'user_reposted': 0}, 'views': {'count': 4573}, 'is_favorite': False, 'donut': {'is_donut': False}, 'short_text_rate': 0.8, 'hash': 'mb7PYe6VctNOeZPXbX0luMPOiLY'}
Поработаем с ним!
Извлечь из элемента i
следующие компоненты:
print(i["id"], i["date"], i["text"])
print(i["likes"]["count"], i["reposts"]["count"], i["views"]["count"],
i["comments"]["count"])
36498 1648034700 Прошу прощения за эту пытку, я старался как мог... #Автономов_ВШЭ #Микроэкономика 145 23 4573 0
Напишите функцию get_posts()
, которая принимает на вход словарь, аналогичный сохранённому в i
, и возвращает список из следующих характеристик:
# добавляем ловлю исключений
# на случай, если какие-то характеристики будут отсутствовать
def get_posts(i):
try:
id_ = i["id"]
date = i["date"]
text = i["text"]
likes = i["likes"]["count"]
repos = i["reposts"]["count"]
views = i["views"]["count"]
comments = i["comments"]["count"]
except:
id_ = i["id"]
date = i["date"]
text = ""
likes = 0
repos = 0
views = 0
comments = 0
L = [id_, date, text, likes, repos, views, comments]
return L
Примените функцию get_posts()
ко всем элементам списка items_wall
и сохраните полученные результаты в список posts
.
posts = []
for i in items_wall:
p = get_posts(i)
posts.append(p)
Прочитайте в документации к API ВКонтакте про аргумент offset
в методе wall.get
. Используя полученную информацию и блоки кода ниже, выгрузите и сохраните в список items_more
данные ещё по 9200 постам на стене сообщества.
Подсказка: чтобы расширять список правильным образом, используйте метод .extend()
, а не .append()
, он добавляет не один элемент, а сразу несколько (см. примеры ниже).
# с append()
A = []
for i in range(5):
B = [1, 2, 3]
A.append(B)
print(A)
[[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]]
# с extend()
A = []
for i in range(5):
B = [1, 2, 3]
A.extend(B)
print(A)
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
params_wall_long = {"access_token" : token,
"domain" : domain,
"count" : 100,
"offset" : 100,
"v" : v}
items_more = []
for i in range(92):
req_wall_long = requests.get(main_wall, params = params_wall_long)
json_wall_long = req_wall_long.json()
items_wall_long = json_wall_long['response']['items']
items_more.extend(items_wall_long)
params_wall_long["offset"] = params_wall_long["offset"] + 100
time.sleep(1.5)
print(i)
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
Теперь извлечём из каждого элемента items_more
нужную информацию и расширим список posts
, который у нас уже был до этого:
for i in items_more:
p = get_posts(i)
posts.append(p)
len(posts) # все идёт по плану
7081
posts[202]
[26132, 1563175800, '*идет экзамен по эконому, Касаткина встает и берет в руки микрофон\n\n- Московское время 11 часов 15 минут.\n\n#Касаткина_ВШЭ #экономика #фкмд #медиаком', 273, 2, 13871, 0]
Преобразуем результат в датафрейм, добавим названия столбцов:
dat = pd.DataFrame(posts)
dat.columns = ["id", "timestamp", "post", "likes",
"reposts", "views", "comments"]
Несколько строк датафрейма для примера:
dat.head()
id | timestamp | post | likes | reposts | views | comments | |
---|---|---|---|---|---|---|---|
0 | 36498 | 1648034700 | Прошу прощения за эту пытку, я старался как мо... | 145 | 23 | 4573 | 0 |
1 | 36496 | 1647948300 | Я как-то помогал своему другу составить резюме... | 177 | 19 | 5974 | 0 |
2 | 36491 | 1647861900 | Задача менеджера - принуждать. Задача директор... | 115 | 20 | 5634 | 1 |
3 | 36488 | 1647775500 | Если вы возьмёте интервью у Абрамовича, мы с р... | 81 | 5 | 4515 | 1 |
4 | 36486 | 1647689100 | Удобно когда твой любовник одной с тобой веры,... | 115 | 23 | 5689 | 0 |
Разобьём текст поста по #
, чтобы извлечь тэги:
dat["post"][10]
'А мне кажется, он написал просто потому что ему захотелось. \n\n#Виноградов_ВШЭ'
with_tags = dat["post"].str.split("#", expand = True)
with_tags
0 | 1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|---|
0 | Прошу прощения за эту пытку, я старался как мо... | Автономов_ВШЭ | Микроэкономика | None | None | None |
1 | Я как-то помогал своему другу составить резюме... | Аникин_ВШЭ | ССиСС | None | None | None |
2 | Задача менеджера - принуждать. Задача директор... | Аникин_ВШЭ | ССиСС | None | None | None |
3 | Если вы возьмёте интервью у Абрамовича, мы с р... | Слободенюк_ВШЭ | ССиСС | None | None | None |
4 | Удобно когда твой любовник одной с тобой веры,... | Козицкая_ВШЭ | None | None | None | None |
... | ... | ... | ... | ... | ... | ... |
7076 | None | None | None | None | None | |
7077 | None | None | None | None | None | |
7078 | None | None | None | None | None | |
7079 | None | None | None | None | None | |
7080 | None | None | None | None | None |
7081 rows × 6 columns
Основная информация – это первые два тэга, имя преподавателя и курс (по крайней мере, в большинстве случаев это так). Заберём для дальнейшей работы только их:
small = with_tags.loc[:, 0:2]
small.columns = ["text", "teacher", "course"]
Склеим датафрейм dat
с датафреймом small
по столбцам:
final = pd.concat([dat, small], axis = 1)
Заполним пропуски – добавим «пустой» текст в ячейки, где нет никаких значений:
final = final.fillna("")
Избавимся от лишних пробелов и отступов в текстовых данных:
final["text"] = final["text"].apply(lambda x: x.strip())
final["teacher"] = final["teacher"].apply(lambda x: x.strip())
final["course"] = final["course"].apply(lambda x: x.strip())
Осталось поработать с форматом времени в столбце timestamp
.
final["timestamp"]
0 1648034700 1 1647948300 2 1647861900 3 1647775500 4 1647689100 ... 7076 1387918264 7077 1387917399 7078 1387917345 7079 1387916061 7080 1387914004 Name: timestamp, Length: 7081, dtype: int64
t = final["timestamp"][0]
t
1648034700
Импортируем из модуля datetime
функцию datetime
, она поможет нам получить дату и время в привычном формате:
from datetime import datetime
datetime.utcfromtimestamp(t)
datetime.datetime(2022, 3, 23, 11, 25)
datetime.utcfromtimestamp(t).strftime('%Y-%m-%d %H:%M:%S')
'2022-03-23 11:25:00'
Напишем функцию для преобразования временной метки:
def time_transform(t):
r = datetime.fromtimestamp(t).strftime('%Y-%m-%d %H:%M:%S')
return r
Применим её ко всем элементам столбца:
final["datetime"] = final["timestamp"].apply(time_transform)
final.head()
id | timestamp | post | likes | reposts | views | comments | text | teacher | course | datetime | |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 36498 | 1648034700 | Прошу прощения за эту пытку, я старался как мо... | 145 | 23 | 4573 | 0 | Прошу прощения за эту пытку, я старался как мо... | Автономов_ВШЭ | Микроэкономика | 2022-03-23 14:25:00 |
1 | 36496 | 1647948300 | Я как-то помогал своему другу составить резюме... | 177 | 19 | 5974 | 0 | Я как-то помогал своему другу составить резюме... | Аникин_ВШЭ | ССиСС | 2022-03-22 14:25:00 |
2 | 36491 | 1647861900 | Задача менеджера - принуждать. Задача директор... | 115 | 20 | 5634 | 1 | Задача менеджера - принуждать. Задача директор... | Аникин_ВШЭ | ССиСС | 2022-03-21 14:25:00 |
3 | 36488 | 1647775500 | Если вы возьмёте интервью у Абрамовича, мы с р... | 81 | 5 | 4515 | 1 | Если вы возьмёте интервью у Абрамовича, мы с р... | Слободенюк_ВШЭ | ССиСС | 2022-03-20 14:25:00 |
4 | 36486 | 1647689100 | Удобно когда твой любовник одной с тобой веры,... | 115 | 23 | 5689 | 0 | Удобно когда твой любовник одной с тобой веры,... | Козицкая_ВШЭ | 2022-03-19 14:25:00 |
Теперь можем разбить дату-время по пробелу, чтобы получить отдельные столбцы с датой и временем (механизм нам уже известен, мы разбивали пост по #
выше):
dt = final["datetime"].str.split(" ", expand = True)
dt.columns = ["date", "time"]
final2 = pd.concat([final, dt], axis = 1)
final2
id | timestamp | post | likes | reposts | views | comments | text | teacher | course | datetime | date | time | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 36498 | 1648034700 | Прошу прощения за эту пытку, я старался как мо... | 145 | 23 | 4573 | 0 | Прошу прощения за эту пытку, я старался как мо... | Автономов_ВШЭ | Микроэкономика | 2022-03-23 14:25:00 | 2022-03-23 | 14:25:00 |
1 | 36496 | 1647948300 | Я как-то помогал своему другу составить резюме... | 177 | 19 | 5974 | 0 | Я как-то помогал своему другу составить резюме... | Аникин_ВШЭ | ССиСС | 2022-03-22 14:25:00 | 2022-03-22 | 14:25:00 |
2 | 36491 | 1647861900 | Задача менеджера - принуждать. Задача директор... | 115 | 20 | 5634 | 1 | Задача менеджера - принуждать. Задача директор... | Аникин_ВШЭ | ССиСС | 2022-03-21 14:25:00 | 2022-03-21 | 14:25:00 |
3 | 36488 | 1647775500 | Если вы возьмёте интервью у Абрамовича, мы с р... | 81 | 5 | 4515 | 1 | Если вы возьмёте интервью у Абрамовича, мы с р... | Слободенюк_ВШЭ | ССиСС | 2022-03-20 14:25:00 | 2022-03-20 | 14:25:00 |
4 | 36486 | 1647689100 | Удобно когда твой любовник одной с тобой веры,... | 115 | 23 | 5689 | 0 | Удобно когда твой любовник одной с тобой веры,... | Козицкая_ВШЭ | 2022-03-19 14:25:00 | 2022-03-19 | 14:25:00 | |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
7076 | 6 | 1387918264 | 0 | 0 | 0 | 0 | 2013-12-25 00:51:04 | 2013-12-25 | 00:51:04 | ||||
7077 | 5 | 1387917399 | 0 | 0 | 0 | 0 | 2013-12-25 00:36:39 | 2013-12-25 | 00:36:39 | ||||
7078 | 4 | 1387917345 | 0 | 0 | 0 | 0 | 2013-12-25 00:35:45 | 2013-12-25 | 00:35:45 | ||||
7079 | 3 | 1387916061 | 0 | 0 | 0 | 0 | 2013-12-25 00:14:21 | 2013-12-25 | 00:14:21 | ||||
7080 | 2 | 1387914004 | 0 | 0 | 0 | 0 | 2013-12-24 23:40:04 | 2013-12-24 | 23:40:04 |
7081 rows × 13 columns
final2.to_excel("posts.xlsx")
final2[final2["course"] == "Психология"]
id | timestamp | post | likes | reposts | views | comments | text | teacher | course | datetime | date | time | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
210 | 26108 | 1562830200 | В науке тоже могут быть разные эксцессы, мне к... | 61 | 0 | 8622 | 0 | В науке тоже могут быть разные эксцессы, мне к... | Скворцов_ВШЭ | Психология | 2019-07-11 10:30:00 | 2019-07-11 | 10:30:00 |
248 | 25965 | 1561300200 | Я вам повторяю - ассоцианисты не лохи.\n\n#Скв... | 71 | 0 | 6975 | 0 | Я вам повторяю - ассоцианисты не лохи. | Скворцов_ВШЭ | Психология | 2019-06-23 17:30:00 | 2019-06-23 | 17:30:00 |
251 | 25953 | 1561195801 | В психологии думать не надо: сказали "ассоциац... | 107 | 0 | 7090 | 0 | В психологии думать не надо: сказали "ассоциац... | Скворцов_ВШЭ | Психология | 2019-06-22 12:30:01 | 2019-06-22 | 12:30:01 |
337 | 25525 | 1558940700 | Я вам повторяю - ассоцианисты не лохи.\n\n#Скв... | 76 | 0 | 4912 | 0 | Я вам повторяю - ассоцианисты не лохи. | Скворцов_ВШЭ | Психология | 2019-05-27 10:05:00 | 2019-05-27 | 10:05:00 |
393 | 25290 | 1557215400 | *на паре про экзистенциальную психологию*:\n"Я... | 104 | 0 | 5793 | 1 | *на паре про экзистенциальную психологию*:\n"Я... | Скворцов_ВШЭ | Психология | 2019-05-07 10:50:00 | 2019-05-07 | 10:50:00 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
3381 | 14066 | 1507396200 | "Здравствуйте, топор. Я - Толя Скворцов".\n\n#... | 69 | 2 | 4857 | 0 | "Здравствуйте, топор. Я - Толя Скворцов". | Скворцов_ВШЭ | Психология | 2017-10-07 20:10:00 | 2017-10-07 | 20:10:00 |
3438 | 13804 | 1506922740 | С. - Если Вышка уже такая многопрофильная, то,... | 157 | 3 | 5302 | 1 | С. - Если Вышка уже такая многопрофильная, то,... | Хачатурова_ВШЭ | Психология | 2017-10-02 08:39:00 | 2017-10-02 | 08:39:00 |
3480 | 13690 | 1506517680 | "Вселенная тоже пыхтит"\n\n#Скворцов_ВШЭ #Псих... | 81 | 0 | 5010 | 0 | "Вселенная тоже пыхтит" | Скворцов_ВШЭ | Психология | 2017-09-27 16:08:00 | 2017-09-27 | 16:08:00 |
3534 | 13444 | 1505928960 | Может он выбрасывается, потому что у него дети... | 69 | 2 | 5282 | 0 | Может он выбрасывается, потому что у него дети. | Молчанова_ВШЭ | Психология | 2017-09-20 20:36:00 | 2017-09-20 | 20:36:00 |
3544 | 13406 | 1505821920 | "Это относится и к мужчинам тоже: если вы не п... | 153 | 2 | 5554 | 0 | "Это относится и к мужчинам тоже: если вы не п... | Россохин_ВШЭ | Психология | 2017-09-19 14:52:00 | 2017-09-19 | 14:52:00 |
88 rows × 13 columns
final2[final2["teacher"].str.contains("тамб")]
id | timestamp | post | likes | reposts | views | comments | text | teacher | course | datetime | date | time |
---|