DEV Community

agenthustler
agenthustler

Posted on

How to Scrape Sports Injury Reports for Fantasy Analytics

Injury reports are the most time-sensitive data in fantasy sports. Here's how to build an automated injury report scraper for fantasy analytics.

Why Automate Injury Tracking?

Fantasy sports platforms update injury statuses with varying delays. By scraping primary sources directly, you get earlier access, historical data for drafts, and automated lineup optimization.

Core Setup

import requests
from bs4 import BeautifulSoup
import pandas as pd
from datetime import datetime
import time
import random

PROXY_URL = "http://api.scraperapi.com"
API_KEY = "YOUR_SCRAPERAPI_KEY"

def fetch_page(url, render_js=False):
    params = {
        "api_key": API_KEY,
        "url": url,
        "render": str(render_js).lower()
    }
    resp = requests.get(PROXY_URL, params=params, timeout=30)
    resp.raise_for_status()
    return BeautifulSoup(resp.text, "html.parser")
Enter fullscreen mode Exit fullscreen mode

Scraping NFL Injury Reports

def scrape_nfl_injuries():
    url = "https://www.espn.com/nfl/injuries"
    soup = fetch_page(url, render_js=True)

    injuries = []
    team_sections = soup.find_all("div", class_="injuries-team")

    for section in team_sections:
        team_name = section.find("h3")
        if not team_name:
            continue
        team = team_name.text.strip()

        rows = section.find_all("tr")[1:]
        for row in rows:
            cols = row.find_all("td")
            if len(cols) >= 4:
                injuries.append({
                    "team": team,
                    "player": cols[0].text.strip(),
                    "position": cols[1].text.strip(),
                    "injury": cols[2].text.strip(),
                    "status": cols[3].text.strip(),
                    "scraped_at": datetime.now().isoformat()
                })

    return pd.DataFrame(injuries)
Enter fullscreen mode Exit fullscreen mode

Change Detection System

class InjuryTracker:
    def __init__(self):
        self.previous_state = {}

    def detect_changes(self, current_df):
        changes = []

        for _, row in current_df.iterrows():
            player_key = f"{row['player']}_{row['team']}"
            current_status = row["status"]
            previous = self.previous_state.get(player_key)

            if previous is None:
                changes.append({
                    "player": row["player"],
                    "team": row["team"],
                    "change_type": "NEW",
                    "old_status": None,
                    "new_status": current_status,
                    "timestamp": datetime.now().isoformat()
                })
            elif previous != current_status:
                changes.append({
                    "player": row["player"],
                    "team": row["team"],
                    "change_type": "UPDATE",
                    "old_status": previous,
                    "new_status": current_status,
                    "timestamp": datetime.now().isoformat()
                })

            self.previous_state[player_key] = current_status

        return changes
Enter fullscreen mode Exit fullscreen mode

Fantasy Impact Scoring

def calculate_fantasy_impact(injury_df, player_stats):
    status_weights = {
        "Out": 1.0,
        "Doubtful": 0.85,
        "Questionable": 0.5,
        "Probable": 0.15
    }

    impacts = []
    for _, injury in injury_df.iterrows():
        weight = status_weights.get(injury["status"], 0.5)
        player_data = player_stats.get(injury["player"], {})
        avg_points = player_data.get("avg_fantasy_points", 0)

        impacts.append({
            "player": injury["player"],
            "status": injury["status"],
            "risk_score": weight,
            "projected_loss": avg_points * weight,
            "recommendation": "BENCH" if weight > 0.6 else "MONITOR" if weight > 0.3 else "START"
        })

    return sorted(impacts, key=lambda x: x["projected_loss"], reverse=True)
Enter fullscreen mode Exit fullscreen mode

Scaling Your Scraper

Sports sites get heavy traffic on game days:

  • ScraperAPI — JavaScript rendering for ESPN and dynamic sports sites
  • ThorData — Residential proxies for rate-limited sports APIs
  • ScrapeOps — Monitor scraper health during peak game-day loads

Conclusion

Automated injury tracking gives fantasy players a real information edge. Build the scraper, set up change detection, and let the data drive your lineup decisions.

Top comments (0)