使用 Rvest 获取文本值

Getting the text values using Rvest

相关页面是这样的: https://tolltariffen.toll.no/tolltariff/headings/03.02?language=en(点击OPEN ALL LEVELS获取完整数据)

我正在使用 RSelenium 加载页面,然后获取页面源并使用 rvest 捕获所需字段。这是我要捕获的数据。

到目前为止我提出的代码将一些描述数据分成多个块,这对我来说没有用。

    x <- remdr$getPageSource()
    xpg <- read_html(x[[1]])
    
    # get the HS descriptions
    treeView <- xpg %>%
      html_nodes(xpath = '//*/div[@class="MuiGrid-root MuiGrid-container MuiGrid-wrap-xs-nowrap"]') %>%
      html_nodes(xpath = '//*/p[contains(@class, "MuiTypography-body1")]') %>%
      html_nodes('span') %>%
      html_text(trim = TRUE)

我需要按顺序列出所有描述。

更新:这是输出格式。说明和 8 位代码

总体思路:

RSelenium 不是严格必需的,您可以避免启动浏览器的开销。有一个 API 调用,您可以在浏览器网络选项卡中看到,它提供了感兴趣的内容,并且可以在不需要额外配置请求的情况下调用它,例如headers.

如何以您想要的格式从 API 响应中提取您想要的项目的问题变成了一个有趣的挑战(至少对我而言),因为我们不知道 1) 有多少此响应中可能存在嵌套级别(以及未来可能的嵌套级别) 2) 嵌套级别是否可以在给定响应中针对感兴趣的项目在不同列表中变化 3) 在给定级别是否会有 commodityCode(尽管模式似乎是在给定列表的最深层次上有一个);我们需要考虑如何为输出生成等长的 columns/lists。这些只是一些开始的考虑因素,我将在下面继续讨论我是如何处理的。


API调用:

* 您可以点击下面的许多小图放大


API响应:

这个请求 return 嵌套 JSON:

感兴趣的内容是响应中的命名列表列表,可通过 parent“键”$headingItems:

访问

这些命名列表中的每一个都按照网页上的级别嵌套:

您可以看到 headingItems 的重复访问器键(红色框),上面显示的第一个作为 parent 列表存储在 data 中。

在此之下,以级别(橙色框)表示,是您要查找的扩展条目;嵌套在响应中 JSON.

最后,我们有描述(绿色方框),其中包含 html 您想要的描述性文本,以及文本的英语和挪威语版本:

除此之外,在嵌套的 headingItems 中存在 commodityCode 键:


方法和挑战:

鉴于 commodityCode 可以在不同的级别并且可能不存在(除非假定它始终存在于给定列表的最大深度),并且不知道可以有多少级别的 headingItem,我选择的方法是使用正则表达式在布尔掩码中识别相关的 child 命名列表的名称(尽管出于此处的目的我们可以只说逻辑向量);一个掩码用于英文headers,一个用于商品代码。我分别处理每个 child 列表,使用 purrr::map 并应用自定义函数将数据提取为 data.table/data.frame.

掩码示例(描述|文本):

TRUE 值用于以下链接访问器(链接取决于深度):

注意一些访问器路径是如何重复的。因此,这意味着我不使用掩码来检索名称和提取关联值。相反,我保留了 TRUE 和 FALSE 值,因此两个向量的长度相等。我将两个逻辑向量组合为 data.table 中的列;以及 child 列表中的整组值:

这项工作是在自定义函数 get_data 中完成的,然后我还执行以下步骤:

  1. 我只筛选存在 TRUE 值的行,即我希望检索的值

  1. 应用一个函数,利用 gsub() 删除 non-breaking 空格,并使用 read_html() 将那些实际 html 的描述转换为文本。 N.B。有些条目实际上不是 html,而是由 if 语句处理。在这些情况下,输入值为 returned:

  1. 此时代码和 descriptions/text 位于同一列中:

我使用 commodity_code 中的布尔值来更新 TRUE 中的列值以匹配文本列,并换行 if 以将 FALSE 替换为 NA。

  1. 知道描述和相关代码之间实际上有 1 行偏移量(如果适用),然后我将商品列值向下移动一行以与描述正确对齐:

  1. 然后我只保留 description_header_flag 为 TRUE 的行:

  1. 最后,我删除了现在不需要的标志列:

这让我从函数中得到一个干净的 data.table 到 return。


生成最终输出:

当 map() 将上面的自定义函数应用到一个列表 returns 一个 data.tables 的列表时,我然后简单地调用 rbindlist() 将它们组合成一个data.table:

df <- rbindlist(map(data, get_data))

例如,这可以写入 csv。

fwrite(df, 'result.csv')

df 中的示例行:


N.B。我 return a data.table 因为你在你想要的输出中显示了 2 列。


R:

library(jsonlite)
library(tidyverse)
library(rvest)
library(data.table)

get_data <- function(x) {
  y <- x %>% unlist(recursive = T)
  t <- data.table(text = y, description_header_flag = grepl("(?:headingItems\.)description\.en$|^description.en$", names(y)), commodity_code = grepl("*commodityCode$", names(y)))
  t <- t[description_header_flag | commodity_code, ]
  t$text <- map2(t$text, t$description_header_flag, ~ gsub(intToUtf8(160), " ", if (.y & str_detect(.x, pattern = "<div>|<p>")) {
    html_text(read_html(.x))
  } else {
    .x
  }))
  t$commodity_code <- map2(t$commodity_code, t$text, ~ if (.x) {
    .y
  } else {
    NA
  })
  t[, commodity_code := c(NA, commodity_code[.I - 1])]
  t <- t[description_header_flag == T, ]
  t[, description_header_flag := NULL]
  return(t)
}

data <- jsonlite::read_json("https://tolltariffen.toll.no/api/search/headings/03.02") %>% .$headingItems

df <- rbindlist(map(data, get_data))

fwrite(df, "result.csv")

示例输出:


学分:

  1. gsub 解决方案取自:@shabbychef here

  2. 行移位解决方案改编自:@Gary Weissman here