← Back to blog

ESPN API with Python: Scrape 60,000+ Games for Sports Analytics

2026-03-24 espn python scraping tutorial data

ESPN doesn't have an official public API — but they have an undocumented one that powers their website. It's free, returns JSON, and covers every major sport. Here's how to use it.

The Endpoints

ESPN's scoreboard API follows a consistent pattern:

https://site.api.espn.com/apis/site/v2/sports/{sport}/{league}/scoreboard?dates={YYYYMMDD}

Examples: - NBA: basketball/nba/scoreboard?dates=20261225 - NFL: football/nfl/scoreboard?dates=20261015 - NCAAMB: basketball/mens-college-basketball/scoreboard?dates=20260315

Basic Python Example

import requests

url = "https://site.api.espn.com/apis/site/v2/sports/basketball/nba/scoreboard"
resp = requests.get(url, params={"dates": "20261225"})
data = resp.json()

for event in data["events"]:
    home = event["competitions"][0]["competitors"][0]
    away = event["competitions"][0]["competitors"][1]
    print(f"{away['team']['abbreviation']} @ {home['team']['abbreviation']}: "
          f"{away['score']}-{home['score']}")

What You Can Extract

Each game event contains: - Scores: home/away, by period - Game clock: period, time remaining, status (scheduled/in-progress/final) - Team info: full name, abbreviation, logo URL, record - Win probability: ESPN's own WP estimate (useful as a feature, not a trading signal) - Leaders: top scorers, rebounders, assists

Scaling Up: Async Scraping

For production use, you need async requests to scrape thousands of games efficiently:

import aiohttp
import asyncio

async def fetch_scoreboard(session, sport, league, date):
    url = f"https://site.api.espn.com/apis/site/v2/sports/{sport}/{league}/scoreboard"
    async with session.get(url, params={"dates": date}) as resp:
        return await resp.json()

async def scrape_season(sport, league, dates):
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_scoreboard(session, sport, league, d) for d in dates]
        return await asyncio.gather(*tasks)

Rate limiting: ESPN will throttle you if you're too aggressive. Add await asyncio.sleep(0.5) between requests. Our production scraper handles this automatically with exponential backoff.

From Data to Trading

Raw ESPN data is the foundation of everything we build at ZenHodl:

  1. 60,000+ games scraped across 7 sports (2020-2026)
  2. 25.6 million in-game state snapshots (score + time + period every few seconds)
  3. Elo ratings computed from game results for 900+ teams
  4. WP models trained on these snapshots to predict fair win probability

The gap between our model's fair probability and Polymarket's ask price is what creates tradeable edges.

Want the Full Pipeline?

This blog post covers the basics. The ZenHodl course Module 1 goes deep: - Async scraper with retry logic and rate limiting - Multi-sport support (NBA, NFL, NCAAMB, CFB, NHL, MLB) - Efficient Parquet storage - Data quality checks and validation - Module 1 is completely freedownload it here

Or if you just want the data without scraping it yourself, check out our ESPN WP Training Dataset — 25.6M rows, ready to use.


Part of the ZenHodl blog. We write about sports analytics, prediction markets, and building trading bots with Python.

Want to build this yourself?

The ZenHodl course teaches you to build a complete prediction market bot in 6 notebooks.