- PVSM.RU - https://www.pvsm.ru -

Аналитическая платформа данных по игре Dota 2: источники

Мотивация и цели

Всем привет! Я инженер данных в Альфа-банке и еще на моменте обучения профессии хотел сделать пет-проект с обработкой данных и интересной инфраструктурой. Пришел к выводу, что это должна быть компьютерная игра, и для меня лучше всего подошла Dota 2, но с получением сырых данных было много трудностей, в которые не было времени вникнуть, и я откладывал этот проект долгое время.

Позже мне поручили подготовить проект для обучения студентов основам инженерии данных и специфике работы с данными. Я решил вернуться к той идее и геймифицировать обучение: игровые данные, на мой взгляд, помогают быстрее вовлечься в задачи, которые на бизнес-примерах могут выглядеть слишком абстрактно.

При выборе игры у меня было одно опасение: данные должны быть одновременно интересными и достаточно понятными широкой аудитории. Dota 2 показалась хорошим вариантом. В ней много сущностей и сценариев: игроки, герои, предметы, игровые события, экономика матча, команды и результат.

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

Источники

Для проекта я использую 2 API: Steam Web API и Stratz GraphQL. Они дополняют друг друга и работают параллельно в своих потоках — Steam API находит новые актуальные матчи, а сервис Stratz парсит их на события и хранит на своей стороне.

Сначала я рассматривал Steam как единственный источник и в его документации описаны endpoint’ы для получения детальных данных — IDOTA2Match_570/GetMatchDetails (docs [1]), который должен возвращать детальную информацию о матче по match_id, однако в текущем состоянии им невозможно пользоваться — https://github.com/ValveSoftware/Dota-2/issues/2715 [2].

Steam все еще остается главным инструментом для получения актуальных матчей, но обогащением занимается Stratz, который по match_id из Steam выдает все события, нас интересующие.

Steam Web API

Ключ Steam API можно получить на странице регистрации [3]. После входа в Steam достаточно указать домен приложения и сохранить выданный ключ для запросов к API.

Основной endpoint проекта - GetMatchHistoryBySequenceNum (docs [4]). Он возвращает матчи начиная с заданного 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 [5] (войти через 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: каркас данных

Здесь лежат все сущности со стабильной структурой и явными связями. Некоторые из них:

Сущность

Смысл

Примеры полей

matches

Один матч

match_id, start_time, duration, radiant_win, stratz_parsed_at

match_players

Привязка игроков к матчу

match_id, account_id, hero_id, win

players

Публичный профиль игрока

account_id, personaname, profile_url

heroes

Справочник героев

hero_id, localized_name

items

Справочник предметов

item_id, localized_name

В этой же базе я храню техническое состояние запусков в discover_runs, enrich_runs и cleanup_runs. Это позволяет ответить не только на вопрос “сколько данных есть”, но и «почему данные не появились в последнем запуске».

MongoDB: события матча

События отличаются друг от друга по структуре и объёму, поэтому для них я выбрал MongoDB. Коллекции разделены по типам событий, некоторые из них:

Коллекция

Пример данных

Для чего нужна

death_events

время смерти, игрок, убийца

Анализ убийств и темпа матча

purchase_events

время и itemId

Построение предметов

gold_events

время и изменение золота

Экономика игры

experience_events

время и опыт

Темп прокачки

tower_damage_events

время, башня, урон

Давление на объекты

rune_events

время, игрок, тип руны

Контроль карты

Общий ключ для соединения источников – 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 с детальными событиями.

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

Автор: vechkasov__02

Источник [6]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/big-data/457978

Ссылки в тексте:

[1] docs: https://steamapi.xpaw.me/IDOTA2Match_570#GetMatchDetails

[2] https://github.com/ValveSoftware/Dota-2/issues/2715: https://github.com/ValveSoftware/Dota-2/issues/2715

[3] странице регистрации: https://steamcommunity.com/dev/apikey

[4] docs: https://steamapi.xpaw.me/IDOTA2Match_570#GetMatchHistoryBySequenceNum

[5] stratz.com: https://stratz.com/api

[6] Источник: https://habr.com/ru/articles/1081990/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1081990