用 WorkBuddy 完成一次真实的工程资料归档:从 24 份文件名全是流水号的扫描件,到一册 153 页、带封面和目录的质量资料。全程记录方法、代码和踩过的坑。
标签:#WorkBuddy#
制造企业交付成套设备时,随产品提交的质量证明文件是多方来源的汇总物:外购件合格证、外协件质量证明、内部过程检验记录、装配记录、出厂检查报告。
纸质件扫描之后,得到的是一堆文件名毫无规律、内容无法检索的图像型 PDF:
doc05419720261012154222.pdf doc05419820261012154251.pdf doc05419920261012154323.pdf ...
人工按目录逐份核对、排序、合并、编页码,既费时又极易错漏。本次任务的对象是一个成套液压系统项目:
素材 | 说明 |
|---|---|
扫描件 | 24 份图像型 PDF,共 50 页,不含文本层 |
明细表 | 1 份 Excel,规定了应提交的 15 类材质证明及其份数 |
任务过程中又追加了 5 份过程记录扫描件、1 份出厂检查报告、2 个工作表的目录页,最终形成 153 页资料册。
扫描件是纯图像、无文本层,常规文本提取直接返回空字符串。思路是先转成图,再交给视觉识别:
import pypdfium2 as pdfium
from PIL import Image, ImageDraw, ImageFont
doc = pdfium.PdfDocument("scan.pdf")
img = doc[0].render(scale=2.2).to_pil().convert("RGB")
W = 640
img = img.resize((W, int(img.height * W / img.width)), Image.LANCZOS)
img.save("page_01.png")关键取舍:默认缩略图只够看清标题。遇到"一张纸上贴十几张小合格证"的密集页,要单独按 3 倍比例重渲染至 1500 px 宽,才能辨认小字与厂家标识。
为什么不用 OCR:本地 OCR 需要额外装模型,中文小字与印章的辨识效果反而更差;渲染拼版 + 视觉识别零依赖、更准。
明细表给了 15 类条目,扫描件却有 24 份 —— 数量对不上,说明存在"一类条目对应多份扫描件"的情况。
逐页识别后得到三种典型形态:
形态 | 表现 | 处理 |
|---|---|---|
一对一 | 一份扫描件就是一个条目 | 直接对应 |
一对多 | 同类合格证分几次扫描 | 按序号合并为一个文件 |
多对一 | 一页纸张贴多张同类小证 | 归入同一条目,按张数计数 |
重命名之前,先做一次独立的总量校验:把明细表里每一类声明的份数,与逐页清点出来的实际张数逐条比对。
结果全部吻合,其中两类很能说明问题:
为什么这一步值得单独做:如果直接按顺序改名而不校验数量,一旦扫描时漏扫一张或重复扫一张,错误会被"整齐的重命名"掩盖过去,后面几乎不可能发现。先勾稽,等于给整个归档上了第一道保险。
命名规则:{两位序号} {明细名称}({厂家}).pdf,同一条目下的多份扫描件合并为一个 PDF。
from pypdf import PdfReader, PdfWriter
wr = PdfWriter()
for src in ["a.pdf", "b.pdf", "c.pdf"]:
for page in PdfReader(src).pages:
wr.add_page(page)
with open("01 合格证(某公司).pdf", "wb") as f:
wr.write(f)合并完成后:
明细表里的清单是"活的",而资料册里的目录还停留在纸质表格阶段。于是把明细表的两个工作表回填成 PDF 并入册:
最终成册结构:
P1 封面(由工作表 A 导出) P2 质量数据目录(由工作表 B 导出,共 20 条) P3 ~ P152 正文:序号 01~19 的全部证明与记录 P153 序号 20:出厂检查报告
把目录页放进同一册,而不是让目录和正文分离,是这次一个明显的改进:拿到册子的人翻第一页就知道里面有什么、共几页。
要往明细表里追加行。常规做法是「读 → 写回」,但该工作簿里嵌有图片(每个工作表一张企业 logo)。
用常规库整体重存,图片与打印设置会丢失 —— 文件能打开、数据也对,但图片没了,而且往往过几天才发现。
正确做法:外科式改包。 把 xlsx 当作 zip,只重写需要变的部件:
# 只重写 worksheet XML 与 sharedStrings.xml
# 其余部件字节级原样搬运,连目录项与压缩元数据一起复刻
zout = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
for info in zin.infolist():
zi = zipfile.ZipInfo(info.filename, date_time=info.date_time)
zi.compress_type = info.compress_type
zi.external_attr = info.external_attr
zout.writestr(zi, b"" if info.is_dir() else blobs[info.filename])第一次追加行时按之前记录的样式编号写入,结果新行字号变大、没有边框、文字不换行、跑到表格外面去了。
根因:期间文件被用 Excel 手工打开并保存过。Excel 重存会重排样式表 —— 样式总数从 12 变成 16,所有样式编号整体位移。
# ❌ 错误:硬编码样式编号
'<c r="B25" s="10" t="s"><v>54</v></c>'
# ✅ 正确:从相邻行现场提取
smap = dict(re.findall(r'<c r="([A-D])24" s="(\d+)"', row24_xml))
'<c r="B25" s="{}" t="s"><v>54</v></c>'.format(smap['B'])这个坑还有个副作用:它把我引向了错误方向 —— 我以为是"分页裁切/缩放不当",在页面设置上反复调整,白费了好几轮。看到"新加的行样式异常",第一反应应该是查样式索引,而不是查排版。
把工作表导出成 PDF 时,想用"缩放以适应一页"保证表格落在一页内,但设置后没有生效 —— 读回缩放比例仍是 100%,工作表仍被判定为跨页。
解决办法:改用固定缩放比例。先读出工作表实际内容高度(约 974 pt),A4 纸高 841.9 pt,扣除边距可用约 800 pt,据此把缩放定在 72%,一次成功。
ws.PageSetup.PrintArea = 'A1:D25'
ws.PageSetup.Zoom = 72 # 固定缩放,别用 FitToPage
ws.ExportAsFixedFormat(0, out) # 0 = xlTypePDF验证方法:导出后不要只看页数,还要用 PDF 文本定位接口搜索关键行文字,取出坐标框 —— 确认它落在页面矩形内,才算真的没被裁掉。
期望的文件名里含一个半角星号。实测:半角 * 在 Windows 文件名中非法,直接抛权限错误;全角 *、乘号 ×、连字符 - 都可以。
这条看似琐碎,但一旦没测就写进脚本,会在最后一步整体失败。
追加的扫描件里,有一份文件名与其余高度相似,但内容中的"用户名称"字段指向的是另一个客户。
判定依据:不看文件名,看单据内的"用户名称/项目名称"字段。文件名可以随手起,单据内的字段是业务事实。
项目 | 结果 |
|---|---|
扫描件归类 | 24 份 → 按明细合并为 15 个规范命名文件 |
数量勾稽 | 全部条目声明份数与实际清点逐条吻合 |
追加素材 | 5 份 → 4 份并入(1 份判定为他项目,剔除) |
明细表 | 15 条 → 20 条;图片与样式完好 |
资料册 | 153 页,含封面、目录、正文、出厂检查报告 |
页码核验 | 抽样 8 个关键页渲染比对,各段页界与序号完全对应 |
原始文件 | 全部保留(移入备份目录),零删除 |
归档前
处理中
交付前
这类任务的难点从来不是"写代码",而是判断在哪一步做校验、在哪里会被数据的"隐性变化"坑到。
5 个坑里,坑 2(样式索引漂移)和坑 5(混入他项目文件)最隐蔽 —— 前者让你以为是排版问题,后者让你以为是命名问题,而真正的原因是数据本身。
如果你也在做工程资料或审核材料的归档,希望这份实录能帮你少走两轮弯路。
#WorkBuddy#
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。