1

我正在尝试使用 RStudio / Hadley Wickham 'httr' R 包从 Okta API GET 请求('列出分配给应用程序的用户')中返回所有记录。以下请求可以很好地获得每次调用的最大记录限制 (500):

oktaurl <- "https://mydomain.okta.com/api/v1/apps/applicationID/users?limit=500"

oktagetjson <- with_verbose(content(GET(oktaurl,
                                        add_headers("Authorization" = "bearer myapikey",
                                                       "Content-Type" =  "application/json;charset=UTF-8"))))

用 'jsonlite' 和 R 将 'oktagetjson' 返回的数据解析成可用的数据框不是问题;但是,这个特定的 API 调用很难限制为每次调用最多 500 条记录,因此我需要以某种方式检索和分页所有“链接:”标头以获取所有数千条记录。“链接:”标头本身采用以下形式:

Link: <https://mydomain.okta.com/api/v1/apps/applicationID/users?limit=500>; rel="self"

Link: <https://mydomain.okta.com/api/v1/apps/applicationID/users?after=random cursor string&limit=500>; rel="next"

(Okta API 文档在这里描述了它们的分页结构)

我被困在这里:

  1. 我可以在调用 'oktgetjson <- with_verbose(content(GET(oktaurl, etc ... ) ...) ' 来获取我的 oktagetjston 时,在 R / RStudio 控制台中看到上面列出的前两个分页 'Link:' 标头对象,但“链接:”标题不作为对象本身的一部分返回。调用headers(HEAD("https://mydomain.okta.com/api/v1/apps/<applicationID>/users"))返回一些标题,但不返回分页“链接:”标题
  2. “链接:”标题包含随机光标字符串,所以我无法猜测它们的实际格式
  3. 即使我可以检索所有必需的“链接:”标题,我也不知道如何在 R 中调用/迭代/分页/递归地跟踪所有这些以构建包含数千条记录的整个数据集的对象。

不幸的是,由于请求、服务提供商和数据的性质,我无法提供具有真实链接和示例数据的完全可重复的示例,但我希望这个概念足够清晰,有人可以为我指明正确的方向——即使那个方向是不要使用'httr'包或R来完成这项工作。

谢谢您的考虑。

4

2 回答 2

1

刚刚花了一些时间来解决这个问题,并想分享一个更轻松的替代解决方案。在没有一些额外逻辑的情况下找不到正则表达式下一个链接的好方法,但是如果您的 API 返回可以遵循的完整链接,则可以重用。

library(jsonlite) 
library(httr)      
library(stringr)

res <- GET(<yourURL>,token)
resDF <- fromJSON(httr::content(res, as = "text"))
while (grepl("next", res$headers$link) == 'TRUE')
{
  res <- GET(
    ifelse(grepl("prev", res$headers$link) == 'TRUE',
           str_match(res$headers$link, "prev, <(.*)>; rel=next")[1,2]
           ,
           str_match(res$headers$link, "first, <(.*)>; rel=next")[1,2]
    )
    ,token)
  resDF <- rbind(resDF, fromJSON(httr::content(res, as = "text")))
}
于 2018-05-10T22:01:45.083 回答
0

不久前一起破解了一些可行的方法,但肯定不会赢得任何优雅奖。已对其进行了修改以将用户也分配给 Okta 应用程序。如果您正在审核/加入其他公司/目录数据,这很有用。

library(jsonlite)
library(dplyr)
library(httr)
library(purrr)
library(stringi)
library(tidyr)

# create character vector to hold URLs we'll use later when we GET content
url_list <- as.character()

# list placeholder for GET content
okta_content <- list()

# initial URL construction parts for first URL
okta_urllimit = as.character("200")
okta_baseurl <- paste0("https://<your company>.okta.com/api/v1/users?limit=",okta_urllimit)

# next URL construction parts for 'next' URLs
basenexturl <- "https://<your company>.okta.com/api/v1/users?after="
baselimiturl <- "&limit=200"

# Pass initial URL to get first batch
okta_get01 <- httr::GET(okta_baseurl,
                         config = (
                           add_headers(Authorization = "SSWS <your Okta API key>")))


# append the URL vector 
url_list <- append(url_list, okta_baseurl)


# unlist the all_headers list element from the URL
testallheaders <- as.character(unlist(okta_get01$all_headers))

okta_content <- append(okta_content,content(okta_get01))

# if "next" is in the second link URL (testallheaders[16]) then iterate for as long as
# the next URL header element has "next" in it

while (
  grepl("next",testallheaders[16]) == 'TRUE'
)

{ 
  # parse the sha value 
  testparsenext <- regmatches(testallheaders[16], gregexpr('(?<=after=).*?(?=&limit)',testallheaders[16], perl=T))[[1]]
  # and create URL
  oktaurlnext <- paste0(basenexturl,testparsenext,baselimiturl)


  # iterate and replace 'okta_baseurl' with each subsquent oktaurlnext

  okta_get01 <- httr::GET(oktaurlnext,
                           config = (
                             add_headers(Authorization = "SSWS <your Okta API key>")))

  testallheaders <- as.character(unlist(okta_get01$all_headers))
  url_list <- append(url_list, oktaurlnext)
  okta_content <- append(okta_content,content(okta_get01))

  next
}


# Parse the results into something usable

oktagettojson <- toJSON(okta_content, simplifyDataFrame = TRUE, flatten = TRUE, recursive = TRUE)
oktagetdf <- fromJSON(oktagettojson, simplifyDataFrame = TRUE, flatten = TRUE)
dfnames <- names(oktagetdf)
oktagetdf <- oktagetdf %>% map_if(is.list, as.character)
oktagetdf <- do.call(cbind, lapply(oktagetdf, data.frame, stringsAsFactors=FALSE))
names(oktagetdf) <- dfnames

# adding columns to separate AD domain mastered account and domain names
oktagetdf <- separate(oktagetdf, profile.login,
                      into = c("credPrefix", "credSuffix"), sep = "@", remove = FALSE, extra = "drop")

# select some data frame columns of interest
okta_allusers <- subset(oktagetdf, select = c("id","status","created","lastLogin","profile.login","credPrefix", "credSuffix","profile.firstName","profile.lastName","profile.email","credentials.provider.type","credentials.provider.name"))
于 2016-07-26T19:01:19.817 回答