如何用R的pdftools正确拆分双栏PDF文本并提取表格?
双栏PDF表格提取与分栏优化问题
我需要从双栏格式的PDF文档中提取表格,使用pdftools提取文本时,两栏内容会被拼接在一起,且表格有时会跨两栏分布,导致需要大量清理才能提取数据。
现有代码如下:
library(pdftools) library(stringr) library(dplyr) txt <- pdf_text("https://www.govinfo.gov/content/pkg/BUDGET-2025-APP/pdf/BUDGET-2025-APP-1-3.pdf") left_rows <- list() right_rows <- list() for (i in seq_along(txt)) { lines <- str_split(txt[[i]], "\n")[[1]] lines <- str_trim(lines) locs <- str_locate_all(txt[[i]], regex("2025 est\\.", ignore_case = TRUE))[[1]] if (nrow(locs) == 0) { split_pos <- floor(max(nchar(lines)) / 2) } else { split_pos <- locs[1,2] } for (line in lines) { if (nchar(line) < split_pos) { left_col <- line right_col <- "" } else { left_col <- str_trim(substr(line, 1, split_pos)) right_col <- str_trim(substr(line, split_pos + 1, nchar(line))) } left_fields <- str_split(left_col, "\\s{2,}")[[1]] right_fields <- str_split(right_col, "\\s{2,}")[[1]] if (length(left_fields) > 0 && any(left_fields != "")) { left_rows <- append(left_rows, list(left_fields)) } if (length(right_fields) > 0 && any(right_fields != "")) { right_rows <- append(right_rows, list(right_fields))}}}
当前问题在于:现有代码基于“2025 est.”的位置或行长度中点确定固定拆分位置split_pos,但由于行长度不一,拆分后左右栏内容仍会错误合并,无法有效分离两栏。请问是否有更优方法可将双栏PDF文本拆分为独立的左右栏,并将两栏内容上下堆叠?
解决方案
方法1:用布局识别工具直接提取(推荐)
pdftools仅提取文本不保留布局信息,改用tabulizer包可直接识别PDF的栏位结构,甚至一键提取表格:
library(tabulizer) # 提取指定页码的表格,自动识别双栏布局 tables <- extract_tables("BUDGET-2025-APP-1-3.pdf", pages = 1:3, method = "stream") # 将左右栏表格上下堆叠(若跨栏则自动合并为完整表格) combined_table <- do.call(rbind, tables)
注:stream方法适合结构化表格,若PDF是扫描件则需用ocr方法。
方法2:优化文本拆分逻辑(基于空白频率统计)
如果坚持用pdftools,不要用固定拆分点,而是统计每行空白区域的分布,找到最密集的空白位置作为分栏边界:
library(pdftools) library(stringr) library(dplyr) txt <- pdf_text("BUDGET-2025-APP-1-3.pdf") # 自定义函数:通过空白频率计算最优分栏位置 get_split_pos <- function(page_text) { lines <- str_split(page_text, "\n")[[1]] %>% str_trim() lines <- lines[lines != ""] if (length(lines) == 0) return(NA) # 统计每个字符位置的空白出现次数 char_positions <- lapply(lines, function(line) str_locate_all(line, "\\s")[[1]][,1]) %>% unlist() pos_freq <- table(char_positions) # 过滤掉首尾10%的位置,避免误判边缘空白 total_chars <- max(nchar(lines)) valid_pos <- as.integer(names(pos_freq)) valid_pos <- valid_pos[valid_pos > total_chars*0.1 & valid_pos < total_chars*0.9] if (length(valid_pos) == 0) { return(floor(total_chars / 2)) } else { # 取出现频率最高的位置作为分栏点 return(as.integer(names(which.max(pos_freq[as.character(valid_pos)])))) } } left_rows <- list() right_rows <- list() for (i in seq_along(txt)) { page <- txt[[i]] split_pos <- get_split_pos(page) if (is.na(split_pos)) next lines <- str_split(page, "\n")[[1]] %>% str_trim() for (line in lines) { if (nchar(line) == 0) next left_col <- str_trim(substr(line, 1, split_pos)) right_col <- str_trim(substr(line, split_pos + 1, nchar(line))) if (left_col != "") { left_rows <- append(left_rows, list(str_split(left_col, "\\s{2,}")[[1]])) } if (right_col != "") { right_rows <- append(right_rows, list(str_split(right_col, "\\s{2,}")[[1]])) } } } # 合并左右栏内容(左栏在前,右栏在后) combined_rows <- c(left_rows, right_rows)
方法3:跨栏表格的补充处理
如果表格跨栏,提取后可通过字段数量判断行结构:
- 若左栏字段数与表格列数匹配,右栏属于下一行内容,直接堆叠即可;
- 若左右栏字段数之和等于表格列数,说明是同一行跨栏,需要合并左右栏的字段列表。
内容的提问来源于stack exchange,提问作者osu-statistics
相关产品推荐
相关产品推荐

