CFB Scoring Offense Scraper

library(rvest)
library(dplyr)

Scraper Function

The function below scrapes the national team scoring offense leaders from cfbstats.com for any given season year (2016–2025).

#' Scrape CFB scoring offense leaders from cfbstats.com
#'
#' @param year Integer. Season year (e.g., 2025). Available years: 2016–2025.
#' @return A data frame with one row per team.
scrape_cfbstats_scoring <- function(year) {
  url <- sprintf(
    "https://cfbstats.com/%d/leader/national/team/offense/split01/category09/sort01.html",
    year
  )

  page <- tryCatch(
    read_html(url),
    error = function(e) stop("Failed to fetch page for year ", year, ": ", e$message)
  )

  tbl <- page |>
    html_element("table.leaders") |>
    html_table(header = TRUE)

  names(tbl) <- c("rank", "team", "g", "td", "fg", "x1xp", "x2xp", "safety", "points", "points_per_g")

  tbl |>
    mutate(year = year) |>
    mutate(across(c(rank, g, td, fg, x1xp, x2xp, safety, points), as.integer)) |>
    mutate(points_per_g = as.numeric(points_per_g))
}

Example: Scrape a Single Year

scoring_2025 <- scrape_cfbstats_scoring(2025)

head(scoring_2025)
# A tibble: 6 × 11
   rank team          g    td    fg  x1xp  x2xp safety points points_per_g  year
  <int> <chr>     <int> <int> <int> <int> <int>  <int>  <int>        <dbl> <dbl>
1     1 North Te…    14    85    12    77     4      0    631         45.1  2025
2     2 Notre Da…    12    70     5    63     2      1    504         42    2025
3     3 Indiana      16    87    19    87     0      0    666         41.6  2025
4     4 Utah         13    72    11    68     1      1    537         41.3  2025
5     5 USF          13    67    18    62     2      2    526         40.5  2025
6     6 Tennessee    13    68    14    67     0      0    517         39.8  2025

Example: Scrape Multiple Years

scrape_cfbstats_scoring_years <- function(years) {
  lapply(years, function(yr) {
    message("Scraping ", yr, "...")
    Sys.sleep(0.5)  # be polite to the server
    scrape_cfbstats_scoring(yr)
  }) |>
    bind_rows()
}

scoring_multi <- scrape_cfbstats_scoring_years(2023:2025)
Scraping 2023...
Scraping 2024...
Scraping 2025...
scoring_multi |>
  group_by(year) |>
  slice_max(points_per_g, n = 5) |>
  select(year, rank, team, points, points_per_g)
# A tibble: 15 × 5
# Groups:   year [3]
    year  rank team        points points_per_g
   <int> <int> <chr>        <int>        <dbl>
 1  2023     1 LSU            592         45.5
 2  2023     2 Oregon         619         44.2
 3  2023     3 USC            544         41.8
 4  2023     4 Oklahoma       542         41.7
 5  2023     5 Georgia        562         40.1
 6  2024     1 Miami (FL)     571         43.9
 7  2024     2 Indiana        537         41.3
 8  2024     3 Ole Miss       502         38.6
 9  2024     4 Texas Tech     489         37.6
10  2024     5 Boise State    522         37.3
11  2025     1 North Texas    631         45.1
12  2025     2 Notre Dame     504         42  
13  2025     3 Indiana        666         41.6
14  2025     4 Utah           537         41.3
15  2025     5 USF            526         40.5