Python批量给PDF文件添加图片水印(附源码)

27 阅读9分钟

在日常办公中,经常需要给合同、报表等PDF文件的最后一页加盖图片水印(如公章、校验章)。手动处理单文件效率低下,批量处理又面临格式兼容、水印定位变形、异常报错等问题。本文将基于Python实现批量PDF水印添加功能,支持水印位置精准控制、大小灵活调整,并针对常见报错提供解决方案,适用于各类办公场景。

结果.png

yin.png

一、核心需求与技术选型

1. 核心功能需求

  • 批量读取指定目录下所有PDF文件,避免手动逐个处理;
  • 仅在PDF最后一页添加图片水印(shuiyin.png),不影响其他页面;
  • 支持水印位置精准定位(自定义水平、垂直偏移量);
  • 支持水印大小调整,保持图片宽高比不变形;
  • 自动生成输出目录,规范命名输出文件,不修改原始PDF;
  • 兼容不规范PDF文件,避免解析报错中断批量任务。

2. 技术栈选型

结合功能需求与兼容性考虑,最终选型如下库组合(替换原有PyPDF2以解决解析异常):

  • PyMuPDF(fitz) :替代PyPDF2,对畸形PDF兼容性更强,解析速度快,可有效避免非法字符报错;
  • reportlab:用于绘制图片水印并生成临时PDF,支持透明通道(PNG透明水印生效);
  • Pillow:获取水印图片原始宽高比,保证水印拉伸无变形;
  • os/io:处理文件目录、路径及内存缓冲区,实现批量文件遍历与临时数据存储。

二、环境准备

通过pip安装所需依赖库,执行以下命令:

pip install PyMuPDF Pillow reportlab

依赖说明:

  • PyMuPDF(fitz):版本建议≥1.23.0,保证PDF解析兼容性;
  • Pillow:版本建议≥9.0.0,支持多种图片格式宽高比获取;
  • reportlab:版本建议≥3.6.0,确保图片水印绘制与PDF合并正常。

三、完整实现代码

以下代码整合了批量处理、水印定位、大小调整、异常容错等功能,可直接复制使用,只需根据实际需求修改配置参数。

import fitz  # PyMuPDF核心库
import os
from reportlab.pdfgen import canvas
import io
from PIL import Image

def add_image_watermark_to_last_page(pdf_path, watermark_image_path, output_pdf_path, shuiyin_x, shuiyin_y, shuiyin_width):
    """
    单个PDF处理:给最后一页添加图片水印(支持定位、大小调整,无变形)
    :param pdf_path: 原始PDF文件路径
    :param watermark_image_path: 水印图片路径(shuiyin.png)
    :param output_pdf_path: 带水印PDF输出路径
    :param shuiyin_x: 水印左上角水平偏移量(向右为正,原点在页面左下角)
    :param shuiyin_y: 水印左上角垂直偏移量(向上为正,原点在页面左下角)
    :param shuiyin_width: 水印目标宽度(高度按宽高比自动计算,避免变形)
    """
    # 1. 文件合法性校验
    if not os.path.exists(pdf_path):
        raise FileNotFoundError(f"原始PDF文件不存在:{pdf_path}")
    if not os.path.exists(watermark_image_path):
        raise FileNotFoundError(f"水印图片不存在:{watermark_image_path}")
    
    # 2. 打开原始PDF并获取页面信息
    doc = fitz.open(pdf_path)
    total_pages = len(doc)
    if total_pages == 0:
        doc.close()
        raise ValueError(f"原始PDF文件为空,无页面可添加水印:{pdf_path}")
    
    # 3. 获取最后一页尺寸(用于适配水印大小与位置)
    last_page = doc[total_pages - 1]
    page_rect = last_page.rect  # 页面矩形区域(x0, y0, x1, y1)
    page_width = page_rect.width
    page_height = page_rect.height
    
    # 4. 计算水印大小(保持原始宽高比,避免变形)
    with Image.open(watermark_image_path) as img:
        img_original_width, img_original_height = img.size
        img_aspect_ratio = img_original_height / img_original_width  # 高/宽比例
    watermark_height = shuiyin_width * img_aspect_ratio  # 自动适配高度
    
    # 5. 生成临时水印PDF(支持透明通道)
    packet = io.BytesIO()  # 内存缓冲区,避免生成临时文件
    c = canvas.Canvas(packet, pagesize=(page_width, page_height))
    # 绘制水印(定位、大小、透明通道均生效)
    c.drawImage(
        watermark_image_path,
        shuiyin_x,
        shuiyin_y,
        width=shuiyin_width,
        height=watermark_height,
        mask='auto'  # 启用PNG透明通道
    )
    c.save()
    packet.seek(0)  # 重置缓冲区指针至起始位置
    
    # 6. 合并水印到PDF最后一页
    watermark_doc = fitz.open("pdf", packet.read())  # 读取临时水印PDF
    last_page.show_pdf_page(page_rect, watermark_doc, 0)  # 合并水印层
    
    # 7. 保存最终文件并关闭资源
    doc.save(output_pdf_path)
    doc.close()
    watermark_doc.close()
    
    print(f"✅ 处理完成:{output_pdf_path}(水印大小:{shuiyin_width:.1f}×{watermark_height:.1f},定位:x={shuiyin_x}, y={shuiyin_y})")

def batch_process_pdfs(input_dir, watermark_image_path, output_dir=None, shuiyin_x=100, shuiyin_y=100, shuiyin_width=200):
    """
    批量处理PDF:遍历目录下所有PDF,统一添加水印
    :param input_dir: 待处理PDF所在目录(默认当前目录)
    :param output_dir: 带水印PDF输出目录(默认创建with_watermark子目录)
    :param shuiyin_x/y/width: 全局水印配置(位置、大小)
    """
    # 1. 初始化输出目录(不存在则自动创建)
    if output_dir is None:
        output_dir = os.path.join(input_dir, "with_watermark")
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
        print(f"📁 已创建输出目录:{output_dir}")
    
    # 2. 筛选目录下所有PDF文件(排除输出目录内文件,避免重复处理)
    pdf_files = []
    for filename in os.listdir(input_dir):
        if filename.lower().endswith(".pdf"):
            pdf_file_path = os.path.join(input_dir, filename)
            if not pdf_file_path.startswith(os.path.abspath(output_dir)):
                pdf_files.append((filename, pdf_file_path))
    
    # 3. 无PDF文件处理逻辑
    if not pdf_files:
        print("⚠️  未在指定目录找到可处理的PDF文件")
        return
    
    # 4. 批量处理每个PDF(单个文件报错不中断整体任务)
    print(f"🔍 共找到 {len(pdf_files)} 个待处理PDF文件,开始批量添加水印...")
    print(f"🌐 全局水印配置:宽度={shuiyin_width},定位:x={shuiyin_x}, y={shuiyin_y}")
    for filename, pdf_path in pdf_files:
        # 输出文件名规范:原始文件名_with_watermark.pdf
        output_filename = f"{os.path.splitext(filename)[0]}_with_watermark.pdf"
        output_pdf_path = os.path.join(output_dir, output_filename)
        
        try:
            add_image_watermark_to_last_page(
                pdf_path=pdf_path,
                watermark_image_path=watermark_image_path,
                output_pdf_path=output_pdf_path,
                shuiyin_x=shuiyin_x,
                shuiyin_y=shuiyin_y,
                shuiyin_width=shuiyin_width
            )
        except Exception as e:
            print(f"❌ 处理失败 {filename}{str(e)}")
    
    print(f"\n🎉 批量处理结束!所有带水印PDF已保存至:{output_dir}")

if __name__ == "__main__":
    # -------------------------- 核心配置参数(按需修改)--------------------------
    WATERMARK_IMAGE = "shuiyin.png"  # 水印图片路径(与脚本同目录可直接填文件名)
    INPUT_DIRECTORY = "."            # 待处理PDF目录("."表示当前目录,可填绝对路径)
    OUTPUT_DIRECTORY = None          # 输出目录(默认创建with_watermark子目录)
    SHUIYIN_X = 100                  # 水印水平偏移量(向右为正)
    SHUIYIN_Y = 100                  # 水印垂直偏移量(向上为正)
    SHUIYIN_WIDTH = 200              # 水印宽度(高度自动适配,控制水印大小)
    # ----------------------------------------------------------------------------
    
    # 执行批量处理
    try:
        batch_process_pdfs(
            input_dir=INPUT_DIRECTORY,
            watermark_image_path=WATERMARK_IMAGE,
            output_dir=OUTPUT_DIRECTORY,
            shuiyin_x=SHUIYIN_X,
            shuiyin_y=SHUIYIN_Y,
            shuiyin_width=SHUIYIN_WIDTH
        )
    except Exception as e:
        print(f"🚨 批量处理程序异常终止:{str(e)}")

四、核心功能配置指南

脚本核心配置集中在if __name__ == "__main__":模块,可根据实际需求快速调整,无需修改核心逻辑。

1. 水印位置调整(shuiyin_x、shuiyin_y)

PDF页面坐标体系以左下角为原点(0,0) ,水平方向向右为正,垂直方向向上为正,单位为“点(pt)”(1英寸=72点,A4页面宽度约595pt、高度约842pt)。

  • 左移水印:减小shuiyin_x(如shuiyin_x=50,靠近页面左侧);
  • 右移水印:增大shuiyin_x(如shuiyin_x=300,靠近页面右侧);
  • 下移水印:减小shuiyin_y(如shuiyin_y=50,靠近页面底部);
  • 上移水印:增大shuiyin_y(如shuiyin_y=500,靠近页面顶部);
  • 示例:若需将水印放在右下角,可结合页面宽度计算(需在add_image_watermark_to_last_page函数内调整): # 右下角定位(右侧、底部各留100pt边距) `` shuiyin_x = page_width - shuiyin_width - 100 ``shuiyin_y = 100

2. 水印大小调整(shuiyin_width)

脚本通过控制水印宽度(shuiyin_width)实现大小调整,高度会根据图片原始宽高比自动计算,确保水印无拉伸变形,支持两种调整方案:

方案1:固定尺寸(适合统一尺寸PDF)

直接设置shuiyin_width为固定值,水印大小不随PDF页面变化,适合处理一批尺寸相同的PDF。

  • 缩小水印:shuiyin_width=150(宽度150pt,高度自动适配);
  • 放大水印:shuiyin_width=300(宽度300pt,高度自动适配)。

方案2:相对尺寸(适合多样尺寸PDF)

将水印宽度设置为PDF页面宽度的百分比,自适应不同尺寸PDF,保证水印显示比例一致。需修改add_image_watermark_to_last_page函数内的水印宽度计算逻辑:

# 替换原有watermark_height计算前的代码
watermark_ratio = 0.3  # 水印宽度为页面宽度的30%(可调整0.2~0.5)
shuiyin_width = page_width * watermark_ratio  # 相对宽度
watermark_height = shuiyin_width * img_aspect_ratio  # 自适应高度

3. 目录与文件配置

  • 水印图片路径:若shuiyin.png与脚本不在同一目录,需填写绝对路径(如"D:/images/shuiyin.png");
  • 待处理PDF目录:INPUT_DIRECTORY可填绝对路径(如"D:/pdfs/to_process"),批量处理该目录下所有PDF;
  • 输出目录:OUTPUT_DIRECTORY可自定义绝对路径(如"D:/pdfs/processed"),默认在待处理目录下创建with_watermark子目录。

五、常见问题与解决方案

1. 报错:Illegal character in Name Object (b'/\xfeG~\xe0')

问题原因

原始PDF文件包含非标准命名对象、特殊字符或编码混乱的元数据,PyPDF2解析兼容性弱,触发非法字符校验报错。

解决方案

本文脚本已采用PyMuPDF(fitz)替代PyMuPDF,其对畸形PDF兼容性极强,可直接规避该错误。若仍使用PyPDF2,可通过修改源码临时解决:

  1. 执行pip show PyPDF2获取库安装路径;
  2. 打开安装路径下的PyPDF2/generic.py文件,找到NameObject类的__init__方法;
  3. 注释掉非法字符校验的异常抛出代码: # 注释以下两行代码 `` # if not self.is_valid(name): ``# raise PdfReadError(f"Illegal character in Name Object ({self._name})")

2. 水印变形、模糊

问题原因

未保持图片原始宽高比,或水印大小设置超出图片本身分辨率。

解决方案

  • 严格通过宽高比自动计算水印高度,不手动修改watermark_height;
  • 确保shuiyin.png原始分辨率足够(建议≥300dpi),避免放大后模糊;
  • 相对尺寸方案下,水印比例控制在0.2~0.5之间,兼顾清晰度与页面占比。

3. 水印透明通道失效(PNG透明背景变白色)

问题原因

reportlab绘制图片时未启用透明通道支持。

解决方案

确保drawImage方法中添加mask='auto'参数(本文脚本已包含),启用PNG透明通道解析:

c.drawImage(..., mask='auto')

4. 单个PDF处理失败,批量任务中断

问题原因

单个PDF文件损坏、路径错误或权限不足,未捕获异常导致整体任务终止。

解决方案

脚本已在批量处理循环中添加try-except捕获异常,单个文件处理失败会打印错误信息并跳过,不影响其他文件处理。

六、使用步骤总结

  1. 安装依赖库:执行pip install PyMuPDF Pillow reportlab
  2. 准备文件:将脚本保存为batch_pdf_watermark.py,将shuiyin.png与待处理PDF放在同一目录(或修改脚本路径参数);
  3. 配置参数:根据需求调整脚本中的水印位置(shuiyin_x/y)、大小(shuiyin_width)及目录参数;
  4. 运行脚本:终端执行python3 batch_pdf_watermark.py
  5. 查看结果:处理完成后,在输出目录(with_watermark)中获取带水印的PDF文件。