基于R优化NHL API数据爬取的技术求助
NHL API 进球数据提取优化方案
背景
我是R语言新手,对NHL API完全陌生,使用nhlapi包提取所有进球记录时,代码可运行但效率极低:遍历34370场游戏耗时约11小时,处理嵌套的球员列表耗时约2小时,急需优化建议。
以下是精简后的原始代码:
require("nhlapi") require("data.table") require("tidyverse") require("hms") assign("last.warning", NULL, envir = baseenv()) # 示例游戏ID列表,全量为34370个 cSelGames <- c(2021020001, 2021020002, 2021020003, 2021020004) liNumGames <- length(cSelGames) print(liNumGames) # 遍历拉取游戏数据 Sys.time() dtGoals <- data.table() for (liGameNum in 1:liNumGames) { lblob_feed <- nhl_games_feed(gameId = cSelGames[liGameNum]) ldtFeed <- as.data.table(c(lblob_feed[[1]]$gamePk, lblob_feed[[1]]$liveData$plays$allPlays)) setnames(ldtFeed, 1, "gamePk") if ('result.eventCode' %in% colnames(ldtFeed)) { if (!('team.triCode' %in% colnames(ldtFeed))) {ldtFeed[, ':=' (team.triCode = NA)]} if (!('result.strength.code' %in% colnames(ldtFeed))) {ldtFeed[, ':=' (result.strength.code = NA)]} if (!('result.emptyNet' %in% colnames(ldtFeed))) {ldtFeed[, ':=' (result.emptyNet = NA)]} ldtGoals_new <- ldtFeed[(result.eventTypeId == 'GOAL') ,list(gamePk, result.eventCode, players, result.description , team.triCode, about.period, about.periodTime , about.goals.away, about.goals.home, result.strength.code, result.emptyNet)] dtGoals <- rbindlist(list(dtGoals, ldtGoals_new), use.names=TRUE, fill=TRUE) } } Sys.time() # 处理嵌套的players字段 dtGoal_player <- data.table() for (i in 1:dtGoals[,.N]) { s_result.eventCode <- dtGoals[i,result.eventCode] dtGoal_player_new <- as.data.table(dtGoals[i,players[[1]]]) dtGoal_player_new[, ':=' (result.eventCode=s_result.eventCode)] dtGoal_player <- rbindlist(list(dtGoal_player, dtGoal_player_new), use.names=TRUE, fill=TRUE) } dtGoals[, players:=NULL] dtGoal_player[, lag.playerType:=c('nomatch', playerType[-.N]), by=result.eventCode] dtGoal_player[, playerType2:=ifelse((playerType==lag.playerType),'Assist2',playerType)] dtGoal_player_t <- dcast.data.table(dtGoal_player, result.eventCode ~ playerType2 , value.var='player.id', fun.aggregate=max) # 合并数据 dtGoals <- merge(dtGoals, dtGoal_player_t, by="result.eventCode") Sys.time()
问题解答
1. 能否通过NHL API直接提取指定列/行数据?
NHL公开API本身不支持请求时筛选特定字段或行,必须全量拉取feed后本地处理,但可以通过以下方式减少拉取成本:
- 批量请求游戏数据:
nhl_games_feed支持传入多个gameId(比如一次传50个),将HTTP请求次数从3万+降至几百次,大幅减少网络耗时。 - 选择必要的feed类型:若不需要实时数据,可尝试指定
feedType="boxscore"(仅拉取赛事统计摘要),但如果需要完整进球事件,仍需拉取live类型feed。
2. 嵌套players字段转data.table的高效方法
完全不需要循环处理,用data.table原生操作可将2小时的处理时间压缩到几分钟内:
# 直接展开嵌套的players列表,自动关联result.eventCode dtGoal_player <- dtGoals[, rbindlist(players, idcol = FALSE), by = result.eventCode] # 处理重复的Assist类型,用分组内的行号生成Assist1/Assist2 dtGoal_player[, playerType2 := fifelse( playerType == "Assist", paste0("Assist", rowid(result.eventCode, playerType)), playerType ), by = result.eventCode] # 转宽表 dtGoal_player_t <- dcast.data.table( dtGoal_player, result.eventCode ~ playerType2, value.var = "player.id", fun.aggregate = max )
核心逻辑是用rbindlist直接批量展开嵌套列表,用rowid替代循环生成的lag判断,避免逐行操作的性能损耗。
3. 构建全面NHL赛事事件数据库的建议
- 本地缓存原始数据:用
qs包(高效存储R对象)或SQLite缓存拉取的游戏feed,避免重复请求API,测试时可直接读取本地数据。 - 并行请求优化:用
furrr包实现并行拉取,将游戏ID分成多个批次,每个批次用并行任务处理,进一步减少网络等待时间:library(furrr) plan(multisession, workers = 4) # 根据CPU核心数调整 game_batches <- split(cSelGames, ceiling(seq_along(cSelGames)/50)) # 每50个游戏为一批 all_feeds <- future_map(game_batches, nhl_games_feed) - 增量更新机制:维护一个已处理游戏ID的列表(存在本地文件或数据库),每次仅拉取新增的赛季/未处理的游戏,无需全量重新拉取。
- 数据类型优化:将字符型字段(如
team.triCode)转为因子,时间字段(如about.periodTime)转为hms类型,减少内存占用并提升后续查询速度。 - 错误处理:用
purrr::possibly包裹nhl_games_feed,添加重试机制,避免单个请求失败导致整个程序中断:safe_nhl_feed <- possibly(nhl_games_feed, otherwise = NULL) lblob_feed <- safe_nhl_feed(gameId = cSelGames[liGameNum]) - 数据库存储:将最终结构化数据存入SQLite或PostgreSQL,方便后续查询、筛选和分析,比纯R对象更稳定且支持多工具访问。
内容的提问来源于stack exchange,提问作者rks13
相关产品推荐
相关产品推荐

