Understat xG & Shot Data - Football Analytics avatar

Understat xG & Shot Data - Football Analytics

Pricing

from $0.80 / 1,000 player-season aggregate rows

Go to Apify Store
Understat xG & Shot Data - Football Analytics

Understat xG & Shot Data - Football Analytics

Scrapes Understat: player/team season aggregates, per-match xG/PPDA/win-probabilities, shot-level data with x/y coords + xG, situation/formation/game-state breakdowns, player career histories, and fixtures across the Big 5 + RFPL (2014–2025). Multi-season + multi-league in a single run.

Pricing

from $0.80 / 1,000 player-season aggregate rows

Rating

5.0

(1)

Developer

Omar Eldeeb

Omar Eldeeb

Maintained by Community

Actor stats

2

Bookmarked

6

Total users

4

Monthly active users

9 days ago

Last modified

Share

Understat xG & Shot Data — Football Analytics

Scrapes understat.com: player and team season aggregates, per-match xG/PPDA/win-probabilities, shot-level data with x/y coordinates, situation/formation/game-state breakdowns, player career multi-season histories, and upcoming fixtures across the Big 5 European leagues + RFPL (2014/15 → 2025/26).

Why this actor

Most Understat scrapers stop at league-level aggregates. This one exposes every data tier Understat has, in a single run, with multi-season + multi-league support out of the box.

WorkloadTypical alternativeThis actor
EPL one-season player stats (~600 players)$0.59$0.59 (parity)
All 6 leagues × one season player stats$3.04~$2.99
5 years EPL player stats in one runimpossible (5 separate runs)~$2.95
Shot-level data, EPL one season (~9,500 shots)unavailable~$7.22
Every EPL match + shots in one rununavailable~$7.22

Pricing is pure pay-per-event. Per-row meters mean you pay for what you extract — no per-run fee, no per-league surcharge.

Modes

Pick a mode to determine what the actor scrapes:

ModeWhat you getSource
league_playersOne row per (player × season). xG, xA, npxG, xGChain, xGBuildup, per-90 derivations, position, appearances./league/{LEAGUE}/{SEASON} HTML or /main/getPlayersStats/ POST when filters are set
league_teamsOne row per (team × season). W/D/L, GF/GA, xG/xGA, npxG/npxGA, npxGD, PPDA, deep, points, xPoints./league/{LEAGUE}/{SEASON} (aggregated from per-match history array)
league_matchesOne row per played match. Score, xG home/away, win-probability forecast./league/{LEAGUE}/{SEASON} datesData
match_shotsOne row per shot. x/y normalized pitch coordinates, xG model value, situation, body part, result, derived distance and angle, score at time of shot./match/{ID} (parses shotsData + match_info)
team_situationsLong-format: one row per (team × season × breakdown × label). Covers situation (OpenPlay/SetPiece/Corner/FreeKick/Penalty), formation (4-3-3 etc), gameState (leading/tied/trailing), timing, shotZones, attackSpeed, result./team/{TEAM}/{SEASON} statisticsData
player_careerOne row per (player × every season the player appears in). Full multi-season trajectory for any player ID./player/{ID} playersData
fixturesOne row per upcoming match. Date/time, teams, pre-match win-probability forecast (xG fields are null until played)./league/{LEAGUE}/{SEASON} datesData (isResult=false)

match_shots is the killer feature — most scrapers don't expose it.

Pricing

EventPrice
league-season-scraped$0.01 — one league × one season "discovery" fetch
team-season-scraped$0.005
player-season-scraped$0.0008
match-scraped$0.004
shot-scraped$0.0006
situation-breakdown-scraped$0.003
player-career-scraped$0.0008
fixture-scraped$0.0008

No actor-start fee. No per-league surcharge. First 100 chargeable events of every run are free — enough to taste every tier including a 75-shot heatmap and a multi-season career bundle.

Cost cap

The maxCostUsd input (default $25) is a hard cap. The actor estimates cost up-front from your scope and warns you. Mid-run, every charge is checked against the cap; once hit, the run stops gracefully and writes a cost_cap_reached status row to the dataset.

Without this, a typo like all 6 leagues × all 11 seasons × match_shots could cost ~$375. With it, you're protected.

Sample inputs

Player season stats (parity workload)

{
"mode": "league_players",
"leagues": ["EPL"],
"seasons": [2024]
}

Multi-season comparison (one run, not five)

{
"mode": "league_players",
"leagues": ["EPL"],
"seasons": [2020, 2021, 2022, 2023, 2024]
}

Forwards-only with date range

{
"mode": "league_players",
"leagues": ["La_liga"],
"seasons": [2024],
"positions": ["F"],
"dateStart": "2024-08-01",
"dateEnd": "2024-12-31",
"minAppearances": 5
}

Shot map for a single match

{
"mode": "match_shots",
"matchIds": [26630]
}

All shots in a season (heavy — uses your cost cap)

{
"mode": "match_shots",
"leagues": ["EPL"],
"seasons": [2024],
"maxCostUsd": 10
}

Mo Salah's complete career

{
"mode": "player_career",
"playerIds": [1250]
}

Arsenal's situation breakdowns

{
"mode": "team_situations",
"leagues": ["EPL"],
"seasons": [2024],
"teams": ["Arsenal"]
}

Upcoming Premier League fixtures

{
"mode": "fixtures",
"leagues": ["EPL"],
"seasons": [2025]
}

Output

Every row carries:

  • type — discriminator (player_season, team_season, match, shot, team_situation, player_career_season, fixture, or status)
  • _meta.mode — which mode produced the row
  • _meta.scrapedAt — ISO 8601 timestamp
  • _meta.source — always "understat"

Set includeRawJson: true to attach the original Understat record under _raw (useful for power users; off by default to keep the dataset light).

Resume

Long runs check-point progress to a named key-value store keyed by mode + input scope. If a run is interrupted, the next run with the same input picks up where it left off — already-completed (league × season) or (matchId) or (playerId) units are skipped.

Limits

  • leagues × seasons ≤ 60 — protects against accidental over-scope (e.g. 6 × 11 = 66 league-seasons).
  • Cost cap defaults to $25/run.
  • match_shots throttles requests to 6 s between matches to mirror the empirically safe rate. Expect a full EPL season (~380 matches × ~25 shots) to take ~40 minutes.
  • RFPL team names are Cyrillic — the actor URL-encodes them automatically.
  • Future fixtures have null xG fields until played.

Coverage

LeagueSlugEarliest season
English Premier LeagueEPL2014/15
Spanish La LigaLa_liga2014/15
German BundesligaBundesliga2014/15
Italian Serie ASerie_A2014/15
French Ligue 1Ligue_12014/15
Russian Premier LeagueRFPL2014/15

Notes

  • This actor scrapes a publicly accessible website. Be mindful of Understat's resources — the built-in throttle is conservative, and match_shots adds an extra delay because of its high fan-out.
  • The xG values are Understat's neural-net model output. If you need the underlying training data or the model itself, you'll need to talk to them.
  • Data freshness lags Understat's updates by minutes-to-hours depending on their crawl cadence.