# NHL Advanced Stats Scraper: MoneyPuck xG, Corsi, Odds (`parseforge/nhl-advanced-stats-scraper`) Actor

Scrape NHL expected goals, Corsi, on-ice splits, line combos, playoff odds and game win probabilities back to the 2008-09 season.

- **URL**: https://apify.com/parseforge/nhl-advanced-stats-scraper.md
- **Developed by:** [ParseForge](https://apify.com/parseforge) (community)
- **Categories:** News, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.45 / 1,000 skater season rows

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

[![ParseForge](https://raw.githubusercontent.com/ParseForge/apify-assets/main/banner.jpg)](https://apify.com/parseforge?fpr=vmoqkp)

### MoneyPuck NHL Advanced Stats Scraper: xG, Corsi, Odds

**Scrape every MoneyPuck NHL table back to the 2008-09 season: 4,700 skater rows, 490 goalie rows, 3,000 line combinations and 160 team rows per season.** Every row carries expected goals, Corsi, Fenwick, on-ice and off-ice splits, danger buckets and per-60 rates. No login or API key. Export to CSV, JSON, Excel, or XML.

MoneyPuck publishes its model output as flat files and the NHL publishes a public schedule and standings API, but neither gives you a filtered, joined, one-row-per-entity table. This Actor reads both, applies your filters on the raw upstream row, and returns exactly the seasons, situations, teams and columns you asked for.

| Who uses it | What they scrape NHL advanced stats for |
|---|---|
| Hockey bettors | Model win probability against the market price, game by game |
| Fantasy players | Expected goals and per-60 rates to find undervalued skaters |
| Sports analysts | On-ice and off-ice splits for player impact models |
| Media and content teams | Line combination usage and team danger profiles for previews |
| Data scientists | Eighteen seasons of shot-quality data as a training set |

### What it does

This Actor collects NHL advanced statistics from MoneyPuck and the NHL public API, and returns each entity as a flat row. Every row carries:

- 🧾 **Core fields:** player or team, position, situation, season, games played, ice time, points, goals, assists, shots and shot attempts.
- 🎯 **Expected goals:** raw xG, flurry and score adjusted xG, goals above expected, and xG per 60.
- 🧊 **On-ice context:** on-ice and off-ice xG, Corsi and Fenwick percentages, ice time rank.
- 🥅 **Goalie model output:** goals saved above expected, expected save percentage, rebound control.
- 🎲 **Odds and predictions:** playoff and Stanley Cup probabilities, and the model versus market win probability for each game.
- 📊 **Computed signals:** points per 60, goals per 60, shooting percentage, faceoff win percentage, goal and expected goal differential.

Results export to CSV, JSON, Excel, or XML, or stream from the API.

### What you can do with NHL advanced stats

**📈 Find the games where the model disagrees with the book.**

The game prediction rows carry the MoneyPuck win probability and the implied market probability side by side, plus the difference between them, so a scan of one date range surfaces the disagreements without any odds maths of your own.

**🔍 Rank skaters by what they created, not what went in.**

Goals above expected, xG per 60 and on-ice xG percentage separate a shooter on a hot streak from one who is generating chances, over any season back to 2008-09.

**🧠 Build a player impact model.**

Turn on the on-ice offence and defence blocks and every skater row carries roughly sixty extra columns of what happened while that player was on the ice, in the situation you selected.

**🧊 Study line combinations and deployment.**

The line dataset returns 3,000 forward lines and defence pairings per season with ice time rank, expected goals share and danger buckets, which is where usage decisions actually show up.

### Why choose this scraper

| | What you get |
|---|---|
| Ten tables in one Actor | Skaters, goalies, teams, lines, playoff odds, game predictions, schedule, standings and two game-log tables |
| Eighteen seasons | Regular season and playoffs, every season from 2008-09 to the latest published one |
| Five situation splits | All situations, 5 on 5, 5 on 4, 4 on 5 and other strengths |
| Filters run before billing | Team, position, name, games played, ice time, points and expected goals are applied to the full upstream row |
| Optional columns are optional | Six column blocks are off by default and billed only on the rows where they actually returned data |
| No key, no login | Both upstreams are public, so there is nothing to register and no rate limit to negotiate |

### How it compares

There are several NHL scrapers on Apify and most of them read scoreboards: ESPN scores, live odds, or the box score tables from Hockey Reference. None of them return the MoneyPuck model output, which is the file hockey analysts actually work from. The closest neighbours are our own `hockey-reference-scraper`, which gives you the traditional counting stats, and the various `nhl-stats-scraper` listings that wrap the NHL public API. This Actor is the one that carries expected goals, danger buckets, on-ice splits and win probabilities.

| | This Actor | Hockey Reference scrapers | ESPN and scoreboard scrapers |
|---|---|---|---|
| Expected goals and danger buckets | Yes | No | No |
| On-ice and off-ice splits | Yes | No | No |
| Line and pairing combinations | Yes | No | No |
| Playoff odds and win probabilities | Yes | No | Odds only, no model |
| Seasons available | 2008-09 onward | Full history, counting stats | Current season |
| Situation splits | Five | None | None |

### What an NHL skater row looks like

```json
{
  "playerId": "8478043",
  "playerName": "Sam Lafferty",
  "team": "CHI",
  "position": "C",
  "situation": "all",
  "season": "2025",
  "seasonLabel": "2025-26",
  "gameType": "regular",
  "gamesPlayed": 29,
  "icetimeMinutes": 249.32,
  "timeOnIcePerGameMinutes": 8.6,
  "shifts": 354,
  "iceTimeRank": 327,
  "gameScore": 0.83,
  "points": 2,
  "goals": 1,
  "primaryAssists": 0,
  "secondaryAssists": 1,
  "shotsOnGoal": 9,
  "missedShots": 5,
  "blockedShotAttempts": 6,
  "shotAttempts": 20,
  "xGoals": 0.85,
  "xGoalsAdjusted": 0.78,
  "goalsAboveExpected": 0.15,
  "shootingPct": 11.11,
  "pointsPer60": 0.481,
  "goalsPer60": 0.241,
  "xGoalsPer60": 0.205,
  "shotAttemptsPer60": 4.813,
  "rebounds": 2,
  "reboundGoals": 0,
  "hits": 35,
  "takeaways": 2,
  "giveaways": 15,
  "dZoneGiveaways": 3,
  "faceoffsWon": 26,
  "faceoffsLost": 26,
  "faceoffWinPct": 50,
  "penalties": 2,
  "penaltyMinutes": 4,
  "penaltiesDrawn": 1,
  "shotsBlockedByPlayer": 5,
  "onIceXGoalsPct": 39,
  "onIceCorsiPct": 47,
  "onIceFenwickPct": 45,
  "offIceXGoalsPct": 43,
  "sourceUrl": "https://moneypuck.com/moneypuck/playerData/seasonSummary/2025/regular/skaters.csv",
  "rowType": "skater",
  "scrapedAt": "2026-08-28T19:19:25.896Z"
}
```

### Configure the run

Pick the tables you want in **Data to scrape**, choose a **Season** and a **Game type**, and select which **Game situations** you want split out. Everything else is a filter: team, position, player name, minimum games, minimum ice time, minimum points and minimum expected goals are all applied to the raw upstream row, so you can filter on a column without buying the block that contains it. When you select several tables, the row cap is split evenly between them and anything one table leaves unused rolls to the next.

Every skater in the latest published season, no extras:

```json
{
  "datasets": ["skaters"],
  "season": "latest",
  "gameType": "regular",
  "situations": ["all"],
  "maxItems": 5000
}
```

Colorado's 5 on 5 skaters with the on-ice and danger columns:

```json
{
  "datasets": ["skaters"],
  "season": "2025",
  "situations": ["5on5"],
  "teams": ["COL"],
  "minIcetimeMinutes": 200,
  "includeOnIceFor": true,
  "includeOnIceAgainst": true,
  "includeDangerBreakdown": true,
  "maxItems": 100
}
```

Model versus market win probability for a week of games:

```json
{
  "datasets": ["gamePredictions", "schedule"],
  "dateFrom": "2026-04-01",
  "dateTo": "2026-04-07",
  "maxItems": 200
}
```

### Pricing

Pay per event. You are charged for the rows you receive, plus one event per source file downloaded and one start event per run.

| Event | Price |
|---|---|
| Actor start | $0.02 per run |
| Source file scanned | $0.01 per file |
| Skater, goalie, team, line, playoff odds and game prediction rows | $0.005 per row |
| Schedule, standings and game log rows | $0.003 per row |
| Player biography block | $0.004 per enriched row |
| On-ice offence and on-ice defence blocks | $0.002 per row each |
| Danger, zone starts, expected goals detail and off-ice blocks | $0.0015 per row each |

| Rows | Typical cost |
|---|---|
| 100 skater rows | $0.53 |
| 1,000 skater rows | $5.03 |
| 10,000 skater rows | $50.03 |

Volume tiers apply: Bronze pays 3.8% less, Silver 7.4% less, Gold and above 11% less.

### Free users

Free Apify accounts get a 10 row preview per run, which is enough to check the schema and the field names. [Upgrade to a paid plan](https://apify.com/pricing?fpr=vmoqkp) to lift the cap to a million rows per run.

### Run it

1. Create a free [Apify account](https://apify.com/?fpr=vmoqkp). New accounts get $5 in free credit.
2. Open the Actor, leave the default input, and press Start to see ten skater rows.
3. Pick the tables, the season and the filters you want, then raise Max Items.
4. Export the dataset to CSV, JSON, Excel or XML, or read it from the API.

### Use with AI agents (MCP)

```
claude mcp add apify --transport sse https://mcp.apify.com/sse --header "Authorization: Bearer YOUR_APIFY_TOKEN"
```

Then ask in plain language:

- "Get me every Colorado skater at 5 on 5 last season with more than 200 minutes of ice time."
- "Which games this week does the model like more than the sportsbook does?"
- "Pull the top 50 skaters by goals above expected for the 2024-25 season."

### Troubleshooting

**No results came back.** The most common cause is a situation filter that does not exist in the table you picked. Line and pairing rows exist for 5 on 5 and other strengths, not for every split. Start with `all` and narrow from there.

**Fewer rows than I asked for.** When you select several tables the cap is split between them, and a table that runs out of matching rows hands the remainder to the next one. Raise Max Items, or select fewer tables.

**A column I expected is missing.** The six column blocks are off by default. Tick the block you need, for example on-ice offence, and the columns appear on the rows that have them.

**Playoff odds all read 0 or 100.** The simulation file is a live file. Between seasons it holds the finished season, where every probability has collapsed to a certainty. During the season it holds live odds.

**The run is slow.** Game logs are one file download per player or per team. Lower Max players or teams for game logs, or select the season summary tables instead.

### FAQ

| Question | Answer |
|---|---|
| Where does the data come from? | MoneyPuck's published model files and the NHL public API. Both are public and neither needs a key. |
| How far back does it go? | The 2008-09 season, for both regular season and playoffs. |
| What does a season number mean? | The season's starting year, MoneyPuck style. 2025 is the 2025-26 season. |
| What are the situations? | All situations, 5 on 5, 5 on 4 power play, 4 on 5 penalty kill, and other strengths. |
| Are old team codes handled? | Yes. Los Angeles is `L.A` before 2017 upstream, and a filter on LAK matches those rows too. |
| Can I filter without paying for the column? | Yes. Filters run on the full upstream row before projection, so filtering by expected goals does not require the expected goals detail block. |
| Is the market win probability included? | Yes, on game prediction rows, together with the model probability and the difference between them. |
| How fast is it? | Measured at 9,756 rows in 15.4 seconds on a 512 MB run, with a 267 MB peak. |
| Does it need a proxy? | No. Both upstreams answer plain HTTP requests. |

### Related actors

- [Hockey Reference Scraper](https://apify.com/parseforge/hockey-reference-scraper?fpr=vmoqkp) for the traditional counting stats.
- [NHL Stats Scraper](https://apify.com/parseforge/nhl-stats-scraper?fpr=vmoqkp) for the official league tables.
- [NHL Roster and Schedule Scraper](https://apify.com/parseforge/nhl-roster-schedule-scraper?fpr=vmoqkp) for rosters and fixtures.
- [Barttorvik College Basketball Scraper](https://apify.com/parseforge/barttorvik-college-basketball-scraper?fpr=vmoqkp) for the same kind of model output in NCAA basketball.
- [Sportsbook Review Scraper](https://apify.com/parseforge/sportsbook-review-scraper?fpr=vmoqkp) for the market prices to compare against.

Browse the full [ParseForge collection](https://apify.com/parseforge?fpr=vmoqkp) for more scrapers.

Need help? Email parseforge@protonmail.com with your run ID, your input, and what you expected.

Disclaimer: this is an unofficial tool, not affiliated with MoneyPuck or the National Hockey League. It reads only public data that both sources publish without a login. Statistics about public sporting events are not personal data under GDPR, CCPA or PIPL; player names appear because they are part of the public record of a professional competition.

# Actor input Schema

## `datasets` (type: `array`):

Which NHL tables to pull. Skaters, goalies, teams and lines are MoneyPuck season summaries; playoff odds are MoneyPuck simulations; schedule and standings come from the NHL public API; game logs are one row per game per entity.

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

Season start year, MoneyPuck style: 2025 is the 2025-26 season. Leave on Latest to take the most recent published season automatically.

## `gameType` (type: `string`):

Regular season or playoffs. Both are published for every season back to 2008-09.

## `situations` (type: `array`):

MoneyPuck splits every table by situation. 'all' is the full-strength-plus-special-teams total; the others are the strength states.

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

Free users: limited to 10 items (preview). Paid users: up to 1,000,000.

## `teams` (type: `array`):

Filter by team code. Historical codes are matched automatically, so LAK also matches the L.A rows used before 2017.

## `positions` (type: `array`):

Skater positions as MoneyPuck labels them. Goalies live in their own dataset, not here.

## `playerName` (type: `string`):

Case-insensitive substring match on the player name, for example 'McDavid'.

## `playerIds` (type: `array`):

NHL player IDs to pull game logs for, for example 8478402. When empty, the game-log datasets take the highest-ice-time players that pass the filters.

## `minGamesPlayed` (type: `integer`):

Drop rows below this many games. Useful to cut the long tail of one-game call-ups.

## `minIcetimeMinutes` (type: `number`):

Drop rows below this much total ice time in the season, in minutes.

## `minPoints` (type: `number`):

Drop skater rows below this many points.

## `minXGoals` (type: `number`):

Drop rows below this many individual expected goals. Decimals are honoured, so 12.5 works.

## `dateFrom` (type: `string`):

First date for the schedule, game predictions and standings datasets, as YYYY-MM-DD. Defaults to seven days ago.

## `dateTo` (type: `string`):

Last date for the schedule, game predictions and standings datasets, as YYYY-MM-DD. Defaults to today.

## `maxGameLogEntities` (type: `integer`):

How many players or teams the game-log datasets walk. Each one is a separate file download.

## `includeDangerBreakdown` (type: `boolean`):

Adds low, medium and high danger shots, expected goals and goals to every row.

## `includeOnIceFor` (type: `boolean`):

Adds the ~30 on-ice-for columns: shots, expected goals, rebounds and danger buckets while the player was on the ice.

## `includeOnIceAgainst` (type: `boolean`):

Adds the ~30 on-ice-against columns, which is what a defensive-impact model needs.

## `includeZoneStarts` (type: `boolean`):

Adds offensive, defensive, neutral and on-the-fly shift starts and ends, plus time on bench.

## `includeExpectedGoalsDetail` (type: `boolean`):

Adds the expected rebound, freeze, play-stopped and score-adjusted components behind the xG number.

## `includeOffIceSplits` (type: `boolean`):

Adds the team's expected goals and shot attempts while the player was on the bench, plus the after-shift carryover columns.

## `includePlayerBio` (type: `boolean`):

Looks each skater or goalie up in the NHL public API and adds height, weight, birth date and place, shoots or catches, draft position and headshot.

## Actor input object example

```json
{
  "datasets": [
    "skaters"
  ],
  "season": "latest",
  "gameType": "regular",
  "situations": [
    "all"
  ],
  "maxItems": 10,
  "maxGameLogEntities": 5,
  "includeDangerBreakdown": false,
  "includeOnIceFor": false,
  "includeOnIceAgainst": false,
  "includeZoneStarts": false,
  "includeExpectedGoalsDetail": false,
  "includeOffIceSplits": false,
  "includePlayerBio": false
}
```

# Actor output Schema

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

No description

## `csv` (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 = {
    "datasets": [
        "skaters"
    ],
    "season": "latest",
    "gameType": "regular",
    "situations": [
        "all"
    ],
    "maxItems": 10,
    "maxGameLogEntities": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("parseforge/nhl-advanced-stats-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 = {
    "datasets": ["skaters"],
    "season": "latest",
    "gameType": "regular",
    "situations": ["all"],
    "maxItems": 10,
    "maxGameLogEntities": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("parseforge/nhl-advanced-stats-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 '{
  "datasets": [
    "skaters"
  ],
  "season": "latest",
  "gameType": "regular",
  "situations": [
    "all"
  ],
  "maxItems": 10,
  "maxGameLogEntities": 5
}' |
apify call parseforge/nhl-advanced-stats-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,parseforge/nhl-advanced-stats-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/aapXt5MvubGVZ17EA/builds/frz6N31sufZhoIHQx/openapi.json
