R rvest 抓取反应性 iframe table

R rvest scraping reactive iframe table

我正在尝试从 iframe 网站中的 table 中提取数据(所有国家和年份)。下面是我试过的一些代码。

通过阅读此处的各种帖子,我设法获得了 iframe https://apps7.unep.org/contribution/ 中 table 的实际网站地址。我尝试了几种提取数据的方法,包括各种 xpath 和 html 节点,但似乎没有任何效果。我之前没有处理过反应性 tables,非常感谢您的帮助。如果有人能为我提供正确的代码,那将是非常棒的。

我愿意接受 selenium 是绝对必要的,但不太了解它,如果可能的话我更喜欢 rvest

提前致谢

library(rvest)
library(tidyverse)
library(dplyr)

# This address has the table within an iframe
url = "https://www.unep.org/about-un-environment/funding-and-partnerships/check-your-contributions"

# This table gets the website where the data in the iframe actually comes from 
base_website = url %>%
  read_html(url) %>%
  html_node("iframe") %>%
  html_attr("src") 

# I would like to get the data from the table on the above website 
my_table = read_html(base_website) %>%
  #extract(1)
  #html_node("body div") %>% 
  html_table()

我建议使用包 datapasta。将行和列复制并粘贴到文本编辑器以清除名称行。然后在插件部分使用 datapasta paste_as_tribble 进行复制和粘贴。

library(datapasta)

mydata<- tibble::tribble(.... ) head(mydata)
#A tibble: 6 x 6
  Country                  Region           Share   Pledge     Paid Unpaid
  <chr>                    <chr>            <dbl>    <dbl>    <dbl>  <dbl>
1 Netherlands              Europe        9090909. 9229600  9229600       0
2 Germany                  Europe        8857042. 8887815. 8887815.      0
3 France                   Europe        7550550  7550550  7550550       0
4 United States of America North America       0  6615000  6615000       0
5 Belgium                  Europe        4687600  6110370  6110370       0
6 Sweden                   Europe        5053036  5053036  5053036       0

解释:

每个页面都有动态内容,包括 iframe 文档。数据来自 API 个基于可用年份的调用。

年份由一个 js 文件确定 https://apps7.unep.org/contribution/js/app.72fa461e.js,您可以从您请求的 iframe 文档中提取该文件。

currentYear: function() {
        var e = new Date;
        return e.getFullYear()
    },
    yearsRange: function() {
        for (var e = this.currentYear, t = [], a = e; a >= 1973; a--) t.push(a);
        return t
    }

您可以从此文件中提取开始年份,并对当前年份使用相同的逻辑。

接下来,生成完整的年份集并与基础 API 端点结合:

years <- start_year:end_year

requests <- paste0("https://apps7.unep.org/dwh/api/v1/contribution/by-year/", years)

发出请求以将 JSON 响应收集到列表中。然后应用自定义函数将数据提取为 DataFrame。这涉及生成 DataFrame 的嵌套调用,以便处理每个 API 响应包含 resp$data$amt 的嵌套列表。内部 DataFrame 映射到最终的外部 DataFrame。

您需要处理不同年份和地区的项目数量不同的事实,例如2019 年

我通过合并一个包含所有可能键的 DataFrame 来做到这一点

map_dfr(., function(x) {
      merge(map(x, ~ ifelse(is.null(.x), NA, .x)) %>%
              data.frame(), df_blank, all = T) %>% .[rowSums(is.na(.)) != ncol(.), ]
    })

在同一部分中,我还处理了从 2019 年开始出现在某些 region_name 条目中的 NULL:

我还确保将年份添加到返回的内部 DataFrame。

您可以根据需要重新排列列。

速度相当快,但您可以开始优化,例如并行化请求。


回复:

library(rvest)
library(jsonlite)
library(tidyverse)

year_data <- function(x) {
  info <- x$resp$data$amt
  year_name <- info %>% names()
  year <- year_name %>% as.integer()
  year_df <- info %>%
    .[[year_name]] %>%
    map_dfr(., function(x) {
      merge(map(x, ~ ifelse(is.null(.x), NA, .x)) %>%
              data.frame(), df_blank, all = T) %>% .[rowSums(is.na(.)) != ncol(.), ]
    }) %>%
    mutate(Year = year)
  return(year_df)
}

df_blank <- data.frame(
  "country_iso2" = NA_character_,
  "country_iso3" = NA_character_,
  "country_name" = NA_character_,
  "ec" = NA_character_,
  "ef_paid" = NA_integer_,
  "ef_pledged" = NA_integer_,
  "region_name" = NA_character_,
  "unep_region_code" = NA_character_,
  "visc" = NA_character_
)

url <- "https://www.unep.org/about-un-environment/funding-and-partnerships/check-your-contributions"

# This table gets the website where the data in the iframe actually comes from
contribution_iframe_src <- url %>%
  read_html(url) %>%
  html_element("iframe[src*='/contribution/']") %>%
  html_attr("src")

contribution_page <- read_html(contribution_iframe_src) # this is a dynamic page requiring JS to run. Instead, go to the JS file and extract required info.

contribution_years_js_file <- contribution_page %>%
  html_element("[href*='contribution/js']") %>%
  html_attr("href") %>%
  url_absolute(contribution_iframe_src)

js_file <- read_html(contribution_years_js_file)

start_year <- js_file %>%
  html_text() %>%
  str_match("yearsRange.*>=(\d{4});") %>%
  .[, 2] %>%
  as.integer()
end_year <- as.integer(format(Sys.Date(), "%Y"))

years <- start_year:end_year

requests <- paste0("https://apps7.unep.org/dwh/api/v1/contribution/by-year/", years)

data <- map(requests, read_json)

df <- map_dfr(data, year_data)

示例输出: