BikeControl (SwiftControl)
Use Zwift Click, Zwift Play, Shimano Di2, or any Bluetooth controller with MyWhoosh, Rouvy, Zwift, and more. Free to try on iOS, Android, Windows, and macOS.
翻訳待ち
No updates detected yet. GitHub releases will appear here.
WNBA Hindsight: lead trackers, scoring runs and player ratings for WNBA games
翻訳待ち
Loading the full README.
Read the full README ↗Lead trackers, scoring runs and player ratings for WNBA games, built from the play-by-play data on wnba.com. Live site: https://babak4.github.io/WNBA-Hindsight/
The project has a Python tool that extracts the raw play-by-play data from wnba.com, and a static web app (the viewer) that draws the results.
The goal is to turn each game into immutable records containing the scoreboard time, the score after the action, whether it was a team or player action, and for player actions the player name and action. The extraction tool covers the first step: capturing the raw play-by-play JSON for a game, as the site publishes it.
.python-version)uv sync # creates .venv with Python 3.14
The virtual environment lives in .venv/, which is excluded from git.
Pass the URL of a game's play-by-play page:
uv run fetch_pbp.py https://www.wnba.com/game/gsv-vs-atl-1042600303/play-by-play
Output:
wrote data/gsv-vs-atl-1042600303.json (538,987 bytes)
The file is named after the game slug in the URL (the part after /game/), and is saved in the data/ directory, which is created if needed.
| Option | Description |
|---|---|
--out-dir DIR | Write to DIR instead of data/ |
--force | Overwrite the file if it already exists |
By default an existing file is never overwritten, so a stored game stays unchanged. Re-running for the same game prints an error and exits with code 1 unless you pass --force.
The script exits with code 1 and prints a message to stderr if:
--force was not given)wnba.com pages are built with Next.js, and all the page data is embedded in a <script id="__NEXT_DATA__"> tag. The script saves that JSON pretty-printed (2-space indent), so the file is easy to read and diff. Keys, key order and values are exactly as published; only the whitespace differs. It also checks that play-by-play data is present.
Reading the plays from a stored file:
import json
with open("data/gsv-vs-atl-1042600303.json") as f:
data = json.load(f)
actions = data["props"]["pageProps"]["playByPlay"]["actions"]
print(len(actions), actions[0]["description"])
This section describes the stored file, using data/gsv-vs-atl-1042600303.json (Atlanta Dream 97, Golden State Valkyries 71, WNBA Finals Game 3) as the reference. Only the parts that matter for play-by-play are covered. Counts are for that game.
The file is the page's Next.js payload. Almost all of it is site chrome (header, footer, ads, translations, analytics). Two objects under props.pageProps matter:
| Path | What it is |
|---|---|
props.pageProps.playByPlay.actions | The play-by-play: a list of 447 action objects, one per row on the page |
props.pageProps.game | Game metadata: teams, rosters, officials, arena, final stats |
Everything else at the top level (page, query, buildId, locale, ...) is routing information. query.props repeats the URL parts: ["gsv-vs-atl-1042600303", "play-by-play"].
playByPlay| Key | Meaning |
|---|---|
gameId | "1042600303", same as the number at the end of the URL slug |
source | Name of the upstream feed ("hanaV3") |
videoAvailable | 1 if video clips exist for the game |
actions | The list of plays, in game order |
game| Key | Meaning |
|---|---|
gameId, gameCode | "1042600303", "20261021/GSVATL" (date and away/home tricodes) |
gameStatus, gameStatusText, period | 3, "Final", 4 |
gameTimeUTC, gameEt | Scheduled tip-off |
homeTeamId, awayTeamId | Team IDs. Home is Atlanta (1611661330), away is Golden State (1611661331), even though the URL reads gsv-vs-atl |
homeTeam, awayTeam | teamId, teamName, teamCity, teamTricode, final score, timeoutsRemaining, and a statistics object with the team box score |
homeTeamPlayers, awayTeamPlayers | Rosters (12 each): personId, name (full), nameI (initial and surname), firstName, familyName |
officials | Referees: personId, name, assignment |
arena, attendance, duration | Venue and game length |
seriesGameNumber, seriesText, gameLabel | Series context ("Game 3", "ATL leads 2-1", "WNBA Finals") |
The rosters are the only place where a full player name appears. Actions carry only the surname or an initial and surname, so join on personId to get the full name.
Every action has exactly these 23 keys, whatever its type. Fields that do not apply are empty ("") or 0.
This is the beginning of the README. Read the rest at the original. Read the full README ↗