Network Trail
Путеводитель по социальному графу — вставьте профиль, получите структуру за ним
Исследовательская платформа, которая превращает открытые данные социальной сети в то, по чему можно ходить: обходит связи профиля через официальные API, выясняет, на какие сообщества распадается граф, и ранжирует признаки, общие для этих сообществ. Шесть сервисов — фронтенд на React, API на Flask, доменный слой, распределённый воркер запросов, аналитический сервис на FastAPI и пул токенов — построены за три года и опубликованы одним репозиторием.
- Роль
- В одиночку — архитектура, бэкенд, флот воркеров, фронтенд
- Период
- сент. 2019 — февр. 2022

Граф друзей одного профиля после выделения сообществ. Кластеры — это школа, университетский поток, место работы и родной город; ничего из этого в самом профиле не написано.
Проблема
Это можно делать руками. Открыть профиль, открыть список друзей, открыть тридцать из них, заметить, что восемь учились в одной школе, что четверо из них ещё и окончили один вуз, а те, кто окончил, — те же, кто постит из одного города. Это работает и занимает вечер на человека, поэтому так никто и не делает, кроме случаев, когда ставки достаточно высоки.
NTrail был попыткой отдать машине скучную половину: обход, соединения, подсчёт, ранжирование того, что действительно необычно для набора людей, — и оставить суждение тому, кто читает результат.
Как это работает
Интерфейс — строка запроса, а не форма. GET vk.user <id> friends возвращает
множество; это множество можно разбить на сообщества, пересечь с другим
множеством или спросить, что общего у его участников. Поскольку любой результат
— множество, результат одного запроса является входом следующего, и анализ
превращается в последовательность мелких шагов, а не в один большой
сконфигурированный отчёт.
Под капотом архитектуру определили три проблемы:
Лимиты запросов. ВК разрешает около трёх запросов в секунду на токен, а у Instagram пригодного API не было вовсе. Один клиент на масштабе графа бесполезен, поэтому запросы стали атомарными единицами — метод, токен, параметры, всё нужное для самостоятельного выполнения, — а воркер берёт их пачками, раскладывает по ядрам и машинам, кеширует каждый результат и возвращает их вместе.
Грязные данные. Намеренно неверный возраст, пустые поля, скрытые списки друзей. Поэтому атрибуты выводятся из окружения, а не читаются из профиля: если сорок процентов друзей человека сидят в графе одного вуза, это сигнал сильнее, чем пустое поле «образование».
Структура важнее атрибутов. Самое полезное в графе друзей — не отдельный профиль, а форма. Сообщества получаются из модулярности Louvain на графе, и каждое сообщество ранжируется по тому, что общего у его участников. Кластеры на скриншоте выше — это школа, университетский поток, место работы и родной город; ни одного из них в профиле не указано.
Чего стоило это опубликовать
Проект жил в шести репозиториях с сабмодулями, указывающими друг на друга, и в
двух из них закоммиченный env_file нёс секрет приложения ВК, access-токен и
пароль от базы. Публикация означала:
- переписать все истории, вычистив эти значения, и отозвать токены;
- слить шесть репозиториев в один через
git filter-repo, каждый в свой каталог, чтобы все 470 коммитов пережили переезд; - полностью убрать продуктовые скриншоты — это были снимки настоящих профилей, с именами и лицами людей, которые не соглашались появляться в чьём-то портфолио. Граф выше — единственный артефакт, который показывает работу, никого не показывая.
Что бы я сделал иначе
Доменный слой и воркер были правильным разделением, и оба пережили бы переписывание. Бэкенд на Flask накопил логику планирования запросов, которой место в ядре, а фронтенд ходил в захардкоженный хост API — ровно та деталь, из-за которой проект перестаёт разворачиваться кем-либо, кроме автора. Аналитический сервис появился поздно и так и не заменил бэкенд, который должен был заменить, — цена решения строить вторую вещь раньше, чем закончена первая.
- GET vk.user <id> friends
- Список друзей профиля как множество, с которым можно работать дальше
- GET vk.community <id> members
- Все участники группы, готовые к пересечению с другим множеством
- split clusters
- Разбить текущее множество на сообщества по структуре графа
- interesting properties
- Ранжировать то, что есть общего у текущего множества, — школа, вуз, город, возраст
Интерфейс — это строка запроса, а не форма. Любой результат адресуем, и результат может быть входом следующего запроса.
- frontend
- React 16 + Redux; sigma / vis для графа, recharts для распределений
- backend
- API на Flask поверх PostgreSQL — объекты запросов, выборочное исполнение, кеширование
- core
- Доменный слой: VKUser, VKCommunity — friends(), groups(), posts()
- worker
- Исполняет пачки атомарных запросов к API по ядрам и кеширует результаты
- api
- Сервисный слой на FastAPI + Redis с аналитическим стеком
- credentials
- Пул токенов, из которого черпают воркеры
Шесть репозиториев, ставшие шестью каталогами, каждый со своей историей.
$ docker run --rm -v $PWD/frontend:/app node:16-alpine npm run buildThe build folder is ready to be deployed.$ docker compose up -d --buildContainer ntrail-db-1 StartedContainer ntrail-backend-1 Started$ curl -o /dev/null -w '%{http_code}' http://127.0.0.1:8090/api/200MVP снова запускается — статическая сборка за nginx, бэкенд в контейнере рядом.