基于R/Python将扫描版PDF转换为结构化表格的技术求助
扫描版PDF OCR文本转结构化表格方案
问题背景
我有一份扫描版PDF(示例:p1.pdf),已经用R的tesseract、magick等工具完成OCR提取,得到了raw_tibble,但现有教程的整理方法不适用,需要把提取的文本转换成包含Name、Address等字段的结构化表格,支持R或Python实现。
已执行的R代码:
library(tesseract) library(magick) library(png) library(pdftools) library(tidyverse) pngfile <- pdftools::pdf_convert('p1.pdf', dpi = 600) text <- tesseract::ocr(pngfile) cat(text) raw_img <- image_read(pngfile) raw_img %>% image_ocr() num_only <- tesseract::tesseract( options = list(tessedit_char_whitelist = c(".0123456789 ")) ) raw_img %>% image_quantize(colorspace = 'gray') %>% image_threshold() %>% image_crop(geometry_area(100, 0, 600, 40)) %>% ocr(engine = num_only) combo <- tesseract::tesseract( options = list( tessedit_char_whitelist = paste0( c(letters, LETTERS, " ", ".0123456789 (-)"), collapse = "") )) raw_text <- raw_img %>% image_quantize(colorspace = "gray") %>% image_transparent("white", fuzz = 22) %>% image_background("white") %>% image_threshold() %>% image_crop(geometry_area(0, 0, 110, 45)) %>% ocr(engine = combo) raw_tibble <- raw_text %>% str_split(pattern = "\n") %>% unlist() %>% tibble(data = .)
期望的最终表格格式:
id Name Address 1 1 (John Smith) Email:jsmith@gmail.com AB p:999-999-9999 2 2 Jane Smith Email:janes@gmail.com p:111-111-1111 3 3 Henry Smith 201 Fake St,Some City, Some State A1C B23 P:111-222-3333 4 4 Jason Smith 301 Fake St Some City,Some State A1C B23 P:555-555-5555 5 5 Luke Smith lsmith2@gmail.com 401 Fake St Some City, Some State A1C B23 P: 555-555-5555 501 Fake St Some City, Some State A1C B23 p:666-666-6666 601 Fake St Some City, Some State A1C B23 p:666-666-6666 Place_Year_Graduation Reg_Year 1 ABC University 2001, BCC University 2002,DEF University 2003 2000 2 HGH University/2001, Some Other School 2002 2000 3 University ABC 1999 4 Univer 123 2005 5 ABC College 2010 final = structure(list(id = 1:5, Name = c(" (John Smith) Email:jsmith@gmail.com", "Jane Smith Email:janes@gmail.com", "Henry Smith", "Jason Smith", "Luke Smith lsmith2@gmail.com"), Address = c("AB p:999-999-9999", "p:111-111-1111", "201 Fake St,Some City, Some State A1C B23 P:111-222-3333", "301 Fake St Some City,Some State A1C B23 P:555-555-5555", "401 Fake St Some City, Some State A1C B23 P: 555-555-5555 501 Fake St Some City, Some State A1C B23 p:666-666-6666 601 Fake St Some City, Some State A1C B23 p:666-666-6666" ), Place_Year_Graduation = c("ABC University 2001, BCC University 2002,DEF University 2003", "HGH University/2001, Some Other School 2002", "University ABC", "Univer 123", "ABC College"), Reg_Year = c(2000, 2000, 1999, 2005, 2010)), class = "data.frame", row.names = c(NA, -5L))
R实现方案
核心思路
先清理OCR提取的文本行,再根据排版规律分组单条记录,最后用正则表达式匹配提取目标字段。
library(tidyverse) # 1. 预处理:清理空行、多余空格 clean_tibble <- raw_tibble %>% filter(data != "") %>% mutate(data = str_squish(data)) # 2. 识别单条记录的起始行(示例:以大写开头的姓名为记录起始,需根据实际排版调整) clean_tibble <- clean_tibble %>% mutate(is_record_start = str_detect(data, "^[A-Z][a-z]+ [A-Z][a-z]+")) %>% mutate(record_id = cumsum(is_record_start)) # 3. 合并同一条记录的多行内容 grouped_text <- clean_tibble %>% group_by(record_id) %>% summarise(full_text = str_c(data, collapse = " ")) %>% ungroup() %>% filter(record_id != 0) # 过滤可能的表头行 # 4. 正则提取目标字段 final_table <- grouped_text %>% mutate( # 提取Name:匹配从开头到地址/邮箱/院校前的内容 Name = str_extract(full_text, "^.*?(?=(?:Email:|\\d{3} Fake St|p:|University|College))"), # 提取Address:匹配包含街道、电话的内容 Address = str_extract(full_text, "(?:\\d{3} Fake St|AB|p:).*?(?=(?:University|College|\\d{4}))"), # 提取毕业院校及年份 Place_Year_Graduation = str_extract(full_text, "(?:University|College|School).*?(?=\\d{4})"), # 提取注册年份 Reg_Year = as.integer(str_extract(full_text, "\\d{4}$")) ) %>% # 补全缺失字段的内容 mutate( Name = str_squish(ifelse(is.na(Name), str_extract(full_text, "^.*?(?=\\d{3} Fake St)"), Name)), Address = str_squish(ifelse(is.na(Address), str_extract(full_text, "(p:|AB).*"), Address)), Place_Year_Graduation = str_squish(ifelse(is.na(Place_Year_Graduation), str_extract(full_text, "(University|College).*"), Place_Year_Graduation)) ) %>% mutate(id = row_number()) %>% select(id, Name, Address, Place_Year_Graduation, Reg_Year) # 查看结果 print(final_table)
Python实现方案
依赖安装
pip install pytesseract pdf2image pandas
核心思路
和R方案逻辑一致:预处理文本→分组记录→正则提取字段。
import pytesseract from pdf2image import convert_from_path import pandas as pd import re # 1. PDF转图片并OCR(如果已有raw_text可跳过此步) images = convert_from_path('p1.pdf', dpi=600) raw_text = "" for img in images: raw_text += pytesseract.image_to_string(img) # 2. 预处理文本行 lines = [line.strip() for line in raw_text.split('\n') if line.strip()] # 3. 分组单条记录(示例:以大写姓名开头为记录起始) record_groups = [] current_group = [] for line in lines: if re.match(r'^[A-Z][a-z]+ [A-Z][a-z]+', line): if current_group: record_groups.append(' '.join(current_group)) current_group = [line] else: current_group.append(line) if current_group: record_groups.append(' '.join(current_group)) # 4. 提取目标字段 data = [] for idx, text in enumerate(record_groups, 1): # 提取Name name_match = re.search(r'^.*?(?=(Email:|\d{3} Fake St|p:|University|College))', text) name = name_match.group().strip() if name_match else re.search(r'^.*?(?=\d{3} Fake St)', text).group().strip() # 提取Address addr_match = re.search(r'(?:\d{3} Fake St|AB|p:).*?(?=(University|College|School|\d{4}))', text) address = addr_match.group().strip() if addr_match else re.search(r'(p:|AB).*', text).group().strip() # 提取毕业信息 grad_match = re.search(r'(University|College|School).*?(?=\d{4})', text) grad_info = grad_match.group().strip() if grad_match else re.search(r'(University|College).*', text).group().strip() # 提取注册年份 reg_year = int(re.search(r'\d{4}$', text).group()) if re.search(r'\d{4}$', text) else None data.append({ 'id': idx, 'Name': name, 'Address': address, 'Place_Year_Graduation': grad_info, 'Reg_Year': reg_year }) # 生成结构化表格 final_df = pd.DataFrame(data) print(final_df)
注意事项
- 正则表达式需根据实际PDF的排版特征调整,比如姓名格式、地址前缀、年份位置等,上述代码是基于示例格式编写的,需适配你的PDF内容
- 如果OCR识别错误较多,优先优化图片预处理步骤:调整dpi、图片阈值、裁剪区域等
- 复杂排版的扫描PDF,可尝试结合
layoutparser做版面分析,先识别表格区域再提取内容
内容的提问来源于stack exchange,提问作者stats_noob
相关产品推荐
相关产品推荐

