You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 20:55:10