用 pdfplumber + 正则 解析工程量清单 PDF:提取项目编码、综合单价并导出 Excel

装修报价、工程结算最头疼的就是手里一份几十上百页的工程量清单 PDF,想把它变成可筛选、可汇总的结构化数据,却只能靠人工一行行抄。本文用 pdfplumber 把国标清单计价表(带表头:序号/项目编码/项目名称/项目特征/单位/工程量/综合单价/合价...)解析出来,再用正则把"项目编码""综合单价"等关键字段抽干净,最后用 openpyxl 导出成一张能直接发给甲方对账的 Excel。

一、为什么不直接用 pdfplumber.extract_table

pdfplumber 的 page.extract_table() 对规整的表格很好用,但工程清单常有跨页续表、合并单元格、表头重复出现,直接 extract_table 会丢行或把表头当数据。更稳的做法是:先按行拿文本,再用正则识别"这是不是一行清单项"。

python 复制代码
import pdfplumber, re, os

PDF = "工程量清单.pdf"

def is_item(line):
    # 清单项特征:开头是数字序号,且行内含 12 位项目编码(国标)
    return bool(re.match(r'^\s*\d+[\.\)]?\s', line)) and bool(re.search(r'\d{12}', line))

rows = []
with pdfplumber.open(PDF) as pdf:
    for pi, page in enumerate(pdf.pages):
        text = page.extract_text() or ""
        for line in text.splitlines():
            if is_item(line):
                rows.append((pi + 1, line))
print(f"共识别到 {len(rows)} 行疑似清单项")

二、用正则在单行里拆字段

国标清单计价表的每行,项目编码是 12 位数字,综合单价/合价是金额(带小数),单位是短中文(m²、m、项)。我们用几个针对性正则分别抓取:

python 复制代码
CODE = re.compile(r'(\d{12})')                      # 12 位项目编码
MONEY = re.compile(r'(\d{1,3}(?:,\d{3})*(?:\.\d{1,2})?)')  # 千分位/小数金额
UNIT = re.compile(r'([m³㎡m²cm个套项遍遍次处]|平方米|米|项)')

def parse(line):
    code = CODE.search(line)
    units = UNIT.findall(line)
    moneys = [float(m.replace(',', '')) for m in MONEY.findall(line)]
    # 金额通常:单价、合价(合价=单价×量,取最大的两个)
    moneys.sort()
    price = moneys[-2] if len(moneys) >= 2 else (moneys[-1] if moneys else 0.0)
    total = moneys[-1] if len(moneys) >= 2 else 0.0
    name = line
    if code:
        name = line.replace(code.group(1), '').strip()
    return {
        'code': code.group(1) if code else '',
        'name': name[:60],
        'unit': units[-1] if units else '',
        'price': price,
        'total': total,
    }

注意:MONEY 会同时抓到工程量(如 12.5)、单价、合价。工程清单里"合价"一般是该行最大金额,"单价"次之,所以取排序后最后两个最稳。若你的表列序不同,按列位置取更准------可改用 extract_table 拿到列后再取值。

三、聚合成结构化列表

python 复制代码
records = []
for pi, line in rows:
    r = parse(line)
    r['page'] = pi
    records.append(r)

# 按项目编码去重,保留综合单价/合价最高的一条(不同页续表常重复)
best = {}
for r in records:
    k = r['code'] or r['name']
    if k not in best or r['total'] > best[k]['total']:
        best[k] = r
final = list(best.values())
print(f"去重后 {len(final)} 条清单")

四、导出 Excel 对账

中文列名 openpyxl 原生支持,直接写即可:

python 复制代码
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment

wb = Workbook(); ws = wb.active
ws.append(["页码", "项目编码", "项目名称", "单位", "综合单价(元)", "合价(元)"])
for r in final:
    ws.append([r['page'], r['code'], r['name'], r['unit'], r['price'], r['total']])
ws.append(["", "", "合计", "", "", round(sum(r['total'] for r in final), 2)])

for c in ws[1]:
    c.font = Font(bold=True)
for row in ws.iter_rows():
    for c in row:
        c.alignment = Alignment(horizontal='left', vertical='center')
ws.column_dimensions['C'].width = 50
wb.save("工程量清单_解析.xlsx")
print("已导出 工程量清单_解析.xlsx")

五、避坑提示

  • 跨页续表:清单常一页写不下,下页不带表头继续,务必按页遍历再按行识别,别只取首页。
  • 金额列歧义 :若表里有"人工费""机械费"分列,金额正则会多抓,建议改用 extract_table 拿列索引精确取值,正则只做兜底。
  • 编码缺失行:有些清单项编码为空(如措施项目),去重键要回退到"名称+单位",否则会误并。
  • 千分位 :金额里 1,234.56 的逗号必须先 replace(',','') 再 float,否则报错。
  • 字体 :pdfplumber 解析中文依赖 PDF 内嵌字体,极少数扫描件(图片型 PDF)需要先 OCR,纯文本型 PDF 才能直接抽。

这套脚本把"几十页 PDF → 一张可筛选 Excel"从几小时人工压缩到几秒,正好对应工程报价里最费眼的核对环节。把它和在线报价系统串起来,就是一份现成的清单解析工具雏形。

相关推荐
小熊熊知识库1 天前
双层PDF转换软件
pdf·双层pdf转换
AI导出鸭PC端6 天前
Perplexity批量导出没有PDF模式?AI导出鸭插件补上最后一块拼图
人工智能·pdf·c#·ai导出鸭
Ravenq~6 天前
无头转换:把 .docx 变成 PDF,不打开编辑器
pdf·编辑器
联犀6 天前
纯 Go 的 CAD 开源库 go-cad:直接解析 DWG/DXF,现在也能导出 PDF
开发语言·golang·pdf
FinelyYang10 天前
uniapp使用kkfileview在线预览ppt,excel,word,pdf类型文件
pdf·uni-app·powerpoint
WORLDPO922610 天前
QTE-020-01-L/F/C-D-A-K-TR_实物_数据手册PDF_国产替代
pdf
asdzx6711 天前
C# 操作 PDF 元数据:从属性写入到字段提取的完整流程
前端·pdf·c#
WORLDPO922611 天前
SFSDT-10/15/20/25/35-28-G-19.69-SR_实物图_数据手册PDF_国产替代
pdf·pcb工艺·pcb
梦想出海-Phoebe11 天前
Gemini新增文件编辑功能:PDF、Word、Excel处理体验分享
pdf·word·excel
开开心心就好11 天前
网页图片批量下载工具,输入链接就保存
服务器·算法·leetcode·支持向量机·pdf·ocr·1024程序员节