Quickstart
Get from installation to useful replay data with the high-level Python API.
Install
pip install gem-dota
Or with uv:
uv add gem-dota
Get a replay
gem.parse() expects a decompressed Source 2 .dem replay file.
You can download replays from OpenDota by opening a match and clicking "Download Replay". The Dota 2 client also stores downloaded replays under Watch -> Recent Games.
Parse a match
import gem
from gem.constants import hero_display
match = gem.parse("my_replay.dem")
winner = "Radiant" if match.radiant_win else "Dire" if match.radiant_win is False else "?"
print(f"Match {match.match_id} | {match.duration_minutes:.1f} min | winner: {winner}")
for player in match.players:
print(
f"{hero_display(player.hero_name):<18}"
f" KDA {player.kills}/{player.deaths}/{player.assists}"
f" NW {player.net_worth:>6,}"
f" LH/DN {player.last_hits}/{player.denies}"
)
gem.parse() returns a ParsedMatch. Most consumers can stay at this level and work
with fields such as match.players, match.combat_log, match.wards,
match.fights, match.opendota_teamfights, match.draft, and
match.radiant_gold_adv.
Find one player
axe = gem.find_player(match, "Axe")
if axe:
print(axe.hero_name)
print(axe.kills, axe.deaths, axe.assists)
print(axe.damage_by_type)
find_player() accepts display names ("Anti-Mage"), loose names ("anti mage"), and
full NPC names ("npc_dota_hero_antimage").
Export data
JSON
json_str = gem.parse_to_json("my_replay.dem", indent=2)
match = gem.parse("my_replay.dem")
data = gem.to_dict(match)
Use JSON when you want the full nested ParsedMatch shape.
DataFrames
frames = gem.parse_to_dataframe("my_replay.dem")
print(sorted(frames))
print(frames["player_summary"].head())
print(frames["combat_log"].head())
Use DataFrames for pandas, notebooks, and ML pipelines. Every table is flat and starts
with a match_id column. Common tables include player_summary (one row per player),
player_timeseries, players_minute, positions, combat_log, wards,
objectives, fights, neutral_item_finds, and per-player event logs. Pass
include=["analysis"] or include=["opendota"] for the optional post-parse analysis
and OpenDota-shaped tables; see Time-Series & DataFrames.
Parquet
paths = gem.parse_to_parquet("my_replay.dem", output_dir="./out")
print(paths[:3])
Command line
# Match summary
python -m gem my_replay.dem
# JSON to stdout
python -m gem my_replay.dem --format json > match.json
# Parquet files
python -m gem parse my_replay.dem --format parquet --output ./out
# Batch parse a folder
python -m gem batch replays/ --format parquet --output ./out --workers 4
See the CLI Reference for all options, including the report asset-cache commands used by HTML report generation.
Next steps
| Task | Read |
|---|---|
Inspect every ParsedMatch field | Full Match Data |
| Work with pandas tables and time series | Time-Series & DataFrames |
| Analyze combat log events | Combat Log |
| Work with fight windows | Fight Detection |
| Build custom parser callbacks | Custom Extractors |
| Understand parser internals | Parser Internals |