r - 使用 lapply 和 tryCatch 只返回一条记录,而不是多条
问题描述
我还是 R 新手,我正在尝试使用 lapply 和 tryCatch 通过 pro-football-reference 站点访问数据,但我在下面使用的代码仅返回 1 个表。
我想获取所有存在的记录。某些 URL 将不存在,这就是我使用 tryCatch 跳过这些的原因。这是我的尝试:
library(rvest)
library(stringr)
#create a master dataframe to store all of the results
complete <- data.frame()
datesVector <- c("201909080", "201909050")
teamsVector <- c("chi", "crd", "car")
for (i in 1:length(datesVector)) {
for (j in 1:length(teamsVector)) {
# create a url template
URL.base <- "http://www.pro-football-reference.com/boxscores/"
URL.end <- ".htm"
#create the dataframe with the dynamic values
URL <- paste0(URL.base, datesVector[i], teamsVector[j], URL.end)
tryCatch({
dfList <- lapply(URL, function(i) {
webpage <- read_html(i)
draft_table <- html_nodes(webpage, 'table')
draft <- html_table(draft_table)[[3]]
})
}, error = function(e) print(URL)
)
complete <- do.call(rbind, dfList)
}
}
我将有一个非常长的向量,其中包含许多日期和多个团队,并且想要尝试每种组合,如果页面存在,则提取表中的数据并将其添加到我的数据框中。
但是下面的代码只返回 22 个观察值,来自这个链接:
https://www.pro-football-reference.com/boxscores/201909050chi.htm
事实上,存在另一个页面(因此应该返回数据),即另外 22 个观察点:
https://www.pro-football-reference.com/boxscores/201909080car.htm
上面包含了另一个团队,以尝试测试错误并跳过它。
我在上面做错了什么?
解决方案
这是您的任务的解决方案,它使用purrr
和其他一些tidyverse
方法。请参阅下面的说明。
library(tidyverse)
combos <- expand.grid(team = teamsVector, date = datesVector)
urls <- paste0(URL.base, combos$date, combos$team, URL.end)
output <- urls %>%
map(possibly(read_html, otherwise = NA_character_)) %>%
discard(is.character) %>%
map(function(html) html_nodes(html, 'table') %>% html_table) %>%
pluck(3) %>%
map_dfr(function(data) {
col_lvl1 <- colnames(data)
col_lvl2 <- data[1, ] %>% unname
actual_data <- data[2:nrow(data), ] %>% unname
col_multi_lvl <- paste(col_lvl1, col_lvl2, sep = "_")
colnames(actual_data) <- col_multi_lvl
return(actual_data %>% as_tibble(.name_repair = make.names))
}) %>%
filter(str_detect(Passing_Cmp, "Passing|Cmp", negate = TRUE)) %>%
mutate_at(vars(-starts_with("X_")), as.numeric)
输出:
output %>% head(15)
# A tibble: 15 x 22
X_Player X_Tm Passing_Cmp Passing_Att Passing_Yds...5 Passing_TD Passing_Int Passing_Sk Passing_Yds...9
<chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Matthew… DET 27 45 385 3 0 3 24
2 Kerryon… DET 0 0 0 0 0 0 0
3 C.J. An… DET 0 0 0 0 0 0 0
4 Ty John… DET 0 0 0 0 0 0 0
5 Marvin … DET 0 0 0 0 0 0 0
6 T.J. Ho… DET 0 0 0 0 0 0 0
7 Danny A… DET 0 0 0 0 0 0 0
8 Kenny G… DET 0 0 0 0 0 0 0
9 J.D. Mc… DET 0 0 0 0 0 0 0
10 Jesse J… DET 0 0 0 0 0 0 0
11 Nick Ba… DET 0 0 0 0 0 0 0
12 Kyler M… ARI 29 54 308 2 1 5 33
13 David J… ARI 0 0 0 0 0 0 0
14 Christi… ARI 0 0 0 0 0 0 0
15 Chase E… ARI 0 0 0 0 0 0 0
# … with 13 more variables: Passing_Lng <dbl>, Passing_Rate <dbl>, Rushing_Att <dbl>, Rushing_Yds <dbl>,
# Rushing_TD <dbl>, Rushing_Lng <dbl>, Receiving_Tgt <dbl>, Receiving_Rec <dbl>, Receiving_Yds <dbl>,
# Receiving_TD <dbl>, Receiving_Lng <dbl>, Fumbles_Fmb <dbl>, Fumbles_FL <dbl>
我知道这并不能完全回答你的问题,“我做错了什么?” - 我开始解决您方法中的问题,并且遇到了足够多的障碍,因此我觉得提出一个可行的解决方案并清楚地注释它可能会更容易。希望这将使您更容易了解所涉及的各种挑战,并让您对最初的尝试有所了解。
首先,要处理死 URL 的问题,我建议使用safely()
or possibly()
。它们是不错的tryCatch
风格包装器,可为您提供如何发现错误的选项,而且它们具有直观的语法。因此,您可以从构建您感兴趣的所有 URL 的列表开始:
combos <- expand.grid(team=teamsVector, date=datesVector)
urls <- paste0(URL.base, combos$date, combos$team, URL.end)
urls
[1] "http://www.pro-football-reference.com/boxscores/201909080chi.htm"
[2] "http://www.pro-football-reference.com/boxscores/201909080crd.htm"
[3] "http://www.pro-football-reference.com/boxscores/201909080car.htm"
[4] "http://www.pro-football-reference.com/boxscores/201909050chi.htm"
[5] "http://www.pro-football-reference.com/boxscores/201909050crd.htm"
[6] "http://www.pro-football-reference.com/boxscores/201909050car.htm"
然后将其输入possibly
-wrapped read_html()
,使用map()
:
urls %>%
map(possibly(read_html, otherwise = NA_character_)) # throw NA if bad URL
错误的 URL 会产生一个NA
type 的值character
,所以我们可以discard()
返回字符条目(其他的都是一些 HTML 对象):
discard(is.character) %>%
这给我们留下了一个有效抓取网页的列表,我们可以map()
再次使用该列表来迭代该列表并提取表格数据。该pluck()
函数是一个很好的子集到列表中的方法,以获得[[3]]
您想要的表索引:
map(function(html) html_nodes(html, 'table') %>% html_table) %>%
pluck(3) %>%
这些表的一个棘手之处在于它们具有多索引列 - 顶部列包含“通过”或“接收”等类别,而子列指定统计信息(“TD”、“Int”等)。将 R 放入数据框中并不容易,因为它需要一组列名,而不是两个。这需要一些黑客攻击。我选择提取列和子列名称并将它们与下划线结合起来(例如“Passing_TD”):
# note: map_dfr just specifies a data frame output, joined row-wise
map_dfr(function(data) {
col_lvl1 <- colnames(data)
col_lvl2 <- data[1, ] %>% unname
actual_data <- data[2:nrow(data), ] %>% unname
col_multi_lvl <- paste(col_lvl1, col_lvl2, sep = "_")
colnames(actual_data) <- col_multi_lvl
return(actual_data %>% as_tibble(.name_repair = make.names))
}) %>%
最后还有一个额外的问题,即表在每个表的中途重复列名。我不确定您想如何处理它,因为看起来每个子表中的信息可能非常不同。因为我只是想演示方法,所以我选择将这些行排除在外,就好像所有行都属于一个大表一样。之后,我将数字列恢复为数字数据类型(所有数字列都被重新定义为字符数据类型,因为它们的行中包含列名,它们是字符串)。
filter(str_detect(Passing_Cmp, "Passing|Cmp", negate = TRUE)) %>%
mutate_at(vars(-starts_with("X_")), as.numeric)
推荐阅读
- html - 如何使用 chart.js 显示图表?
- r - 为什么 dplyr::mutate_at() 在 rowwise-tibble 中的第一个元素上也会对其余元素生效?
- domain-driven-design - 如何使用事件溯源建模关系
- gradle - 在 Gradle 中,如何对延迟评估的属性进行验证(在扩展上)?
- django - 在 Django 中将模型字段设置为必填
- react-native - 在反应导航中通过 onPress() 传递对象参数
- javascript - 使用javascript在字符串中查找顺序
- typescript - 如何告诉 Typescript 我已经对变量进行了空检查?
- asp.net-mvc - Razor 页面,检测 textview 中的文本变化
- .net - 管理服务未出现在 ISS 控制台上