如何高效获取ESPN NHL全部有效gameId及对应比赛日期?
问题
需要获取ESPN NHL技术统计页面(https://www.espn.com/nhl/boxscore/_/gameId/)下的所有有效gameId,每个有效URL对应唯一gameId(示例:https://www.espn.com/nhl/boxscore/_/gameId/4014559236)。已知不同赛季gameId起始值不同:2023-2024赛季起始为4014559236,2007-2008赛季起始为271009021,目标是尽可能获取最早的有效gameId。
当前使用的R代码仅能小范围验证URL有效性,但覆盖271009021到4014559236的超大范围时效率极低,同时希望获取每个gameId对应的比赛日期。
当前低效代码示例:
library(httr) library(purrr) library(RCurl) urls <- paste0("https://www.espn.com/nhl/boxscore/_/gameId/",4014559236:4014559240) safe_url_logical <- map(urls, http_error) temp <- cbind(unlist(safe_url_logical), unlist(urls)) colnames(temp) <- c("logical","url") temp <- as.data.frame(temp) safe_urls <- temp %>% dplyr::filter(logical=="FALSE") dead_urls <- temp %>% dplyr::filter(logical=="TRUE") df_exist <- list() for (i in 1:nrow(safe_urls)) { url <- as.character(safe_urls$url[i]) exist <- url.exists(url) df_exist <- rbind(df_exist, url) } urls = df_exist game_ids = sub('.*\\/', '', urls) print(game_ids) [1] "401559238" "401559239" "401559240"
高效实现方案
1. 优先用ESPN公开API替代盲遍历
ESPN的赛事数据有公开API接口,无需逐个验证URL:
- 先请求NHL赛季列表接口,获取各赛季的完整赛事集合
- 直接从赛事数据中提取
gameId和对应比赛日期,无需遍历猜ID - 核心优势:完全避免无效请求,数据准确率100%
2. 二分法快速定位最早有效gameId
如果必须通过URL验证,用二分法替代全范围遍历:
- 针对已知的ID范围(271009021到4014559236),不断取中间值验证有效性,逐步缩小有效区间
- 示例逻辑:
- 初始化左边界
left=271009021,右边界right=4014559236 - 计算中间值
mid=(left+right)//2,验证mid对应的URL是否有效 - 若有效,尝试往左缩小范围(
right=mid);若无效,往右缩小(left=mid+1) - 重复直到
left==right,得到最早的有效gameId
- 初始化左边界
3. 并行请求优化批量验证
若需批量验证URL,用并行请求替代串行循环:
- 使用
furrr包实现并行映射,比purrr串行处理效率提升数倍 - 先通过
HEAD请求快速过滤4xx/5xx状态码的无效URL,再对疑似有效URL做二次验证并提取日期
4. 提取比赛日期的高效方法
获取有效URL后,直接从页面HTML提取日期:
- ESPN的boxscore页面中,比赛日期通常在
.game-info__date类标签下 - 用
rvest包快速解析HTML并提取日期文本,无需额外请求
改进后的R代码示例
library(httr) library(furrr) library(rvest) library(dplyr) # 验证单gameId有效性并提取日期 check_valid_game <- function(game_id) { url <- paste0("https://www.espn.com/nhl/boxscore/_/gameId/", game_id) # 先快速判断HTTP状态码 resp <- HEAD(url, timeout(5)) if (http_error(resp)) return(NULL) # 解析页面提取日期 page <- read_html(url) date <- page %>% html_element(".game-info__date") %>% html_text() if (!is.na(date)) { return(data.frame(game_id = game_id, date = date, url = url)) } else { return(NULL) } } # 二分法查找最早有效gameId find_earliest_game_id <- function(left, right) { while (left < right) { mid <- floor((left + right) / 2) url <- paste0("https://www.espn.com/nhl/boxscore/_/gameId/", mid) resp <- HEAD(url, timeout(3)) if (!http_error(resp)) { right <- mid } else { left <- mid + 1 } } # 最终验证 if (!http_error(HEAD(paste0("https://www.espn.com/nhl/boxscore/_/gameId/", left), timeout(3)))) { return(left) } else { return(NULL) } } # 批量处理流程 plan(multisession, workers = 8) # 开启并行计算 start_id <- find_earliest_game_id(271009021, 4014559236) end_id <- 4014559236 # 分批次处理,避免一次性请求过多 batch_size <- 1000 batches <- split(start_id:end_id, ceiling(seq_along(start_id:end_id)/batch_size)) valid_games <- map_dfr(batches, function(batch) { map_dfr(batch, check_valid_game) }) # 输出结果 print(valid_games)
内容的提问来源于stack exchange,提问作者Michael
相关产品推荐
相关产品推荐

