批量读取2000+Excel文件到DataFrame时触发rId1 not found报错
报错产生原因
read_excel(readxl包)读取xlsx格式文件时,本质是将文件作为压缩包解压后读取内部存储关系映射的XML配置,rId1 not found报错的核心触发原因是解析时找不到对应关系ID指向的文件组件,并非只有过旧Excel格式才会触发,批量读取大量文件时常见触发场景如下:
- 文件匹配规则存在漏洞:代码里用的
pattern = "*xls"是模糊匹配,会把后缀带xls字符的所有文件都纳入读取范围,最常见的是Excel打开文件时自动生成的~$xxx.xlsx隐藏临时锁文件,这类文件不是合法的完整Excel文件,没有完整的内部结构,解析时必然触发该报错。文件量小时文件夹内无残留临时文件/未打开任何Excel文件就不会触发,文件量上升后,操作过程中打开过Excel、或是之前异常退出残留的锁文件都会被匹配到。 - 异常文件混入:2000个文件的样本量下,很容易混入后缀被手动改成.xls/.xlsx的非Excel文件(比如改了后缀的csv、html表格、损坏的半截文件),这类文件同样不满足Excel内部结构要求,会触发报错。之前读取100个文件时未混入这类异常文件,因此运行正常。
- 代码本身存在逻辑缺陷:原for循环每次读取都会直接覆盖
data变量,即便不报错最终也只会留存最后一个文件的内容;第一版purrr写法中先取全路径又调用basename()裁掉路径,再重新拼接路径时如果工作路径设置不对,也可能出现路径匹配偏差。
解决方案
- 第一步:修正文件匹配规则,过滤无效文件
不要用模糊匹配规则,改用正则精准匹配.xls/.xlsx后缀,同时过滤掉Excel自动生成的临时锁文件,注意R中路径写正斜杠,不要用Windows默认的反斜杠:
library(tidyverse) library(readxl) # 设置文件存储路径 target_path <- "C:/your_file_path/" # 精准匹配合法Excel文件 valid_files <- list.files( path = target_path, pattern = "\\.(xls|xlsx)$", full.names = TRUE ) # 过滤~$开头的临时锁文件 valid_files <- valid_files[!grepl("~\\$", basename(valid_files))]
- 第二步:加容错机制,定位异常文件
给读取逻辑加错误捕获,不会因为单个坏文件中断整个批量流程,同时可以直接定位到出问题的具体文件,不用挨个排查:
# 构造安全读取函数,读取失败时返回错误信息不中断流程 safe_read <- safely(~read_excel(.x, col_types = "text")) read_res <- tibble(file = valid_files) %>% mutate( res = map(file, safe_read), content = map(res, "result"), err = map(res, "error") ) # 提取读取失败的文件及对应报错 failed_files <- read_res %>% filter(!map_lgl(err, is.null)) %>% mutate(err_msg = map_chr(err, as.character)) %>% select(file, err_msg)
- 第三步:处理异常文件+合并有效数据
查看failed_files里的异常文件:如果是残留临时文件直接删除即可;如果是后缀错误/格式损坏的文件,手动打开后另存为标准.xlsx格式再重新读取。最后把所有读取成功的文件合并成一个DataFrame即可:
# 合并所有读取成功的文件,加来源文件列方便溯源 final_data <- read_res %>% filter(map_lgl(err, is.null)) %>% transmute(file, content) %>% unnest(content)
- 如果习惯用for循环实现,修正原来的覆盖变量bug即可:
# 先创建和文件数量等长的列表存结果,避免每次循环覆盖变量 data_list <- vector("list", length(valid_files)) for (i in seq_along(valid_files)) { data_list[[i]] <- read_excel(valid_files[i], col_types = "text") # 可选加进度提示,每读100个文件输出一次进度 if (i %% 100 == 0) message("已完成", i, "个文件读取") } # 最后合并所有数据 final_data_loop <- bind_rows(data_list, .id = "file_seq")
内容的提问来源于stack exchange,提问作者Dre Day
相关产品推荐
相关产品推荐

