用 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"从几小时人工压缩到几秒,正好对应工程报价里最费眼的核对环节。把它和在线报价系统串起来,就是一份现成的清单解析工具雏形。

相关推荐
Felicia-侧听1 小时前
PDF转OFD的三种方法:在线、离线与开源方案对比(附操作教程与避坑指南)
pdf·开源
优化控制仿真模型3 小时前
2026日语能力考试N1、N2、N3、N4、N5历年真题及答案合集PDF(2010-2026年)
经验分享·pdf
jack186895 小时前
PDF怎么批量转成JPG?两种方法实测对比
pdf
MetaEnchanter6 小时前
PDF 转 Word:只改其中几页,不用整份重新整理
pdf·word
优化控制仿真模型6 小时前
【2026年】全国大学生数学竞赛历年真题及答案解析PDF(2009-2026年)
经验分享·pdf
小豆庄16 小时前
视频人脸打码追踪:转场丢失、漏检和ID撞号的处理
目标检测·计算机视觉·开源·音视频·视频·python3.11
麦序子1 天前
用 Python + python-docx 批量生成带表格样式的装修报价单 Word 文档(含合计公式)
开发语言·python·自然语言处理·word·python3.11
我命由我123451 天前
Python 开发 - Python 项目部署(Windows 与 Linux)
java·linux·windows·后端·python·java-ee·python3.11
FOAF-lambda1 天前
Python实战:基于OpenCV与单调栈,精准定位PDF中最大的空白区域
python·opencv·pdf