Друзья друзей Вконтакте на Ruby

September 23, 2009 data scraping Mechanize Ruby Вконтакте

Захотелось мне написать нечто непосредственно полезное.

Немного логики

Если несколько твоих знакомых знакомы с Васей Пупкиным, то с большой вероятностью ты тоже с ним знаком. Логично?

Реализация

На «Моем круге», например есть такое понятие, как «второй круг» – друзья твоих друзей. На Вконтакте «второй круг» просто так не посмотришь. То есть можно, конечно, лазить по друзьям и высматривать знакомые лица (если Лицо не поставило идиотскую аватарку) и имена (если Лицо не пытается анонимизироваться). У меня на это нет времени.

Задача: написать скрипт, который собирает «второй круг» автоматически и сортирует по числу общих знакомых.

Много бук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 ', '

Заключение

Есть мысль сделать более удобную библиотеку для доступа к данным Вконтакте, только я сомневаюсь в полезности такой библиотеки. Разве что проводить всякий статистический анализ, или автоматически рассылать сообщения/приглашения/спам.

Если кому-нибудь такое интересно, пишите в комменты.

Buy Me a Coffee at ko-fi.com