Python Developer
0,0
рейтинг
29 апреля 2014 в 12:13

Разработка → Анализ дружеских связей VK с помощью Python из песочницы

Совсем недавно на Хабре появилась статья о реализации дружеских связей в ВКонтакте с помощью Wolfram Mathematica. Идея мне понравилась, и, естественно, захотелось сделать такой же граф, используя Python и d3. Вот, что из этого получилось.

Внимание! В статье будут присутствовать части кода, описывая самые важные действия, но следует учесть, что проект претерпит еще не одно изменение в своей кодовой базе. Заинтересовавшиеся могут найти исходники на GitHub.

Разобьем задачу по элементам:
  • Создание и авторизация приложения.
  • Получение данных.
  • Визуализация графа.

Что для этого нам понадобится:
  • Python 3.4
  • requests
  • d3
  • Mozilla FireFox, так как в Chrome нельзя использовать XMLHttpRequest для загрузки локальных файлов (никто не мешает сделать python -m http.server 8000)

Создание и авторизация приложения


Чтобы получить доступ к API ВКонтакте, нам необходимо создать Standalone-приложение, после чего мы сможем использовать нужные нам методы API, которые будут описаны далее. Приложение создается здесь — выберем Standalone-приложение. Нас попросят ввести код-подтверждения, высланный на мобильный, после чего мы попадем на страницу управления приложением. На вкладке Настройки нам пригодится ID приложения для получения access_token.
Далее нам надо авторизовать наше приложение. Этот процесс состоит из 3х этапов.

Аутентификации пользователя на сайте ВКонтакте

Для этого сформируем url, как показано ниже:


https://oauth.vk.com/authorize?client_id=IDприложения&scope=friends,offline&redirect_uri=https://oauth.vk.com/blank.html&display=page&v=5.21&response_type=token

Цитируя vk.com/dev/auth_mobile:
APP_ID – идентификатор Вашего приложения;
PERMISSIONS – запрашиваемые права доступа приложения;
DISPLAY – внешний вид окна авторизации, поддерживаются: page, popup и mobile.
REDIRECT_URI – адрес, на который будет передан access_token.
API_VERSION – версия API, которую Вы используете.

В нашем случае PERMISSIONS — это доступ к друзьям и к API в любое время со стороннего сервера (бессрочный токен). Если адрес сформирован правильно, нам предложат ввести логин и пароль.

Разрешение доступа к своим данным

Далее разрешаем приложению доступ к необходимой информации:

Получение access_token

После авторизации приложения клиент будет перенаправлен на REDIRECT_URI. Нужная нам информация будет заключена в ссылке.

https://oauth.vk.com/blank.html#access_token=ACCESS_TOKEN&expires_in=0&user_id=USER_ID

Редактируем файл settings.py, вставляя туда полученные access_token и user_id. Теперь мы можем осуществлять запросы к API ВКонтакте.

Получение данных


Для начала разберем методы, которые будем использовать для данной цели.

Поскольку нужна хоть какая-то информация об id пользователя, по которому будет строиться граф, нам пригодиться users.get. Он принимает как один id, так и несколько, список полей, информация из которых нам необходима, а также падеж, в котором будет склоняться фамилия и имя. Мой метод base_info() получает список id и возвращает информацию о пользователе с фотографией.

def base_info(self, ids):
		"""read https://vk.com/dev/users.get"""
		r = requests.get(self.request_url('users.get', 'user_ids=%s&fields=photo' % (','.join(map(str, ids))))).json()
		if 'error' in r.keys():
			raise VkException('Error message: %s. Error code: %s' % (r['error']['error_msg'], r['error']['error_code']))
		r = r['response']
		# Проверяем, если id из settings.py не деактивирован
		if 'deactivated' in r[0].keys():
			raise VkException("User deactivated")
		return r

Это может быть важно для тех, кто захочет отправлять в него id из friends.getMutual, таким образом произведя на свет огромное число запросов. Об этом позже.
Теперь нам надо получить информацию о друзьях пользователя, в чем нам и поможет метод friends.get. Из всех его параметров, перечисленных в документации, используем user_id, который находится в нашем setting.py и fields. Дополнительными полями будут id друзей, их имена, фамилии и фотографии. Ведь хочется, чтобы в узлах были миниатюры их фотографий.

def friends(self, id):
		"""
		read https://vk.com/dev/friends.get
		Принимает идентификатор пользователя
		"""
		r = requests.get(self.request_url('friends.get',
				'user_id=%s&fields=uid,first_name,last_name,photo' % id)).json()['response']
		#self.count_friends = r['count']
		return {item['id']: item for item in r['items']}

Далее наступает самое интересное.
Список id общих друзей между двумя пользователями возвращает метод friends.getMutual. Это хорошо, потому что мы получаем только id, а более расширенная информация у нас уже есть, благодаря friends.get. Но никто не запрещает сделать вам лишнюю сотню-другую запросов, используя users.get. Схемы расположены чуть-чуть пониже.
Теперь определимся, как будем использовать friends.getMutual. Если у пользователя N-друзей, то надо сделать N-запросов, чтобы по каждому другу мы получили список общих друзей. К тому же нам надо будет делать задержки, чтобы у нас было допустимое количество запросов в секунду.
Предположим, что у сканируемого нами id есть 25 друзей.
Всего 52 запроса — это слишком многовато, поэтому вспомним, что users.get может принимать список id:
25 друзей — 28 запросов, но как писалось выше, информация у нас уже имеется, благодаря friends.get.

И тут нам пригодится execute, который позволит запустить последовательность методов. У него есть единственный параметр code, он может содержать до 25 обращений к методам API.
То есть в итоге код в VKScript будет примерно таким:

return {
“id": API.friends.getMutual({"source_uid":source, "target_uid":target}), // * 25
...
};

Найдитесь те, кто напишет, как сократить данный код, не используя все время API.friends.getMutual.
Теперь нам надо всего лишь отправлять партиями id друзей по 25 в каждой. На нашем примере схема будет выглядеть так:

А ведь мы могли с помощью for отправлять каждого друга в friends.getMutual, а потом еще узнавать более детальную информацию через users.get.
Далее составим человеко понятную структуру, где уже вместо id друга и списка id ваших общих друзей, будет информация из friends.get. В итоге получим нечто вроде:

[({Ваш друг}, [{общий друг}, {еще один общий друг}]),({Ваша подруга}, None)]

В словарях находится id, имя, фамилия, фото, в списках — словари общих друзей, если общих друзей нет, то None. Кортежами все это разделяется.

def common_friends(self):
		"""
		read https://vk.com/dev/friends.getMutual and read https://vk.com/dev/execute
		Возвращает в словаре кортежи с инфой о цели и списком общих друзей с инфой
		"""
		def parts(lst, n=25):
			""" разбиваем список на части - по 25 в каждой """
			return [lst[i:i + n] for i in iter(range(0, len(lst), n))]

		result = []
		for i in parts(list(self.all_friends.keys())):
			# Формируем code (параметр execute)
			code = 'return {'
			for id in i:
				code = '%s%s' % (code, '"%s": API.friends.getMutual({"source_uid":%s, "target_uid":%s}),' % (id, 
								self.my_id, id))
			code = '%s%s' % (code, '};')

			for key, val in requests.get(self.request_url('execute', 'code=%s' % code)).json()['response'].items():
				if int(key) in list(self.all_friends.keys()):
					# берем инфу из уже полного списка
					result.append((self.all_friends[int(key)], [self.all_friends[int(i)] for i in val] if val else None))

		return result

Итак, если хочется посмотреть свой список друзей и общих с ними друзей, запускаем:

python main.py

Визуализация графа


Выбор пал на d3, а именно на Curved Links. Для этого надо сгенерировать json, который будет примерно такого содержания:

{ 
"nodes": [ 
        {"name":"Myriel","group":1, "photo": "path"}, 
        {"name":"Napoleon","group":1, "photo": "path"}, 
        {"name":"Mlle.Baptistine","group":1, "photo": "path"} 
        ], 
"links":[ 
        {"source":1,"target":0,"value":1}, 
        {"source":2,"target":0,"value":8} 
        ] 
}

Немного видоизменяя index.html, узлами становятся фотографии друзей.

Если хочется сразу визуализировать граф:

python 2d3.py

В папке web появится файл miserables.json. Не забываем открывать index.html в Mozilla FireFox или используем python -m http.server 8000 и открываем в Chrome.

Визуализация подтормаживает при большом количестве друзей, поэтому на будущее я думаю об использовании WebGL.

Так выглядит граф дружеских связей одного из моих друзей. Связи — это все.

Конечно, мне было интересно, у кого работает быстрее.

В статье, которая меня вдохновила, написано:
На моих 333 друзьях это заняло 119 секунд.

На момент написания этой статьи, у Himura в ВКонтакте был 321 друг. У меня это заняло 9 секунд (работа всей программы, а не одного friends.getMutual).

В заключение


Всю необходимую информацию об использованных методах можно найти в щедро написанной документации ВКонтакте, однако мной была обнаружена пара ошибок: не была описана ошибка с кодом 15 ('error_msg': 'Access denied: user deactivated', 'error_code': 15), догадаться можно, что она значит, и uid вместо user_id в документации к методу friends.get. Спустя 2 дня:


Как говорилось вначале, проект можно найти на GitHub, буду рад, если он понравится ещё кому-то и я получу много вкусных пулл реквестов…

UPD (27.05.2014):
Как мне подсказал WTFRU7, я добавил возможность использования хранимых процедур. Для этого нужно перейти по ссылке.
Создаем хранимую процедуру getMutual. Копируем содержимое execute_getMutual.js в форму и сохраняем. Не забываем скачать более новую версию. Финальный вид нашей схемы будет таким:

UPD (16.06.2014):
Получаем бессрочный токен.
UPD (11.07.2014):
Добавлены схемы-пояснения.
UPD (14.11.2014):
Продолжение
Лев Тонких @STLEON
карма
13,0
рейтинг 0,0
Python Developer
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (39)

  • +5
    habrahabr.ru/post/144758/
    Всё уже сделано до вас.
    • +1
      Я об этом знал. В комментах было написано. У меня как раз с использованием VkScript.
  • 0
    Ну так как, подтеврдилась теория шести рукопожатий или нет?)
    з.ы. а «изолированные» друзья — это любовницы или боты?)
    • 0
      Теорию пока не проверял, в изолированных и я есть)
  • –1
    Нафига?
  • +1
    Спасибо! Сгенерировал себе красивый такой граф.

    Кстати, то что сгенерировалось в web/ проще всего посмотреть через встроенный HTTP-сервер у питона:
    Для Python 2.x:

    python -m SimpleHTTPServer [PORT]

    Для Python 3.x:

    python -m http.server [PORT]
    • 0
      Так я это и написал, только для 3ей ветки
  • +1
    Интерактивный граф друзей (нативное ВК-приложение).
    • 0
      А вы сами-то его пробовали? Оно такое быстрое… У меня связи минуты 2 грузило, при том, что друзей меньше 20.
      • 0
        Откройте вкладку и идите по своим делам. Сделать пару кликов явно проще, чем возиться со скриптами. И внешний вид мне больше нравится.
  • 0
    Совет дня: вместо того, чтобы долбить execute и передавать в параметр code супердлинную строку метода — пользуйте хранимые процедуры. И траффик сэкономите и удобнее на порядок. Актуально для длинных методов, ибо у ВК вроде ограничение на длинну get-запроса под 2000 символов — от этого сильно страдает метод users.get — они обещают 1000 id максимум, а на практике выходит 375, если id из промежутка [100000000;+бесконечность).

    User deactivated — про это упоминается в документации, просто Вы немного не там смотрели: vk.com/dev/fields — это описание объекта users. Там же найдете скрытые поля типа интересов и тд, которые не описаны в документации к методу, но работают. Нисколько не придираюсь к документации ВК — она очень мне нравится, но многое у них, конечно, упущено — если поэкспериментировать с полями запросов можно вытаскивать очень хитрую информацию.

    Еще для такого рода приложений круто получать токен в автоматическом режиме — без веббраузера.
    • 0
      Используйте POST запрос, чтобы передавать 1000 id
      • 0
        Спасибо, но где об этом написано в доке?
        • 0
          Не знаю.
  • 0
    По поводу хранимых процедур — спасибо, буду пробовать.
    • 0
      да, обязательно попробуйте — очень удобно!
  • 0
    А сам пользователь там фиксируется? Сначала получаете список друзей, потом — связь пользователя и этих людей. Получается, что в выдаче его не будет.
    • 0
      Вы сами ответили на свой вопрос.
  • 0
    У меня getMutual для некоторых друзей выдает список в 500 человек, которых я ваще не знаю.
    • 0
      Такое бывает, если добавлять людей, которых вы не знаете.
      • 0
        Я их точно не добавлял. В подписчиках их тоже нет.
        • 0
          А вот тут тоже самое?
          • 0
            Вот ведь интересно получается. Сейчас все правильно работает. И через скрипт и через форму.
            Вчера через форму тоже выдавал 503 человека. Ну, не вчера, а в 7 утра.
  • 0
    Вот ведь интересно получается. Сейчас все правильно работает. И через скрипт и через форму.
    Вчера через форму тоже выдавал 503 человека. Ну, не вчера, а в 7 утра.
    • 0
      Извините.
  • 0
    Круто! Обязательно попробую как-нибуть )

    Скорость работы, конечно, поразительная. Сразу видно что Python ориентирован на работу с сетью лучше чем Mathematica. Но, за-то, он не сможет выделить сообщества в одну строчку :=P
    • 0
      Так можно и еще ускорить с помощью хранимых процедур, но я встал на проблеме с VkScript, агент поддержки ВКонтакте сказал, что
      В VKSctipt, видимо, провернуть такое не получится.
      Про выделение сообществ в одну строчку — такой задачи изначально не стояло, утверждаете, что не сможет. Вы пробовали?
      • 0
        Нет, я не пробовал. Но полагаю что средства для этого будут использованы явно не нативные. А еще, мне кажется что просто получения и визуализации данных недостаточно, обязательно должна быть какая-то аналитика. И в данном случае сообщества — самая крутая аналитика
    • 0
      + дело не в Python или Mathematica…
      Как я понял, вы кидали в friends.getMutual пары source-target, что очень долго, а я делал это по 25 пар в запросе, используя execute
      • 0
        о, я неее, не додумал/доискал этих шутк. Когда будет возможность внимательно прочитать Вашу статью, наверно, и моя система начнет работать быстрее
        • 0
          Вот видите. Перед тем, чтобы что-то утверждать, надо попробовать, а перед разработкой — прочесть документацию.
          • 0
            Ахаха, было бы время заниматься «разработкой» — обязательно… Моя статья как и система была написана жертвуя учебой и сном… ((
            Если бы под безумной идеей нарисовать граф друзей было бы хоть какое-то обоснование кроме инвайта на хабр, обязательно поисследовал бы глубже.
            • 0
              По-моему, инвайт на Хабр — вполне такое весомое обоснование.
              • 0
                Когда я начинал делать эту штуку, я ещё не знал что она будет достаточно крута для хабра. По этому разработка — по-моиму слишком громкое слово, хотя очень приятно. Кстати, спасибо огромное за интерес к статье, меня впервые цитировали
        • 0
          Кстати, в комментариях к Вашей статье execute пару раз упоминался
  • 0
    А на user_id я и сам наткнулся когда анализировал чужие страницы ))
    У них на самом деле есть 2 документации по API — одна, кажется, старая и ее не обновляют, а во второй больше намного параметров и возможностей. И во второй то все верно написано. В последних блокнотах я исправил эту ошибку, а вот скрины обновлять лень
  • 0
    Теперь работает с хранимыми процедурами. Обновление в конце статьи.
  • 0
    Добавил получение бессрочного токена
  • 0
    В статью добавлены схемы-пояснения

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.