PDF 合并加页码代码一共使用 PyPDF2、reportlab 两个第三方库,搭配 Python 内置io内存流、os文件库。
1 库总览
表格
|-------------------------|-------------------------------------|
| 库 | 作用 |
| PyPDF2 | PDF 读取、合并、页面叠加、写出 PDF 文件,处理已有 PDF |
| reportlab.pdfgen.canvas | 生成全新 PDF 图层(页码图层),绘制文字、字体,用来生成页码水印页 |
| io.BytesIO | 内存字节流,全部 PDF 操作在内存完成,不生成临时磁盘 PDF 文件 |
| os | 文件夹遍历、拼接文件路径、判断文件是否存在 |
| re | 文件名数字排序正则,和 PDF 编辑无关 |
2 PyPDF2 核心类 & 函数解析
① PdfMerger()
merger = PdfMerger()
merger.append(file)
merger.write(temp_memory)
merger.close()
PdfMerger:专门用于 PDF 合并的工具类
.append(file):追加一个 PDF 文件,按调用顺序把 PDF 全部拼在一起;支持磁盘路径 / 内存 BytesIO 对象
.write(temp_memory):将合并完成的完整 PDF 写入字节流(这里没有写磁盘,写到内存)
.close():关闭 merger,释放文件资源
② PdfReader()
merged_pdf = PdfReader(temp_memory)
num_layer = PdfReader(buf)
src_page = merged_pdf.pagesindex
PdfReader:读取 PDF 对象,可以读磁盘文件,也可以直接读 BytesIO 内存流
.pagesindex:PDF 所有页面列表,下标从 0 开始,获取第 index 页页面对象
src_page.mediabox:页面画布边界对象,.width、.height获取 PDF 页面宽高(新版本返回 Decimal,代码中强制float()转换)
③ PdfWriter()
pdf_writer = PdfWriter()
pdf_writer.add_page(src_page)
pdf_writer.write(f)
PdfWriter:用来组装、输出最终 PDF 的写入器
.add_page(src_page):把处理完成的一页加入输出队列
.write(f):把全部页面写入打开的磁盘二进制文件句柄,生成最终 pdf
④ 关键函数 src_page.merge_page(num_layer.pages0)
实现页码叠加的核心函数
src_page.merge_page(num_layer.pages0)
作用:把一个 PDF 页面图层叠加到当前页面上
逻辑:reportlab 生成只有页码的空白 PDF 页num_layer.pages0,叠加到原始 PDF 页面src_page底部,实现页码水印效果。
⚠注意:是图层叠加,不是修改原 PDF 文本;页码是画上去的图形文字。
3 reportlab 库(生成页码图层)
from reportlab.pdfgen import canvas
c = canvas.Canvas(buf, pagesize=(w, h))
c.setFont("Helvetica", font_size)
c.drawCentredString(w / 2, page_bottom_margin, page_text)
c.save()
canvas.Canvas(buf, pagesize=(w, h))
创建一个 PDF 画布对象,输出目标是内存buf(BytesIO),设置页面尺寸和原 PDF 页面宽高保持一致
.font("Helvetica", font_size)
设置画布绘制文字的字体、字号;你后续改成 Times New Roman 就修改这里字体名
.drawCentredString(x, y, text)
绘制居中文字
x:水平坐标;w/2就是页面水平中点
y:垂直坐标,PDF 坐标原点在页面左下角!! y=0 就是最底边;page_bottom_margin=20就是距离底边 20pt 的位置画页码
.save()
将画布内容输出到 BytesIO,完成页码图层 PDF 生成。
PDF 坐标关键点:
原点 (0,0):页面左下角
单位:pt(磅),1 英寸 = 72pt
4 io.BytesIO 内存流(非常关键)
temp_memory = io.BytesIO()
buf = io.BytesIO()
temp_memory.seek(0)
buf.seek(0)
BytesIO:内存里面的二进制文件,完全不读写磁盘
流程:
PdfMerger 合并 PDF → 写入temp_memory内存
seek(0):指针移动回到字节流开头,后面PdfReader才能从头读取内容
reportlab 生成页码图层写入buf内存流,再 seek (0) 供 PdfReader 读取
好处:全程无临时中间 PDF 文件,速度快;缺点大文件会占用内存。
5 整体完整数据流
磁盘多个PDF文件 → PdfMerger合并 → BytesIO内存合并PDF
→ PdfReader读取合并好的PDF,循环每一页
↓
reportlab Canvas(内存buf)生成只有页码的透明图层
↓
merge_page() 将页码图层叠加原始PDF页面
↓
PdfWriter收集全部叠加完页码的页面
→ write()写入磁盘输出文件【合并带页码.pdf】
补充:代码中两个模式说明
custom_file_list != None:手动指定文件名列表,严格按照列表顺序合并,只合并列表中写的 PDF;会校验文件是否存在。
custom_file_list = None:扫描文件夹全部.pdf文件,使用 sort_key 自定义排序。
常见坑总结
PyPDF2 新版本mediabox.width返回 Decimal 类型,和 reportlab float 运算报错,所以代码用float()强制转换。
reportlab 画布坐标原点左下角,y 值越小越靠近底边,太小会被 PDF 阅读器裁切。
merge_page是图层叠加,页码属于图形对象,不是 PDF 文本对象,不能复制选中页码文字。
reportlab 默认字体 Helvetica 不支持中文,需要注册 TTF 字体(Times New Roman / 中文字体)。