Skip to content

PFR Parsers

parsers

PFR HTML parsers package.

Re-exports the individual parser classes for convenient access::

from griddy.pfr.utils.parsers import ScheduleParser, GameDetailsParser

AwardsParser

Parses PFR awards, HOF, and Pro Bowl pages into structured data dicts.

parse_award

parse_award(html, *, award)

Parse an award history page and return a dict for model validation.

Returns:

Type Description
Dict[str, Any]

A dict with keys award, winners.

Source code in src/griddy/pfr/parsers/awards.py
def parse_award(self, html: str, *, award: str) -> Dict[str, Any]:
    """Parse an award history page and return a dict for model validation.

    Returns:
        A dict with keys ``award``, ``winners``.
    """
    soup = BeautifulSoup(html, "html.parser")

    table = soup.find("table", id="awards")
    if table is None:
        return {"award": award, "winners": []}

    winners = self._parse_award_rows(table)
    return {"award": award, "winners": winners}

parse_hof

parse_hof(html)

Parse the Hall of Fame page and return a dict for model validation.

Returns:

Type Description
Dict[str, Any]

A dict with key players.

Source code in src/griddy/pfr/parsers/awards.py
def parse_hof(self, html: str) -> Dict[str, Any]:
    """Parse the Hall of Fame page and return a dict for model validation.

    Returns:
        A dict with key ``players``.
    """
    soup = BeautifulSoup(html, "html.parser")

    table = soup.find("table", id="hof_players")
    if table is None:
        return {"players": []}

    players = self._parse_hof_rows(table)
    return {"players": players}

parse_probowl

parse_probowl(html, *, year)

Parse a Pro Bowl roster page and return a dict for model validation.

Returns:

Type Description
Dict[str, Any]

A dict with keys year, players.

Source code in src/griddy/pfr/parsers/awards.py
def parse_probowl(self, html: str, *, year: int) -> Dict[str, Any]:
    """Parse a Pro Bowl roster page and return a dict for model validation.

    Returns:
        A dict with keys ``year``, ``players``.
    """
    soup = BeautifulSoup(html, "html.parser")

    table = soup.find("table", id="pro_bowl")
    if table is None:
        return {"year": year, "players": []}

    players = self._parse_probowl_rows(table)
    return {"year": year, "players": players}

BirthdaysParser

Parses the PFR birthdays page.

parse

parse(html)

Parse the birthdays page.

Parameters:

Name Type Description Default
html str

Raw HTML of the PFR birthdays page.

required

Returns:

Type Description
Dict[str, Any]

A dict ready for Birthdays.model_validate().

Raises:

Type Description
ParsingError

If the birthdays table is not found.

Source code in src/griddy/pfr/parsers/birthdays.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse the birthdays page.

    Args:
        html: Raw HTML of the PFR birthdays page.

    Returns:
        A dict ready for ``Birthdays.model_validate()``.

    Raises:
        ParsingError: If the birthdays table is not found.
    """
    soup = BeautifulSoup(html, "html.parser")

    title = self._extract_title(soup)
    month, day = self._extract_month_day(title)

    table = soup.find("table", id="birthdays")
    if table is None:
        raise ParsingError(
            "Could not find birthdays table in the HTML.",
            selector="birthdays",
            html_sample=html[:500],
        )

    players = self._parse_table(table)

    return {
        "title": title,
        "month": month,
        "day": day,
        "players": players,
    }

BirthplacesParser

Parses the PFR birthplaces pages (landing and filtered).

parse_landing

parse_landing(html)

Parse the birthplaces landing page.

Parameters:

Name Type Description Default
html str

Raw HTML of the PFR birthplaces landing page.

required

Returns:

Type Description
Dict[str, Any]

A dict ready for BirthplaceLanding.model_validate().

Raises:

Type Description
ParsingError

If the birthplaces table is not found.

Source code in src/griddy/pfr/parsers/birthplaces.py
def parse_landing(self, html: str) -> Dict[str, Any]:
    """Parse the birthplaces landing page.

    Args:
        html: Raw HTML of the PFR birthplaces landing page.

    Returns:
        A dict ready for ``BirthplaceLanding.model_validate()``.

    Raises:
        ParsingError: If the birthplaces table is not found.
    """
    soup = BeautifulSoup(html, "html.parser")

    title = self._extract_landing_title(soup)

    table = soup.find("table", id="birthplaces")
    if table is None:
        raise ParsingError(
            "Could not find birthplaces table in the HTML.",
            selector="birthplaces",
            html_sample=html[:500],
        )

    locations = self._parse_landing_table(table)

    return {
        "title": title,
        "locations": locations,
    }

parse_filtered

parse_filtered(html)

Parse the birthplaces filtered (by location) page.

Parameters:

Name Type Description Default
html str

Raw HTML of the PFR birthplaces filtered page.

required

Returns:

Type Description
Dict[str, Any]

A dict ready for BirthplaceFiltered.model_validate().

Raises:

Type Description
ParsingError

If the birthplaces table is not found.

Source code in src/griddy/pfr/parsers/birthplaces.py
def parse_filtered(self, html: str) -> Dict[str, Any]:
    """Parse the birthplaces filtered (by location) page.

    Args:
        html: Raw HTML of the PFR birthplaces filtered page.

    Returns:
        A dict ready for ``BirthplaceFiltered.model_validate()``.

    Raises:
        ParsingError: If the birthplaces table is not found.
    """
    soup = BeautifulSoup(html, "html.parser")

    title = self._extract_filtered_title(soup)
    country, state = self._extract_country_state(title)

    table = soup.find("table", id="birthplaces")
    if table is None:
        raise ParsingError(
            "Could not find birthplaces table in the HTML.",
            selector="birthplaces",
            html_sample=html[:500],
        )

    players = self._parse_filtered_table(table)

    return {
        "title": title,
        "country": country,
        "state": state,
        "players": players,
    }

CoachProfileParser

Parses PFR coach profile pages into comprehensive data dicts.

parse

parse(html)

Parse a PFR coach profile page into a JSON-serializable dict.

Parameters:

Name Type Description Default
html str

Raw HTML string of a PFR coach profile page.

required

Returns:

Type Description
Dict[str, Any]

A dict with keys: bio, coaching_results, coaching_results_totals,

Dict[str, Any]

coaching_ranks, coaching_history, challenge_results, worked_for,

Dict[str, Any]

employed.

Source code in src/griddy/pfr/parsers/coach_profile.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse a PFR coach profile page into a JSON-serializable dict.

    Args:
        html: Raw HTML string of a PFR coach profile page.

    Returns:
        A dict with keys: bio, coaching_results, coaching_results_totals,
        coaching_ranks, coaching_history, challenge_results, worked_for,
        employed.
    """
    cleaned = re.sub(r"<!--(.*?)-->", r"\1", html, flags=re.DOTALL)
    soup = BeautifulSoup(cleaned, "html.parser")

    result: Dict[str, Any] = {}
    result["bio"] = self._parse_bio(soup)

    results, totals = self._parse_coaching_results(soup)
    result["coaching_results"] = results
    result["coaching_results_totals"] = totals

    result["coaching_ranks"] = self._parse_coaching_ranks(soup)
    result["coaching_history"] = self._parse_coaching_history(soup)
    result["challenge_results"] = self._parse_challenge_results(soup)
    result["worked_for"] = self._parse_coaching_tree_table(soup, "worked_for")
    result["employed"] = self._parse_coaching_tree_table(soup, "employed")

    return result

CupsOfCoffeeParser

Parses the PFR 'Cups of Coffee' page.

parse

parse(html)

Parse the cups of coffee page.

Parameters:

Name Type Description Default
html str

Raw HTML of the PFR cups of coffee page.

required

Returns:

Type Description
Dict[str, Any]

A dict ready for CupsOfCoffee.model_validate().

Raises:

Type Description
ParsingError

If the coffee table is not found.

Source code in src/griddy/pfr/parsers/cups_of_coffee.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse the cups of coffee page.

    Args:
        html: Raw HTML of the PFR cups of coffee page.

    Returns:
        A dict ready for ``CupsOfCoffee.model_validate()``.

    Raises:
        ParsingError: If the coffee table is not found.
    """
    soup = BeautifulSoup(html, "html.parser")

    title = self._extract_title(soup)

    table = soup.find("table", id="coffee")
    if table is None:
        raise ParsingError(
            "Could not find coffee table in the HTML.",
            selector="coffee",
            html_sample=html[:500],
        )

    entries = self._parse_table(table)

    return {
        "title": title,
        "entries": entries,
    }

DraftParser

Parses PFR draft pages into structured data dicts.

parse_year_draft

parse_year_draft(html, *, year)

Parse a year draft page and return a dict for model validation.

Returns:

Type Description
Dict[str, Any]

A dict with keys year, picks.

Source code in src/griddy/pfr/parsers/draft.py
def parse_year_draft(self, html: str, *, year: int) -> Dict[str, Any]:
    """Parse a year draft page and return a dict for model validation.

    Returns:
        A dict with keys ``year``, ``picks``.
    """
    soup = BeautifulSoup(html, "html.parser")

    table = soup.find("table", id="drafts")
    if table is None:
        return {"year": year, "picks": []}

    picks = self._parse_year_draft_rows(table)
    return {"year": year, "picks": picks}

parse_combine

parse_combine(html, *, year)

Parse a combine page and return a dict for model validation.

Returns:

Type Description
Dict[str, Any]

A dict with keys year, entries.

Source code in src/griddy/pfr/parsers/draft.py
def parse_combine(self, html: str, *, year: int) -> Dict[str, Any]:
    """Parse a combine page and return a dict for model validation.

    Returns:
        A dict with keys ``year``, ``entries``.
    """
    soup = BeautifulSoup(html, "html.parser")

    table = soup.find("table", id="combine")
    if table is None:
        return {"year": year, "entries": []}

    entries = self._parse_combine_rows(table)
    return {"year": year, "entries": entries}

parse_team_draft

parse_team_draft(html, *, team)

Parse a team draft page and return a dict for model validation.

Returns:

Type Description
Dict[str, Any]

A dict with keys team, picks.

Source code in src/griddy/pfr/parsers/draft.py
def parse_team_draft(self, html: str, *, team: str) -> Dict[str, Any]:
    """Parse a team draft page and return a dict for model validation.

    Returns:
        A dict with keys ``team``, ``picks``.
    """
    soup = BeautifulSoup(html, "html.parser")

    table = soup.find("table", id="draft")
    if table is None:
        return {"team": team, "picks": []}

    picks = self._parse_team_draft_rows(table)
    return {"team": team, "picks": picks}

ExecutiveProfileParser

Parses a PFR executive profile page into structured data dicts.

parse

parse(html)

Parse a full executive profile page.

Returns:

Type Description
Dict[str, Any]

A dict with keys bio, exec_results, and

Dict[str, Any]

exec_results_totals.

Source code in src/griddy/pfr/parsers/executive_profile.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse a full executive profile page.

    Returns:
        A dict with keys ``bio``, ``exec_results``, and
        ``exec_results_totals``.
    """
    soup = BeautifulSoup(html, "html.parser")

    bio = self._parse_bio(soup)

    table = soup.find("table", id="exec_results")
    if table is None:
        return {
            "bio": bio,
            "exec_results": [],
            "exec_results_totals": [],
        }

    results = self._parse_results(table)
    totals = self._parse_totals(table)

    return {
        "bio": bio,
        "exec_results": results,
        "exec_results_totals": totals,
    }

FantasyParser

Parses PFR Fantasy Rankings pages into structured data dicts.

parse_top_players

parse_top_players(html)

Parse the top fantasy players page.

Returns:

Type Description
Dict[str, Any]

A dict with key players.

Source code in src/griddy/pfr/parsers/fantasy.py
def parse_top_players(self, html: str) -> Dict[str, Any]:
    """Parse the top fantasy players page.

    Returns:
        A dict with key ``players``.
    """
    soup = BeautifulSoup(html, "html.parser")

    table = soup.find("table", id="fantasy")
    if table is None:
        return {"players": []}

    players = self._parse_player_rows(table)
    return {"players": players}

parse_matchups

parse_matchups(html)

Parse a fantasy matchups page.

Works for any position (QB, WR, RB, TE) since the parser reads whichever data-stat columns are present in the table.

Returns:

Type Description
Dict[str, Any]

A dict with key players.

Source code in src/griddy/pfr/parsers/fantasy.py
def parse_matchups(self, html: str) -> Dict[str, Any]:
    """Parse a fantasy matchups page.

    Works for any position (QB, WR, RB, TE) since the parser reads
    whichever ``data-stat`` columns are present in the table.

    Returns:
        A dict with key ``players``.
    """
    soup = BeautifulSoup(html, "html.parser")

    table = soup.find("table", id="fantasy_stats")
    if table is None:
        return {"players": []}

    players = self._parse_matchup_rows(table)
    return {"players": players}

parse_points_allowed

parse_points_allowed(html)

Parse a fantasy points allowed page.

Works for any position (QB, WR, RB, TE) since the parser reads whichever data-stat columns are present in the table.

Returns:

Type Description
Dict[str, Any]

A dict with key teams.

Source code in src/griddy/pfr/parsers/fantasy.py
def parse_points_allowed(self, html: str) -> Dict[str, Any]:
    """Parse a fantasy points allowed page.

    Works for any position (QB, WR, RB, TE) since the parser reads
    whichever ``data-stat`` columns are present in the table.

    Returns:
        A dict with key ``teams``.
    """
    soup = BeautifulSoup(html, "html.parser")

    table = soup.find("table", id="fantasy_def")
    if table is None:
        return {"teams": []}

    teams = self._parse_points_allowed_rows(table)
    return {"teams": teams}

parse_redzone_passing

parse_redzone_passing(html)

Parse the red zone passing page.

Returns:

Type Description
Dict[str, Any]

A dict with key players.

Source code in src/griddy/pfr/parsers/fantasy.py
def parse_redzone_passing(self, html: str) -> Dict[str, Any]:
    """Parse the red zone passing page.

    Returns:
        A dict with key ``players``.
    """
    soup = BeautifulSoup(html, "html.parser")

    table = soup.find("table", id="fantasy_rz")
    if table is None:
        return {"players": []}

    players = self._parse_rz_passing_rows(table)
    return {"players": players}

parse_redzone_receiving

parse_redzone_receiving(html)

Parse the red zone receiving page.

Returns:

Type Description
Dict[str, Any]

A dict with key players.

Source code in src/griddy/pfr/parsers/fantasy.py
def parse_redzone_receiving(self, html: str) -> Dict[str, Any]:
    """Parse the red zone receiving page.

    Returns:
        A dict with key ``players``.
    """
    soup = BeautifulSoup(html, "html.parser")

    table = soup.find("table", id="fantasy_rz")
    if table is None:
        return {"players": []}

    players = self._parse_rz_receiving_rows(table)
    return {"players": players}

parse_redzone_rushing

parse_redzone_rushing(html)

Parse the red zone rushing page.

Returns:

Type Description
Dict[str, Any]

A dict with key players.

Source code in src/griddy/pfr/parsers/fantasy.py
def parse_redzone_rushing(self, html: str) -> Dict[str, Any]:
    """Parse the red zone rushing page.

    Returns:
        A dict with key ``players``.
    """
    soup = BeautifulSoup(html, "html.parser")

    table = soup.find("table", id="fantasy_rz")
    if table is None:
        return {"players": []}

    players = self._parse_rz_rushing_rows(table)
    return {"players": players}

GameDetailsParser

Parses PFR boxscore pages into comprehensive game data dicts.

parse

parse(html)

Parse a PFR boxscore page into a comprehensive JSON-serializable dict.

Extracts all game-specific data from /boxscores/{game_id}.htm pages, including:

  • Scorebox metadata (teams, final scores, records, coaches)
  • Quarter-by-quarter linescore
  • Scoring plays
  • Game info (coin toss, weather, surface, vegas line, etc.)
  • Officials
  • Expected points summary
  • Team stats (first downs, yards, turnovers, etc.)
  • Player offense (passing, rushing, receiving)
  • Player defense (interceptions, tackles, sacks)
  • Kick/punt returns
  • Kicking & punting
  • Home/visitor starters
  • Home/visitor snap counts
  • Home/visitor drives

Many tables on PFR boxscore pages are hidden inside HTML comments as an anti-scraping measure. The BaseSDK pre-processes the HTML to uncomment these tables before this parser runs.

Parameters:

Name Type Description Default
html str

Raw HTML string of a PFR boxscore page.

required

Returns:

Type Description
Dict[str, Any]

A dict with all extracted game data.

Source code in src/griddy/pfr/parsers/game_details.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse a PFR boxscore page into a comprehensive JSON-serializable dict.

    Extracts all game-specific data from
    ``/boxscores/{game_id}.htm`` pages, including:

    * Scorebox metadata (teams, final scores, records, coaches)
    * Quarter-by-quarter linescore
    * Scoring plays
    * Game info (coin toss, weather, surface, vegas line, etc.)
    * Officials
    * Expected points summary
    * Team stats (first downs, yards, turnovers, etc.)
    * Player offense (passing, rushing, receiving)
    * Player defense (interceptions, tackles, sacks)
    * Kick/punt returns
    * Kicking & punting
    * Home/visitor starters
    * Home/visitor snap counts
    * Home/visitor drives

    Many tables on PFR boxscore pages are hidden inside HTML comments
    as an anti-scraping measure.  The BaseSDK pre-processes the HTML to
    uncomment these tables before this parser runs.

    Args:
        html: Raw HTML string of a PFR boxscore page.

    Returns:
        A dict with all extracted game data.
    """
    soup = BeautifulSoup(html, "html.parser")

    result: Dict[str, Any] = {}

    result["scorebox"] = self._parse_scorebox(soup)
    result["linescore"] = self._parse_linescore(soup)

    result["scoring"] = self._parse_table_rows(soup, "scoring")
    result["game_info"] = self._parse_kv_table(soup, "game_info")
    result["officials"] = self._parse_kv_table(soup, "officials")
    result["expected_points"] = self._parse_table_rows(soup, "expected_points")
    result["team_stats"] = self._parse_team_stats(soup)
    result["player_offense"] = self._parse_table_rows(soup, "player_offense")
    result["player_defense"] = self._parse_table_rows(soup, "player_defense")
    result["returns"] = self._parse_table_rows(soup, "returns")
    result["kicking"] = self._parse_table_rows(soup, "kicking")
    result["home_starters"] = self._parse_table_rows(soup, "home_starters")
    result["vis_starters"] = self._parse_table_rows(soup, "vis_starters")
    result["home_snap_counts"] = self._parse_table_rows(soup, "home_snap_counts")
    result["vis_snap_counts"] = self._parse_table_rows(soup, "vis_snap_counts")
    result["home_drives"] = self._parse_table_rows(soup, "home_drives")
    result["vis_drives"] = self._parse_table_rows(soup, "vis_drives")

    return result

LastUndefeatedParser

Parses the PFR last-undefeated-team page.

parse

parse(html)

Parse the last-undefeated-team page.

Parameters:

Name Type Description Default
html str

Raw HTML of the PFR last-undefeated-team page.

required

Returns:

Type Description
Dict[str, Any]

A dict ready for LastUndefeated.model_validate().

Raises:

Type Description
ParsingError

If the undefeated_teams table is not found.

Source code in src/griddy/pfr/parsers/last_undefeated.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse the last-undefeated-team page.

    Args:
        html: Raw HTML of the PFR last-undefeated-team page.

    Returns:
        A dict ready for ``LastUndefeated.model_validate()``.

    Raises:
        ParsingError: If the undefeated_teams table is not found.
    """
    soup = BeautifulSoup(html, "html.parser")

    title = self._extract_title(soup)

    table = soup.find("table", id="undefeated_teams")
    if table is None:
        raise ParsingError(
            "Could not find undefeated_teams table in the HTML.",
            selector="undefeated_teams",
            html_sample=html[:500],
        )

    entries = self._parse_table(table)

    return {
        "title": title,
        "entries": entries,
    }

LeadersParser

Parses PFR leader pages into structured data dicts.

parse

parse(html, *, stat, scope)

Parse a leaders page and return a dict ready for model validation.

Parameters:

Name Type Description Default
html str

Raw HTML content of the leaders page.

required
stat str

The stat key (e.g. "pass_yds").

required
scope str

The scope (e.g. "career", "single_season").

required

Returns:

Type Description
Dict[str, Any]

A dict with keys stat, scope, title, entries.

Source code in src/griddy/pfr/parsers/leaders.py
def parse(self, html: str, *, stat: str, scope: str) -> Dict[str, Any]:
    """Parse a leaders page and return a dict ready for model validation.

    Args:
        html: Raw HTML content of the leaders page.
        stat: The stat key (e.g. ``"pass_yds"``).
        scope: The scope (e.g. ``"career"``, ``"single_season"``).

    Returns:
        A dict with keys ``stat``, ``scope``, ``title``, ``entries``.
    """
    soup = BeautifulSoup(html, "html.parser")

    title = None
    h1 = soup.find("h1")
    if h1:
        title = h1.get_text(strip=True)

    entries = self._parse_entries(soup, stat)

    return {
        "stat": stat,
        "scope": scope,
        "title": title,
        "entries": entries,
    }

MultiSportPlayersParser

Parses the PFR 'Multisport Athletes' page.

parse

parse(html)

Parse the multisport athletes page.

Parameters:

Name Type Description Default
html str

Raw HTML of the PFR multisport athletes page.

required

Returns:

Type Description
Dict[str, Any]

A dict ready for MultiSportPlayers.model_validate().

Raises:

Type Description
ParsingError

If the multisport table is not found.

Source code in src/griddy/pfr/parsers/multi_sport_players.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse the multisport athletes page.

    Args:
        html: Raw HTML of the PFR multisport athletes page.

    Returns:
        A dict ready for ``MultiSportPlayers.model_validate()``.

    Raises:
        ParsingError: If the multisport table is not found.
    """
    soup = BeautifulSoup(html, "html.parser")

    title = self._extract_title(soup)

    table = soup.find("table", id="multisport")
    if table is None:
        raise ParsingError(
            "Could not find multisport table in the HTML.",
            selector="multisport",
            html_sample=html[:500],
        )

    entries = self._parse_table(table)

    return {
        "title": title,
        "entries": entries,
    }

MultiTeamPlayersParser

Parses the PFR multi-franchise players results page.

parse

parse(html)

Parse the multi-team players results page.

Parameters:

Name Type Description Default
html str

Raw HTML of the PFR multi-franchise players results page.

required

Returns:

Type Description
Dict[str, Any]

A dict ready for MultiTeamPlayers.model_validate().

Raises:

Type Description
ParsingError

If no stats tables are found.

Source code in src/griddy/pfr/parsers/multi_team_players.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse the multi-team players results page.

    Args:
        html: Raw HTML of the PFR multi-franchise players results page.

    Returns:
        A dict ready for ``MultiTeamPlayers.model_validate()``.

    Raises:
        ParsingError: If no stats tables are found.
    """
    soup = BeautifulSoup(html, "html.parser")

    title = self._extract_title(soup)
    total_players = self._extract_total_players(soup)
    teams = self._extract_teams(soup)
    top_players = self._extract_top_players(soup)

    stats_tables: List[Dict[str, Any]] = []
    idx = 0
    while True:
        table_id = f"{self._TABLE_ID_PREFIX}{idx}"
        table = soup.find("table", id=table_id)
        if table is None:
            break

        category = self._extract_table_category(soup, table_id)
        players = self._parse_table(table)

        stats_tables.append(
            {
                "category": category,
                "teams": teams,
                "players": players,
            }
        )
        idx += 1

    if not stats_tables:
        raise ParsingError(
            "Could not find any multifranchise_stats tables in the HTML.",
            selector="multi_team_stats",
            html_sample=html[:500],
        )

    return {
        "title": title,
        "total_players": total_players,
        "teams": teams,
        "top_players": top_players,
        "stats_tables": stats_tables,
    }

NonQBPassersParser

Parses the PFR non-quarterback passers page.

parse

parse(html)

Parse the non-quarterback passers page.

Parameters:

Name Type Description Default
html str

Raw HTML of the PFR non-QB passers page.

required

Returns:

Type Description
Dict[str, Any]

A dict ready for NonQBPassers.model_validate().

Raises:

Type Description
ParsingError

If the nonqb_passers table is not found.

Source code in src/griddy/pfr/parsers/non_qb_passers.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse the non-quarterback passers page.

    Args:
        html: Raw HTML of the PFR non-QB passers page.

    Returns:
        A dict ready for ``NonQBPassers.model_validate()``.

    Raises:
        ParsingError: If the nonqb_passers table is not found.
    """
    soup = BeautifulSoup(html, "html.parser")

    title = self._extract_title(soup)

    table = soup.find("table", id="nonqb_passers")
    if table is None:
        raise ParsingError(
            "Could not find nonqb_passers table in the HTML.",
            selector="nonqb_passers",
            html_sample=html[:500],
        )

    entries = self._parse_table(table)

    return {
        "title": title,
        "entries": entries,
    }

NonSkillPosTdParser

Parses the PFR non-skill position TD scorers page.

parse

parse(html)

Parse the non-skill position TD scorers page.

Parameters:

Name Type Description Default
html str

Raw HTML of the PFR odd TD scorers page.

required

Returns:

Type Description
Dict[str, Any]

A dict ready for NonSkillPosTdScorers.model_validate().

Raises:

Type Description
ParsingError

If the odd_scorers table is not found.

Source code in src/griddy/pfr/parsers/non_skill_pos_td.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse the non-skill position TD scorers page.

    Args:
        html: Raw HTML of the PFR odd TD scorers page.

    Returns:
        A dict ready for ``NonSkillPosTdScorers.model_validate()``.

    Raises:
        ParsingError: If the odd_scorers table is not found.
    """
    soup = BeautifulSoup(html, "html.parser")

    title = self._extract_title(soup)

    table = soup.find("table", id="odd_scorers")
    if table is None:
        raise ParsingError(
            "Could not find odd_scorers table in the HTML.",
            selector="odd_scorers",
            html_sample=html[:500],
        )

    entries = self._parse_table(table)

    return {
        "title": title,
        "entries": entries,
    }

OctopusTrackerParser

Parses the PFR octopus tracker page.

parse

parse(html)

Parse the octopus tracker page.

Parameters:

Name Type Description Default
html str

Raw HTML of the PFR octopus tracker page.

required

Returns:

Type Description
Dict[str, Any]

A dict ready for OctopusTracker.model_validate().

Raises:

Type Description
ParsingError

If the octopus table is not found.

Source code in src/griddy/pfr/parsers/octopus_tracker.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse the octopus tracker page.

    Args:
        html: Raw HTML of the PFR octopus tracker page.

    Returns:
        A dict ready for ``OctopusTracker.model_validate()``.

    Raises:
        ParsingError: If the octopus table is not found.
    """
    soup = BeautifulSoup(html, "html.parser")

    title = self._extract_title(soup)

    table = soup.find("table", id="octopus")
    if table is None:
        raise ParsingError(
            "Could not find octopus table in the HTML.",
            selector="octopus",
            html_sample=html[:500],
        )

    entries = self._parse_table(table)

    return {
        "title": title,
        "entries": entries,
    }

OfficialProfileParser

Parses PFR official profile pages into comprehensive data dicts.

parse

parse(html)

Parse a PFR official profile page into a JSON-serializable dict.

Parameters:

Name Type Description Default
html str

Raw HTML string of a PFR official profile page.

required

Returns:

Type Description
Dict[str, Any]

A dict with keys: bio, official_stats, games.

Source code in src/griddy/pfr/parsers/official_profile.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse a PFR official profile page into a JSON-serializable dict.

    Args:
        html: Raw HTML string of a PFR official profile page.

    Returns:
        A dict with keys: bio, official_stats, games.
    """
    cleaned = re.sub(r"<!--(.*?)-->", r"\1", html, flags=re.DOTALL)
    soup = BeautifulSoup(cleaned, "html.parser")

    result: Dict[str, Any] = {}
    result["bio"] = self._parse_bio(soup)
    result["official_stats"] = self._parse_official_stats(soup)
    result["games"] = self._parse_games(soup)

    return result

OvertimeTiesParser

Parses the PFR overtime ties page.

parse

parse(html)

Parse the overtime ties page.

Parameters:

Name Type Description Default
html str

Raw HTML of the PFR overtime ties page.

required

Returns:

Type Description
Dict[str, Any]

A dict ready for OvertimeTies.model_validate().

Raises:

Type Description
ParsingError

If the overtime ties table is not found.

Source code in src/griddy/pfr/parsers/overtime_ties.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse the overtime ties page.

    Args:
        html: Raw HTML of the PFR overtime ties page.

    Returns:
        A dict ready for ``OvertimeTies.model_validate()``.

    Raises:
        ParsingError: If the overtime ties table is not found.
    """
    soup = BeautifulSoup(html, "html.parser")

    title = self._extract_title(soup)

    table = soup.find("table", id="ot_ties")
    if table is None:
        raise ParsingError(
            "Could not find ot_ties table in the HTML.",
            selector="ot_ties",
            html_sample=html[:500],
        )

    entries = self._parse_table(table)

    return {
        "title": title,
        "entries": entries,
    }

PlayerProfileParser

PlayerProfileParser()

Parses a PFR player profile page into a structured dict.

Extracts biographical metadata, jersey number history, career statistics (regular season and postseason), transactions, leaderboard appearances, and navigation links from a player's Pro Football Reference page.

Source code in src/griddy/pfr/parsers/player_profile.py
def __init__(self) -> None:
    """Initialize the parser with an empty soup reference."""
    self.soup: BeautifulSoup | None = None

parse

parse(html)

Parse a PFR player profile page into a structured dict.

Uncomments hidden HTML tables, then extracts bio, jersey numbers, summary stats, full career statistics, transactions, navigation links, and leaderboard data.

Parameters:

Name Type Description Default
html str

Raw HTML string of a PFR /players/{letter}/{player_id}.htm page.

required

Returns:

Type Description
PlayerProfile

A dict suitable for validation into a

PlayerProfile

class:~griddy.pfr.models.PlayerProfile model.

Source code in src/griddy/pfr/parsers/player_profile.py
def parse(self, html: str) -> PlayerProfile:
    """Parse a PFR player profile page into a structured dict.

    Uncomments hidden HTML tables, then extracts bio, jersey numbers,
    summary stats, full career statistics, transactions, navigation
    links, and leaderboard data.

    Args:
        html: Raw HTML string of a PFR
            ``/players/{letter}/{player_id}.htm`` page.

    Returns:
        A dict suitable for validation into a
        :class:`~griddy.pfr.models.PlayerProfile` model.
    """
    cleaned_html = re.sub(r"<!--(.*?)-->", r"\1", html, flags=re.DOTALL)
    self.soup = BeautifulSoup(cleaned_html, features="html.parser")
    bio = self._parse_meta_panel(panel=self.soup.find(id="meta"))
    jersey_numbers = self._parse_jersey_numbers(
        tag=self.soup.find(class_="uni_holder")
    )
    summary_stats = self._parse_stats_summary(
        tag=self.soup.find(class_="stats_pullout")
    )

    stats_tables = self.soup.find_all("table", class_="stats_table")
    full_stats = self._extract_all_stats(stats_tables=stats_tables)

    transactions_div = self.soup.find(id="div_transactions")
    transactions = (
        self._parse_transactions(tag=transactions_div) if transactions_div else []
    )

    bottom_nav_div = self.soup.find(id="bottom_nav_container")
    player_links = (
        self._parse_bottom_nav(tag=bottom_nav_div) if bottom_nav_div else {}
    )

    leaderboard_div = self.soup.find(id="div_leaderboard")
    leader_boards = (
        self._parse_leader_boards(tag=leaderboard_div) if leaderboard_div else {}
    )

    return {
        "bio": bio,
        "jersey_numbers": jersey_numbers,
        "summary_stats": summary_stats,
        "statistics": full_stats,
        "transactions": transactions,
        "links": player_links,
        "leader_boards": leader_boards,
    }

PlayersBornBeforeParser

Parses the PFR active players born before a date page.

parse

parse(html)

Parse the active players born before a date page.

Parameters:

Name Type Description Default
html str

Raw HTML of the PFR active players born before page.

required

Returns:

Type Description
Dict[str, Any]

A dict ready for PlayersBornBefore.model_validate().

Raises:

Type Description
ParsingError

If the players table is not found.

Source code in src/griddy/pfr/parsers/players_born_before.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse the active players born before a date page.

    Args:
        html: Raw HTML of the PFR active players born before page.

    Returns:
        A dict ready for ``PlayersBornBefore.model_validate()``.

    Raises:
        ParsingError: If the players table is not found.
    """
    soup = BeautifulSoup(html, "html.parser")

    title = self._extract_title(soup)
    month, day, year = self._extract_month_day_year(title)

    table = soup.find("table", id="players")
    if table is None:
        raise ParsingError(
            "Could not find players table in the HTML.",
            selector="players",
            html_sample=html[:500],
        )

    players = self._parse_table(table)

    return {
        "title": title,
        "month": month,
        "day": day,
        "year": year,
        "players": players,
    }

PronunciationGuideParser

Parses the PFR 'Pronunciation Guide' page.

parse

parse(html)

Parse the pronunciation guide page.

Parameters:

Name Type Description Default
html str

Raw HTML of the PFR pronunciation guide page.

required

Returns:

Type Description
Dict[str, Any]

A dict ready for PronunciationGuide.model_validate().

Raises:

Type Description
ParsingError

If the pronunciation list is not found.

Source code in src/griddy/pfr/parsers/pronunciation_guide.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse the pronunciation guide page.

    Args:
        html: Raw HTML of the PFR pronunciation guide page.

    Returns:
        A dict ready for ``PronunciationGuide.model_validate()``.

    Raises:
        ParsingError: If the pronunciation list is not found.
    """
    soup = BeautifulSoup(html, "html.parser")

    title = self._extract_title(soup)

    content = soup.find("div", id="content")
    if content is None:
        raise ParsingError(
            "Could not find #content div in the HTML.",
            selector="content",
            html_sample=html[:500],
        )

    ul = content.find("ul")
    if ul is None:
        raise ParsingError(
            "Could not find pronunciation list in the HTML.",
            selector="content ul",
            html_sample=html[:500],
        )

    entries = self._parse_list(ul)

    return {
        "title": title,
        "entries": entries,
    }

QBWinsParser

Parses the PFR quarterback wins page.

parse

parse(html)

Parse the quarterback wins page.

Parameters:

Name Type Description Default
html str

Raw HTML of the PFR quarterback wins page.

required

Returns:

Type Description
Dict[str, Any]

A dict ready for QBWins.model_validate().

Raises:

Type Description
ParsingError

If the qb_wins table is not found.

Source code in src/griddy/pfr/parsers/qb_wins.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse the quarterback wins page.

    Args:
        html: Raw HTML of the PFR quarterback wins page.

    Returns:
        A dict ready for ``QBWins.model_validate()``.

    Raises:
        ParsingError: If the qb_wins table is not found.
    """
    soup = BeautifulSoup(html, "html.parser")

    title = self._extract_title(soup)

    table = soup.find("table", id="qb_wins")
    if table is None:
        raise ParsingError(
            "Could not find qb_wins table in the HTML.",
            selector="qb_wins",
            html_sample=html[:500],
        )

    entries = self._parse_table(table)

    return {
        "title": title,
        "entries": entries,
    }

ScheduleParser

Parses the PFR season-schedule HTML table.

parse

parse(html)

Parse the PFR season-schedule table into a list of ScheduleGame models.

Looks for <table id="games">, iterates over <tbody> <tr> rows, and skips:

  • Separator rows (class="thead")
  • Rows where all data cells are empty (e.g. the "Playoffs" label row)

Parameters:

Name Type Description Default
html str

Raw HTML string of a PFR /years/{season}/games.htm page.

required

Returns:

Type Description
List[ScheduleGame]

A list of ScheduleGame models, one per game.

Raises:

Type Description
ParsingError

If <table id="games"> is not found in the HTML.

Source code in src/griddy/pfr/parsers/schedule.py
def parse(self, html: str) -> List[ScheduleGame]:
    """Parse the PFR season-schedule table into a list of ScheduleGame models.

    Looks for ``<table id="games">``, iterates over ``<tbody> <tr>`` rows,
    and skips:

    * Separator rows (``class="thead"``)
    * Rows where *all* data cells are empty (e.g. the "Playoffs" label row)

    Args:
        html: Raw HTML string of a PFR ``/years/{season}/games.htm`` page.

    Returns:
        A list of ``ScheduleGame`` models, one per game.

    Raises:
        ParsingError: If ``<table id="games">`` is not found in the HTML.
    """
    soup = BeautifulSoup(html, "html.parser")
    table = soup.find("table", id="games")
    if table is None:
        raise ParsingError(
            "Could not find <table id='games'> in the HTML.",
            selector="games",
            html_sample=html[:500],
        )

    tbody = table.find("tbody")
    if tbody is None:
        raise ParsingError(
            "Could not find <tbody> inside the games table.",
            selector="games tbody",
            html_sample=html[:500],
        )

    games: List[ScheduleGame] = []

    for row in tbody.find_all("tr"):
        # Skip separator header rows that repeat column names mid-table.
        if "thead" in (row.get("class") or []):
            continue

        game_data: Dict[str, Any] = {}
        cells = row.find_all(["th", "td"])

        all_empty = True
        for cell in cells:
            stat = cell.get("data-stat")
            if not stat:
                continue
            extracted = self._extract_cell(cell, stat)
            game_data.update(extracted)
            # Check if this cell has meaningful text content.
            text = cell.get_text(strip=True)
            if text and text != "Playoffs":
                all_empty = False

        # Skip label-only rows (e.g. the "Playoffs" divider).
        if all_empty:
            continue

        games.append(game_data)

    return games

SchoolsParser

Parses PFR Schools & Colleges pages into structured data dicts.

parse_colleges

parse_colleges(html)

Parse the colleges / universities index page.

Returns:

Type Description
Dict[str, Any]

A dict with key colleges.

Source code in src/griddy/pfr/parsers/schools.py
def parse_colleges(self, html: str) -> Dict[str, Any]:
    """Parse the colleges / universities index page.

    Returns:
        A dict with key ``colleges``.
    """
    soup = BeautifulSoup(html, "html.parser")

    table = soup.find("table", id="college_stats_table")
    if table is None:
        return {"colleges": []}

    colleges = self._parse_college_rows(table)
    return {"colleges": colleges}

parse_high_schools

parse_high_schools(html)

Parse the high schools index page.

Returns:

Type Description
Dict[str, Any]

A dict with key schools.

Source code in src/griddy/pfr/parsers/schools.py
def parse_high_schools(self, html: str) -> Dict[str, Any]:
    """Parse the high schools index page.

    Returns:
        A dict with key ``schools``.
    """
    soup = BeautifulSoup(html, "html.parser")

    table = soup.find("table", id="high_schools")
    if table is None:
        return {"schools": []}

    schools = self._parse_high_school_rows(table)
    return {"schools": schools}

SeasonOverviewParser

Parses PFR season overview and stat category pages.

parse

parse(html)

Parse a PFR season overview page into a JSON-serializable dict.

Parameters:

Name Type Description Default
html str

Raw HTML string of a PFR /years/{year}/ page.

required

Returns:

Type Description
Dict[str, Any]

A dict with keys: afc_standings, nfc_standings, playoff_results,

Dict[str, Any]

afc_playoff_standings, nfc_playoff_standings, and team stat tables.

Source code in src/griddy/pfr/parsers/season_overview.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse a PFR season overview page into a JSON-serializable dict.

    Args:
        html: Raw HTML string of a PFR ``/years/{year}/`` page.

    Returns:
        A dict with keys: afc_standings, nfc_standings, playoff_results,
        afc_playoff_standings, nfc_playoff_standings, and team stat tables.
    """
    cleaned = re.sub(r"<!--(.*?)-->", r"\1", html, flags=re.DOTALL)
    soup = BeautifulSoup(cleaned, "html.parser")

    result: Dict[str, Any] = {}
    result["afc_standings"] = self._parse_standings(soup, "AFC")
    result["nfc_standings"] = self._parse_standings(soup, "NFC")
    result["playoff_results"] = self._parse_playoff_results(soup)
    result["afc_playoff_standings"] = self._parse_playoff_standings(
        soup, "afc_playoff_standings"
    )
    result["nfc_playoff_standings"] = self._parse_playoff_standings(
        soup, "nfc_playoff_standings"
    )

    for table_id in _TEAM_STAT_TABLE_IDS:
        result[table_id] = self._parse_generic_table(soup, table_id)

    return result

parse_stats

parse_stats(html)

Parse a PFR stat category page into a JSON-serializable dict.

Parameters:

Name Type Description Default
html str

Raw HTML string of a PFR /years/{year}/{category}.htm page.

required

Returns:

Type Description
Dict[str, Any]

A dict with keys: regular_season, postseason — each a list of

Dict[str, Any]

per-player stat dicts.

Source code in src/griddy/pfr/parsers/season_overview.py
def parse_stats(self, html: str) -> Dict[str, Any]:
    """Parse a PFR stat category page into a JSON-serializable dict.

    Args:
        html: Raw HTML string of a PFR ``/years/{year}/{category}.htm`` page.

    Returns:
        A dict with keys: regular_season, postseason — each a list of
        per-player stat dicts.
    """
    cleaned = re.sub(r"<!--(.*?)-->", r"\1", html, flags=re.DOTALL)
    soup = BeautifulSoup(cleaned, "html.parser")

    result: Dict[str, Any] = {"regular_season": [], "postseason": []}

    # Find all stat tables (they have data-stat columns in their rows).
    for table in soup.find_all("table", id=True):
        table_id = table.get("id", "")
        if not table_id:
            continue

        rows = self._parse_player_table(table)
        if not rows:
            continue

        if table_id.endswith("_post"):
            result["postseason"] = rows
        else:
            result["regular_season"] = rows

    return result

parse_week

parse_week(html)

Parse a PFR week summary page into a JSON-serializable dict.

Parameters:

Name Type Description Default
html str

Raw HTML string of a PFR /years/{year}/week_{N}.htm page.

required

Returns:

Type Description
Dict[str, Any]

A dict with keys: games, players_of_the_week, top_passers,

Dict[str, Any]

top_receivers, top_rushers, top_defenders.

Source code in src/griddy/pfr/parsers/season_overview.py
def parse_week(self, html: str) -> Dict[str, Any]:
    """Parse a PFR week summary page into a JSON-serializable dict.

    Args:
        html: Raw HTML string of a PFR ``/years/{year}/week_{N}.htm`` page.

    Returns:
        A dict with keys: games, players_of_the_week, top_passers,
        top_receivers, top_rushers, top_defenders.
    """
    cleaned = re.sub(r"<!--(.*?)-->", r"\1", html, flags=re.DOTALL)
    soup = BeautifulSoup(cleaned, "html.parser")

    result: Dict[str, Any] = {}
    result["games"] = self._parse_game_summaries(soup)
    result["players_of_the_week"] = self._parse_potw(soup)

    for table_id, key in _WEEK_LEADER_TABLES.items():
        result[key] = self._parse_week_leader_table(soup, table_id)

    return result

StadiumParser

Parses PFR stadium pages into comprehensive data dicts.

parse

parse(html)

Parse a PFR stadium page into a JSON-serializable dict.

Parameters:

Name Type Description Default
html str

Raw HTML string of a PFR stadium page.

required

Returns:

Type Description
Dict[str, Any]

A dict with keys: bio, leaders, best_games, best_playoff_games,

Dict[str, Any]

game_summaries.

Source code in src/griddy/pfr/parsers/stadium.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse a PFR stadium page into a JSON-serializable dict.

    Args:
        html: Raw HTML string of a PFR stadium page.

    Returns:
        A dict with keys: bio, leaders, best_games, best_playoff_games,
        game_summaries.
    """
    cleaned = re.sub(r"<!--(.*?)-->", r"\1", html, flags=re.DOTALL)
    soup = BeautifulSoup(cleaned, "html.parser")

    result: Dict[str, Any] = {}
    result["bio"] = self._parse_bio(soup)
    result["leaders"] = self._parse_record_table(soup, "leaders")
    result["best_games"] = self._parse_best_games_table(soup, "games")
    result["best_playoff_games"] = self._parse_best_games_table(
        soup, "playoff_games"
    )
    result["game_summaries"] = self._parse_game_summaries(soup)

    return result

StandingsOnDateParser

Parses the PFR standings-on-date page.

parse

parse(html)

Parse the standings-on-date page.

Parameters:

Name Type Description Default
html str

Raw HTML of the PFR standings page.

required

Returns:

Type Description
Dict[str, Any]

A dict ready for StandingsOnDate.model_validate().

Raises:

Type Description
ParsingError

If no conference standings tables are found.

Source code in src/griddy/pfr/parsers/standings_on_date.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse the standings-on-date page.

    Args:
        html: Raw HTML of the PFR standings page.

    Returns:
        A dict ready for ``StandingsOnDate.model_validate()``.

    Raises:
        ParsingError: If no conference standings tables are found.
    """
    soup = BeautifulSoup(html, "html.parser")

    title = self._extract_title(soup)

    teams: List[Dict[str, Any]] = []
    for table_id in _CONFERENCE_TABLES:
        table = soup.find("table", id=table_id)
        if table:
            teams.extend(self._parse_table(table, table_id))

    if not teams:
        raise ParsingError(
            "Could not find AFC or NFC standings tables in the HTML.",
            selector="AFC_standings, NFC_standings",
            html_sample=html[:500],
        )

    return {
        "title": title,
        "teams": teams,
    }

StatisticalMilestonesParser

Parses the PFR statistical milestones page.

parse

parse(html)

Parse the statistical milestones page.

Parameters:

Name Type Description Default
html str

Raw HTML of the PFR milestones page.

required

Returns:

Type Description
Dict[str, Any]

A dict ready for StatisticalMilestones.model_validate().

Raises:

Type Description
ParsingError

If the milestones table is not found.

Source code in src/griddy/pfr/parsers/statistical_milestones.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse the statistical milestones page.

    Args:
        html: Raw HTML of the PFR milestones page.

    Returns:
        A dict ready for ``StatisticalMilestones.model_validate()``.

    Raises:
        ParsingError: If the milestones table is not found.
    """
    soup = BeautifulSoup(html, "html.parser")

    title = self._extract_title(soup)
    stat = self._extract_stat(soup)

    # The milestones table is directly in the page
    milestones_table = soup.find("table", id="milestones")
    if milestones_table is None:
        raise ParsingError(
            "Could not find milestones table in the HTML.",
            selector="milestones",
            html_sample=html[:500],
        )

    milestones = self._parse_milestones_table(milestones_table)

    leaders_table = soup.find("table", id="leaders")
    career_leaders = (
        self._parse_leaders_table(leaders_table) if leaders_table else []
    )

    return {
        "title": title,
        "stat": stat,
        "milestones": milestones,
        "career_leaders": career_leaders,
    }

SuperBowlParser

Parses PFR Super Bowl pages into structured data dicts.

parse_history

parse_history(html)

Parse the Super Bowl history page.

Returns:

Type Description
Dict[str, Any]

A dict with key games.

Source code in src/griddy/pfr/parsers/superbowl.py
def parse_history(self, html: str) -> Dict[str, Any]:
    """Parse the Super Bowl history page.

    Returns:
        A dict with key ``games``.
    """
    soup = BeautifulSoup(html, "html.parser")

    table = soup.find("table", id="super_bowls")
    if table is None:
        return {"games": []}

    games = self._parse_history_rows(table)
    return {"games": games}

parse_leaders

parse_leaders(html)

Parse the Super Bowl leaders page.

Returns:

Type Description
Dict[str, Any]

A dict with key tables.

Source code in src/griddy/pfr/parsers/superbowl.py
def parse_leaders(self, html: str) -> Dict[str, Any]:
    """Parse the Super Bowl leaders page.

    Returns:
        A dict with key ``tables``.
    """
    soup = BeautifulSoup(html, "html.parser")

    tables_data: List[Dict[str, Any]] = []

    for table in soup.find_all("table"):
        caption = table.find("caption")
        if caption is None:
            continue

        category = caption.get_text(strip=True)
        entries = self._parse_leader_rows(table)
        tables_data.append({"category": category, "entries": entries})

    return {"tables": tables_data}

parse_standings

parse_standings(html)

Parse the Super Bowl standings page.

Returns:

Type Description
Dict[str, Any]

A dict with key teams.

Source code in src/griddy/pfr/parsers/superbowl.py
def parse_standings(self, html: str) -> Dict[str, Any]:
    """Parse the Super Bowl standings page.

    Returns:
        A dict with key ``teams``.
    """
    soup = BeautifulSoup(html, "html.parser")

    table = soup.find("table", id="standings")
    if table is None:
        return {"teams": []}

    teams = self._parse_standings_rows(table)
    return {"teams": teams}

FranchiseParser

Parses PFR team franchise pages into comprehensive data dicts.

parse

parse(html)

Parse a PFR team franchise page into a JSON-serializable dict.

Parameters:

Name Type Description Default
html str

Raw HTML string of a PFR team franchise page.

required

Returns:

Type Description
Dict[str, Any]

A dict with keys: meta, team_index.

Source code in src/griddy/pfr/parsers/team_franchise.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse a PFR team franchise page into a JSON-serializable dict.

    Args:
        html: Raw HTML string of a PFR team franchise page.

    Returns:
        A dict with keys: meta, team_index.
    """
    cleaned = re.sub(r"<!--(.*?)-->", r"\1", html, flags=re.DOTALL)
    soup = BeautifulSoup(cleaned, "html.parser")

    result: Dict[str, Any] = {}
    result["meta"] = self._parse_meta(soup)
    result["team_index"] = self._parse_team_index(soup)

    return result

TeamSeasonParser

Parses PFR team season pages into comprehensive data dicts.

parse

parse(html)

Parse a PFR team season page into a JSON-serializable dict.

Parameters:

Name Type Description Default
html str

Raw HTML string of a PFR team season page.

required

Returns:

Type Description
Dict[str, Any]

A dict with keys: meta, team_stats, games, team_conversions,

Dict[str, Any]

passing, passing_post, rushing_and_receiving.

Source code in src/griddy/pfr/parsers/team_season.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse a PFR team season page into a JSON-serializable dict.

    Args:
        html: Raw HTML string of a PFR team season page.

    Returns:
        A dict with keys: meta, team_stats, games, team_conversions,
        passing, passing_post, rushing_and_receiving.
    """
    cleaned = re.sub(r"<!--(.*?)-->", r"\1", html, flags=re.DOTALL)
    soup = BeautifulSoup(cleaned, "html.parser")

    result: Dict[str, Any] = {}
    result["meta"] = self._parse_meta(soup)
    result["team_stats"] = self._parse_labeled_table(soup, "team_stats")
    result["games"] = self._parse_games(soup)
    result["team_conversions"] = self._parse_labeled_table(soup, "team_conversions")
    result["passing"] = self._parse_player_table(soup, "passing")
    result["passing_post"] = self._parse_player_table(soup, "passing_post")
    result["rushing_and_receiving"] = self._parse_player_table(
        soup, "rushing_and_receiving"
    )

    return result

UniformNumbersParser

Parses the PFR uniform numbers page.

parse

parse(html)

Parse the uniform numbers page.

Parameters:

Name Type Description Default
html str

Raw HTML of the PFR uniform numbers page.

required

Returns:

Type Description
Dict[str, Any]

A dict ready for UniformNumbers.model_validate().

Raises:

Type Description
ParsingError

If the uniform_number table is not found.

Source code in src/griddy/pfr/parsers/uniform_numbers.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse the uniform numbers page.

    Args:
        html: Raw HTML of the PFR uniform numbers page.

    Returns:
        A dict ready for ``UniformNumbers.model_validate()``.

    Raises:
        ParsingError: If the uniform_number table is not found.
    """
    soup = BeautifulSoup(html, "html.parser")

    title = self._extract_title(soup)
    number = self._extract_number(title)
    team = self._extract_team(title)

    table = soup.find("table", id="uniform_number")
    if table is None:
        raise ParsingError(
            "Could not find uniform_number table in the HTML.",
            selector="uniform_number",
            html_sample=html[:500],
        )

    players = self._parse_table(table)

    return {
        "title": title,
        "number": number,
        "team": team,
        "players": players,
    }

UpcomingMilestonesParser

Parses the PFR upcoming milestones page.

parse

parse(html)

Parse the upcoming milestones page.

Parameters:

Name Type Description Default
html str

Raw HTML of the PFR upcoming milestones page.

required

Returns:

Type Description
Dict[str, Any]

A dict ready for UpcomingMilestones.model_validate().

Raises:

Type Description
ParsingError

If the milestones table is not found.

Source code in src/griddy/pfr/parsers/upcoming_milestones.py
def parse(self, html: str) -> Dict[str, Any]:
    """Parse the upcoming milestones page.

    Args:
        html: Raw HTML of the PFR upcoming milestones page.

    Returns:
        A dict ready for ``UpcomingMilestones.model_validate()``.

    Raises:
        ParsingError: If the milestones table is not found.
    """
    soup = BeautifulSoup(html, "html.parser")

    title = self._extract_title(soup)
    description = self._extract_description(soup)

    milestones_table = soup.find("table", id="upcoming_milestones")
    if milestones_table is None:
        raise ParsingError(
            "Could not find upcoming milestones table in the HTML.",
            selector="upcoming_milestones",
            html_sample=html[:500],
        )

    milestones = self._parse_table(milestones_table)

    leaderboards_table = soup.find("table", id="upcoming_leaderboards")
    leaderboards = (
        self._parse_table(leaderboards_table, extract_leader_href=True)
        if leaderboards_table
        else []
    )

    return {
        "title": title,
        "description": description,
        "milestones": milestones,
        "leaderboards": leaderboards,
    }