Лабораторная работа №4. Работа с API ВКонтакте
Эта работа посвящена взаимодействию с API ВКонтакте.
Чтобы начать работать с API от вас требуется зарегистрировать новое приложение. Для этого зайдите на форму создания нового Standalone приложения https://vk.com/editapp?act=create и следуйте инструкциям. Вашему приложению будет назначен идентификатор, который потребуется для выполнения работы.
Запросы к API ВКонтакте имеют следующий формат (из документации):
https://api.vk.com/method/METHOD_NAME?PARAMETERS&access_token=ACCESS_TOKEN&v=V
где:
METHOD_NAME
- это название метода API, к которому Вы хотите обратиться.PARAMETERS
- входные параметры соответствующего метода API, последовательность парname=value
, объединенных амперсандом&
.ACCESSS_TOKEN
- ключ доступа.V
- используемая версия API (в настоящий момент 5.53).
Например, чтобы получить список друзей, с указанием их пола, нужно выполнить следующий запрос:
https://api.vk.com/method/friends.get?fields=sex&access_token=0394a2ede332c9a13eb82e9b24631604c31df978b4e2f0fbd2c549944f9d79a5bc866455623bd560732ab&v=5.53
Чтобы получить токен доступа вы можете воспользоваться написанным для вас скриптом access_token.py
следующим образом:
$ python access_token.py YOUR_CLIENT_ID -s friends,messages
где вместо YOUR_CLIENT_ID
необходимо подставить идентификатор вашего приложения.
После выполнения команды откроется новая вкладка браузера, из адресной строки которого вам необходимо скопировать токен доступа.
Далее приведено содержимое файла access_token.py
:
import webbrowser
import argparse
def get_access_token(client_id, scope):
assert isinstance(client_id, int), 'clinet_id must be positive integer'
assert isinstance(scope, str), 'scope must be string'
assert client_id > 0, 'clinet_id must be positive integer'
url = """\
https://oauth.vk.com/authorize?client_id={client_id}&\
redirect_uri=https://oauth.vk.com/blank.hmtl&\
scope={scope}&\
&response_type=token&\
display=page\
""".replace(" ", "").format(client_id=client_id, scope=scope)
webbrowser.open_new_tab(url)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("client_id", help="Application Id", type=int)
parser.add_argument("-s",
dest="scope",
help="Permissions bit mask",
type=str,
default="",
required=False)
args = parser.parse_args()
get_access_token(args.client_id, args.scope)
Задание №1. Требуется написать функцию прогнозирования возраста пользователя по возрасту его друзей.
def get_friends(user_id, fields):
""" Returns a list of user IDs or detailed information about a user's friends """
assert isinstance(user_id, int), "user_id must be positive integer"
assert isinstance(fields, str), "fields must be string"
assert user_id > 0, "user_id must be positive integer"
# PUT YOUR CODE HERE
pass
Для выполнения этого задания нужно получить список всех друзей для указанного пользователя, отфильтровать тех у кого возраст не указан или указаны только день и месяц рождения.
Для выполнения запросов к API мы будем использовать библиотеку requests
:
$ pip install requests
Список пользователей можно получить с помощью метода friends.get
. Ниже приведен пример обращения к этому методу для получения списка всех друзей указанного пользователя:
domain = "https://api.vk.com/method"
access_token = # PUT YOUR ACCESS TOKEN HERE
user_id = # PUT USER ID HERE
query_params = {
'domain' : domain,
'access_token': access_token,
'user_id': user_id,
'fields': 'sex'
}
query = "{domain}/friends.get?access_token={access_token}&user_id={user_id}&fields={fields}&v=5.53".format(**query_params)
response = requests.get(query)
Функция requests.get
выполняет GET запрос и возвращает объект Response
, который представляет собой ответ сервера на посланный нами запрос.
Объект Response
имеет множество атрибутов:
>>> response.<tab>
response.apparent_encoding response.history response.raise_for_status
response.close response.is_permanent_redirect response.raw
response.connection response.is_redirect response.reason
response.content response.iter_content response.request
response.cookies response.iter_lines response.status_code
response.elapsed response.json response.text
response.encoding response.links response.url
response.headers response.ok
Нас будет интересовать только метод response.json
, который возвращает JSON объект:
>>> response.json()
{'response': {'count': 136,
'items': [{'first_name': 'Drake',
'id': 1234567,
'last_name': 'Wayne',
'online': 0,
'sex': 1},
{'first_name': 'Gracie'
'id': 7654321,
'last_name': 'Habbard',
'online': 0,
'sex': 0},
...
>>> response.json()['response']['count']
136
>>> response.json()['response']['items'][0]['first_name']
'Drake'
Поле count
содержит число записей, а items
список словарей с информацией по каждому пользователю.
Выполняя запросы мы не можем быть уверены, что не возникнет ошибок. Возможны различные ситуации, например:
- есть неполадки в сети;
- удаленный сервер по какой-то причине не может обработать запрос;
- мы слишком долго ждем ответ от сервера.
В таких случаях необходимо попробовать повторить запрос. При этом повторные запросы желательно посылать не через константные промежутки времени, а по алгоритму экспоненциальной задержки.
Напишите функцию get()
, которая будет выполнять GET-запрос к указанному адресу, а при необходимости повторять запрос указанное число раз по алгоритму экспоненциальной задержки:
def get(url, params={}, timeout=5, max_retries=5, backoff_factor=0.3):
""" Выполнить GET-запрос
:param url: адрес, на который необходимо выполнить запрос
:param params: параметры запроса
:param timeout: максимальное время ожидания ответа от сервера
:param max_retries: максимальное число повторных запросов
:param backoff_factor: коэффициент экспоненциального нарастания задержки
"""
# PUT YOUR CODE HERE
pass
>>> get("https://httpbin.org/get")
>>> <Response [200]>
>>> get("https://httpbin.org/delay/2", timeout=1)
ReadTimeout: HTTPSConnectionPool(host='httpbin.org', port=443): Read timed out. (read timeout=1)
>>> get("https://httpbin.org/status/500")
HTTPError: 500 Server Error: INTERNAL SERVER ERROR for url: https://httpbin.org/status/500
>>> get("https://noname.com", timeout=1)
ConnectionError: HTTPSConnectionPool(host='noname.com', port=443): Max retries exceeded with url: /
На текущий момент вы должны заполнить тело функции get_friends
так, чтобы она возвращала список друзей для указанного пользователя. Аргумент fields
представляет из себя строку, в которой через запятую указываются какие поля необходимо получить по каждому пользователю.
Теперь мы можем написать функцию age_predict
для "наивного" прогнозирования возраста пользователя с идентификатором user_id
(под "наивным" прогнозированием подразумевается вычисление среднего арифметического или медианы):
def age_predict(user_id):
"""
>>> age_predict(???)
???
"""
assert isinstance(user_id, int), "user_id must be positive integer"
assert user_id > 0, "user_id must be positive integer"
# PUT YOUR CODE HERE
pass
try...except
, где except
будет содержать только pass
.
Задание №2: Требуется написать функцию, которая бы выводила график частоты переписки с указанным пользователем.
Давайте начнем с того, что получим всю или часть переписки с указанным пользователем. Для этого вам потребуется реализовать метод API messages.getHistory по аналогии с тем, как вы реализовывали получение списка друзей пользователя:
def messages_get_history(user_id, offset=0, count=20):
assert isinstance(user_id, int), "user_id must be positive integer"
assert user_id > 0, "user_id must be positive integer"
assert isinstance(offset, int), "offset must be positive integer"
assert offset >= 0, "user_id must be positive integer"
assert count >= 0, "user_id must be positive integer"
# PUT YOUR CODE HERE
pass
messages_get_history
нужно учитывать, что API ВКонтакте устанавливает ограничение на число запросов в одну секунду (на сегодняшний день это три запроса). Число сообщений, которое вы можете получить за одно запрос - 200.
Далее приведен пример использования функции messages_get_history
:
>>> user_id = # PUT USER ID HERE
>>> history = messages_get_history(user_id)
>>> from pprint import pprint as pp
>>> message = history['response']['items'][0]
>>> pp(message)
{'body': 'Это текст сообщения.',
'date': 1474811631,
'from_id': USER_ID_HERE,
'id': 168989,
'out': 0,
'read_state': 1,
'user_id': USER_ID_HERE}
Каждое сообщение содержит следующие поля:
body
- текст сообщения;date
- дата отправки сообщения в формате unixtime;from_id
- идентификатор автора сообщения;id
- идентификатор сообщения;out
- тип сообщения (0 — полученное, 1 — отправленное, не возвращается для пересланных сообщений);read_state
- статус сообщения (0 — не прочитано, 1 — прочитано, не возвращается для пересланных сообщений);user_id
- идентификатор пользователя, в диалоге с которым находится сообщение.
Нас интересует поле date
, которое представляет дату отправки сообщения в формате unixtime. Чтобы изменить формат даты представления можно воспользоваться функцией strftime
из модуля datetime
:
>>> from datetime import datetime
>>> date = datetime.fromtimestamp(message['date']).strftime("%Y-%m-%d")
'2016-09-25'
Формат представления указывается в виде строки форматирования, например, %Y-%m-%d
- год, месяц и день, соответственно.
На данный момент вашей задачей является написать функцию count_dates_from_messages
, которая возвращает два списка: список дат и список частоты каждой даты, а принимает список сообщений:
def count_dates_from_messages(messages):
#PUT YOUR CODE HERE
pass
Далее мы воспользуемся сервисом Plot.ly, который предоставляет API для рисования графиков. Запрос содержит информацию о точках, которые нужно отобразить на графике. Вам нужно зарегистрироваться и получить ключ доступа (API KEY
). Для более простого взаимодействия с Plot.ly мы воспользуемся готовым модулем (существуют решения и для других языков):
$ pip3 install plotly
Перед началом его использования нужно провести предварительную настройку, указав ключ доступа и имя пользователя:
import plotly
plotly.tools.set_credentials_file(username='YOUR_USER_NAME', api_key='YOUR_API_KEY')
Ниже приведен пример построения графика, где переменная x
содержит даты, а y
- количество сообщений в этот день:
import plotly.plotly as py
import plotly.graph_objs as go
from datetime import datetime
x = [datetime(year=2016, month=09, day=23),
datetime(year=2016, month=09, day=24),
datetime(year=2016, month=09, day=25)]
data = [go.Scatter(x=x,y=[142, 50, 8])]
py.iplot(data)
Созданный график вы можете найти в своем профиле:
Задание №3: Требуется написать функцию get_network()
, которая для указанного списка пользователей users_ids
строит граф и представляет его либо в виде матрицы смежности (as_edgelist=False
), либо в виде списка ребер (as_edgelist=True
). В полученном графе необходимо выделить сообщества и визуализировать результат.
def get_network(users_ids, as_edgelist=True):
""" Building a friend graph for an arbitrary list of users """
# PUT YOUR CODE HERE
pass
Поиск сообществ на графе (community detection) является хорошо изученной задачей, а ряд наиболее известных алгоритмов реализован в библиотеке igraph
.
$ pip install python-igraph
$ pip install numpy
$ pip install cairocffi
$ brew install cairo # Только для MacOS X. Для других ОС см. https://www.cairographics.org/download/
from igraph import Graph, plot
import numpy as np
vertices = [i for i in range(7)]
edges = [
(0,2),(0,1),(0,3),
(1,0),(1,2),(1,3),
(2,0),(2,1),(2,3),(2,4),
(3,0),(3,1),(3,2),
(4,5),(4,6),
(5,4),(5,6),
(6,4),(6,5)
]
g = Graph(vertex_attrs={"label":vertices},
edges=edges, directed=False)
N = len(vertices)
visual_style = {}
visual_style["layout"] = g.layout_fruchterman_reingold(
maxiter=1000,
area=N**3,
repulserad=N**3)
plot(g, **visual_style)
g.simplify(multiple=True, loops=True)
communities = g.community_edge_betweenness(directed=False)
clusters = communities.as_clustering()
print(clusters)
Clustering with 7 elements and 2 clusters
[0] 0, 1, 2, 3
[1] 4, 5, 6
pal = igraph.drawing.colors.ClusterColoringPalette(len(clusters))
g.vs['color'] = pal.get_many(clusters.membership)