Ця робота присвячена взаємодії з API ВКонтакте .
Зауваження: Якщо ви не знайомі з терміном "API", то рекомендую прочитати статтю: What is an API? In English, please.Щоб почати працювати з API від вас вимагається зареєструвати новий додаток. Для цього треба зайти на форму створення нового Standalone додатки com/editapp?act=create> https://vk.com/editapp?act=create і дотримуйтесь інструкцій. Вашому додатку буде призначений ідентифікатор, який буде потрібно для виконання роботи.
де:
- 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" passДля виконання цього завдання потрібно отримати список всіх друзів для зазначеного користувача, відфільтрувати тих у кого вік не зазначений або вказані тільки день і місяць народження.
Для виконання запитів до API ми будемо використовувати бібліотеку requests :
$ Pip install requestsСписок користувачів можна отримати за допомогою методу friends.get . Нижче наведено приклад звернення до цього методу для отримання списку всіх друзів зазначеного користувача:
domain = "https://api.vk.com/method" access_token = user_id = 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: коефіцієнт експоненціального наростання затримки "" "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 "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 "passКоментар: У реалізації функції messages_get_history потрібно враховувати, що API ВКонтакте встановлює обмеження на кількість запитів в одну секунду (на сьогоднішній день це три запиту). Число повідомлень, яке ви можете отримати за одне запит - 200.
Нижче наведено приклад використання функції messages_get_history:
user_id = 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): 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" "" passПошук спільнот на графі (community detection) є добре вивченою завданням, а ряд найбільш відомих алгоритмів реалізований в бібліотеці igraph.
$ Pip install python-igraph $ pip install numpy $ pip install cairocffi $ brew install cairo 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) Com/editapp?Com/method/METHOD_NAME?
Get?
Com/authorize?
Get?