如何通过函数将 URL 传递给 dplyr:mutate 时修复 "invalid char in json text"

How to fix "invalid char in json text" when passing a URL via function to dplyr:mutate

我编写了一个函数来通过 API 检索数据。输出格式为JSON。 https://jsoneditoronline.org/?id=ac0ec7ececae49ca92599ff912458a84

对于每个查询,一个变量(路径)应该改变。此变量位于列 (product_folder) 中的数据框 (product_folders_summarised) 中。

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

func_visibility <- function(product_folder) {
  api_url <- "https://api.where-the-data-comes-from.com/example"
  api_key <- "_API_KEY_"
  format <- "json"
  request <-
    fromJSON(
      paste0(api_url, "?api_key=",api_key,"&format=",format,"&path=",product_folder),
      simplifyVector = TRUE,
      simplifyDataFrame = TRUE,
      flatten = TRUE
    )
  request <- lapply(request, function(x) {
    x[sapply(x, is.null)] <- NA
    unlist(x)
  })
  request <- as.data.frame(t(request$answer))
  request <- select(request, -sichtbarkeitsindex.path, -sichtbarkeitsindex.date)
  return(request) 
}

product_folders_summarised <- product_folders_summarised %>%
  dplyr::mutate(visibility_value = func_visibility(product_folder))

数据帧结构如下:

|product_folder|value_1|value_2|
|https://www.example.de/folder/|this|that|
|https://www.example.de/anotherfolder/|...|...|

我希望数据框 (product_folders_summarised) 的值取自列 (product_folder),然后传递给函数,然后 visibility_value 添加为列。

相反,我收到了错误消息

Error: lexical error: invalid char in json text.
                                       https://api.https://api.where-the-data-comes-from.com/example.
                     (right here) ------^

我现在已经按照 r2evans 的建议调整了我的功能。

func_visibility <- function(path) {
  api_url <- "https://api.where-the-data-comes-from.com/example"
  api_key <- "_API_KEY_"
  format <- "json"
  request <- paste0(api_url,"?api_key=",api_key,"&format=",format,"&path=",path)
  request <- lapply(request, jsonlite::fromJSON)
  request <- lapply(request, function(x) {
    x[sapply(x, is.null)] <- NA
    as.data.frame(t(x))  
    unlist(x)
  })
  return(request)
}
product_folders_summarised_short <- product_folders_summarised_short %>%
  dplyr::mutate(sichtbarkeitsindex_value = func_visibility(product_folder))

现在从 API 检索数据。数据被写入数据框的新最后一列:

c(method = "domain.sichtbarkeitsindex", answer.sichtbarkeitsindex.path = "https://www.example.de/folder/", answer.sichtbarkeitsindex.date = "2019-09-02T00:00:00+02:00", answer.sichtbarkeitsindex.value = "0", credits.used = "1")

在我的第一次尝试中(参见第一个代码块),我将数据转换为数据帧。

request <- as.data.frame(t(request$answer)),
request <- select(request, -sichtbarkeitsindex.path, -sichtbarkeitsindex.date),

应用于单个 URL,这有效。现在我整合了

`as.data.frame(t(x))`,

但我只得到 API 中的数据存储为字符向量的结果。

您认为将数据作为字符向量写入数据帧的最后一列是否更容易,以便在将第一个函数传递给另一个函数后将向量分配给新的数据帧?

如果您深入了解 jsonlite::fromJSON 如何处理其参数,您会发现它调用了:

  • jsonlite::fromJSON
    • jsonlite:::parse_and_simplify
      • jsonlite:::parseJSON
        • jsonlite:::parse_string

(注意大多数是内部的,不是导出的)...最后一个函数是

function (txt, bigint_as_char) 
{
    if (length(txt) > 1) {
        txt <- paste(txt, collapse = "\n")
    }
    .Call(R_parse, txt, bigint_as_char)
}

这意味着您的 json 字符串向量正在使用 \n 折叠成长度 1(这对我来说似乎很奇怪......)。因此,有效的 JSON 向量实际上变成了 ndjson(换行符分隔 json),而 fromJSON 不会。

两个选项:

  1. 在您的 lapply 中进行 json 解析。

    jsonvec <- c('{"a":1}', '{"b":2}')
    lapply(jsonvec, jsonlite::fromJSON)
    # [[1]]
    # [[1]]$a
    # [1] 1
    # [[2]]
    # [[2]]$b
    # [1] 2
    
  2. 使用jsonlite::stream_in做json)并禁用简化:

    jsonvec <- c('{"a":1}', '{"b":2}')
    jsonlite::stream_in(textConnection(jsonvec), simplifyDataFrame = FALSE)
    #  Imported 2 records. Simplifying...
    # [[1]]
    # [[1]]$a
    # [1] 1
    # [[2]]
    # [[2]]$b
    # [1] 2
    
  3. 使用 Vectorize 将非矢量友好函数转换为矢量友好函数。

    jsonvec <- c('{"a":1}', '{"b":2}')
    Vectorize(jsonlite::fromJSON, USE.NAMES=FALSE)(jsonvec)
    # $a
    # [1] 1
    # $b
    # [1] 2
    

    这可以让您只用 Vectorize(fromJSON) 替换代码中 fromJSON 的任何实例,注意它 returns 然后您可以在向量上使用的函数。

鉴于你是依赖fromJSON下载数据,我建议第一种或第三种方案。

我现在采取了不同的方法。解决方案是使用rowwise() %>%。否则整列将用作向量。

library(tidyverse)
library(httr)
library(jsonlite)

func_sichtbarkeit <- function(pfad, output) {
  httr::GET(
    url = "https://api.where-the-data-comes-from.com/example",
    query = list(
      api_key = "_API_KEY_",
      format = "json",
      country ="de",
      date = "2019-09-08",
      daily = "true",
      path = pfad
    )
  ) -> res
  httr::warn_for_status(res) # Prüfen auf Status 200 und bei Bedarf warnen
  out <- httr::content(res, as = "text", encoding = "UTF-8") # Abrufen der Daten als text und URF-8 encodiert
  out <- jsonlite::fromJSON(out) # JSON parsen
  answer_raw <- out$answer$sichtbarkeitsindex # Zutreffenden Datensatz aus dem JSON auswählen
  visibility_raw <- answer_raw[[1]][["value"]] # Reduktion auf SI Wert
  return(visibility_raw)
}

all_search_2019_09_08 <- all_search %>%
  rowwise() %>%
  dplyr::mutate(visibility_value_2019_09_08 = func_sichtbarkeit(address))