Друзья друзей Вконтакте на Ruby
September 23, 2009 in ProjectsЗахотелось мне написать нечто непосредственно полезное.
Немного логики
Если несколько твоих знакомых знакомы с Васей Пупкиным, то с большой вероятностью ты тоже с ним знаком. Логично?
Реализация
На «Моем круге», например есть такое понятие, как «второй круг» – друзья твоих друзей. На Вконтакте «второй круг» просто так не посмотришь. То есть можно, конечно, лазить по друзьям и высматривать знакомые лица (если Лицо не поставило идиотскую аватарку) и имена (если Лицо не пытается анонимизироваться). У меня на это нет времени.
Задача: написать скрипт, который собирает «второй круг» автоматически и сортирует по числу общих знакомых.
Много букoв?
Можешь просто скачать работающий скрипт с Github. В него только нужно дописать свои собственные логин/пароль. Кроме того, пригодятся гемы: mechanize
, json
, haml
.
Я бы сделал онлайн-версию, но, боюсь, паранойя помешает тебе им воспользоваться.
Авторизация
Над задачей автоматизированного логина в контакт бьются многие. Тысячи их. На Ruby эта задача решается буквально в пять строчек. Встречайте Mechanize:
# создаем наш виртуальный браузер agent = WWW::Mechanize.new # получаем форму логина login_form = agent.get('http://vkontakte.ru').form('login') # параметры авторизации... login_form.email = 'vas@pupkin.com' login_form.pass = 'gfhjkm' # авторизуемся agent.submit( agent.submit(login_form, login_form.buttons.first).forms.first )
Mechanize – это библиотека, реализующая доступ к сайтам с сохранением состояния – в первую очередь, куков. Как браузер. Еще она позволяет обрабатывать получаемые страницы, например, доставать из них формы и ссылки, или произвольные элементы с помощью CSS-селекторов и XPath. Рекомендую.
Последняя строчка скрипта такая же странная, как и то, что после логина тебя перебрасывает на какую-то страницу, а потом уже на твой профиль. Там на самом деле производится отправка еще одной формы, да.
Разбор друзей
Не поверишь, но Контакт отдает список друзей в json в теле страницы friends.php
. Ура! Не надо будет парсить HTML.
# достаем страницу с друзьями... friends_page = agent.get('http://vkontakte.ru/friends.php') # ...и выкусываем из нее строчку с json-объектом. friends_json_string = friends_page.body.match(/^\s+var friendsData = (\{.+\});$/)[1] # получаем хеш friends_json = JSON.parse friends_json_string # OH SHI...
Не совсем ура: json этот почему-то неправильный и средствами Ruby не парсится. Проверяем строчку валидатором JSON Lint. Обнаруживаем (пока) три проблемы: 1) одинарные кавычки; 2) числовые ключи без кавычек; 3) некорректные символы. Все это исправляется: да, я знаю, так можно попортить значения, но для данной задачи это неважно
friends_json_string = friends_json_string.gsub("'","\"").gsub(/(\d+):/,"\"$1\":").gsub(/[\x00-\x19]/," ") friends_json = JSON.parse friends_json_string # profit!
Анализ
Дальше все просто – собираем второй круг, для каждого товарища оттуда считаем количество общих знакомых, сортируем и выводим. Дальше идет более или менее псевдокод.
# {id => name, ...} names = {} # [id, id,...] my_friends = [] # {id => [id, id, id], ...} common_friends = {} # get_friends - это такая функция, которая возвращает массив типа [{:id => id, :name => name}, ...] get_friends.each do |friend| # собираем своих друзей my_friends << friend[:id] # запоминаем имена для удобства names[friend[:id]] = friend[:name] end my_friends.each do |friend_id| get_friends(friend_id).each do |his_friend| # добавляем друга в список общих знакомых common_friends[his_friend[:id]] ||= [] common_friends[his_friend[:id]] << friend_id # опять-таки запоминаем имя names[his_friend[:id]] = his_friend[:name] end end # отбрасываем своих друзей, а также людей, у которых только один общий знакомый common_friends.reject! {|id,friends| my_friends.include?(id) || (friends.length < 2)} # сортируем оставшихся по количеству общих знакомых common_friends.sort! {|a,b| b[1].length <=> a[1].length}
Все! Остается вывести красиво отформатированный список people
. Конечно, используя haml
.
!!! %html %body %h1="Total people in 2nd circle: #{people.length}" %ul -people.each do |id,friends| %li %div %a{ :href=>"http://vkontakte.ru/id#{id}"}=names[id] ="(#{friends.length})" %div =friends.map{|id| names[id]}.join ', '
Заключение
Есть мысль сделать более удобную библиотеку для доступа к данным Вконтакте, только я сомневаюсь в полезности такой библиотеки. Разве что проводить всякий статистический анализ, или автоматически рассылать сообщения/приглашения/спам.
Если кому-нибудь такое интересно, пишите в комменты.
Понравился пост? Купи мне кофе