Understat xG League & Team Stats Scraper
SPORTSDEVELOPER TOOLS
Independent tool. Trademarks referenced on this page are the property of their respective owners and are used only to identify the public website this tool reads. No affiliation or endorsement.
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)
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,
"npxg_against": 52.4,
"npxg_diff": 9.31,
"deep": 285,
"ppda": 12.39,
"oppda": 9.96,
"points": 66,
"xpoints": 63.2,
"xg_diff": -8.39,
"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) |
npxg_against |
Non-penalty xG conceded |
npxg_diff |
Non-penalty xG differential (npxg minus npxg_against) |
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 |
xpoints |
Expected points — understat's xG-based points model |
xg_diff |
Goals scored minus expected goals (finishing over/underperformance) |
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.