Алла Тамбовцева, НИУ ВШЭ
Сегодня мы немного поработаем с API. API – программный интерфейс приложения, сокращение от Application Programming Interface. Этот интерфейс позволяет выполнять различные операции автоматически, через приложение. Если API нам нужен исключительно как источник данных, можно писать запросы, позволяющие обратиться к хранилищу информации внутри API. Если мы хотим управлять приложением, которое будет выполнять какие-то действия, удаленно, можно написать код, который будет, например, автоматически отвечать на сообщения, когда мы не онлайн, лайкать новый пост друга через 30 секунд после его появления, пересылать на почту фотографии, которые выложили участники диалога и прочее.
Мы будем работать с API социальной сети ВКонтакте. Использовать API для написания и приема сообщений средствами Python мы не будем, а рассмотрим API как источник данных, позволяющий выгрузить все посты со стены пользователя или сообщества. Для работы нам понадобится библиотека vk
, ее нужно установить через pip install vk
в Anaconda Command Prompt (см. пример с selenium
здесь) или запустить строку !pip install vk
прямо в ячейке Jupyter Notebook.
Импортируем библиотеку:
import vk
Теперь нужно авторизоваться: создать приложение и получить токен доступа к нему.
Создать приложение ВКонтакте (пройти по ссылке). Дать название, выбрать Standalone-приложение.
Включить приложение, сделать публичным (Настройки - Состояние и выбрать в выпадающем меню Приложение включено и видно всем).
Авторизоваться: скопировать строку ниже в браузер, в client id
вместо 1 выставить id
своего приложения (первая строка в настроках ‒ ID приложения). Если не хочется ни в чем ограничивать свое приложение, можно оставить scope=all
(у приложения будет доступ ко всему, к чему есть доступ у пользователя).
https://oauth.vk.com/authorize?client_id=1&display=page&redirect_uri=http://oauth.vk.com/blank.html&scope=all&response_type=token
Скопировать access token
из обновленной адресной строки (все после access_token=
и до &expires_in
, без &
). Никому не показывать! По этому токену можно получить доступ ко всему аккаунту.
# скопировать свой access token без пробелов вместо 1234
token = '1234'
session = vk.Session(access_token = token) # открыть сессию для работы
api = vk.API(session) # подключиться к API
Попробуем сгрузить первые 100 постов со стены неофициального сообщества ВШЭ. Сохраним ссылку на сообщество в переменную group
:
group = 'hseofficial'
Обратите внимание: ссылка должна быть относительной, то есть без части с https:/vk.com/
. Python и так будет знать, что мы работаем с сетью ВКонтакте.
Теперь получим доступ к стене этого сообщества:
# wall.get - "подключаемся" к стене
# count - сколько постов выгрузить (максимум)
# v - версия API
res = api.wall.get(domain = group, count = 100, v = 5.103)
res
Результат, сохраненный в res
, очень похож на словарь. На самом деле, многие API возвращают результаты в формате JSON (JavaScript Object Notation), который тоже может быть представлен в виде набора пар ключ-значение.
res.keys()
dict_keys(['count', 'items'])
Ключами являются count
и items
. Нужные нам объекты (текст постов, id автора, дата и время публикации и проч.) находятся в items
.
res['items'][0] # первый элемент items - первый пост со всей информацией о нем
{'id': 33156, 'from_id': -132, 'owner_id': -132, 'date': 1586965441, 'marked_as_ads': 0, 'post_type': 'post', 'text': 'Выпускнику Вышки Илье Флаксу (программа "Электронный бизнес") и его команде желаем здоровья и успехов в помощи медикам! Берегите себя\nhttps://www.hse.ru/news/science/357674815.html?fbclid=IwAR2--ert8lpg349pbQf_BpTW0GuIy1lG1mc9Wh39lye7bJexWYgtzSq6PfI', 'attachments': [{'type': 'link', 'link': {'url': 'https://www.hse.ru/news/science/357674815.html?fbclid=IwAR2--ert8lpg349pbQf_BpTW0GuIy1lG1mc9Wh39lye7bJexWYgtzSq6PfI', 'title': 'Очки спасения: как цифровые технологии помогают российским медикам не заразиться COVID-19', 'caption': 'www.hse.ru', 'description': 'В конце марта в больнице в Коммунарке запущен пилотный проект по удаленной координации действий медиков, работающих с пациентами, зараженными коронавирусом. Сейчас система используется в десяти столичных клиниках. Выпускник Вышки Илья Флакс, который руководит этим проектом, рассказал новостной службе портала о том, как «умные очки» помогают врачам, и о перспективах использования виртуальной реальности в медицине.', 'photo': {'id': 457302271, 'album_id': -28, 'owner_id': 2000021373, 'sizes': [{'type': 'k', 'url': 'https://sun9-11.userapi.com/c855720/v855720082/224b4b/imWvr46rPlQ.jpg', 'width': 1000, 'height': 480}, {'type': 'l', 'url': 'https://sun9-54.userapi.com/c855720/v855720082/224b4a/CqdJWUtB1a4.jpg', 'width': 537, 'height': 240}, {'type': 'm', 'url': 'https://sun9-9.userapi.com/c855720/v855720082/224b47/dI-HRSil6eM.jpg', 'width': 130, 'height': 87}, {'type': 'p', 'url': 'https://sun9-50.userapi.com/c855720/v855720082/224b49/TyGYzghmmR8.jpg', 'width': 260, 'height': 173}, {'type': 's', 'url': 'https://sun9-33.userapi.com/c855720/v855720082/224b46/NJPcTZUGv9w.jpg', 'width': 75, 'height': 50}, {'type': 'x', 'url': 'https://sun9-48.userapi.com/c855720/v855720082/224b48/5Px8VxV3NcY.jpg', 'width': 150, 'height': 100}], 'text': '', 'date': 1586965441}, 'is_favorite': False}}], 'post_source': {'type': 'vk'}, 'comments': {'count': 1, 'can_post': 1, 'groups_can_post': True}, 'likes': {'count': 7, 'user_likes': 0, 'can_like': 1, 'can_publish': 1}, 'reposts': {'count': 1, 'user_reposted': 0}, 'views': {'count': 5024}, 'is_favorite': False}
Помимо текста поста можно найти много всего интересного. Например, тип поста (post_type
), дата (date
), id поста (id
), лайки (likes
, которые включают информацию о том, могут ли пользователи лайкать пост и публиковать его, а также собственно число лайков), репосты (reposts
, которые включают число репостов), число просмотров (views
), комментарии (comments
, которые включают информацию о том, могут ли пользователи комментировать пост, и число комментариев), и так далее.
Давайте остановимся на тексте поста, id автора, id поста, дате публикации, числе лайков и репостов. Чтобы извлечь соответствующую информацию, сохраним items
и извлечем из них нужные поля:
items = res['items']
full_list = []
for item in items:
L = [item['from_id'], item['id'], item['text'], item['date'],
item['likes']['count'], item['reposts']['count']] # нужные поля
full_list.append(L) # добавляем в список списков full_list
# несколько элементов списка
full_list[0:3]
[[-132, 33156, 'Выпускнику Вышки Илье Флаксу (программа "Электронный бизнес") и его команде желаем здоровья и успехов в помощи медикам! Берегите себя\nhttps://www.hse.ru/news/science/357674815.html?fbclid=IwAR2--ert8lpg349pbQf_BpTW0GuIy1lG1mc9Wh39lye7bJexWYgtzSq6PfI', 1586965441, 7, 1], [-132, 33155, '', 1586961686, 14, 0], [-132, 33152, '‼Внимание, уважаемые абитуриенты! \n \n🤓Уже 19-го апреля пройдет online_День Школьника на нашей программе ЦИУП! \n \n😎А уже 26-го апреля будет проходить online_День Открытых Дверей! \n \nМы приглашаем вас посетить это мероприятие, на котором вы сможете узнать абсолютно любую информацию о программе непосредственно от её руководства! \n \nИнформация: \n \nhttps://bi.hse.ru/announcements/333188573.html \n \nhttps://hse.ru/ba/digital', 1586453896, 12, 2]]
Видно, что в некоторых постах текста не обнаружено.
Из этого списка списков можно легко сделать датафрейм pandas
. Но прежде посмотрим, как сгрузить следующие 100 (и не только 100) постов со стены. Обычно при работе с API нужно принимать во внимание две вещи: 1) какое ограничение стоит на число запросов за один раз (число постов в нашем случае); 2) какое ограничение стоит на число запросов в минуту. Чтобы действовать в соответствии с этими ограничениями, поступим так: будем грузить каждые следующие 100 постов, добавлять их к нашему списку, потом немного ждать, и грузить еще 100 постов, и так до тех пор, пока не сгрузим желаемое количество.
Давайте для начала выберем это желаемое число постов. Пусть будет 400.
Теперь вопрос: по каким значениям нужно «пробегаться» в цикле, чтобы сгрузить посты с 100 по 400 (первые 100 уже сохранены в res
)? По целым значениям от 2 до 4 включительно, умножая эти значения на 100. В vk.get
есть опция offset
. Она позволяет сдвинуть начало отсчета постов на некоторое число. Так, если мы напишем api.wall.get(domain = group, count = 100, offset = 100, v = 5.103)
, мы получим посты с 100 по 200, так как начало отсчета сдвинулось на 100.
Реализуем описанное выше. Для цикла нам понадобится range()
, а для задержки после выгрузки каждой сотни постов нам пригодится функция sleep
:
from time import sleep
for i in range(1, 3):
res2 = api.wall.get(domain = group, count = 100, offset = 100 * i, v = 5.73)
items2 = res2['items']
items.extend(items2) # добавляем к первой сотне постов в items
sleep(0.5)
# опять выберем только нужные поля
full_list = []
for item in items:
L = [item['from_id'], item['id'], item['text'], item['date'],
item['likes']['count'], item['reposts']['count']]
full_list.append(L)
Оставлось превратить обновленный список items
(список списков) в датафрейм. Импортируем pandas
.
import pandas as pd
Создадим датафрейм:
df = pd.DataFrame(full_list)
df.head(10)
0 | 1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|---|
0 | -132 | 33156 | Выпускнику Вышки Илье Флаксу (программа "Элект... | 1586965441 | 7 | 1 |
1 | -132 | 33155 | 1586961686 | 14 | 0 | |
2 | -132 | 33152 | ‼Внимание, уважаемые абитуриенты! \n \n🤓Уже 19... | 1586453896 | 12 | 2 |
3 | -132 | 33145 | 1585033316 | 7 | 0 | |
4 | -132 | 33144 | Дистанционно День открытых дверей по направлен... | 1585032761 | 12 | 2 |
5 | -132 | 33143 | 1585031426 | 7 | 1 | |
6 | -132 | 33136 | Уважаемые абитуриенты! Все запалнированные мер... | 1584906692 | 13 | 0 |
7 | -132 | 33135 | «Студент не должен чувствовать себя покинутым» | 1584906569 | 14 | 1 |
8 | -132 | 33134 | ВШЭ совместно с Coursera запустила онлайн-курс... | 1584906526 | 29 | 7 |
9 | -132 | 33071 | 1579702735 | 17 | 2 |
Ура! Осталось только дать внятные названия столбцам и разобраться, почему дата представлена в таком виде. что делать со столбцами, мы уже знаем.
df.columns = ['from_id', 'id', 'text', 'date', 'nlikes', 'nrepo']
df.head(10)
from_id | id | text | date | nlikes | nrepo | |
---|---|---|---|---|---|---|
0 | -132 | 33156 | Выпускнику Вышки Илье Флаксу (программа "Элект... | 1586965441 | 7 | 1 |
1 | -132 | 33155 | 1586961686 | 14 | 0 | |
2 | -132 | 33152 | ‼Внимание, уважаемые абитуриенты! \n \n🤓Уже 19... | 1586453896 | 12 | 2 |
3 | -132 | 33145 | 1585033316 | 7 | 0 | |
4 | -132 | 33144 | Дистанционно День открытых дверей по направлен... | 1585032761 | 12 | 2 |
5 | -132 | 33143 | 1585031426 | 7 | 1 | |
6 | -132 | 33136 | Уважаемые абитуриенты! Все запалнированные мер... | 1584906692 | 13 | 0 |
7 | -132 | 33135 | «Студент не должен чувствовать себя покинутым» | 1584906569 | 14 | 1 |
8 | -132 | 33134 | ВШЭ совместно с Coursera запустила онлайн-курс... | 1584906526 | 29 | 7 |
9 | -132 | 33071 | 1579702735 | 17 | 2 |
С датой все интереснее. То, что указано в столбце date
, это дата в виде UNIX-времени (POSIX-времени). Это число секунд, прошедших с 1 января 1970 года. Несмотря на то, что такой формат даты-времени кажется необычным, он довольно широко распространен в разных системах и приложениях (см. подробнее здесь). Этот факт, конечно, радует, но хочется получить дату в более человеческом формате. Давайте напишем функцию для перевода UNIX-времени в формат год-месяц-день-часы-минуты-секунды. Для этого нам понадобится модуль datetime.
from datetime import datetime
def date_norm(date):
d = datetime.fromtimestamp(date) # timestamp - UNIX-время в виде строки
str_d = d.strftime("%Y-%m-%d %H:%M:%S") # %Y-%m-%d %H:%M:%S - год-месяц-день часы:мин:сек
return str_d
Применим нашу функцию к элементам столбца date и создадим новый – date_norm.
df['date_norm'] = df.date.apply(date_norm)
df.head()
from_id | id | text | date | nlikes | nrepo | date_norm | |
---|---|---|---|---|---|---|---|
0 | -132 | 33156 | Выпускнику Вышки Илье Флаксу (программа "Элект... | 1586965441 | 7 | 1 | 2020-04-15 18:44:01 |
1 | -132 | 33155 | 1586961686 | 14 | 0 | 2020-04-15 17:41:26 | |
2 | -132 | 33152 | ‼Внимание, уважаемые абитуриенты! \n \n🤓Уже 19... | 1586453896 | 12 | 2 | 2020-04-09 20:38:16 |
3 | -132 | 33145 | 1585033316 | 7 | 0 | 2020-03-24 10:01:56 | |
4 | -132 | 33144 | Дистанционно День открытых дверей по направлен... | 1585032761 | 12 | 2 | 2020-03-24 09:52:41 |
Можно разбить на части дату и время, а не сохранять одной строкой. Для этого нужно применить метод .split()
для строк и «раздвинуть» столбцы – после разбиения получить не один столбец, а сразу несколько.
df['date_norm'].str.split() # результат разбиения – набор списков
0 [2020-04-15, 18:44:01] 1 [2020-04-15, 17:41:26] 2 [2020-04-09, 20:38:16] 3 [2020-03-24, 10:01:56] 4 [2020-03-24, 09:52:41] ... 295 [2018-04-03, 19:38:43] 296 [2018-04-03, 19:35:02] 297 [2018-03-28, 16:22:38] 298 [2018-03-25, 22:41:05] 299 [2018-03-19, 10:51:37] Name: date_norm, Length: 300, dtype: object
df['date_norm'].str.split(expand = True) # теперь расширили – 2 столбца
0 | 1 | |
---|---|---|
0 | 2020-04-15 | 18:44:01 |
1 | 2020-04-15 | 17:41:26 |
2 | 2020-04-09 | 20:38:16 |
3 | 2020-03-24 | 10:01:56 |
4 | 2020-03-24 | 09:52:41 |
... | ... | ... |
295 | 2018-04-03 | 19:38:43 |
296 | 2018-04-03 | 19:35:02 |
297 | 2018-03-28 | 16:22:38 |
298 | 2018-03-25 | 22:41:05 |
299 | 2018-03-19 | 10:51:37 |
300 rows × 2 columns
new_dat = df['date_norm'].str.split(expand = True) # сохраняем
df['Date'] = new_dat[0] # первый столбец – в Date
df['Time'] = new_dat[1] # второй столбец – в Time
df.head()
from_id | id | text | date | nlikes | nrepo | date_norm | Date | Time | |
---|---|---|---|---|---|---|---|---|---|
0 | -132 | 33156 | Выпускнику Вышки Илье Флаксу (программа "Элект... | 1586965441 | 7 | 1 | 2020-04-15 18:44:01 | 2020-04-15 | 18:44:01 |
1 | -132 | 33155 | 1586961686 | 14 | 0 | 2020-04-15 17:41:26 | 2020-04-15 | 17:41:26 | |
2 | -132 | 33152 | ‼Внимание, уважаемые абитуриенты! \n \n🤓Уже 19... | 1586453896 | 12 | 2 | 2020-04-09 20:38:16 | 2020-04-09 | 20:38:16 |
3 | -132 | 33145 | 1585033316 | 7 | 0 | 2020-03-24 10:01:56 | 2020-03-24 | 10:01:56 | |
4 | -132 | 33144 | Дистанционно День открытых дверей по направлен... | 1585032761 | 12 | 2 | 2020-03-24 09:52:41 | 2020-03-24 | 09:52:41 |
Всё! Материалы о разных методах и функциях для vk.api
можно найти в официальной документации.