# Understat Football xG Scraper: Expected Goals & Shot Stats (`scrapers_lat/understat-football-xg-scraper`) Actor

Scrape Understat Expected Goals (xG) football stats: player, team, league and match xG, xGA, npxG, xGChain, xGBuildup, plus shot-level xG. Top 6 leagues. Optional AI xG insight. Export to JSON, CSV or Excel.

- **URL**: https://apify.com/scrapers\_lat/understat-football-xg-scraper.md
- **Developed by:** [Scrapers Lat](https://apify.com/scrapers_lat) (community)
- **Categories:** AI, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.32 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

[![Understat Football xG Scraper: Expected Goals & Shot Stats](https://scrapers.lat/banners/understat-football-xg-scraper.png)](https://apify.com/scrapers_lat/understat-football-xg-scraper)

## Understat Football xG Scraper: Expected Goals & Shot Stats

Real Expected Goals (xG) analytics for football (soccer): player, team, league, and match xG, plus every shot with its own xG. Here is one real result from a live league run, with every field the actor returns:

```json
{
  "mode": "league",
  "rowType": "league-player",
  "league": "EPL",
  "season": "2024",
  "playerId": "1250",
  "playerName": "Mohamed Salah",
  "teamTitle": "Liverpool",
  "position": "F M",
  "games": 38,
  "time": 3392,
  "goals": 29,
  "xG": 27.706,
  "assists": 18,
  "xA": 15.858,
  "shots": 130,
  "keyPasses": 89,
  "yellowCards": 1,
  "redCards": 0,
  "npg": 20,
  "npxG": 20.856,
  "xGChain": 48.536,
  "xGBuildup": 16.207,
  "xGDiff": 1.294,
  "xADiff": 2.142,
  "xGPer90": 0.735,
  "xAPer90": 0.421,
  "npxGPer90": 0.553,
  "xGChainPer90": 1.288,
  "aiFinishing": null,
  "aiCreativity": null,
  "aiStyle": null,
  "aiFormNote": null,
  "source": "Understat",
  "observedAt": "2026-08-16T22:35:54.541Z"
}
```

The most complete football xG scraper for the top 6 leagues (Premier League, La Liga, Bundesliga, Serie A, Ligue 1, RFPL). It exposes the real advanced metrics generic football scrapers miss: xG, xGA, npxG, xGChain, xGBuildup, xA, and shot-level xG with pitch coordinates, plus derived finishing metrics like goals minus xG and xG per 90. Optional add-ons return every shot and an AI xG performance insight.

**📥 [Input](https://apify.com/scrapers_lat/understat-football-xg-scraper/input-schema) · 📤 [Output](https://apify.com/scrapers_lat/understat-football-xg-scraper/output-schema) · 💰 [Pricing](https://apify.com/scrapers_lat/understat-football-xg-scraper/pricing) · ▶️ [Examples](https://apify.com/scrapers_lat/understat-football-xg-scraper/examples)**

![Apify](https://img.shields.io/badge/Platform-Apify-1CE1CE?logo=apify\&logoColor=white)
![Coverage](https://img.shields.io/badge/Coverage-Top%206%20Leagues-blue)
![Metrics](https://img.shields.io/badge/Metrics-xG%20%7C%20npxG%20%7C%20xGChain-9cf)
![Output](https://img.shields.io/badge/Output-JSON%20%7C%20CSV%20%7C%20Excel-orange)
![Billing](https://img.shields.io/badge/Billing-Pay%20per%20result-brightgreen)

### Table of contents

- [What it does](#what-it-does)
- [Use cases](#use-cases)
- [Quickstart](#quickstart)
- [Modes](#modes)
- [Input reference](#input-reference)
- [Output reference](#output-reference)
- [Run via API and CLI](#run-via-api-and-cli)
- [Fetch results](#fetch-results)
- [Billing and limits](#billing-and-limits)
- [FAQ and troubleshooting](#faq-and-troubleshooting)

### What it does

Pick a mode and the actor returns clean, flat, per-entity rows of Expected Goals data with no nested objects, ready for CSV or Excel:

- **League** mode returns one row per player or per team for a whole league season, with full xG, npxG, xGChain, xGBuildup, xA, and derived finishing and per-90 metrics. Sort and filter by games.
- **Player** mode returns one row per season for a single player, and can optionally add every shot the player has taken (minute, xG, pitch X and Y, result, situation, shot type, who assisted).
- **Team** mode returns one row per squad player plus the team's situation splits (open play, set piece, corner, counter) with xG for and against.
- **Match** mode returns a per-team xG summary for a single match, and can optionally add every shot from both teams with its xG.

Missing source values are returned as `null`. Numbers are parsed, not strings. The actor never fabricates values: it only emits what the source actually reports.

### Use cases

- **Football analytics and modeling.** Build xG models and finishing analysis with real xG, npxG, xGChain, and xGBuildup instead of raw goals.
- **Betting and trading models.** Feed team and player xG, xGA, and expected points into value models and over/under markets.
- **Fantasy football and drafting.** Rank players by xG per 90, xA per 90, and xGChain to find underlying performers before their price rises.
- **Scouting and recruitment.** Compare shot quality, non-penalty xG, and creativity across leagues to shortlist targets.
- **Content and media.** Power match previews, xG timelines, and shot maps with pitch coordinates for every shot.
- **Player performance review.** Spot over- and under-performance of xG (goals minus xG) season by season.

### Quickstart

Open the actor, paste this into the input, and press Run. It returns the top 20 Premier League players of the 2024/2025 season by xG.

```json
{
  "mode": "league",
  "league": "EPL",
  "season": "2024",
  "entity": "players",
  "sortBy": "xG",
  "maxResults": 20
}
```

`season` is the starting year, so `2024` means the 2024/2025 season. `league` accepts codes or names, for example `EPL` or `Premier League`, `La Liga`, `Bundesliga`, `Serie A`, `Ligue 1`, `RFPL`.

### Modes

| Mode | You provide | You get |
|---|---|---|
| `league` | `league`, `season`, `entity` (`players` or `teams`) | One row per player or team for the season |
| `player` | `playerId` | One row per season for the player; optional shot-level rows |
| `team` | `teamName`, `season` | One row per squad player plus team situation splits |
| `match` | `matchId` | Per-team xG summary; optional shot-level rows for both teams |

### Input reference

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `mode` | enum | no | `league` | What to scrape: `league`, `player`, `team`, or `match`. |
| `league` | string | no | `EPL` | League for league mode. Codes or names accepted (`EPL`/`Premier League`, `La Liga`, `Bundesliga`, `Serie A`, `Ligue 1`, `RFPL`). |
| `season` | string | no | `2024` | Season starting year. `2024` is the 2024/2025 season. Used by league and team modes. |
| `entity` | enum | no | `players` | In league mode, return one row per `players` or per `teams`. |
| `sortBy` | enum | no | `xG` | Sort player rows by `xG`, `goals`, `assists`, `xA`, `npxG`, `xGChain`, `xGBuildup`, or `shots`. |
| `minGames` | integer | no | `0` | Only include players with at least this many games (league mode). |
| `playerId` | string | no | `1250` | Understat player ID (player mode). |
| `teamName` | string | no | (none) | Team name (team mode). Spaces are fine, for example `Manchester United`. |
| `matchId` | string | no | (none) | Understat match ID (match mode). |
| `includeShots` | boolean | no | `false` | Player and match modes. Also emit every shot with its xG. Paid plans only. Charged once per player or match. |
| `withAiSummary` | boolean | no | `false` | Add an AI xG performance insight for a player or team. Paid plans only. Billed only when produced. |
| `maxResults` | integer | no | `50` | Maximum result rows to return. Free plans are capped at 10. |

### Output reference

One flat dataset item per entity for the chosen mode. Types: `string`, `number`, or `null` when the source value is absent.

| Field | Type | Description |
|---|---|---|
| `mode` | string | Requested mode. |
| `rowType` | string | Row kind: `league-player`, `team`, `player-season`, `team-player`, `team-situation`, `match-team`, or `shot`. |
| `league` | string | League code. |
| `season` | string | Season starting year. |
| `playerId` | string | Understat player ID. |
| `playerName` | string | Player name. |
| `teamTitle` | string | Team name. |
| `position` | string | Playing position(s). |
| `games` | number | Games or appearances. |
| `time` | number | Minutes played. |
| `goals` | number | Goals scored. |
| `xG` | number | Expected Goals. |
| `xGA` | number | Expected Goals Against (team rows). |
| `assists` | number | Assists. |
| `xA` | number | Expected Assists. |
| `shots` | number | Shots taken. |
| `keyPasses` | number | Key passes. |
| `npg` | number | Non-penalty goals. |
| `npxG` | number | Non-penalty Expected Goals. |
| `xGChain` | number | Total xG of every possession the player was involved in. |
| `xGBuildup` | number | xGChain excluding shots and key passes. |
| `points` | number | League points (team rows). |
| `xPoints` | number | Expected points (team rows). |
| `xGDiff` | number | Goals minus xG (finishing over- or under-performance). |
| `xADiff` | number | Assists minus xA. |
| `xGPer90` | number | xG per 90 minutes. |
| `xAPer90` | number | xA per 90 minutes. |
| `npxGPer90` | number | Non-penalty xG per 90 minutes. |
| `xGChainPer90` | number | xGChain per 90 minutes. |
| `situation` | string | Play situation split (team-situation and shot rows). |
| `shotType` | string | Body part or shot type (shot rows). |
| `result` | string | Shot result: `Goal`, `SavedShot`, `MissedShots`, and so on. |
| `minute` | number | Match minute of the shot. |
| `X` | number | Shot X location, normalized 0 to 1. |
| `Y` | number | Shot Y location, normalized 0 to 1. |
| `assistedBy` | string | Player who assisted the shot. |
| `matchId` | string | Understat match ID. |
| `aiFinishing` | string | AI note on finishing versus xG (add-on), or `null`. |
| `aiCreativity` | string | AI note on chance creation (add-on), or `null`. |
| `aiStyle` | string | AI note on playing or scoring style (add-on), or `null`. |
| `aiFormNote` | string | AI short performance note (add-on), or `null`. |
| `source` | string | Data source label. |
| `observedAt` | string | ISO 8601 timestamp of when the row was collected. |
| `error` | string | `null` on success. On a failed run, a single unbilled item with a populated `error` is written instead. |

### Run via API and CLI

Start a run and read the dataset. Replace `<TOKEN>` with your Apify API token.

Run synchronously and get dataset items in one call:

```bash
curl -X POST "https://api.apify.com/v2/acts/scrapers_lat~understat-football-xg-scraper/run-sync-get-dataset-items?token=<TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"mode":"league","league":"EPL","season":"2024","entity":"players","sortBy":"xG","maxResults":20}'
```

Scrape a single player with shot-level data:

```bash
curl -X POST "https://api.apify.com/v2/acts/scrapers_lat~understat-football-xg-scraper/runs?token=<TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"mode":"player","playerId":"1250","includeShots":true}'
```

Apify CLI:

```bash
apify call scrapers_lat/understat-football-xg-scraper \
  --input '{"mode":"team","teamName":"Liverpool","season":"2024"}'
```

### Fetch results

Every run writes to a dataset. Fetch items as JSON, CSV, or Excel by changing `format`:

```bash
## JSON
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<TOKEN>&clean=true&format=json"

## CSV
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<TOKEN>&clean=true&format=csv"

## Paginate large datasets
curl "https://api.apify.com/v2/datasets/<DATASET_ID>/items?token=<TOKEN>&offset=1000&limit=1000"
```

`<DATASET_ID>` is returned as `defaultDatasetId` in the run object. `clean=true` drops empty and internal fields.

### Billing and limits

- **Pay per result.** You are charged per result row returned (`result` event). See the [pricing tab](https://apify.com/scrapers_lat/understat-football-xg-scraper/pricing) for the current per-result price.
- **Shot-level add-on.** The `shots` event returns every shot for a player or match with its own xG and pitch coordinates. It is charged once per player or match, only when the shot data is actually returned.
- **AI xG insight add-on.** The `ai_summary` event adds a finishing, creativity, style, and form read for a player or team. It is charged only when the model returns usable output.
- **No charge on failure.** If a run errors or an id is not found, the actor writes a single item with a populated `error` field and does not charge for it. Runs that return no data cost nothing.
- **Spend cap respected.** Set `maxTotalChargeUsd` on the run; once reached, the actor stops emitting and charging further billable rows.
- **Free Apify plans** are capped at 10 result rows per run, and the `shots` and `ai_summary` add-ons are disabled.

### FAQ and troubleshooting

**What does xG mean?**
Expected Goals (xG) estimates the probability that a given shot becomes a goal, based on factors like location and situation. Summed over shots it measures the quality of chances created or conceded, which is far more predictive than raw goals.

**What is xGChain and xGBuildup?**
xGChain sums the xG of every possession a player was involved in, and xGBuildup is the same but excludes the player's own shots and key passes. Together they credit players who build attacks without taking the final shot.

**How do I read xGDiff?**
`xGDiff` is goals minus xG. A positive value means the player scored more than their chances suggested (over-performing, clinical finishing or luck); a negative value means under-performance.

**Which leagues and seasons are covered?**
The top 6 leagues: Premier League (EPL), La Liga, Bundesliga, Serie A, Ligue 1, and RFPL, across the seasons the source publishes. Set `season` to the starting year.

**Where do I find a player or match ID?**
IDs come from the source pages. Player and match modes take the numeric ID; team mode takes the team name directly.

**Why are the AI fields null?**
The AI insight is off by default and requires a paid plan. Enable `withAiSummary` on a paid plan to populate `aiFinishing`, `aiCreativity`, `aiStyle`, and `aiFormNote`.

**Is this an official tool?**
No. This actor is independent and has no affiliation with Understat. It reads only data that is publicly available.

### Related scrapers

- [ESPN Soccer Scraper](https://apify.com/scrapers_lat/espn-soccer-scraper): soccer fixtures, results, and standings.
- [Polymarket Prediction Markets Scraper](https://apify.com/scrapers_lat/polymarket-markets-scraper): live odds, volume, and liquidity.
- [Sofascore Scraper](https://apify.com/scrapers_lat/sofascore-scraper): live scores and match statistics.

### More scrapers at scrapers.lat

Built and maintained by [scrapers.lat](https://scrapers.lat), where we publish scrapers for public platforms: company registries, government data, finance, sports, e-commerce and more. Browse the catalog or request a custom scraper at [scrapers.lat](https://scrapers.lat).

***

> Independent tool, not affiliated with Understat. Accesses only publicly available data.

# Actor input Schema

## `mode` (type: `string`):

What to scrape: a whole league table, one player, one team, or one match.

## `league` (type: `string`):

League to scrape in league mode. Accepts codes or names (EPL / Premier League, La Liga, Bundesliga, Serie A, Ligue 1, RFPL).

## `season` (type: `string`):

Season starting year. 2024 means the 2024/2025 season. Used by league and team modes.

## `entity` (type: `string`):

In league mode, return one row per player or one row per team.

## `sortBy` (type: `string`):

Sort order for league/team player rows.

## `minGames` (type: `integer`):

Only include players with at least this many games (league mode).

## `playerId` (type: `string`):

Understat player ID (player mode). Example: 1250 (Mohamed Salah).

## `teamName` (type: `string`):

Team name (team mode). Spaces are fine, e.g. "Manchester United" or "Liverpool".

## `matchId` (type: `string`):

Understat match ID (match mode).

## `includeShots` (type: `boolean`):

Player and match modes: also emit every shot (minute, xG, X/Y location, result, situation, shot type, assisted by). Charged once per player/match. Disabled for free accounts.

## `withAiSummary` (type: `boolean`):

Add an AI xG-based insight (finishing over/under xG, creativity, style, form note) for a player or team. Charged only on a usable AI result. Disabled for free accounts.

## `maxResults` (type: `integer`):

Maximum number of result rows to return. Free accounts are capped at 10.

## Actor input object example

```json
{
  "mode": "league",
  "league": "EPL",
  "season": "2024",
  "entity": "players",
  "sortBy": "xG",
  "minGames": 0,
  "playerId": "1250",
  "includeShots": false,
  "withAiSummary": false,
  "maxResults": 50
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "mode": "league",
    "league": "EPL",
    "season": "2024",
    "entity": "players",
    "sortBy": "xG",
    "minGames": 0,
    "playerId": "1250",
    "maxResults": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapers_lat/understat-football-xg-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "mode": "league",
    "league": "EPL",
    "season": "2024",
    "entity": "players",
    "sortBy": "xG",
    "minGames": 0,
    "playerId": "1250",
    "maxResults": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapers_lat/understat-football-xg-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "mode": "league",
  "league": "EPL",
  "season": "2024",
  "entity": "players",
  "sortBy": "xG",
  "minGames": 0,
  "playerId": "1250",
  "maxResults": 50
}' |
apify call scrapers_lat/understat-football-xg-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapers_lat/understat-football-xg-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Ze7FgYzidINjKy1kO/builds/CcXwKRTfyY3DMRYBp/openapi.json
