PyPDF2+ReportLab叠加不可选半透明文字水印最稳妥:ReportLab生成旋转透明水印PDF页,PyPDF2用add_page()合并而非merge_page();中文需注册NotoSansCJKsc字体;注意临时文件清理与异常捕获。

用 PyPDF2 + ReportLab 在每页叠加文字水印
直接在原 PDF 每页上加不可选、半透明的文字水印,是版权保护最常用也最稳妥的方式。PyPDF2 负责读写 PDF 结构,ReportLab 生成带旋转、透明度、字体控制的水印 PDF 页面,再逐页合并——不依赖图像识别或 OCR,也不修改原文本内容,兼容性好、生成速度快。
关键点在于:水印不能是普通文本图层(否则易被删除),必须作为独立的透明页面叠在原页上方;且需用 setFillAlpha() 控制透明度,用 rotate() 倾斜避免遮挡正文。
常见错误是直接用 canvas.drawString() 写死坐标,导致水印只出现在左下角一小块;正确做法是居中定位后旋转±30°,再平铺覆盖整页背景区域:
from reportlab.pdfgen import canvas
from reportlab.lib.colors import Color
from PyPDF2 import PdfReader, PdfWriter
<p>def create_watermark(text, width=595, height=842):
c = canvas.Canvas("watermark.pdf", pagesize=(width, height))
c.setFillColor(Color(0, 0, 0, 0.08)) # RGBA: alpha=0.08 很淡但可辨
c.setFont("Helvetica-Bold", 60)
c.saveState()
c.translate(width/2, height/2)
c.rotate(30)
c.drawCentredString(0, 0, text)
c.restoreState()
c.save()</p>
合并水印时必须用 PdfWriter.add_page() 而非 merge_page()
很多人误用 page.merge_page(watermark_page),结果水印只出现在第一页,或字体渲染错乱——这是因为 merge_page() 是底层内容流合并,对含透明度/旋转的 ReportLab 页面支持不稳定。
真正可靠的做法是把水印做成单页 PDF,用 PdfWriter.add_page() 将「原页 + 水印页」合成新页。ReportLab 生成的水印 PDF 必须先用 PdfReader 读取,再取其第 0 页:
w_reader = PdfReader("watermark.pdf")- 循环遍历原 PDF 每页:
for page in reader.pages: - 调用
page.merge_page(w_reader.pages[0])→ 错误路径 - 正确路径:
new_page = page后writer.add_page(new_page),并在add_page()前手动叠加水印内容(见下方)
更稳的实操写法是:先用 PdfWriter() 创建空白页,merge_page() 原页,再 merge_page() 水印页(注意顺序:先底后盖):
writer = PdfWriter()
for page in reader.pages:
p = page
p.merge_page(w_reader.pages[0]) # 这里才安全:w_reader.pages[0] 是已解析的 PageObject
writer.add_page(p)
中文水印必须嵌入字体,否则导出后显示为方框
ReportLab 默认不支持中文字体。drawString() 直接传入中文会静默失败,PDF 里只剩空格或方框。必须显式注册 TTF 文件,并用 registerFont() + setFont() 切换。
不要用系统字体路径(如 C:/Windows/Fonts/msyh.ttc),跨平台会挂;推荐把 simhei.ttf 或 NotoSansCJKsc-Regular.otf 和脚本放同一目录:
- 下载开源字体 NotoSansCJKsc(免版权,支持简体中文)
- 用
from reportlab.pdfbase import pdfmetrics和pdfmetrics.registerFont(...) - 注册后才能在
c.setFont("NotoSansCJKsc", 48)中使用该字体名 - 字体名是注册时指定的字符串,不是文件名,别写成
"simhei.ttf"
漏掉注册步骤,运行无报错但输出 PDF 里全是□□□——这是最常被忽略的坑。
批量处理时注意文件锁和内存占用
一次处理几百个 PDF,容易遇到 PermissionError: [Errno 13] Permission denied,尤其是 Windows 下文件被其他程序(如 Adobe Reader、预览窗格)占用;或者 MemoryError 来自 PyPDF2 加载大文件后未释放。
规避方式很实际:
- 用
try/except包裹单文件处理,失败则跳过并记录文件名到error.log - 每次处理完一个文件,显式删掉临时水印 PDF:
os.remove("watermark.pdf") - 不用
PdfReader(filename)直接传路径,改用上下文管理器:with open(filename, "rb") as f: reader = PdfReader(f),确保句柄及时关闭 - 对超大 PDF(>50MB),考虑分批处理(如每 10 页一个子任务),避免单次内存峰值过高
水印逻辑本身轻量,瓶颈永远在 I/O 和 PDF 解析层——别试图用多进程加速 ReportLab 绘图,反而因字体缓存冲突出错。老老实实用单进程+合理异常捕获,更稳。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











