NLPx

Tales of Data Science

API Вконтакте + NetworkX = социальный граф!

interest-graphs-social-graph

Попалась мне тут когда-то интересная задача — быстро и максимально просто (с минимумом привлекаемых библиотек) нарисовать граф пересечения пользователей разных групп Вконтакте. И я ее даже сделал. Затем подумал — а вдруг кому-то еще нужно будет сделать что-то подобное?

Поэтому здесь я расскажу и покажу, как же всё-таки делать такие вещи:

1. Подключаться к веб-сайту вконтакте с помощью Python 2.7 (всегда можно переписать под 3.x, если что)

2. Создавать графы с помощью библиотеки NetworkX (здесь рассматриваем графы пересечений аудитории групп вконтакте)

3. Визуализировать эти графы (тут нам немного понадобится библиотека matplotlib)

Итак, дано:

Задание: нарисовать граф пересечений пользователей разных групп Вконтакте

Базовые инструменты: Python 2.7, NetworkX, matplotlib, numpy

Поехали!

 

Подключаемся к API

Если хорошо поискать в безбрежной сети, то можно найти полезные ресурсы, которые сильно облегчают подключение к АPI вконтакте.

Для начала нужно скачать специальную библиотеку-обертку — vk.com (aka vkontakte.ru) API wrapper. 

Затем очень желательно скачать модуль для упрощения авторизации вконтакте вот тут (за этот скрипт большое спасибо хорошему человеку alexkutsan). Модуль мы будем звать vk_auth и всячески импортировать в дальнейшем, поэтому сохраняйте его в той же директории, где будут лежать основные скрипты. В случае, если ссылка нерабочая, то можно взять vk_auth у меня — напишите в комментариях или отправьте запрос мне на почту.

Итак, у нас есть все, чтобы просто и быстро подключиться к API вконтакте.  Проверим, что все сделали как надо, и контакт теперь пускает нас в свои недра.

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
import vk_auth
import vkontakte
 
def test():
    #параметры для vk_auth.auth - ваш логин вконтакте, ваш пароль вконтакте, id приложения, 
    #и зона доступа, она же scope
    (token,user_id) = vk_auth.auth('your_login', 'your_password', '2951857', 'groups')
    vk = vkontakte.API(token=token)
    print "vk server time is ", vk.getServerTime()
    return 0
 

В этом скрипте нужно указать свой логин и пароль вконтакте (можете не беспокоиться — эта информация никуда не утечёт, можете проверить по коду скрипта vk_auth), в качестве id приложения нужно указать идентификационный номер своего приложения, которое предварительно нужно создать вконтакте вот таким, например, способом.

Если на видео не очень понятно, то на страничке vk.com/developers.php есть запрятанная кнопка «создать приложение» в верхнем правом углу. Сразу в глаза она не бросается — располагается чуть ниже кнопок меню «музыка» и «помощь». Там можно создать приложение и использовать его client_id в качестве id приложения.

Но! Можно использовать и чужой id. Я, например, использовал вот этот — 2951857, который был опубликован вот в этой статье на хабрахабре. Если вдруг у автора будут претензии по использованию этого идентификатора, я готов его отсюда убрать.

Далее нужно указать так называемый scope — зону доступа для нашего парсера. Так как далее мы собираемся парсить группы, то стоит указать ‘group’. Подробнее о том, что можно указать в качестве scope — можно почитать здесь

Все остальное можно оставить как есть. Если все правильно, то выполнив код, показанный выше, мы получим серверное время вконтакте.

Парсим группы

Отлично! Все получилось (по крайней мере должно было).

А теперь попытаемся достать необходимые данные по группам вконтакте — количество участников для каждой группы и список этих самых участников в виде списка ID.

Важно знать, что API вконтакте выдает максимум 1000 пользователей группы – больше от него не допросишься. Впрочем, для проведения приблизительного анализа групп — сойдёт. Если надо больше — придётся парсить страницы групп напрямую.

Функция, которая ниже, берет на вход список имен групп вконтакте, а на выходе отдает нужные нам данные по этим группам.

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
import vk_auth
import vkontakte
import time
 
def get_groups_users(groups_list):
    groups_out = {}
    (token,user_id) = vk_auth.auth('your_login', 'your_password', '2951857', 'groups')
    vk = vkontakte.API(token=token)
    for group in groups_list:
        #здесь му указываем count=10, который выдаст нам 10 пользователей из группы
        #это делается для наглядности. Максимум можно вытащить 1000 пользователей
        groups_out[group] = vk.get('groups.getMembers', group_id=group, sort='id_asc', offset=100, count=10)
        time.sleep(1)
    return groups_out
    
if __name__ == '__main__':
    group_list = ['oldlentach', 'obrazovach', 'superdiscoteka']
    print get_groups_users(group_list)
    >>> {'oldlentach': {u'count': 740868, u'users': [1405, 1418, 1443, 1444, 1447, 1481, 1491, 1494, 1500, 1509]}, 
    'obrazovach': {u'count': 217978, u'users': [3172, 3192, 3196, 3213, 3317, 3328, 3331, 3356, 3361, 3420]}, 
    'superdiscoteka': {u'count': 150538, u'users': [20470, 20479, 20536, 21977, 22426, 22522, 22613, 22881, 23207, 23401]}}
    

Структура выходных данных такова: ключ — это имя группы, значение — это словарь с двумя ключами: u’count’ — количество участников в группе и u’users’ — список ID участников этой группы (максимум 1000, как мы помним)

В качестве имени группы берется ее адрес вконтакте, например, есть группа Образовач, которая находится по адресу http://vk.com/obrazovach и мы берем последнюю часть адреса, т.е. «obrazovach» в качестве имени группы.

Строим социальный граф

Теперь переходим непосредственно к построению социального графа.

Здесь мы будем применять библиотеку NetworkX, которая прекраснейшим образом подходит для составления, анализа и визуализации графов.

Вот таким вот образом можно создать граф для групп вконтакте:

  1. На вход подается словарь, где ключом является название группы вконтакте, а значением — количество участников этой группы и список из максимум 1000 ID участников группы (каждый ID — это ID пользователя вконтакте)
  2. Создаём вершину в графе для каждой группы, в качестве атрибута присваиваем каждой вершине вес, равный количеству участников в группе
  3. Затем для каждой пары вершин, если между ними есть пересечение по ID участников, создается ребро, и в качестве атрибута присваиваем каждому ребру вес, равный количеству пользователей, которые присутствуют в обеих группах.

Атрибуты, которые присваиваются вершинам и ребрам, необходимы для последующей визуализации. Чем больше вес вершины (количество участников в группе), тем больше будет размер вершины на диаграмме. Чем больше вес ребра (пересечение количества участников), тем толще будет ребро на диаграмме.

Вот эта функция как раз и строит описанный выше граф:

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
import networkx as nx

def make_graph(groups_out):
    graph = nx.Graph()
    groups_out = groups_out.items()
    for i_group in xrange(len(groups_out)):
        graph.add_node(groups_out[i_group][0], size=groups_out[i_group][1]['count'])
        for k_group in xrange(i_group+2, len(groups_out)):
            intersection = set(groups_out[i_group][1]['users']).intersection(set(groups_out[k_group][1]['users']))
            if len(intersection) > 0:
                graph.add_edge(groups_out[i_group][0], groups_out[k_group][0], weight=len(intersection))
    return graph
    

Визуализируем

Визуализация проводится с помощью методов NetworkX на базе matplotlib. Подробнее о том, как визуализировать граф, можно прочитать здесь. А вот пример того, как визуализируется именно тот граф, который мы создали выше. Параметры методов говорят сами за себя, так что дополнительных объяснений, полагаю, не требуется 🙂

Вот таким образом можно визуализировать созданный нами граф:

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
import networkx as nx
import matplotlib.pyplot as plt
import numpy

def plot_graph(graph, adjust_nodesize):
    pos=nx.spring_layout(graph, k=1)
    #нормализуем размер вершины для визуализации. Оптимальное значение параметра
    #adjust_nodesize - от 300 до 500
    nodesize = [graph.node[i]['size']/adjust_nodesize for i in graph.nodes()]
    #нормализуем толщину ребра графа. Здесь хорошо подходит 
    #нормализация по Standard Score
    edge_mean = numpy.mean([graph.edge[i[0]][i[1]]['weight'] for i in graph.edges()])
    edge_std_dev = numpy.std([graph.edge[i[0]][i[1]]['weight'] for i in graph.edges()])
    edgewidth = [((graph.edge[i[0]][i[1]]['weight'] - edge_mean)/edge_std_dev) for i in graph.edges()]
    #создаем граф для визуализации
    nx.draw_networkx_nodes(graph, pos,node_size=nodesize, node_color='y', alpha=0.6)
    nx.draw_networkx_edges(graph,pos,width=edgewidth,edge_color='b')
    nx.draw_networkx_labels(graph,pos,fontsize=10)
    #сохраняем и показываем визуализированный граф
    plt.savefig('/path/to/save/figure')
    plt.show()
    

Кстати, можно значительно упростить нормализацию рёбер по Standard Score (он же Z-score), применив метод scipy.stats.mstats.zscore из библиотеки scipy.

Всё просто, не так ли?

Теперь, если собрать всё воедино,

if __name__ == '__main__':
    groups = ['oldlentach', 'echomsk', 'meduzaproject', 'tvrain', 'bbc',
    '1tv', 'izvestia', 'rt_russian', 'lentaru', 'ntv',
    'habr', 'postnauka', 'cloudytech', 'space_live', 'popularmechanics',
    'inbest', 'lovestime', 'orealno', 'devpoymut', 'padik_adik']
    groups_out = get_groups_users(groups)
    graph = make_graph(groups_out)
    plot_graph(graph, 500)
    

то можно нарисовать вот такой вот граф (щелкните по картинке, чтобы увеличить её):

figure_3 (1)

Не скажу, что он максимально эстетичен, но он делается просто и быстро и для поверхностного проведения исследования вполне сойдет. Плюс к этому всегда можно поиграться с цветами, размерами, прозрачностью и так далее 🙂

Интересно, кстати, что между группами хабрахабра, телеканала Дождь (tvrain), Эха Москвы (echomsk), журнала Популярная механика (popularmechanics) и информационного проекта Медуза (meduzaproject) (и еще немного паблика Постнаука (postnauka)) существуют серьёзные связи — аудитория групп довольно сильно пересекается. А группа padik_adik, которая на визуализации предстает изолированной вершиной — это группа «Пацаны поймут» с соответствующим контингентом, который, как можно видеть, не особенно любит связываться со всем остальным миром

Я буду очень признателен, если вы оставите здесь комментарий — можно хвалебный, можно ругательный.

Рекомендуемая литература

Хорошая статья по подключению к API вконтакте с помощью Python и скачиванию стены (рус.)

Статья на хабре, где рассказывается о том, как написать модуль авторизации для  API vk (рус.)

Официальная документация по API vk (рус.)

Еще одна большая статья по парсингу вконтакте и построению социального графа (рус.)

Официальная документация библиотеки NetworkX (англ.)

6,880 просмотров всего, 8 просмотров сегодня

API Вконтакте + NetworkX = социальный граф!
5 3 votes

Leave a Reply

5 Comments on "API Вконтакте + NetworkX = социальный граф!"


Guest
Denis Krylov
11 months 29 days ago

Отличная статья, но никак не моуг допереть как функцию make_graph перепилить под python 3.

Guest
Eugene
1 year 2 months ago

Работа замечательная! Все просто и понятно, правда есть одно НО! О чем нам может сказать пересечение по ID участников при ограничении в 1000 человек, когда рассматриваются группы с аудиторией 150 тысяч и более?

1 year 2 months ago

Резонный вопрос) Действительно, кажется, что ни о чем особо. Немного углублюсь еще в это дело и запилю новый пост, с более серьезным анализом пересечений) Как только он появится, вы об этом узнаете первым)

Guest
Алексей
1 year 6 months ago

Спасибо, отличная работа!

1 year 6 months ago

Всегда рад!