Аналитическая платформа данных по игре Dota 2: источники
Всем привет! Я инженер данных в Альфа-банке и еще на моменте обучения профессии хотел сделать пет-проект с обработкой данных и интересной инфраструктурой. Пришел к выводу, что это должна быть компьютерная игра, и для меня лучше всего подошла Dota 2, но с получением сырых данных было много трудностей, в которые не было времени вникнуть, и я откладывал этот проект долгое время.
Позже мне поручили подготовить проект для обучения студентов основам инженерии данных и специфике работы с данными. Я решил вернуться к той идее и геймифицировать обучение: игровые данные, на мой взгляд, помогают быстрее вовлечься в задачи, которые на бизнес-примерах могут выглядеть слишком абстрактно.
При выборе игры у меня было одно опасение: данные должны быть одновременно интересными и достаточно понятными широкой аудитории. Dota 2 показалась хорошим вариантом. В ней много сущностей и сценариев: игроки, герои, предметы, игровые события, экономика матча, команды и результат.
В итоге я хотел собрать не просто набор таблиц с матчами, а живой контур данных, который регулярно получает новые матчи, дополняет их событиями, сохраняет результат и передаёт его в хранилище данных для дальнейшей аналитики.
Источники
Для проекта я использую 2 API: Steam Web API и Stratz GraphQL. Они дополняют друг друга и работают параллельно в своих потоках — Steam API находит новые актуальные матчи, а сервис Stratz парсит их на события и хранит на своей стороне.
Сначала я рассматривал Steam как единственный источник и в его документации описаны endpoint’ы для получения детальных данных — IDOTA2Match_570/GetMatchDetails (docs), который должен возвращать детальную информацию о матче по match_id, однако в текущем состоянии им невозможно пользоваться — https://github.com/ValveSoftware/Dota-2/issues/2715.
Steam все еще остается главным инструментом для получения актуальных матчей, но обогащением занимается Stratz, который по match_id из Steam выдает все события, нас интересующие.
Steam Web API
Ключ Steam API можно получить на странице регистрации. После входа в Steam достаточно указать домен приложения и сохранить выданный ключ для запросов к API.
Основной endpoint проекта - GetMatchHistoryBySequenceNum (docs). Он возвращает матчи начиная с заданного sequence number, это удобно для инкрементальной загрузки матчей.
GetMatchHistoryBySequenceNumработает отsequence number, не имеет фильтра «от даты» и не даёт надёжного публичного указателя текущей позиции. При старте потока приходится выбирать приближённую точку, а не получать идеально полный поток новых матчей.
params = {
"key": settings.STEAM_API_KEY,
"matches_requested": 100,
"start_at_match_seq_num": start_sequence,
}
response = await client.get(
"https://api.steampowered.com/IDOTA2Match_570/"
"GetMatchHistoryBySequenceNum/v1",
params=params,
)
Из этого ответа удается получить match_id, время начала матча, длительность, победителя, id игроков и выбранных ими героев. Дополнительно из id игроков endpoint’ом GetPlayerSummaries из Steam API порциями для игровок догружаются их nickname (название профиля).
Stratz GraphQL
Для Stratz нужно зарегистрироваться на stratz.com (войти через Steam аккаунт) и создать API токен. Запросы отправляются на https://api.stratz.com/graphql, а токен передается в HTTP-заголовке Authorization: Bearer <token>.
Stratz используется только ради playbackData — массивы событий внутри матча: смерти, покупки, руны, изменения золота и опыта, урон, применение и прокачка способностей. Дополнительно это сервис предоставляет доступ к справочникам предметов, способностей и тд.
Ниже упрощенный пример структуры запросов, в реальном потоке он содержит больше полей:
query {
match(id: 8985356917) {
players {
steamAccountId
playbackData {
deathEvents { time steamAccountId }
purchaseEvents { time itemId }
goldEvents { time amount }
}
}
}
}
Бесплатная квота ключа Stratz позволяет обогатить около 13.7–14 тыс. матчей в сутки. Поэтому проект собирает репрезентативный поток матчей, а не гарантирует полное real-time покрытие всех игр Dota 2.
Потоки и базы данных
Контур источников состоит из трёх процессов: discover — перенос информации об актуальных матчей из Steam API в БД PostgreSQL, enrich — обогащение имеющихся матчей и запись этих событий в БД MongoDB, cleanup — удаление старых матчей или матчей, для которых нет информации у Stratz. discover и enrich запускаются последовательно каждые 15 минут, cleanup — каждые 12 часов. Это дает возможность развернуть проект на легковесной удаленной виртуальной машине и использовать её как буферную зону.
Схема потоков данных:
┌─────────────────────┐
│ Steam Web API │
│ │
│ Матчи, игроки, │
│ герои, справочники │
└─────────┬───────────┘
│
│ GetMatchHistoryBySequenceNum
▼
┌─────────────────────┐
│ discover │
│ Каждые 15 минут │
│ │
│ До 300 новых │
│ матчей за запуск │
└─────────┬───────────┘
│
▼
┌──────────────────────────────────────┐
│ PostgreSQL │
│ │
│ matches, match_players, players, │
│ heroes, items, справочники, │
│ discover_runs, enrich_runs │
└─────────┬────────────────────────────┘
│
│ Необогащённые матчи: match_id
▼
┌─────────────────────┐ ┌─────────────────────┐
│ enrich │──────>│ Stratz GraphQL │
│ Каждые 15 минут │ │ │
│ │<──────│ playbackData: │
│ Получает события │ │ события матча │
└─────────┬───────────┘ └─────────────────────┘
│
│ События playbackData
▼
┌──────────────────────────────────────┐
│ MongoDB │
│ │
│ death_events, purchase_events, │
│ gold_events, experience_events, │
│ rune_events, tower_damage_events, │
│ ability_used_events и другие │
└──────────────────────────────────────┘
┌─────────────────────┐
│ cleanup │
│ │
│ Удаляет устаревшие │
│ данные и очищает │
│ MongoDB и PostgreSQL│
└─────────────────────┘
Kafka могла бы быть следующим шагом при переходе к событийной архитектуре и нескольким потребителям, но для polling API и учебного проекта она добавила бы сложность без пропорциональной пользы.
PostgreSQL: каркас данных
Здесь лежат все сущности со стабильной структурой и явными связями. Некоторые из них:
Сущность | Смысл | Примеры полей |
|---|---|---|
| Один матч |
|
| Привязка игроков к матчу |
|
| Публичный профиль игрока |
|
| Справочник героев |
|
| Справочник предметов |
|
В этой же базе я храню техническое состояние запусков в discover_runs, enrich_runs и cleanup_runs. Это позволяет ответить не только на вопрос “сколько данных есть”, но и «почему данные не появились в последнем запуске».
MongoDB: события матча
События отличаются друг от друга по структуре и объёму, поэтому для них я выбрал MongoDB. Коллекции разделены по типам событий, некоторые из них:
Коллекция | Пример данных | Для чего нужна |
|---|---|---|
| время смерти, игрок, убийца | Анализ убийств и темпа матча |
| время и | Построение предметов |
| время и изменение золота | Экономика игры |
| время и опыт | Темп прокачки |
| время, башня, урон | Давление на объекты |
| время, игрок, тип руны | Контроль карты |
Общий ключ для соединения источников – match_id. Чтобы связать событие с героем, используется пара match_id и steam_account_id: она соединяется с match_players.account_id в PostgreSQL.
Примеры аналитических кейсов
Вся описанная выше система будет использоваться как буферная зона для сбора данных из разрозненных мест чтобы облегчить работу с исходными API. Хранилище данных уже будет собирать эти данные и хранить их историчность, и уже на этих данных можно будет строить гипотезы и аналитику.
Раннее преимущество и победа
Можно сравнить gold/xp события обеих команд в первые 10 или 15 минут и проверить, насколько раннее преимущество связано с победой. Следующий шаг — найти матчи, в которых команда с серьёзным отставанием всё же выиграла, и разобрать, какие события предшествовали этому.
Тайминг первой башни
tower_damage_events и события смерти башен позволяют определить время первой взятой башни. Интересная гипотеза: чем раньше команда разрушает первую башню, тем выше вероятность её победы. Здесь можно разделить матчи по длительности, патчу или рейтинговому диапазону игроков.
Предметы и стиль игры
Из purchase_events можно восстановить порядок покупок и сопоставить его с героем, результатом и динамикой матча. Например, проверить, после каких предметов у команды растёт число убийств или урон по башням.
Заключение
В этой части проекта я построил не аналитическое хранилище, а операционный слой между нестабильными внешними API и хранилищем данных. Он снимает нагрузку с API, фиксирует состояние загрузок, изолирует особенности источников и даёт хранилищу два подготовленных источника: PostgreSQL с сущностями матча и MongoDB с детальными событиями.
Было бы полезно услышать критику текущего решения и узнать насколько может быть интересно развитие такого проекта. В случае положительной обратной связи я продолжу описание автоматизированного хранилища данных в следующей главе и оставлю исходники проекта.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.