Understat xG League & Team Stats Scraper
SPORTSDEVELOPER TOOLS
Understat xG League & Team Stats Scraper
Scrape expected goals (xG), non-penalty xG (NPxG), xGA, PPDA, deep completions, and full W/D/L standings from understat.com — the canonical open xG data source for European football.
Covers all 6 understat leagues (EPL, La Liga, Bundesliga, Serie A, Ligue 1, RFPL) across every available season (2014–present). One aggregated row per team per season.
Features
- 6 leagues in a single run: EPL, La Liga, Bundesliga, Serie A, Ligue 1, RFPL
- All seasons from 2014/2015 onwards — back-fill historical data or track the current season
- Match-level aggregation — sums each team's per-match xG, xGA, NPxG, PPDA, deep completions into a clean season total
- PPDA ratio computed correctly as
passes_attempted / defensive_actions(lower = more pressing) - No proxy required — understat.com is open and datacenter-friendly
Use Cases
- Build football analytics dashboards (xG vs actual goals by team)
- Feed betting or fantasy football models with expected-goals standings
- Compare xG performance across leagues or seasons for research / Substack writing
- Backtest xG-based table predictions
Input
| Field | Type | Default | Description |
|---|---|---|---|
leagues |
array | (all 6) | Which leagues to scrape. Valid values: EPL, La_liga, Bundesliga, Serie_A, Ligue_1, RFPL |
seasons |
array | ["2024"] |
Season start years to scrape. E.g. ["2024"] = 2024/2025, ["2023","2024"] = two seasons |
maxItems |
integer | 10 | Maximum records to return (0 = unlimited) |
Example input — EPL last 3 seasons
{
"leagues": ["EPL"],
"seasons": ["2022", "2023", "2024"],
"maxItems": 0
}
Example input — All leagues, current season
{
"leagues": ["EPL", "La_liga", "Bundesliga", "Serie_A", "Ligue_1", "RFPL"],
"seasons": ["2024"],
"maxItems": 0
}
Output
One record per team per season:
{
"season": "2024/2025",
"league": "EPL",
"team_name": "Aston Villa",
"team_id": "71",
"games_played": 38,
"wins": 19,
"draws": 9,
"losses": 10,
"goals_for": 58,
"goals_against": 51,
"xg_for": 66.39,
"xg_against": 55.0,
"npxg": 61.71,
"deep": 285,
"ppda": 12.39,
"oppda": 9.96,
"points": 66,
"source": "https://understat.com/league/EPL"
}
Field reference
| Field | Description |
|---|---|
season |
Season label (e.g. 2024/2025) |
league |
League name |
team_name |
Team name as shown on understat.com |
team_id |
Understat internal team ID |
games_played |
Total matches played |
wins / draws / losses |
Season record |
goals_for / goals_against |
Goals scored / conceded |
xg_for |
Total expected goals (xG) |
xg_against |
Total expected goals against (xGA) |
npxg |
Non-penalty xG (excludes spot-kick quality) |
deep |
Deep completions — passes into the opposition's danger zone |
ppda |
Passes per defensive action by opponent (lower = team presses more aggressively) |
oppda |
Opponent PPDA — how much the team is pressed |
points |
Total league points |
source |
Source URL |
Notes
- Data is sourced from understat.com's internal XHR API (
/getLeagueData/{league}/{season}), which returns gzip-compressed JSON. All values are aggregated from per-match history. - PPDA and OPPDA are
nullif a team played 0 defensive actions (extremely rare, data gap seasons only). - The RFPL (Russian Premier League) data coverage may be incomplete for recent seasons.
maxItemscounts across all requested league × season combinations. Set to0to scrape everything.