R语言如何创建map函数同时遍历接口地址与请求体执行批量API调用
R 批量遍历两个列表执行API请求的最优实现
问题描述
首先,如下是数据集的dput输出结果:
df <- structure(list(Id = 111323317:111323326, TypeId = c(37L, 37L, 37L, 37L, 37L, 37L, 37L, 37L, 37L, 37L), PlayerId = c(458228L, 458228L, 458228L, 458228L, 458228L, 458228L, 458228L, 458228L, 458228L, 458228L), TeamId = c(1866215L, 1866214L, 1866215L, 1866215L, 1866215L, 1866215L, 1866214L, 1866215L, 1866215L, 1866215L )), row.names = c(NA, -10L), class = "data.frame")
接下来我们将各列数据提取为独立向量:
Ids <- df$Id TypeIds <- df$TypeId PlayerIds <- df$PlayerId TeamIds <- df$TeamId
随后我们引用上述值构建API调用的请求体参数:
data_players = paste0(' { "Context": { "TypeId": "', TypeIds, '" }, "responses": [ { "type": "Players", "PlayerId": "', PlayerIds, '", "TeamId": "', TeamIds, '" } ] } ')
接下来我们使用Ids生成待调用的API地址列表:
api_urls <- sprintf('https://api.sports.com/players/%s/stats', Ids)
最后我们执行API调用(出于保密原因此处为示例地址,非真实接口地址):
get_data <- POST(api_urls[1], add_headers(.headers=headers_upsert), body = data_players)
显然上述示例仅会调用api_urls列表中的第一个地址。我希望创建一个map函数,同时遍历data_players和api_urls两个列表完成批量请求,请问最高效的实现方式是什么?
解决方案
基础单线程实现
针对两个等长序列的同步遍历场景,purrr包的map2系列函数是最简洁高效的原生实现:
library(httr) library(purrr) # 批量请求,返回所有响应结果的列表 all_responses <- map2( .x = api_urls, .y = data_players, .f = ~POST(.x, add_headers(.headers = headers_upsert), body = .y) )
如果需要直接解析返回结果并合并为数据框,可以使用map2_dfr:
all_result_df <- map2_dfr(api_urls, data_players, function(url, body) { resp <- POST(url, add_headers(.headers = headers_upsert), body = body) # 请求成功则解析返回内容,失败则返回空跳过 if (http_status(resp)$category == "Success") { content(resp, "parsed") } else { NULL } })
并行优化实现(IO密集型场景提速明显)
API请求属于IO密集型任务,多线程并行可以大幅降低总耗时,使用furrr包的并行映射函数仅需修改少量代码:
library(httr) library(furrr) # 开启多线程,workers参数为并发请求数,不要超过接口的频率限制 plan(multisession, workers = 4) all_responses <- future_map2( api_urls, data_players, ~POST(.x, add_headers(.headers = headers_upsert), body = .y) ) # 执行完成后关闭并行进程 plan(sequential)
额外优化建议
不推荐用paste0拼接JSON请求体,很容易出现转义错误,改用jsonlite包自动生成更稳妥:
library(jsonlite) data_players <- pmap(df, function(TypeId, PlayerId, TeamId, ...) { toJSON( list( Context = list(TypeId = as.character(TypeId)), responses = list(list(type = "Players", PlayerId = as.character(PlayerId), TeamId = as.character(TeamId))) ), auto_unbox = TRUE ) })
内容的提问来源于stack exchange,提问作者user6855566
相关产品推荐
相关产品推荐

