夜雨聆风学习资料网

ARTICLE · 1204218

Python编程30:每天几十个 PDF 要合成一个?这份 Python 合并教程,能直接复制

Python编程30:每天几十个 PDF 要合成一个?这份 Python 合并教程,能直接复制

工作中,你是不是经常遇到这种困扰,每次到月底、季末、年底报材料的时候。手头攒了二十多个 PDF——扫描件、盖章件、网页导出的文档—上级领导只给一句话:最后合成一个pdf文件。于是你各种网上、线下工具寻找一通,或者干脆打开第一个,复制、粘贴,翻到下一份……忙活一小时,页数对不上,顺序还乱了。

这不是你不会用电脑,而是你在用最笨的办法,做一件本可以几分钟可以完成的事。

01 为什么"合并 PDF"值得专门学一次

先别急着看脚步代码。先回答一个问题:这件事值得你花二十分钟学吗?

值得。因为它符合三个特征:高频、反复出现、纯重复劳动。材料报送、论文附录、合同归档、简历打包、课件整理——几乎每个行政办公场景都会遇到。这样的工作,写一次脚本就能永久复用,边际成本几乎为零。

相比之下,手工方式有三个隐藏成本:一是把文件传到在线工具,等于把可能涉密的资料交到第三方服务器;二是几十个文件来回复制,页序极易出错;三是每次都要重来一遍,永远攒不下经验。

02 先想清楚:合并 PDF 的本质不是简单"拼图",是"按顺序和业务逻辑接页"

很多人一听"合并"就想象成图片拼接,这是误解。

一句话说清:合并 PDF,是把 N 个文件的每一页,按你指定的业务逻辑先后顺序,依次接进一个新文件——像装订,每一页都还是独立的一页,只是被按顺序排进同一本里。

它不等于压缩,不等于把内容糊到一页,也不等于把多份资料变成一张图。它解决的是"多份文件→一份文件"的组织问题,而这件事的关键变量只有一个:逻辑顺序。

03 用什么工具?为什么选 pypdf

Python 合并 PDF 的主流选择,是一个叫 pypdf 的纯 Python 库。它免费、轻量、纯开源,由一个活跃社区长期维护,不依赖任何商业软件。

安装只有一行:

pip install pypdf

装好后,合并的核心逻辑其实不到十行。文末直接给出复制的脚步代码成品。

04 三步写好一个可用的合并脚本

把这件事拆成三步,每一步都对应一个明确目的:

步骤一,收集文件:找到目录下所有要合并的 PDF;

步骤二,确定业务逻辑顺序:梳理清楚谁在前、谁在后,这是全文的"指挥棒";

步骤三,按逻辑顺序写入:用 PdfWriter 一页页接进新文件,最后落盘。

pypdf最小可运行脚步版本:

# merge.pyfrom pypdf import PdfWriter, PdfReaderwriter = PdfWriter()files = ["材料一.pdf", "材料二.pdf", "材料三.pdf"]for f in files:    reader = PdfReader(f)    for page in reader.pages:        writer.add_page(page)writer.write("合并结果.pdf")

把这个文件存成 merge.py,在 PDF 所在目录运行 python merge.py,就能得到按列表顺序拼接好的"合并结果.pdf"。

05 最关键的需求:如何自由指定拼接顺序

合并 PDF 真正容易出错的地方,不是"会不会合并",而是"顺序能不能按我要的来"。你要的往往不是 ABC 顺序,而是"先放封面、中间按材料编号、最后放附录"这种业务顺序。

pypdf 的做法很直接:拼接顺序,就是你传入列表的顺序。想改顺序,就改列表,不用改逻辑:

from pypdf import PdfWriter, PdfReaderwriter = PdfWriter()order = ["封面.pdf", "正文.pdf", "附录.pdf"]for f in order:    reader = PdfReader(f)    for page in reader.pages:        writer.add_page(page)writer.write("合并结果.pdf")

三个实用细节:

一是同一份文件可以拼多次,比如把"目录.pdf"放在开头又放在结尾,直接在列表里写两次即可;

二是想交互式选择顺序,可以运行时让用户输入编号,程序按编号重新排序,不用每次改代码;

三是大量文件时,可以先用 os.walk 递归扫描目录,再按"业务编号"而非文件名排序,避免数字文件名的字典序陷阱。

06 几个容易踩的坑

坑一|把"文件名排序"当成"业务顺序"。扫描出的文件默认按文件名排序,而文件名按字典序,1、10、2、3 会排成你意想不到的顺序。要么用自然排序,要么干脆手动指定顺序列表,别依赖文件名的巧合。

坑二|顺序写死在代码里,改一次就要动一次代码。把顺序抽成单独的列表,甚至放在一个 order.txt 里逐行维护,改顺序不动逻辑,才叫可复用。

坑三|加密 PDF 和只读扫描件。带密码的 PDF 要先解密才能合并;纯图片扫描件合并后仍是图片页,无法检索文字,这是源文件决定的,不是工具的错。

坑四|为了图省事用在线工具。把内部资料上传到第三方服务器,顺序和隐私安全都可能失控。本地脚本零上传,更稳妥。

07 一套能直接复用的判断框架

以后遇到类似需求,先别急着动手,用这个框架判断:

适合用脚本|高频反复出现、文件数量多、顺序有要求、要长期复用——写一次脚本,收益最大。

不太适合|一次性小文件、涉及极高密级的敏感材料、源文件本身格式混乱——用最小成本处理,或先做格式治理。

一句话总结:合并 PDF 不是技术难题,难点在于"顺序可控"和"隐私可控"。这两点,本地脚本都天然满足。

可以直接复制使用的代码脚本:

#!/usr/bin/env python3# -*- coding: utf-8 -*-"""@File: 30_merge_pdf.py@Python Version: 3.12.13@Platform: PyTorch 2.2.1 + cu121@Author: Wei Li (Ithaca)@Date: 2026-10-15@Contact: weili_yzzcq@163.com@Blog: https://2694048168.github.io/blog/#/@Version: V0.1@License: Apache License Version 2.0, January 2004    Copyright 2026. All rights reserved.@Description:将一系列 PDF 文件拼接为单个 PDF,并可自由指定拼接顺序。功能特性:1. 自动扫描目录下(含子目录)的所有 .pdf 文件;2. 提供多种指定拼接顺序的方式:   a. 交互式:运行时在终端按编号选择顺序;   b. 命令行直接传文件(按传入顺序拼接);   c. 通过顺序文件 / 拖入列表指定;3. 支持重复文件(同一文件可拼接多次);4. 输出单一合并后的 PDF 文件。依赖: pypdf安装: pip install pypdf用法示例:  # 方式一:交互式选择顺序  python3 merge_pdfs.py  # 方式二:命令行按顺序传文件(顺序即拼接顺序)  python3 merge_pdfs.py a.pdf b.pdf c.pdf  # 方式三:指定输入目录 + 交互选择  python3 merge_pdfs.py --dir ./pdfs --output merged.pdf  # 方式四:从顺序文件读取(每行一个文件名)  python3 merge_pdfs.py --order order.txt"""import osimport reimport sysimport argparsefrom pypdf import PdfWriter, PdfReaderdef natural_sort_key(name: str):    """自然排序:把文件名中的数字按数值大小排序,而不是按字典序。"""    return [int(t) if t.isdigit() else t.lower() for t in re.split(r"(\d+)", name)]def discover_pdfs(directory: str) -> list:    """递归扫描目录,返回按自然顺序排序的 PDF 文件绝对路径列表。"""    if not os.path.isdir(directory):        sys.exit(f"目录不存在:{directory}")    pdfs = []    for root, _, files in os.walk(directory):        for f in files:            if f.lower().endswith(".pdf"):                pdfs.append(os.path.join(root, f))    return sorted(pdfs, key=lambda p: natural_sort_key(os.path.basename(p)))def read_order_file(order_file: str, pdf_dir: str = ".") -> list:    """从顺序文件读取拼接顺序:每行一个文件名或路径,空行与注释(#)忽略。"""    if not os.path.isfile(order_file):        sys.exit(f"顺序文件不存在:{order_file}")    items = []    with open(order_file, encoding="utf-8") as fh:        for line in fh:            line = line.strip()            if not line or line.startswith("#"):                continue            # 去掉行内注释            line = re.split(r"[#\t]", line)[0].strip()            if not line:                continue            p = line            if not os.path.isabs(p):                p = os.path.join(pdf_dir, p)            items.append(p)    return itemsdef pick_order_interactively(files: list) -> list:    """交互式:让用户在终端按编号选择拼接顺序。"""    print("\n发现以下 PDF 文件(已按自然顺序编号):\n")    for i, f in enumerate(files, 1):        print(f"  {i:>3}. {os.path.basename(f)}")    print(        "\n请输入拼接顺序(用逗号或空格分隔编号,例如:3,1,2 表示先拼第3个、再拼第1个、再拼第2个)。"    )    print("输入 q 退出,输入 a 则全部按当前编号顺序拼接。")    while True:        raw = input("顺序 > ").strip().lower()        if raw in ("q", "quit", "exit"):            sys.exit("已取消。")        if raw in ("a", "all", ""):            return files[:]        tokens = re.split(r"[,\s,、;;]+", raw)        indices = []        ok = True        for t in tokens:            if not t:                continue            try:                n = int(t)            except ValueError:                print(f"  无效编号:{t!r}")                ok = False                break            if not (1 <= n <= len(files)):                print(f"  编号越界:{n}(应在 1~{len(files)} 之间)")                ok = False                break            indices.append(n)        if not ok or not indices:            print("  请重新输入。")            continue        # 允许重复编号,实现同一文件拼接多次        return [files[n - 1] for n in indices]def merge_pdfs(paths: list, output: str) -> None:    """按 paths 顺序合并 PDF,输出到 output。"""    if not paths:        sys.exit("没有可合并的 PDF 文件。")    writer = PdfWriter()    total_pages = 0    seen = set()    dup_warned = False    for idx, p in enumerate(paths, 1):        if not os.path.isfile(p):            sys.exit(f"文件不存在:{p}")        abs_p = os.path.abspath(p)        if abs_p in seen and not dup_warned:            print("提示:检测到同一文件被多次选中,将按选择拼接多次。")            dup_warned = True        seen.add(abs_p)        try:            reader = PdfReader(p)            n = len(reader.pages)            for page in reader.pages:                writer.add_page(page)            total_pages += n            print(f"  [{idx}/{len(paths)}] 已加入:{os.path.basename(p)}({n} 页)")        except Exception as e:            print(f"  警告:无法读取 {p},已跳过({e})")    if writer.get_num_pages() == 0:        sys.exit("合并失败:没有任何页面被加入。")    with open(output, "wb") as fh:        writer.write(fh)    print(f"\n合并完成!共 {len(paths)} 个文件、{total_pages} 页。")    print(f"输出文件:{os.path.abspath(output)}")def main():    parser = argparse.ArgumentParser(        description="将一系列 PDF 文件拼接为一个 PDF,可指定拼接顺序。",        formatter_class=argparse.RawDescriptionHelpFormatter,        epilog=__doc__,    )    parser.add_argument(        "files",        nargs="*",        help="命令行直接传入的 PDF 文件,按传入顺序拼接。",    )    parser.add_argument(        "--dir",        default=".",        help="扫描目录(含子目录),默认当前目录。",    )    parser.add_argument(        "--output",        "-o",        default="merged.pdf",        help="输出文件名,默认 merged.pdf。",    )    parser.add_argument(        "--order",        default=None,        help="从指定顺序文件读取拼接顺序(每行一个文件名)。",    )    parser.add_argument(        "--no-interactive",        action="store_true",        help="不进入交互选择:命令行未传文件时,直接按自然顺序合并全部。",    )    args = parser.parse_args()    # 确定要合并的文件列表(按优先级)    if args.files:        selected = [os.path.abspath(f) for f in args.files]        print(f"使用命令行传入的 {len(selected)} 个文件(按传入顺序拼接)。")    elif args.order:        selected = read_order_file(args.order, args.dir)        print(f"从顺序文件读取 {len(selected)} 个文件。")    else:        all_pdfs = discover_pdfs(args.dir)        if not all_pdfs:            sys.exit(f"在目录 {args.dir} 中未发现任何 PDF 文件。")        if args.no_interactive or len(all_pdfs) == 1:            selected = all_pdfs            print(f"按自然顺序合并 {len(all_pdfs)} 个文件。")        else:            selected = pick_order_interactively(all_pdfs)    merge_pdfs(selected, args.output)# --------------------------if __name__ == "__main__":    main()

写在最后

别指望一次就完美。更稳的做法是:先用两个小文件跑通顺序,确认输出页数对、顺序对,再放正式文件。原文件一定要留备份,输出文件名写清楚,比如"2026材料汇总_共58页.pdf"。

这份教程给你的是一个能复制的起点。真正的效率,是把"每次重来一遍"变成"写好一次,永久复用"。

如果这篇对你有帮助,点个「赞」和「在看」,也欢迎转发给正在受困于这种工作琐事的同事 👇

下一期,我们再见!

相关学习资料