В предыдущей статье мы на основе общих друзей ВКонтакте строили граф, а сегодня поговорим о том, как получить список друзей, друзей друзей и так далее. Предполагается, что вы уже прочли предыдущую статью, и я не буду описывать все заново.
Начнем с того, что просто скачать все id пользователей достаточно легко, список валидных id можно найти в Каталоге пользователей Вконтакте. Наша же задача — получить список друзей выбранного нами id пользователя, их друзей и рекурсивно сколь угодно глубоко, в зависимости от указанной глубины.
Код, опубликованный в статье, будет меняться с течением времени, поэтому более свежую версию можно найти в том же проекте на Github.
Как будем реализовывать:
- Задаем нужную нам глубину
- Отправляем исходные данные либо те id, которые надо исследовать на данной глубине
- Получаем ответ
Что будем использовать:
- Python 3.4
- Хранимые процедуры в ВКонтакте
Задаем нужную нам глубину
Что нам потребуется в начале — это указать глубину (deep), с которой мы хотим работать. Сделать можно это сразу в main.py:
print(a.deep_friends(2)) # такая строчка там уже есть
deep равное 1 — это наши друзья, 2 — это друзья наших друзей и так далее. В итоге мы получим словарь, ключами которого будут id пользователей, а значениями — их список друзей.
Не спешите выставлять большие значения глубины. При 14 моих исходных друзьях и глубине равной 2, количество ключей в словаре составило 2427, а при глубине равной 3, у меня не хватило терпения дождаться завершения работы скрипта, на тот момент словарь насчитывал 223.908 ключей. По этой причине мы не будем визуализировать такой огромный граф, ведь вершинами будут ключи, а ребрами — значения.
Отправление данных
Добиться нужного нам результата поможет уже известный метод friends.get, который будет расположен в хранимой процедуре, имеющей следующий вид:
var targets = Args.targets;
var all_friends = {};
var req;
var parametr = "";
var start = 0;
// из строки с целями вынимаем каждую цель
while(start<=targets.length){
if (targets.substr(start, 1) != "," && start != targets.length){
parametr = parametr + targets.substr(start, 1);
}
else {
// сразу делаем запросы, как только вытащили id
req = API.friends.get({"user_id":parametr});
if (req) {
all_friends = all_friends + [req];
}
else {
all_friends = all_friends + [0];
}
parametr = "";
}
start = start + 1;
}
return all_friends;
Напоминаю, что хранимую процедуру можно создать в настройках приложения, пишется она на VkScript, как и execute, документацию можно прочесть здесь и здесь.
Теперь о том, как она работает. Мы принимаем строку из 25 id, разделенных запятыми, вынимаем по одному id, делаем запрос к friends.get, а нужная нам информация будет приходить в словаре, где ключи — это id, а значения — список друзей данного id.
При первом запуске мы отправим хранимой процедуре список друзей текущего пользователя, id которого указан в настройках. Список будет разбит на несколько частей (N/25 — это и число запросов), связано это с ограничением количества обращений к API ВКонтакте.
Получение ответа
Всю полученную информацию мы сохраняем в словаре, например:
{1:(0, 2, 3, 4), 2: (0, 1, 3, 4), 3: (0, 1, 2)}
Ключи 1, 2 и 3 были получены при глубине равной 1. Предположим, что это и были все друзья указанного пользователя (0).
Если глубина больше 1, то далее воспользуемся разностью множеств, первое из которых — значения словаря, а второе — его ключи. Таким образом, мы получим те id (в данном случае 0 и 4), которых нет в ключах, разобьем их опять на 25 частей и отправим хранимой процедуре.
Тогда в нашем словаре появятся 2 новых ключа:
{1:(0, 2, 3, 4), 2: (0, 1, 3, 4), 3: (0, 1, 2), 0: (1, 2, 3), 4:(1, 2, ….)}
Сам же метод deep_friends() выглядит следующим образом:
def deep_friends(self, deep):
result = {}
def fill_result(friends):
for i in VkFriends.parts(friends):
r = requests.get(self.request_url('execute.deepFriends', 'targets=%s' % VkFriends.make_targets(i))).json()['response']
for x, id in enumerate(i):
result[id] = tuple(r[x]["items"]) if r[x] else None
for i in range(deep):
if result:
# те айди, которых нет в ключах + не берем id:None
fill_result(list(set([item for sublist in result.values() if sublist for item in sublist]) - set(result.keys())))
else:
fill_result(requests.get(self.request_url('friends.get', 'user_id=%s' % self.my_id)).json()['response']["items"])
return result
Конечно, это быстрее, чем кидать по одному id в friends.get без использования хранимой процедуры, но времени все равно занимает порядочно много.
В заключение
Если бы friends.get был похож на users.get, а именно мог принимать в качестве параметра user_ids, то есть перечисленные через запятую id, для которых нужно вернуть список друзей, а не по одному id, то код был бы намного проще, да и количество запросов было в разы меньше.
Данные мы собрали, сегодня на этом все. Далее мы будем с этими данными играться.
Автор: STLEON