Basketball Reference Scraper — NBA Stats Tables avatar

Basketball Reference Scraper — NBA Stats Tables

Pricing

from $16.00 / 1,000 basketball statistics rows

Go to Apify Store
Basketball Reference Scraper — NBA Stats Tables

Basketball Reference Scraper — NBA Stats Tables

Extract Basketball Reference stats tables (per game, totals, advanced, rosters, box scores) as JSON rows for NBA analysis. USD 20 per 1,000 rows.

Pricing

from $16.00 / 1,000 basketball statistics rows

Rating

0.0

(0)

Developer

Muhammad Afzal

Muhammad Afzal

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

10 hours ago

Last modified

Categories

Share

Basketball Reference Scraper turns public Basketball Reference player, team, league, season and box-score pages into one clean JSON row per table row, with every stat keyed by the site's own column name. It is built for NBA analysts, fantasy players, journalists and data pipelines that need per-game, totals, advanced or roster tables without copy-pasting, and it costs USD 20 per 1,000 rows on the FREE tier (USD 0.02 per row, USD 16 per 1,000 on GOLD).

You give it the page URLs and the table IDs you want. It reads each page once, including the tables Basketball Reference hides inside HTML comments, and writes the rows to an Apify dataset you can export as JSON, CSV or Excel or read through the API.

What does the Basketball Reference scraper return?

Each dataset item is one row of one stats table. The envelope stays the same for every table, and the stats themselves sit in data.

FieldWhat it contains
sourceUrlThe Basketball Reference page the row came from
pageTypeplayer, team, league, boxscore or other, from the URL path
pageTitle, entityNamePage title and main heading, for example Stephen Curry
tableId, tableTitleHTML table ID and caption, for example per_game_stats / Per Game Table
rowNumber, rowKeyPosition in the table and a stable key used to remove duplicates
rowLabelFirst non-empty cell, usually the season, player or team
dataEvery cell keyed by its data-stat name, such as games, pts_per_g, fg3_pct, ast_per_g
linksAbsolute links from linked cells (game logs, team seasons, award pages)
scrapedAtUTC time the page was parsed

Numbers become JSON numbers, blank or dash cells become null, and labels such as 2024-25 stay strings. Repeated header rows inside a table are dropped. The run also writes a SUMMARY record with the outcome (DATA, EMPTY, BLOCKED, REJECTED or FAILED), page and table counts, rows delivered and per-page warnings.

How do I scrape NBA stats tables from Basketball Reference?

Paste up to 20 public https://www.basketball-reference.com/ URLs into startUrls and list the table IDs in tableIds. The default reads Stephen Curry's per-game table.

InputDefaultNotes
startUrlsCurry player page1–20 public Basketball Reference URLs; other hosts and Stathead pages are rejected
tableIds["per_game_stats"]Exact HTML table IDs, up to 50
extractAllTablesfalseTake every table on each page instead of tableIds
includeSummaryRowsfalseAlso keep career, total and partial-team rows
maxResults100Row cap for the whole run, 1–5,000
requestDelayMillis3200Gap between page requests, 3,200–15,000 ms
requestTimeoutSeconds30Timeout for one page, 10–60 s

A player's career per-game and advanced tables plus one team season:

{
"startUrls": [
{ "url": "https://www.basketball-reference.com/players/c/curryst01.html" },
{ "url": "https://www.basketball-reference.com/teams/GSW/2025.html" }
],
"tableIds": ["per_game_stats", "advanced"],
"maxResults": 250
}

Which Basketball Reference table IDs can I use?

Use the ID of the HTML table on the page. Common ones are per_game_stats, totals_stats, advanced and roster; league and box-score pages have their own IDs. If you are unsure, run once with extractAllTables: true on one page, or run with a guess: when no requested ID matches, the run ends EMPTY, charges no rows, and SUMMARY lists up to 30 table IDs that page actually has.

Is there a Basketball Reference API for this data?

This Actor gives you API access to the public tables. Call it from apify-client, the Apify REST API, a schedule or an AI agent, and you get the same JSON rows every time, so you do not maintain your own HTML parser.

JavaScript:

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('muhammadafzal/basketball-reference-scraper').call({
startUrls: [{ url: 'https://www.basketball-reference.com/leagues/NBA_2025_per_game.html' }],
tableIds: ['per_game_stats'],
maxResults: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const row of items) console.log(row.rowLabel, row.data.pts_per_g);

Python:

import os
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("muhammadafzal/basketball-reference-scraper").call(run_input={
"startUrls": [{"url": "https://www.basketball-reference.com/players/c/curryst01.html"}],
"tableIds": ["per_game_stats"],
"maxResults": 25,
})
for row in client.dataset(run.default_dataset_id).iterate_items():
print(row["rowLabel"], row["data"].get("pts_per_g"))

Use it from an AI assistant

Basketball Reference Scraper works from Claude, ChatGPT, Cursor or any MCP client through Apify's MCP server. Add https://mcp.apify.com/?tools=muhammadafzal/basketball-reference-scraper to your client and ask, for example: "Get Stephen Curry's per-game stats by season from Basketball Reference and tell me his three best three-point percentage seasons." Each row is charged at the prices below, so ask for a row limit.

What does the output look like?

Two rows from a cloud run of the default input on 2026-10-26 (18 rows, 5 seconds), shortened (the second row shows only some fields):

[
{
"sourceUrl": "https://www.basketball-reference.com/players/c/curryst01.html",
"pageType": "player",
"pageTitle": "Stephen Curry Stats, Height, Weight, Position, Draft Status and more",
"entityName": "Stephen Curry",
"tableId": "per_game_stats",
"tableTitle": "Per Game Table",
"rowNumber": 1,
"rowKey": "49f6ad0b18ea10b23e14",
"rowLabel": "2009-10",
"data": { "year_id": "2009-10", "age": 21, "team_name_abbr": "GSW", "pos": "PG", "games": 80, "fg3_pct": 0.437, "ast_per_g": 5.9, "pts_per_g": 17.5, "awards": "ROY-2" },
"links": { "year_id": "https://www.basketball-reference.com/players/c/curryst01/gamelog/2010/", "team_name_abbr": "https://www.basketball-reference.com/teams/GSW/2010.html" },
"scrapedAt": "2026-10-26T06:55:45.453Z"
},
{
"sourceUrl": "https://www.basketball-reference.com/players/c/curryst01.html",
"pageType": "player",
"tableId": "per_game_stats",
"rowNumber": 2,
"rowLabel": "2010-11",
"data": { "year_id": "2010-11", "age": 22, "games": 74, "fg3_pct": 0.442, "ft_pct": 0.934, "pts_per_g": 18.6, "awards": null },
"scrapedAt": "2026-10-26T06:55:45.453Z"
}
]

Pricing: how much does Basketball Reference data cost?

The Actor uses pay-per-event pricing with platform usage included. You pay for rows written to the dataset, plus one start event per run at the default 256 MB memory.

EventFREEBRONZESILVERGOLDPLATINUMDIAMOND
Statistics row (one dataset item)USD 0.02USD 0.02USD 0.02USD 0.016USD 0.016USD 0.016
Actor start (per started GB of allocated memory)USD 0.005USD 0.005USD 0.005USD 0.005USD 0.005USD 0.005

Worked totals on the FREE tier:

  • Curry's per-game table (18 rows): 18 × USD 0.02 + USD 0.005 = USD 0.365
  • 100 rows: 100 × USD 0.02 + USD 0.005 = USD 2.005

maxResults caps the row charge, and if you set a run-level maximum charge, the Actor checks the remaining budget before every row and stops cleanly. Wrong table IDs, blocked pages and timeouts produce no row charges.

When should I use it, and when not?

Use it when you know the Basketball Reference pages you need and want specific tables as rows: a player's career splits, a team's roster and advanced stats, a season's league-wide per-game table, or the box scores of a few games.

Use something else when:

  • You need baseball, hockey or WNBA stats: Baseball Reference Stats Scraper, Hockey-Reference NHL Stats Scraper or WNBA Stats Scraper.
  • You want Stathead queries, college basketball (a different Sports Reference site) or logged-in pages. They are rejected.
  • You want to mirror the whole site, train an AI model or build a competing stats database. The Actor does not crawl links, and Sports Reference's terms restrict those uses.

Limits and responsible access

  • Speed: one page took about 5 seconds in the 2026-10-26 cloud run. Pages are fetched one at a time at least 3.2 seconds apart, which keeps traffic under the 20-requests-per-minute limit Sports Reference publishes, so 20 pages take a little over a minute.
  • Scope: 20 pages and 5,000 rows per run, no link following, no retry storms. HTTP 403, 429, challenge pages and timeouts are recorded in SUMMARY, and a blocked run returns zero rows.
  • Freshness: pages are read live at run time, so in-season tables reflect the site at that moment.

FAQ

Does it crawl the whole Basketball Reference site?

No. It reads only the URLs you supply, up to 20 per run, and never follows links.

Can I get every table on a page?

Yes. Set extractAllTables: true. It includes tables stored inside HTML comments, which many Basketball Reference pages use for their secondary stats tables.

What happens if my table ID is wrong?

The run ends EMPTY, no rows are charged, and SUMMARY lists the table IDs found on that page so you can retry.

Are career totals and team subtotal rows included?

Not by default. Set includeSummaryRows: true to keep footer, total and partial-team rows such as a traded player's per-team lines.

Do I need a Stathead subscription?

No. The Actor reads only free public pages, and it rejects Stathead URLs.

Can I export the stats to CSV or Google Sheets?

Yes. Apify exports any dataset to CSV, JSON, Excel or XML, and integrations can push it to Google Sheets. The nested data object becomes one column per stat in CSV.

Can I publish the data?

Credit Basketball Reference and keep the sourceUrl. Sports Reference's data-use policy limits bulk redistribution and AI training, so check its terms for your use.

The Actor reads public Basketball Reference pages without logging in, solving CAPTCHAs or bypassing access controls, and it stays under the site's published request limit. Follow Sports Reference's terms of use, data-use policy and bot-traffic policy, and credit Basketball Reference when you publish results. This tool is not affiliated with Sports Reference, and this is not legal advice.

Last verified: 2026-10-27