NHL Advanced Stats Scraper: MoneyPuck xG, Corsi, Odds
parseforge/nhl-advanced-stats-scraper
News & mediaOther
Scrape NHL expected goals, Corsi, on-ice splits, line combos, playoff odds and game win probabilities back to the 2008-09 season.
- Total runs
- 13
- Bookmarked
- 0
- Last modified
- 7 days ago
This scraper was last updated on .
What does the NHL Advanced Stats Scraper: MoneyPuck xG, Corsi, Odds return?
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
{
"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:
{
"datasets": ["skaters"],
"season": "latest",
"gameType": "regular",
"situations": ["all"],
"maxItems": 5000
}
Colorado's 5 on 5 skaters with the on-ice and danger columns:
{
"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:
{
"datasets": ["gamePredictions", "schedule"],
"dateFrom": "2026-04-01",
"dateTo": "2026-04-07",
"maxItems": 200
}
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 to lift the cap to a million rows per run.
Run it
- Create a free Apify account. New accounts get $5 in free credit.
- Open the Actor, leave the default input, and press Start to see ten skater rows.
- Pick the tables, the season and the filters you want, then raise Max Items.
- 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. |
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.
What input does the NHL Advanced Stats Scraper: MoneyPuck xG, Corsi, Odds accept?
| Field | Type | What it does | Default |
|---|---|---|---|
| datasets | 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. | ["skaters"] |
| season | string (19 options) | Season start year, MoneyPuck style: 2025 is the 2025-26 season. Leave on Latest to take the most recent published season automatically. | latest |
| gameType | string (2 options) | Regular season or playoffs. Both are published for every season back to 2008-09. | regular |
| situations | array | MoneyPuck splits every table by situation. 'all' is the full-strength-plus-special-teams total; the others are the strength states. | ["all"] |
| maxItems | integer | Free users: limited to 10 items (preview). Paid users: up to 1,000,000. | 10 |
| teams | array | Filter by team code. Historical codes are matched automatically, so LAK also matches the L.A rows used before 2017. | not set |
| positions | array | Skater positions as MoneyPuck labels them. Goalies live in their own dataset, not here. | not set |
| playerName | string | Case-insensitive substring match on the player name, for example 'McDavid'. | not set |
| playerIds | 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. | not set |
| minGamesPlayed | integer | Drop rows below this many games. Useful to cut the long tail of one-game call-ups. | not set |
| minIcetimeMinutes | number | Drop rows below this much total ice time in the season, in minutes. | not set |
| minPoints | number | Drop skater rows below this many points. | not set |
| minXGoals | number | Drop rows below this many individual expected goals. Decimals are honoured, so 12.5 works. | not set |
| dateFrom | string | First date for the schedule, game predictions and standings datasets, as YYYY-MM-DD. Defaults to seven days ago. | not set |
| dateTo | string | Last date for the schedule, game predictions and standings datasets, as YYYY-MM-DD. Defaults to today. | not set |
| maxGameLogEntities | integer | How many players or teams the game-log datasets walk. Each one is a separate file download. | 5 |
| includeDangerBreakdown | boolean | Adds low, medium and high danger shots, expected goals and goals to every row. | false |
| includeOnIceFor | boolean | Adds the ~30 on-ice-for columns: shots, expected goals, rebounds and danger buckets while the player was on the ice. | false |
| includeOnIceAgainst | boolean | Adds the ~30 on-ice-against columns, which is what a defensive-impact model needs. | false |
| includeZoneStarts | boolean | Adds offensive, defensive, neutral and on-the-fly shift starts and ends, plus time on bench. | false |
| includeExpectedGoalsDetail | boolean | Adds the expected rebound, freeze, play-stopped and score-adjusted components behind the xG number. | false |
| includeOffIceSplits | boolean | Adds the team's expected goals and shot attempts while the player was on the bench, plus the after-shift carryover columns. | false |
| includePlayerBio | 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. | false |
How much does the NHL Advanced Stats Scraper: MoneyPuck xG, Corsi, Odds cost?
from $4.45 per 1,000 results
| Charged for | What it is | Price each |
|---|---|---|
| Actor Start | Charged when the Actor starts running. Number of events charged depends on Actor memory (one event per GB, minimum one event). | $0.02 |
| Source file scanned | Charged once per MoneyPuck file downloaded and parsed, for example one season summary or one career game log. This is the fixed cost of pagination, spread across every row that file produces. | $0.01 |
| Skater season row | Charged once per skater season row written to your dataset: 50 fields covering points, expected goals, on-ice percentages and per-60 rates for one player in one situation. | $0.00445 to $0.005 |
| Goalie season row | Charged once per goalie season row, including goals saved above expected, expected save percentage and rebound control. | $0.00445 to $0.005 |
| Team season row | Charged once per team season row, with for and against splits on shots, expected goals, danger buckets and discipline. | $0.00445 to $0.005 |
| Line or pairing row | Charged once per forward line or defence pairing row, with its ice time rank and its expected goals share. | $0.00445 to $0.005 |
| Playoff odds row | Charged once per team row of MoneyPuck simulation output: playoff, round, conference final, final and Stanley Cup probabilities. | $0.00445 to $0.005 |
| Game prediction row | Charged once per game win probability row, with the model price, the market price and the difference between them. | $0.00445 to $0.005 |
| Schedule row | Charged once per scheduled or finished game read from the NHL public API, with venue, broadcast and final score. | $0.00267 to $0.003 |
| Standings row | Charged once per team standings row, with the home, road, last-ten and streak splits. | $0.00267 to $0.003 |
| Player game log row | Charged once per player game row, one row per game per situation, with the same advanced columns as the season table. | $0.00267 to $0.003 |
| Team game log row | Charged once per team game row, one row per game per situation, back to the 2008-09 season. | $0.00267 to $0.003 |
| Player biography block | Optional. Charged only on rows where the NHL public API returned a biography: height, weight, birth date and place, shoots or catches, draft position and headshot. | $0.00356 to $0.004 |
| Shot danger block | Optional. Charged only on rows that received the low, medium and high danger shots, expected goals and goals columns. | $0.00134 to $0.0015 |
| On-ice offence block | Optional. Charged only on rows that received the on-ice-for columns: shots, expected goals, rebounds and danger buckets while the player was on the ice. | $0.00178 to $0.002 |
| On-ice defence block | Optional. Charged only on rows that received the on-ice-against columns, which is what a defensive impact model needs. | $0.00178 to $0.002 |
| Zone starts block | Optional. Charged only on rows that received offensive, defensive, neutral and on-the-fly shift starts and ends plus time on bench. | $0.00134 to $0.0015 |
| Expected goals detail block | Optional. Charged only on rows that received the expected rebound, freeze, play-stopped and score-adjusted components behind the xG number. | $0.00134 to $0.0015 |
| Off-ice splits block | Optional. Charged only on rows that received the bench-time team splits and the after-shift carryover columns. | $0.00134 to $0.0015 |
Tiered: the lower figure is the price on a higher Apify plan. Billing and the free credit live on Apify.
How do I call the NHL Advanced Stats Scraper: MoneyPuck xG, Corsi, Odds API?
One POST returns the dataset directly. Same shape for every scraper in the library, so swapping the slug is the only change.
curl -X POST "https://api.apify.com/v2/acts/parseforge~nhl-advanced-stats-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"datasets": [
"skaters"
],
"season": "latest",
"gameType": "regular",
"situations": [
"all"
],
"maxItems": 10
}' What example inputs can I use?
Use these inputs to see how a run is configured.
{
"datasets": [
"skaters"
],
"season": "latest",
"gameType": "regular",
"situations": [
"all"
],
"maxItems": 10
} What do users say about the NHL Advanced Stats Scraper: MoneyPuck xG, Corsi, Odds?
No reviews yet. Be the first.
How do I report an issue with the NHL Advanced Stats Scraper: MoneyPuck xG, Corsi, Odds?
We build and maintain this scraper, so a problem with it comes to us. Report it on the Apify listing and the thread stays attached to the scraper where the next person can find it: open an issue.
Broken and urgent, or you would rather not post in public? Write to parseforge@protonmail.com and it reaches the people who wrote it.
What related scrapers can I use?
Run NHL Advanced Stats Scraper: MoneyPuck xG, Corsi, Odds on Apify All scrapers
