# Understat Football xG & Shot Data Scraper (`automation-lab/understat-football-xg-shot-data`) Actor

Export public Understat league, team, player, match, and shot expected-goals records for recurring football scouting and performance analysis.

- **URL**: https://apify.com/automation-lab/understat-football-xg-shot-data.md
- **Developed by:** [Stas Persiianenko](https://apify.com/automation-lab) (community)
- **Categories:** Sports
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.16 / 1,000 item extracteds

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/actors/running/actors-in-store.md#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 & Shot Data Scraper

Export public Understat football xG and shot data as structured league, team, player, match, and shot records.
Use it for recurring scouting, performance analysis, football content, spreadsheets, dashboards, and model datasets.

The Actor supports Understat's six competitions and public league, team, player, and match URLs.
It reads the same structured data used by the public site and does not require an Understat account.

### What can you extract?

Choose the records needed for your analysis:

- league team aggregates;
- league player aggregates;
- match schedules and results;
- match-level shot maps;
- team player aggregates;
- team tactical breakdowns;
- player match histories;
- player shot histories.

Every row has a `recordType`, source URL, scrape timestamp, useful normalized fields, and a `data` object preserving source-specific details.
This lets one run feed a spreadsheet while retaining enough detail for a football model.

### Who is this Understat football data Actor for?

#### Football scouts and analysts

Compare team xG and xGA, inspect player production, or review how chances were created in a specific match.

#### Data scientists

Build repeatable training datasets with expected-goals values, shot coordinates, situations, results, and match context.

#### Sports writers and creators

Support match reports and season reviews with source-linked expected-goals records instead of copying tables manually.

#### Clubs and agencies

Refresh player and team benchmarks on a schedule and connect the dataset to internal reporting tools.

#### Football app builders

Use Apify API or MCP to fetch structured Understat records for prototypes and internal applications.

### Supported leagues and seasons

The league selector supports:

| Input value | Competition |
| --- | --- |
| `EPL` | Premier League |
| `La_liga` | La Liga |
| `Bundesliga` | Bundesliga |
| `Serie_A` | Serie A |
| `Ligue_1` | Ligue 1 |
| `RFPL` | Russian Premier League |

Enter a season by its start year.
For example, `2025` means the 2025/2026 season.
Understat availability differs by competition and season.

### Input options

| Field | Type | What it controls |
| --- | --- | --- |
| `startUrls` | URL list | Public Understat league, team, player, or match pages to extract. |
| `leagues` | string list | League discovery when no explicit URL is supplied. |
| `seasons` | string list | Four-digit season start years used with `leagues`. |
| `recordTypes` | string list | Entity types to include in the default dataset. |
| `maxItems` | integer | Global cap across every URL and selected record type. |

If `startUrls` is present, the Actor processes those URLs and does not add the default league.
If URLs are absent, provide at least one league and one season.

Only `understat.com` public URLs are accepted.
Unsupported hosts and malformed paths fail the run instead of producing misleading empty data.

### Getting started

1. Open the Actor in Apify Console.
2. Choose one or more leagues and season start years, or paste public Understat URLs.
3. Select the record types needed by your workflow.
4. Set `maxItems` to bound the output volume.
5. Click **Start**.
6. Open the default dataset to inspect, download, or integrate the rows.
7. Save the input as an Apify Task if you want to schedule the same extraction.

A simple league table input is:

```json
{
  "leagues": ["EPL"],
  "seasons": ["2025"],
  "recordTypes": ["leagueTeam"],
  "maxItems": 20
}
```

### Extract one match and its shots

Use a public match URL with `match` and `shot` records:

```json
{
  "startUrls": [
    { "url": "https://understat.com/match/27828" }
  ],
  "recordTypes": ["match", "shot"],
  "maxItems": 30
}
```

The match row includes league, season, teams, score context, xG, PPDA, and shot counts in `data` when Understat exposes them.
Shot rows include player, team, minute, xG, result, situation, body part, and pitch coordinates.

### Build a team-season analysis dataset

A team page can return matches, players, and tactical breakdowns in one run:

```json
{
  "startUrls": [
    { "url": "https://understat.com/team/Arsenal/2025" }
  ],
  "recordTypes": ["match", "teamPlayer", "teamBreakdown"],
  "maxItems": 100
}
```

This is useful for season reviews, scouting notebooks, and scheduled dashboard refreshes.
The global item limit applies across all three record types.

### Output fields

Common normalized fields include:

| Field | Meaning |
| --- | --- |
| `recordType` | One of the eight supported entity types. |
| `recordId` | Understat source identifier when present. |
| `league` | Understat league key or name. |
| `season` | Season start year. |
| `team` / `player` | Relevant team or player name. |
| `matchId` | Understat match identifier. |
| `date` | Match date, or shot minute for shot rows. |
| `homeTeam` / `awayTeam` | Match participants. |
| `goals` | Goals for player aggregate rows. |
| `xG` / `xGA` | Expected goals and expected goals against. |
| `xA` | Expected assists. |
| `npxG` / `npxGA` | Non-penalty expected-goals values. |
| `shots` / `minutes` | Player activity totals. |
| `result` | Shot outcome when applicable. |
| `situation` / `bodyPart` | Shot context. |
| `x` / `y` | Understat shot-map coordinates. |
| `sourceUrl` | Public page associated with the row. |
| `scrapedAt` | ISO timestamp for the extraction. |
| `data` | Additional source-specific fields. |

Fields that do not apply to a record type are omitted.
Use `recordType` before interpreting optional columns.

### Example output

A real league-team row has this shape:

```json
{
  "recordType": "leagueTeam",
  "recordId": "71",
  "league": "EPL",
  "season": 2025,
  "team": "Aston Villa",
  "xG": 56.203311,
  "xGA": 56.682839,
  "npxG": 56.203311,
  "npxGA": 55.160499,
  "sourceUrl": "https://understat.com/league/EPL/2025",
  "scrapedAt": "2026-09-01T14:53:23.475Z",
  "data": {
    "games": 38,
    "wins": 19,
    "draws": 8,
    "loses": 11,
    "goals": 58,
    "goalsAgainst": 51,
    "points": 65,
    "expectedPoints": 51.0701
  }
}
```

Values change as Understat updates its public data.
The source URL makes each row traceable.

### How much does it cost to export Understat football records?

The Actor uses pay-per-event pricing:

- a **$0.005 start event** once per run;
- an **item event** once for each useful row saved to the default dataset;
- no charge for rejected, duplicate, empty, or failed records.

The current BRONZE item rate is **$0.0036 per record**, with lower rates on higher Apify tiers.
At that rate, 20 league-team rows cost about **$0.077** including the start event.
A 100-row team analysis costs about **$0.365** including the start event.
Your active Apify tier and the live Console pricing table are authoritative.

Set the output limit to create a predictable upper bound.
Infrastructure usage is covered by the Actor's pay-per-event model when the run is launched under that model.

### Scheduling and recurring analysis

Save a tested input as an Apify Task, then attach a schedule.
Common patterns include:

1. refresh a league table after each match week;
2. export one team's player and tactical data each morning;
3. collect selected player histories for a scouting shortlist;
4. snapshot one match's shots for a post-match report;
5. send dataset changes to a warehouse or webhook workflow.

The Actor returns current source data.
It does not maintain a historical change log between runs, so store snapshots in your destination when comparisons matter.

### Export and integration patterns

From the dataset page you can download JSON, CSV, Excel, XML, or RSS using Apify dataset endpoints.
You can also connect runs to:

- Google Sheets;
- Make;
- Zapier;
- webhooks;
- a database or data warehouse;
- Python or JavaScript notebooks;
- an AI assistant through Apify MCP.

For multi-type datasets, filter on `recordType` in the destination.
Keep `recordId` plus `recordType` as part of a stable deduplication key.

### Run through the Apify API

Replace `<APIFY_TOKEN>` with your token.

#### cURL

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~understat-football-xg-shot-data/runs?token=<APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
    "leagues": ["EPL"],
    "seasons": ["2025"],
    "recordTypes": ["leagueTeam"],
    "maxItems": 20
  }'
```

#### JavaScript

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/understat-football-xg-shot-data').call({
  startUrls: [{ url: 'https://understat.com/match/27828' }],
  recordTypes: ['match', 'shot'],
  maxItems: 30,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

#### Python

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/understat-football-xg-shot-data').call(run_input={
    'startUrls': [{'url': 'https://understat.com/team/Arsenal/2025'}],
    'recordTypes': ['match', 'teamPlayer', 'teamBreakdown'],
    'maxItems': 100,
})

items = client.dataset(run['defaultDatasetId']).list_items().items
print(items)
```

### Use with Apify MCP

Connect Claude Code directly to this Actor:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/understat-football-xg-shot-data"
```

#### Claude Desktop, Cursor, and VS Code MCP setup

Add this Actor-scoped server to the MCP JSON configuration used by Claude Desktop, Cursor, or VS Code:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/understat-football-xg-shot-data"
    }
  }
}
```

Example prompts:

- "Export the EPL 2025 team xG table and rank clubs by xG minus xGA."
- "Get match and shot records from https://understat.com/match/27828 and summarize the largest chances."
- "Collect Arsenal's 2025 matches, players, and tactical breakdowns, then prepare a scouting table."

MCP returns the Actor run and dataset through Apify's normal permissions.

### Reliability and failure behavior

The Actor uses Understat's public structured data requests with a browser-like request shape.
It does not run a browser, download media, or use a residential proxy.

Temporary timeouts, rate limits, and selected 5xx responses receive up to three bounded attempts with short backoff.
Malformed input, unsupported hosts, invalid paths, and deterministic 4xx responses fail without blind retries.

A challenge page or non-JSON response is treated as an upstream error, not as an empty successful dataset.
When a selected record type does not apply to a supplied URL, that type produces no row for that URL.

### Legality and responsible use

- Only public Understat pages are supported.
- No login, account, private data, or CAPTCHA solving is provided.
- Historical availability is controlled by Understat.
- Fixtures and current-season values can change after a later source update.
- `maxItems` stops output globally, so a low limit may end before later record types.
- Shot coordinates use Understat's coordinate convention and may need transformation for a specific visualization.
- The Actor is not a betting or prediction service.

Use reasonable run frequency and concurrency.
Respect Understat's terms, applicable database rights, and the laws governing your use of exported data.
Do not use the Actor to overload the source or misrepresent source data as official club data.

### Troubleshooting

#### Why did I get no shot rows from a league URL?

`shot` records are extracted from a match URL.
Use a public `/match/<id>` URL, or first export `match` rows and run selected match URLs in a later Task.

#### Why are some fields missing?

The default dataset contains several entity types.
Fields only appear when they apply and when Understat exposes them.
Check `recordType` and inspect `data` for source-specific values.

#### Why did the run stop at exactly my limit?

`maxItems` is global across URLs and types.
Raise it if you need later entities in the same run, or split workflows into separate Tasks.

#### What does season 2025 mean?

It is the season starting in 2025, normally displayed as 2025/2026.

#### Can I scrape an arbitrary football site?

No.
The Actor validates the host and only supports public Understat league, team, player, and match routes.

### FAQ

#### Does it require an Understat account?

No.
It uses public football data exposed by Understat.

#### Can I export CSV or Excel?

Yes.
Use the dataset export controls or the corresponding Apify dataset API format.

#### Can I schedule it?

Yes.
Save an input as an Apify Task and attach an Apify schedule.

#### Does it include shot xG and coordinates?

Yes for public match and player shot records.
Select `shot` for match URLs or `playerShot` for player URLs.

#### Are every league and season guaranteed?

No.
The selector lists Understat's supported competitions, but the source controls historical and current availability.

#### Is this an official Understat product?

No.
This independent Actor extracts publicly available Understat data and links every row to its source.

### Related automation-lab Actors

For broader sports workflows, browse the [automation-lab Actor portfolio](https://apify.com/automation-lab).
Combine this specialized expected-goals dataset with automation-lab football match, standings, or player-statistics Actors when a workflow needs additional sources.
Only link datasets after checking their identifiers, competition coverage, and update timing.

### Support

For a reproducible problem, include the Actor run URL, sanitized input, expected record type, and one public Understat URL.
Do not include Apify tokens or other credentials.

# Actor input Schema

## `startUrls` (type: `array`):

Public Understat league, team, player, or match URLs. URL scope and record type filters are applied together.

## `leagues` (type: `array`):

Discover records by Understat league. Requires at least one season when no URL is supplied.

## `seasons` (type: `array`):

Season start years, for example 2025 means the 2025/2026 season.

## `recordTypes` (type: `array`):

Output only the selected entity types that apply to each supplied URL.

## `maxItems` (type: `integer`):

Global maximum number of charged dataset records across all URLs and record types.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://understat.com/match/27828"
    }
  ],
  "leagues": [
    "EPL"
  ],
  "seasons": [
    "2025"
  ],
  "recordTypes": [
    "leagueTeam",
    "leaguePlayer",
    "match",
    "shot"
  ],
  "maxItems": 20
}
```

# Actor output Schema

## `overview` (type: `string`):

Open normalized Understat records in the overview dataset view.

# 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 = {
    "startUrls": [
        {
            "url": "https://understat.com/match/27828"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation-lab/understat-football-xg-shot-data").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 = { "startUrls": [{ "url": "https://understat.com/match/27828" }] }

# Run the Actor and wait for it to finish
run = client.actor("automation-lab/understat-football-xg-shot-data").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 '{
  "startUrls": [
    {
      "url": "https://understat.com/match/27828"
    }
  ]
}' |
apify call automation-lab/understat-football-xg-shot-data --silent --output-dataset

```

## MCP server setup

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

```

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/ioBz7yeuEb5ArU1Cz/builds/XIgLdDBy2FZyq0dyI/openapi.json
