Перейти к содержимому
Борис Новичков
РезюмеGitHub ↗
ENRU
Все работы

Network Trail

Путеводитель по социальному графу — вставьте профиль, получите структуру за ним

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

В архиве
Роль
В одиночку — архитектура, бэкенд, флот воркеров, фронтенд
Период
сент. 2019 — февр. 2022
ntrail — граф друзей
Граф друзей NTrail: около 250 узлов, раскрашенных в четыре плотных сообщества вокруг одного центрального профиля

Граф друзей одного профиля после выделения сообществ. Кластеры — это школа, университетский поток, место работы и родной город; ничего из этого в самом профиле не написано.

Проблема

Это можно делать руками. Открыть профиль, открыть список друзей, открыть тридцать из них, заметить, что восемь учились в одной школе, что четверо из них ещё и окончили один вуз, а те, кто окончил, — те же, кто постит из одного города. Это работает и занимает вечер на человека, поэтому так никто и не делает, кроме случаев, когда ставки достаточно высоки.

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/200

MVP снова запускается — статическая сборка за nginx, бэкенд в контейнере рядом.