R使用Rinat包循环获取iNaturalist数据报rbind列数不匹配错误
问题根因
你遇到的列数不匹配错误,核心是iNaturalist的CSV导出接口不会固定返回全量字段:当某个用户的所有观测都没有某类属性值时,接口返回的CSV会直接省略对应列,不同用户返回的字段集合存在差异,直接用rbind合并自然会报错。除此之外你写的两版循环本身还有几个低级问题:
- 第一版循环遍历的是
usernames对象的序列,取数时却调用了User_no_obs_Oxfordshire$user_login,两个数据框如果行数、顺序不一致,会直接取错用户名 - 第二版循环里调用的是
username$user_login,你根本没有定义过username这个对象,而且循环内只给User_obs赋值,没有做任何合并操作,跑完不会得到任何汇总结果 - 原函数没有做异常捕获,只要碰到网络波动、用户不存在、用户观测数为0的情况,会直接中断整个循环,几千个用户跑到一半崩溃根本没法续跑
可行解决方案
第一步:修复原函数,固定返回列结构
给原函数增加字段对齐逻辑,不管接口返回什么列,最后都统一输出iNaturalist观测数据的标准列集合,缺失列直接填充NA,从根源解决列数不匹配问题:
library(httr) library(curl) # 先跑一次单用户查询,拿到iNat观测CSV的标准列集合 standard_cols <- names(get_inat_obs_user("a_random_kaiser")) # 如果要更稳妥,可以先跑100个用户的查询,把所有出现过的列名汇总成standard_cols,避免漏列 get_inat_obs_user_fixed <- function(username, maxresults = 10000){ # 网络连通性检查 if (!has_internet()) { message("无网络连接,跳过用户:", username) return(NULL) } # 替换成https更稳定 base_url <- "https://www.inaturalist.org/" if (http_error(base_url)) { message("iNaturalist API不可用,跳过用户:", username) return(NULL) } q_path <- paste0(username, ".csv") ping_path <- paste0(username, ".json") ping_query <- "&per_page=1&page=1" # 加异常捕获,防止单用户请求失败中断全量任务 ping <- try(GET(base_url, path = paste0("observations/", ping_path), query = ping_query), silent = T) if(inherits(ping, "try-error")){ message("请求用户", username, "信息失败,跳过") return(NULL) } total_res <- as.numeric(ping$headers$`x-total-entries`) if(is.na(total_res) | total_res == 0){ message("用户", username, "无观测记录或不存在,跳过") return(NULL) } # 拉取第一页数据 page_query <-"&per_page=200&page=1" dat <- try(GET(base_url, path = paste0("observations/", q_path), query = page_query), silent = T) if(inherits(dat, "try-error")){ message("拉取用户", username, "第一页数据失败,跳过") return(NULL) } data_out <- try(read.csv(textConnection(content(dat, as = "text"))), silent = T) if(inherits(data_out, "try-error")){ message("解析用户", username, "数据失败,跳过") return(NULL) } # 翻页拉取剩余数据 if(maxresults > 200){ total_pages <- ceiling(total_res/200) for(i in 2:total_pages){ page_query <- paste0("&per_page=200&page=", i) dat <- try(GET(base_url, path = paste0("observations/", q_path), query = page_query), silent = T) if(inherits(dat, "try-error")){ message("拉取用户", username, "第",i,"页失败,跳过剩余页") break } page_data <- try(read.csv(textConnection(content(dat, as = "text"))), silent = T) if(inherits(page_data, "try-error")){ message("解析用户", username, "第",i,"页失败,跳过剩余页") break } data_out <- rbind(data_out, page_data) } } if(maxresults < nrow(data_out)){ data_out <- data_out[1:maxresults,] } # 核心操作:对齐标准列,缺的列补NA,多余列删除,列顺序统一 missing_cols <- setdiff(standard_cols, names(data_out)) for(col in missing_cols){ data_out[[col]] <- NA } data_out <- data_out[, standard_cols] return(data_out) }
第二步:修复循环逻辑,提升运行效率
不要逐次循环调用rbind(数据量越大速度越慢),改用列表存储每个用户的返回结果,最后一次性合并:
library(dplyr) # 直接提取用户名向量,不要按位置跨数据框取数,避免索引错位 user_list <- usernames$user_login # 提前创建等长列表存结果,比逐次追加效率高几个数量级 result_list <- vector("list", length(user_list)) names(result_list) <- user_list for(i in seq_along(user_list)){ uname <- user_list[i] message("正在处理第",i,"/",length(user_list),"个用户:", uname) result_list[[i]] <- get_inat_obs_user_fixed(uname) # 每处理10个用户随机休眠1-3秒,避免请求太频繁被平台封IP if(i %% 10 == 0){ Sys.sleep(sample(1:3, 1)) } # 每处理50个用户存一次备份,防止中途断网、关机丢进度 if(i %% 50 == 0){ saveRDS(result_list, file = "inat_temp_backup.rds") } } # 过滤掉请求失败、无数据的空结果 result_list <- Filter(Negate(is.null), result_list) # 一次性合并所有数据 User_obs_global <- bind_rows(result_list) # 不想用dplyr的话可以替换成下面这句 # User_obs_global <- do.call(rbind, result_list)
提示:如果全量跑的时候还是碰到列不匹配的问题,说明你最开始定义的
standard_cols不全,只要把新出现的列名加进standard_cols向量再重跑即可。
内容的提问来源于stack exchange,提问作者user18748461
相关产品推荐
相关产品推荐

