Использование R и httr для извлечения данных — запрос GET Okta API с разбитыми на страницы заголовками ссылок

Я пытаюсь использовать пакет RStudio/Hadley Wickham 'httr' R для возврата всех записей из запроса GET Okta API ('Список пользователей, назначенных приложению'). Следующий запрос отлично работает, чтобы получить максимальное количество записей (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"))))

Преобразование возвращенных данных 'oktagejson' в пригодный для использования фрейм данных с помощью 'jsonlite' и R не является проблемой; однако этот конкретный вызов API жестко ограничен максимум 500 записями на вызов, поэтому мне нужно каким-то образом получить и разбить на страницы все заголовки «Link:», чтобы получить все несколько тысяч записей. Сами заголовки Link: имеют вид:

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. Я вижу первые два заголовка «Link:» разбивки на страницы, перечисленные выше, в консоли R/RStudio при вызове «oktagejson ‹- with_verbose(content(GET(oktaurl, etc...) ...)», чтобы получить мой oktagejston объект, но заголовки "Ссылка:" не возвращаются как часть самого объекта. Вызов headers(HEAD("https://mydomain.okta.com/api/v1/apps/<applicationID>/users")) возвращает некоторые заголовки, но не возвращает заголовки "Ссылка:" разбиения на страницы.
  2. Заголовки «Link:» содержат случайные строки курсора, поэтому я не могу угадать их фактический формат.
  3. Даже если бы я мог получить все необходимые заголовки «Link:», я понятия не имею, как вызвать/итерировать/разбить на страницы/рекурсивно пройти через все из них в R, чтобы построить объект из всего набора данных из нескольких тысяч записей.

К сожалению, из-за характера запроса, поставщика услуг и данных я не могу предоставить полностью воспроизводимый пример с реальными ссылками и образцами данных, но я надеюсь, что концепция достаточно ясна, чтобы кто-то указал мне правильное направление - даже если это направление не использовать для этого пакет httr или R.

Спасибо за внимание.


person Gratalis    schedule 09.03.2016    source источник
comment
Похоже, они предоставляют вам новый URL для следующих 500 пользователей. Вы пробовали использовать его?   -  person cory    schedule 10.03.2016
comment
Я работаю над своего рода DSL для выполнения http-запросов, которые обертывают httr, см. этот выпуск github.com/ sckott/request/issues/2 за текущую работу, которая отлично подойдет для вашего варианта использования.   -  person sckott    schedule 10.03.2016
comment
@cory - Да, «следующий» URL-адрес можно проанализировать из возвращаемого заголовка из вызова GET, и он работает для извлечения следующего набора записей. Теперь мне нужно выяснить, как рекурсивно получить все «следующие» URL-адреса и передать их в вызовы «content (GET» для извлечения всех записей. Вот где я застрял в данный момент.   -  person Gratalis    schedule 11.03.2016
comment
@scott - Очень мило! Похоже, вы делаете большие успехи.   -  person Gratalis    schedule 11.03.2016


Ответы (2)


Просто потратил некоторое время на работу над этим и хочу поделиться альтернативным решением, которое немного легче. Не удалось найти отличный способ регулярного выражения следующей ссылки без дополнительной логики, но в остальном довольно многоразовый, если предположить, что ваш 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")))
}
person Andy Lee    schedule 10.05.2018
comment
Спасибо за это, @Энди Ли. Хотелось бы, чтобы httr имел функциональность, аналогичную requests Python здесь... (request.kennethreitz.org/en/master/api/) - person call-in-co; 16.10.2019

Некоторое время назад собрал что-то, что работает, но уж точно не получит никаких наград за элегантность. Изменили его, чтобы пользователи также были назначены приложениям 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"))
person Gratalis    schedule 26.07.2016