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

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

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

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

Реализация

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

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

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

Заключение

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

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

Комментарии

  • Виктор 23 сентября 2009

    Я понимаю что статья скорее ознакомительная чем реальная но если захотите продолжать развивать тему парсинга вконтакта можете продолжить один из существующих проектов: http://github.com/oleganza/vkontakte.ruby
    или форкните и докручивайте то, что нужно

    • Леонид Шевцов 23 сентября 2009

      Вашу библиотеку я видел, но мне хочется чего-то объектного, в духе ActiveResource. Если уж делать библиотеку, то есть: я пока не вижу ей применения.

  • Alex 23 сентября 2009

    У меня почему-то не работает скрипт – ошибка:
    /home/alex/vk.rb:56: undefined method `each’ for nil:NilClass (NoMethodError)

    Что подскажете?

    • Леонид Шевцов 23 сентября 2009

      Странно, скрипт почему-то не нашел список ваших друзей. А на парсинг JSON он не ругался?

  • Alex 24 сентября 2009

    кажется, в первый раз налажал, и не ту версию скрипта запускал, без пароля и мыла.

    В любом случае, сейчас выдает такое
    50
    49

    2
    1
    (haml):13:in `render’: compile error (SyntaxError)
    (haml):13: syntax error, unexpected $end, expecting kEND
    …l_very_temp));}\n», 0, false);
    ^
    from /usr/lib/ruby/gems/1.8/gems/haml-2.2.5/lib/haml/engine.rb:167:in `render’
    from /usr/lib/ruby/gems/1.8/gems/haml-2.2.5/lib/haml/engine.rb:167:in `instance_eval’
    from /usr/lib/ruby/gems/1.8/gems/haml-2.2.5/lib/haml/engine.rb:167:in `render’
    from /home/alex/vk.rb:81
    from /home/alex/vk.rb:81:in `open’
    from /home/alex/vk.rb:81

  • Constantine 24 сентября 2009

    Привет, Лёнчик! А будет ли эта штука работать если твой друг закрыл свой список друзей?

    • Леонид Шевцов 24 сентября 2009

      Никак не будет – таких друзей она игнорирует.

  • Василий 27 сентября 2009

    Сорри за оффтоп, Леня, может напишешь статейку о Haml и Sass? Думаю начать использовать это, правда для пыха. Не уверен еще в полезности затеи.

  • burgua 26 декабря 2009

    Спасибо Леонид!

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

    Вот, кстати, подобные разработки еще одного парня
    http://ruby-ua.blogspot.com/

Оставить комментарий

  • (или OpenID)
  •