NBA Shot Chart & Shot Location Scraper avatar

NBA Shot Chart & Shot Location Scraper

Pricing

from $0.50 / 1,000 shots

Go to Apify Store
NBA Shot Chart & Shot Location Scraper

NBA Shot Chart & Shot Location Scraper

Scrape every NBA field-goal attempt with exact court coordinates: true distance, angle, zone and plot-ready x/y. Adds expected points from the league's own per-zone baseline, so you can see which shots beat the league. Filter by game, player, team or date. No API key, no login.

Pricing

from $0.50 / 1,000 shots

Rating

0.0

(0)

Developer

Elena Vance

Elena Vance

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

8 days ago

Last modified

Share

Every NBA field-goal attempt, with where on the floor it came from — and the spatial fields the league does not publish, computed and verified against a full season.

No API key, no login, no browser. Filter by game, player, team, date or season.

What you get

One row per shot. Real output from a 2026-04-12 game:

DatePlayerTeamMadeZoneOur distanceNBA'sAngleExpected ptsPts over expected
2026-04-12Luka GarzaBOS✅Above the Break 325.7782528.75°1.038+1.962
2026-04-12Franz WagnerORL✅Above the Break 327.7772727.67°1.038+1.962
2026-04-12Paolo BancheroORL✅Restricted Area1.985140.91°1.342+0.658
2026-04-12Paolo BancheroORL❌Restricted Area2.7662−77.47°1.342−1.342
2026-04-12Desmond BaneORL✅Mid-Range18.6231835.82°0.800+1.200

Each row also carries the raw LOC_X/LOC_Y, plot-ready coordinates for the standard 500×470 court grid in both orientations, the game clock as elapsed seconds, home/away and opponent, and NBA's three zone labels.

What makes this different

The distance NBA publishes is wrong by design. SHOT_DISTANCE is floored, not rounded — verified across all 219,160 shots of the 2025-26 regular season, where floor matched every row and round matched 54%. Every published distance therefore runs short by up to a foot, always in the same direction. You get the exact value and NBA's integer beside it.

Expected points, from NBA's own numbers. The endpoint ships a league FG% table per zone alongside every query. Joining it turns a made/missed flag into points over expectation — shot-making separated from shot selection, with no model to trust. The baseline is fetched once for the whole season, deliberately: scoring a single day's shots against that same day's average drives the number to zero by construction.

A season query that is actually complete. shotchartdetail truncates at 102,400 rows and reports nothing — a 2023-24 season request returns a valid 200 whose data stops on 17 January. Summed month by month the same season holds 219,527 shots, so 53% is dropped silently. This Actor splits every league-wide query into windows before asking, and re-splits anything that still comes back at the cap.

Home/away that is right. The obvious source for it, MATCHUP, is usually written from the row team's point of view — in one captured slate, 17 rows of 18 were, and Orlando's own row read MEM @ ORL. Deriving the side from that string mislabels roughly one row in twenty. This uses the shot row's own home-team field instead.

Works in the offseason. The default run needs no input and returns the most recent day actually played, resolving back a season when the newest one has been announced but not started.

Modes

Give itYou get
nothingThe most recent day of play, league-wide
gameIdsEvery shot in those games (~180 each) — the cheapest way to run it
playerIdsThat player's whole season (~1,300 shots)
playerIds + gameIdsJust those players, in just those games
teamIdsThat team's season (~7,400 shots)
dateFrom / dateToLeague-wide over that window, split automatically
season + seasonTypeRegular season, playoffs, pre-season, play-in or All-Star

maxItems caps both the result count and the cost. IDs are NBA Stats IDs — 201939 is Stephen Curry, 1610612744 is Golden State.

Why this is cheap to run

Runs in 256 MB with no browser and no proxy on a healthy run, so the apify-actor-start fee is charged once rather than four times. A default run finishes in 3.5 seconds; peak memory plateaus around 160 MB no matter how long the run is.

What this Actor does not return

  • Anything that is not a field-goal attempt. Made and missed shots only — no free throws, rebounds, assists or turnovers.
  • Modelled shot quality. expectedPoints is the league's average from that zone. It is an honest baseline, not an xG-style model — defender distance and shot clock are not in this feed, so no number here pretends to include them.
  • Player tracking or defender data. Served by different stats.nba.com endpoints with a different shape; not joined here.
  • Seasons before 1996-97. Shot locations do not exist before then, and the Actor refuses those rather than returning an empty result that looks like a quiet day.
  • SHOT_ATTEMPTED_FLAG / GRID_TYPE. Constant on every row, so emitting them would cost you storage and tell you nothing.

Example runs

{ "gameIds": ["0022500578"], "maxItems": 200 }
{ "playerIds": [201939], "season": "2025-26" }
{ "teamIds": [1610612744], "season": "2025-26", "seasonType": "Playoffs" }
{ "dateFrom": "2026-01-15", "dateTo": "2026-01-31", "maxItems": 20000 }

Notes

Unofficial and not affiliated with or endorsed by the NBA. Data comes from the same public endpoints nba.com's own statistics pages read.

docs/architecture.md records every measurement behind the claims above, including the header/TLS matrix, the cap proof and the full-season geometry verification.