# Basketball Reference Table Scraper (`muhammadafzal/basketball-reference-scraper`) Actor

Extract normalized stats rows from public Basketball Reference player, team, league, and box-score tables.

- **URL**: https://apify.com/muhammadafzal/basketball-reference-scraper.md
- **Developed by:** [Muhammad Afzal](https://apify.com/muhammadafzal) (community)
- **Categories:** Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $16.00 / 1,000 basketball statistics 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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Basketball Reference Scraper

> **Free-plan limit:** Apify free-plan runs can return at most **5 dataset records per run** from this Actor, even if you request more.

Extract normalized statistics rows from public Basketball Reference player, team, league, season, and box-score pages. The Actor is designed for analysts, researchers, journalists, fantasy-basketball workflows, and data pipelines that need a small, clearly attributed slice of a public stats page.

The Actor makes one static HTML request per URL, reads ordinary tables and tables embedded inside HTML comments, and writes one dataset item per table row. It does not crawl discovered links, log in, access Stathead, solve challenges, or build a site-wide archive.

### What it extracts

Each dataset item has a stable envelope while the statistics remain flexible enough for different Basketball Reference tables.

| Field | Meaning |
|---|---|
| `sourceUrl` | Public Basketball Reference page containing the row |
| `pageType` | `player`, `team`, `league`, `boxscore`, or `other` |
| `pageTitle` / `entityName` | Page title and primary heading |
| `tableId` / `tableTitle` | Source HTML table ID and caption |
| `rowNumber` / `rowKey` | Row position and stable deduplication key |
| `rowLabel` | First non-empty cell, often a season, player, or team |
| `data` | Values keyed by Basketball Reference `data-stat` names |
| `links` | Absolute source links keyed by the linked cell's `data-stat` name |
| `scrapedAt` | UTC extraction timestamp |

Numeric cells become JSON numbers, blank or dash cells become `null`, and labels such as `2024-25` remain strings. Repeated in-table header rows are removed. Footer, partial-team, and total rows are excluded by default and can be enabled with `includeSummaryRows`.

### When to use it

Use this Actor when you know the public page URL and want rows from one or more named tables. Common IDs include `per_game_stats`, `totals_stats`, `advanced`, `roster`, and page-specific box-score table IDs. If a requested ID is wrong, the run returns an `EMPTY` outcome and lists available table IDs in `SUMMARY`.

Do not use it for Stathead, authenticated pages, bulk mirroring, AI model training, or creating a database that competes with Sports Reference. Basketball Reference's current policies permit responsible use of individual pages subject to its terms, attribution, and rate limits; the user remains responsible for ensuring their intended use is permitted.

### Input

| Field | Default | Notes |
|---|---:|---|
| `startUrls` | Stephen Curry player page | 1–20 public HTTPS Basketball Reference URLs |
| `tableIds` | `per_game_stats` | Exact table IDs; ignored by `extractAllTables` |
| `extractAllTables` | `false` | Extract every table on supplied pages, without crawling links |
| `includeSummaryRows` | `false` | Include footer, total, and partial-team rows |
| `maxResults` | `100` | Global 1–5000 row cap |
| `requestDelayMillis` | `3200` | Minimum 3200 ms to remain under 20 requests/minute |
| `requestTimeoutSeconds` | `30` | Per-page timeout, 10–60 seconds |
| `proxyConfiguration` | no proxy | Optional stable Apify Proxy route when direct cloud traffic is blocked |

Example input:

```json
{
  "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"],
  "includeSummaryRows": false,
  "maxResults": 250,
  "requestDelayMillis": 3200,
  "proxyConfiguration": { "useApifyProxy": false }
}
```

API example:

```bash
curl -X POST "https://api.apify.com/v2/acts/YOUR_USERNAME~basketball-reference-scraper/runs?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"startUrls":[{"url":"https://www.basketball-reference.com/leagues/NBA_2025_per_game.html"}],"tableIds":["per_game_stats"],"maxResults":25}'
```

Keep tokens in secrets or request headers in production; do not commit them to source code.

### Output example

```json
{
  "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": "2af44bb5a2ee71d1f79d",
  "rowLabel": "2009-10",
  "data": {
    "year_id": "2009-10",
    "age": 21,
    "team_name_abbr": "GSW",
    "games": 80,
    "pts_per_g": 17.5
  },
  "links": {
    "year_id": "https://www.basketball-reference.com/players/c/curryst01/gamelog/2010/"
  },
  "scrapedAt": "2026-08-29T12:00:00.000Z"
}
```

The named `SUMMARY` key-value record reports `DATA`, `EMPTY`, `BLOCKED`, `REJECTED`, or `FAILED`, page and table counts, delivered records, and per-page warnings. Diagnostics never appear as fake dataset rows.

### Pay-per-event pricing

Current Apify event prices and platform-usage treatment are listed in the dedicated [Pricing section](#pricing) below.

A 100-row run costs up to `$2` in Actor events. A one-row run costs `$0.02`. Platform or proxy usage may be charged separately only when the live Actor pricing surface says so. `maxResults` gives a predictable result-event ceiling, and the Actor never manually charges the synthetic start or dataset-item events.

If a caller sets Apify's run-level `maxTotalChargeUsd`, the Actor checks the remaining synthetic dataset-item budget before every write and stops cleanly when the cap is exhausted. `SUMMARY.recordsDelivered` and `resultEvents` count only rows actually written.

### Reliability and responsible access

Sports Reference publishes a limit of 20 requests per minute for Basketball Reference and may block a session for up to a day after violations. This Actor uses a single request stream, enforces at least 3.2 seconds between page requests, caps inputs at 20 pages, performs no recursive crawling, and makes no hidden retry storm. HTTP 403, 429, challenges, timeouts, wrong table IDs, and page changes are classified in `SUMMARY`.

Direct static HTML is the default and cheapest route. If a cloud route is blocked, you can enable an authorized Apify Proxy configuration; the Actor keeps one stable proxy session. It does not defeat CAPTCHAs or other access controls. A blocked run returns zero fabricated rows and zero result events.

Respect [Sports Reference's terms](https://www.sports-reference.com/termsofuse.html), [data-use policy](https://www.sports-reference.com/data_use.html), and [bot traffic limits](https://www.sports-reference.com/bot-traffic.html). Credit Basketball Reference and the exact `sourceUrl` when publishing results. Do not use the output for prohibited AI training, bulk redistribution, or a material substitute for Sports Reference or its data providers.

For support, open an issue on the Actor's Apify page with the run ID, source URL, requested table IDs, and a redacted copy of `SUMMARY`.

### Pricing

This Actor uses **pay-per-event** pricing. These are the current Apify Store event prices:

| Event | Price (USD) | When it is charged |
|---|---:|---|
| `apify-default-dataset-item` | FREE: $0.02; BRONZE: $0.02; SILVER: $0.02; GOLD: $0.016; PLATINUM: $0.016; DIAMOND: $0.016 | Basketball statistics row — One schema-valid Basketball Reference table row written to the default dataset. |
| `apify-actor-start` | $0.005 | Actor Start — Charged when the Actor starts running. Number of events charged depends on Actor memory (one event per GB, minimum one event). — Charged once per run. |

Apify platform usage for this Actor run is included alongside the event prices above; no separate per-run platform-usage fee is passed to users.

This covers Apify platform usage for this Actor run. Other Apify products or usage outside this Actor run may still follow your account plan.

# Actor input Schema

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

Use this for public player, team, league, season, or box-score pages on basketball-reference.com. Enter 1–20 HTTPS URLs, for example https://www.basketball-reference.com/players/c/curryst01.html. Stathead and authenticated pages are not supported.

## `tableIds` (type: `array`):

Use this to select exact HTML table IDs, for example per\_game\_stats, totals\_stats, advanced, or roster. Defaults to per\_game\_stats. This field is ignored when Extract all tables is enabled.

## `extractAllTables` (type: `boolean`):

Use this when you need every stats table found on each supplied page, including tables embedded in HTML comments. Defaults to false; it does not follow links or crawl the site.

## `includeSummaryRows` (type: `boolean`):

Use this to retain footer, partial-team, and total rows in addition to ordinary body rows. Defaults to false to avoid duplicate career/team aggregates.

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

Use this to cap dataset rows and pay-per-event cost across all pages. Enter 1–5000; the default is 100. The Actor stops before writing rows beyond this limit.

## `requestDelayMillis` (type: `integer`):

Use this to slow requests beyond the required responsible-use floor. Enter 3200–15000 milliseconds; the default 3200 keeps traffic below Basketball Reference's published 20-request-per-minute limit.

## `requestTimeoutSeconds` (type: `integer`):

Use this to bound one page request. Enter 10–60 seconds; the default is 30. This is not the total Actor run timeout.

## `proxyConfiguration` (type: `object`):

Use this only when your direct cloud route is blocked. The default uses no proxy; if enabled, keep one stable proxy session for the run. Proxy traffic can add platform cost.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.basketball-reference.com/players/c/curryst01.html"
    }
  ],
  "tableIds": [
    "per_game_stats"
  ],
  "extractAllTables": false,
  "includeSummaryRows": false,
  "maxResults": 100,
  "requestDelayMillis": 3200,
  "requestTimeoutSeconds": 30,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Schema-validated table rows in the default dataset.

## `summary` (type: `string`):

Outcome, page counts, table counts, delivered rows, and warnings.

# 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://www.basketball-reference.com/players/c/curryst01.html"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("muhammadafzal/basketball-reference-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 = { "startUrls": [{ "url": "https://www.basketball-reference.com/players/c/curryst01.html" }] }

# Run the Actor and wait for it to finish
run = client.actor("muhammadafzal/basketball-reference-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 '{
  "startUrls": [
    {
      "url": "https://www.basketball-reference.com/players/c/curryst01.html"
    }
  ]
}' |
apify call muhammadafzal/basketball-reference-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,muhammadafzal/basketball-reference-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/mnGgRNf7nhVmJSutT/builds/ecDRRmIPBzrLrGMWd/openapi.json
