R语言Web Scraping爬取fbref英超数据时生成多命名data frame的方法
核心实现思路
针对你目前的需求,两个最核心的解决逻辑:
- 不需要手动给每个球队的dataframe单独命名存入全局环境,最高效的存储方式是用R的列表(list)承接所有球队的dataframe,列表的每个元素用对应球队名命名,既不会出现变量名冲突,也方便后续批量调用、导出。
- 循环内所有重复的、不需要的页面抓取操作全部删掉,只保留当前球队页面的请求,减少无效请求。
现有代码的问题修正
- 循环内部重复执行了抓取全联赛球队名的操作,完全冗余,会无谓增加请求次数、拖慢爬取速度,直接使用循环外提前拿到的球队名列表即可。
- 用大括号包裹赋值的写法没有把最终生成的dataframe作为输出返回,每一轮循环生成的临时df会被下一轮循环直接覆盖,根本不会保留下来。
- 组装dataframe时漏了提前提取的
Result字段,最终生成的表格会缺列。 - 部分CSS选择器没有限定在目标比赛表格
#matchlogs_for的范围内,可能抓取到页面其他位置的同类元素,导致字段错位。 - 没有做请求容错,某一支球队页面爬取失败就会导致整个循环中断,之前爬取到的数据全部丢失。
优化后的完整代码
library(rvest) # 提前抓取联赛页面基础信息 page <- "https://fbref.com/en/comps/9/Premier-League-Stats" scraped_page <- read_html(page) # 提取所有球队的跳转链接 teamLinks <- scraped_page %>% html_nodes("#stats_squads_standard_for a") %>% html_attr("href") teamLinks <- paste0("https://fbref.com/", teamLinks) # 提取所有球队名称列表 Team <- scraped_page %>% html_nodes('#stats_squads_standard_for .left') %>% html_text() %>% as.character() # 提前定义所有字段对应的CSS选择器(增加表格范围限定,避免抓错元素) comp_sel <- "#matchlogs_for .left:nth-child(3) a" venue_sel <- "#matchlogs_for .left:nth-child(6)" result_sel <- "#matchlogs_for .left+ .center" GF_sel <- "#matchlogs_for .right:nth-child(8)" GA_sel <- "#matchlogs_for .right:nth-child(9)" Opp_sel <- "#matchlogs_for .left:nth-child(10)" xg_sel <- "#matchlogs_for td.left+ .right" xga_sel <- "#matchlogs_for .right:nth-child(12)" poss_sel <- "#matchlogs_for td:nth-child(13)" formation_sel <- "#matchlogs_for .left:nth-child(16)" # 初始化空列表存储所有球队的数据 team_data_list <- vector("list", length = length(Team)) names(team_data_list) <- Team # 逐队爬取数据 for (i in seq_along(teamLinks)) { # 容错处理,单队爬取失败不中断整体流程 tryCatch({ current_url <- teamLinks[i] scraped_url <- read_html(current_url) # 逐字段提取比赛数据 Comp <- scraped_url %>% html_nodes(comp_sel) %>% html_text() Venue <- scraped_url %>% html_nodes(venue_sel) %>% html_text() Result <- scraped_url %>% html_nodes(result_sel) %>% html_text() Goals_For <- scraped_url %>% html_nodes(GF_sel) %>% html_text() Goals_Against <- scraped_url %>% html_nodes(GA_sel) %>% html_text() Opponent <- scraped_url %>% html_nodes(Opp_sel) %>% html_text() xG <- scraped_url %>% html_nodes(xg_sel) %>% html_text() xGA <- scraped_url %>% html_nodes(xga_sel) %>% html_text() Possession <- scraped_url %>% html_nodes(poss_sel) %>% html_text() Formation <- scraped_url %>% html_nodes(formation_sel) %>% html_text() # 组装当前球队的dataframe current_df <- data.frame( Comp, Venue, Result, Goals_For, Goals_Against, Opponent, xG, xGA, Possession, Formation ) # 将当前df存入列表的对应位置 team_data_list[[i]] <- current_df # 控制请求频率,避免被站点限制访问 Sys.sleep(3) }, error = function(e) { # 记录爬取失败的球队信息 message(paste("爬取失败:", Team[i], ",错误原因:", e$message)) }) }
列表存储的使用方法
不需要手动把每个球队的df命名成全局变量,直接通过列表索引就能访问对应数据:
# 查看某支球队的比赛数据,比如曼城 team_data_list[["Manchester City"]] # 批量导出所有球队的数据为单独的CSV文件 for (team_name in names(team_data_list)) { write.csv( team_data_list[[team_name]], file = paste0(team_name, "_match_stats.csv"), row.names = FALSE, fileEncoding = "UTF-8" ) }
进一步优化的小技巧
- 用
seq_along(teamLinks)替代1:length(teamLinks)的写法,避免当链接列表为空时出现索引错误。 - 如果你需要把所有球队的数据合并成一个大表,直接用
dplyr::bind_rows(team_data_list, .id = "球队名称")就能一键合并,比单独存储全局变量效率高很多。 - 爬取完成后可以检查每个df的列长度是否一致,如果出现长度不匹配的情况,说明对应的CSS选择器抓错了元素,需要调整选择器的范围。
内容的提问来源于stack exchange,提问作者BruDonnelly
相关产品推荐
相关产品推荐

