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

23 сентября 2009

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

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

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

Реализация

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

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

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

Заключение

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

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



Тринадцать комментариев. Напиши еще один
  1. Eb80888dc5dd290cc89381754c507393 # 23 сентября 2009 Виктор написал:

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

    1. 777894ea5153122bfa6b83f5bbf23622 # 23 сентября 2009 Леонид Шевцов (автор) написал:

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

  2. 191727d2abd3330de5828922be07ef64 # 23 сентября 2009 Alex написал:

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

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

    1. 777894ea5153122bfa6b83f5bbf23622 # 23 сентября 2009 Леонид Шевцов (автор) написал:

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

  3. 191727d2abd3330de5828922be07ef64 # 24 сентября 2009 Alex написал:

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

    В любом случае, сейчас выдает такое
    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
    
    1. 777894ea5153122bfa6b83f5bbf23622 # 24 сентября 2009 Леонид Шевцов (автор) написал:

      Может, это из-за копипасты? Haml очень чувствителен к пробелам. Github позволяет скачать скрипт и в чистом виде – http://github.com/leonid-shevtsov/vkontakte-friends-parser/raw/master/parser.rb (кроме того, я добавил в него немного проверок)

  4. B4ffac15443c1230d7bfed4b33a36916 # 24 сентября 2009 Constantine написал:

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

    1. 777894ea5153122bfa6b83f5bbf23622 # 24 сентября 2009 Леонид Шевцов (автор) написал:

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

  5. 86601e5039ba231c5e529f29c56f86c2 # 27 сентября 2009 Василий написал:

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

  6. Bea4c383a20768fb25baf2272d909780 # 26 декабря 2009 burgua написал:

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

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

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

  7. 5c513dbd33569659d74dc78dfc57c54e # 21 октября 2010 ikido написал:

    Идея оформить все в духе ActiveResource очень даже интересная.
    На что только не пойдешь в отсутствие нормального api ))

    1. 777894ea5153122bfa6b83f5bbf23622 # 21 октября 2010 Леонид Шевцов (автор) написал:

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

  8. 85ef78ae239cd01a66341daa1d86b38c # 08 января 2011 Корсаков Игорь написал:

    Огромное спасибо за гем Механайз, очень давно искал подобное решение!) успехов Тебе!)

(нужна разметка?)

  • **жирный**
  • > цитата

отменить