
简介这是一份面向Windows用户的PDF页码计数小工具资源包适合需要批量查看PDF页数、整理电子书或归档文件的人员使用。工具通过指定PDF所在文件夹或单个文件路径即可快速获得对应PDF的页码数免去逐份打开查看的麻烦。压缩包共5个文件包括可直接运行的exe程序、Python脚本、PDF格式测试样例以及docx格式使用说明整体大小约8.69MB。exe适合无编程基础者开箱即用py源码便于有Python基础的读者理解统计逻辑并二次修改脚本核心逻辑精简可进一步封装进批处理工作流测试PDF与实际文档可用来验证不同文件下的统计表现使用说明则对路径填写和运行方式作了提示。目前已有508人学习下载适合日常办公、图书整理、资料归档等需快速统计PDF页码的Windows用户。1. “pdf页码计数工具”到底解决了谁的什么需求从一摞 PDF 到一张页数清单“pdf页码计数工具”这几个字没接过批量文档的人只会当一条普通搜索真落到档案整理、投标文件汇编、文印店报价的工位上它就是一天开工的第一步。常见场景是对方丢过来一个压缩包或共享目录里面几百个 PDF 等着转档、装订、结算而所有后续工作都从“这一批到底多少页”开始。手工操作是挨个双击、看底部状态栏、再手抄进 Excel五百个文件就是小半天。这个工具要做的就是把目录扫一遍读出每个 PDF 的页数落成一张带文件名的清单再把可执行程序、脚本和使用说明压进同一个 rar 发给办公室同事不懂 Python 的人也能双击运行。适合档案室、文印店、做资料交接和文档自动化的工程师。先给结论页数统计本身的代码不难真正花时间的是加密、编码、损坏文件这些边界填平了才叫工具否则只是个脚本。2. 页数藏在 PDF 的页树里为什么读取 /Count 比数页面更可靠2.1 页数不是文件大小能估出来的页面对象与页树结构一个 PDF 在磁盘上并不是一张平坦的“大图”而是一组带编号对象的集合。里面至少有目录对象 Catalog、页树根节点 Pages、若干中间节点、Page 叶子节点以及内容流。页面被组织成一棵树每个中间节点上都写着一个 /Count 值表示这个子树下的页面数根节点的 /Count 就是整份文档的页数。阅读器打开文件时只需要读根节点的 /Count 就能在侧栏显示总页数不用先把所有页面渲染一遍这就是 PDF 能快速翻页的原因。但这个设计也留下一个坑/Count 只是缓存值极少数生成不规范的 PDF 会在根节点写错或者叶子里挂着没被页树引用的孤儿页面。Acrobat 打开时可能自动修正而自动化脚本读到的却是原始值。所以你才会看到同一个文件用工具读 87 页、Acrobat 显示 88 页的怪事。“读取页数”从来不是读一个整数这么简单而是“遍历页树自己数叶子数量”和“信任根节点缓存”两条路线的选择。明白了这一层你才能理解为什么不同工具读同一份文件会差一两页它们对页树不完整性的容忍策略不一样。2.2 两条读取路径直接解析页树还是调用 pypdf 的封装函数路径一直接解析交叉引用表和对象流找到根 Pages 节点取 /Count。速度快写起来也短但它扛不住现在很常见的对象流压缩。很多扫描版 PDF、虚拟打印驱动生成的文件会把对象塞进 Compressed Object Stream你得先做 FlateDecode 解压再解析内容等于自己实现了半个 PDF 解析器。路径二用 pypdf 或 PyPDF2 的 PdfReader 打开文件len(reader.pages) 返回页数库内部处理了对象流、交叉引用流和异常情况这是更稳的选择。做一个页数统计工具我倾向把“读取页数”完全交给库自己只负责文件收集、异常兜底和结果输出。选型理由很朴素库比人脑稳而且 pypdf 对畸形文件会抛出明确异常而不是静默数错。唯一要注意的是新旧版本 API 差异老代码里常见 PdfFileReader新版本要写 PdfReader复制代码时别混。另外页数本身也是 PDF 元信息里最常被提取的参数pdf转word、pdf转markdown 这类任务评估工作量时第一件事也是数页数。把统计结果当成一张基础表后面接报价、接拆分、接转档全都靠它。想知道一份文件页树干不干净可以先做个最小验证不需要写脚本qpdf --show-pages sample.pdf | head -20如果 qpdf 报“cant find page”或某个节点解析失败说明这份 PDF 页树有毛病后面任何工具读出来的页数都要留个心眼不能直接当最终数。2.3 工具 rar 里到底装什么脚本、依赖与可执行程序的分工“工具.rar”这个形态决定了一种分发思路主力使用者往往不是程序员而是需要双击运行的文员维护者则是希望脚本还能改的工程师。所以压缩包里我一般放四样东西可直接运行的 exe、Python 源文件、依赖清单、使用说明。exe 用 PyInstaller 或 Nuitka 打包给不会配环境的人用源文件和 requirements 给接手的同事让他在新机器上能复现使用说明写清楚“先解压再双击不要直接在压缩包内运行”。选 rar 而不是 zip没有高深理由就是这类办公工具内网流转的习惯文件服务器对 zip 和 exe 的扩展名限制更严rar 相对通行还能顺手加密码控制外发范围。代价是 rar 密码一旦丢失网上那些密码恢复工具往往要跑很久所以密码只适合做分发控制不适合保护真正敏感的内容敏感内容应该在 PDF 层面就做加密。这一点第 4 章会展开讲。3. 把页数统计落进 ExcelPython 脚本、三个必调参数与字段设计3.1 最小可跑版本递归扫描目录并输出 CSV下面这段代码是大多数人复制去改的第一版写在这里也是为了后面几章有共同上下文。它做的事只有三件按后缀收集文件、逐个读页数、写 CSV失败文件单独进 error.log不中断整个批跑。# scan_pdf_pages.py # 依赖pip install pypdf # 用法python scan_pdf_pages.py --input D:\pdfs --output pages.csv --recursive import argparse import csv import sys from pathlib import Path from pypdf import PdfReader def count_pages(path): reader PdfReader(str(path)) return len(reader.pages) def main(): parser argparse.ArgumentParser(descriptionPDF页码计数工具) parser.add_argument(--input, requiredTrue, help要扫描的目录或单个PDF文件) parser.add_argument(--output, defaultpages.csv, help输出CSV路径) parser.add_argument(--recursive, actionstore_true, help递归扫描子目录) args parser.parse_args() src Path(args.input) if src.is_file(): files [src] elif src.is_dir(): pattern **/*.pdf if args.recursive else *.pdf files list(src.glob(pattern)) else: sys.exit(输入路径不存在) rows [] errors [] for f in sorted(files): try: rows.append((f.name, count_pages(f), f.stat().st_size, str(f.parent))) except Exception as e: errors.append((str(f), str(e))) with open(args.output, w, newline, encodingutf-8-sig) as fh: writer csv.writer(fh) writer.writerow([文件名, 页数, 文件大小(字节), 所在目录]) writer.writerows(rows) if errors: with open(error.log, w, encodingutf-8) as fh: for path, msg in errors: fh.write(f{path}\t{msg}\n) print(f成功统计 {len(rows)} 个文件失败 {len(errors)} 个结果见 {args.output}) if __name__ __main__: main()逻辑上有个容易忽视的点glob 的**/*.pdf在 Windows 上能匹配.PDF在 Linux 和 macOS 上则区分大小写会漏掉大写后缀文件。所以后面才有第 4 章的文件头校验不能只靠后缀判断。sorted(files)保证同一批文件每次运行输出顺序一致这对后续对账很重要。输出用utf-8-sig编码写文件Excel 双击打开不乱码第 5 章会说乱码是怎么来的。运行命令和参数项也很简单python scan_pdf_pages.py --input D:\pdfs --output pages.csv --recursive跑完你会得到一个四列的 CSV。如果文件数量上千建议先小范围试跑一个子目录确认输出字段没有错位再全量执行。这个习惯能帮你省掉一次“跑完才发现路径列成了乱码”的返工。3.2 三个要调的参数输入目录、输出路径、排序方式这版脚本暴露了三个最常用的调整点。第一个是输入目录一定要给绝对路径不要用相对路径否则从 rar 解压到不同位置后脚本可能找不到文件。第二个是输出路径文件名带上日期比如pages_20250112.csv避免同一目录下反复覆盖历史结果如果要留底就按日期归档。第三个是排序方式默认按文件名排序是为了和交接清单对账但当你面对几百个文件时建议先按文件大小升序排让几 MB 的小文件先跑完把超过 200MB 的扫描版留在最后。这样即使中途卡住也已经拿到了大部分结果。扩展脚本时一般会给这几个参数设计含义如下表参数默认值作用建议设置--input必填扫描目录或单个文件绝对路径--outputpages.csv结果文件路径pages_20250112.csv--recursive关闭是否递归子目录归档目录开启--sortname排序方式name/size/pages大文件场景切 size--exclude无排除路径关键字逗号分隔backup,tmp,__MACOSX加--sort size的实现并不复杂把第 3.1 节的sorted(files)换成按stat().st_size排序即可。但注意不要只改排序而不把相应列写进 CSV你最后还是要面对输出顺序和实际处理顺序不一致的问题最好把“排序字段”也作为一列输出方便复盘。3.3 输出 CSV 的字段设计与对账思路实际交付时我给 CSV 的默认列是六列文件名、页数、文件大小、修改时间、相对路径、异常信息。前四列是给文员看的后两列是给工程师排查用的。相比最小版本多出来的“修改时间”常用于区分同名副本备份目录里常有相同文件名页数一模一样只有时间戳能看出新旧。相对路径而不是绝对路径则是考虑同一批文件被拷贝到另一台电脑时绝对路径会失效相对路径还能回溯到文件位置。页数统计结果里一定要带文件大小原因是同为 10 页的文件纯文本版和扫描版体积差几十倍体积异常大的 PDF 通常混了大量图片或未压缩流后续转 word、做水印、拆分合并时耗时会显著增加。拿到 CSV 后的对账步骤我一般固定做三件事第一按页数升序排列页数 0 和 1 的文件优先人工打开第二用 Excel 的 SUM 算出总页数跟交接单上的“预计页数”对比差异超过 10% 就去查目录结构是不是漏了子目录第三把文件名列和交接清单做一次 VLOOKUP找出“有清单但没统计到”或者“统计到但清单没有”的文件。这套动作做完基本能把输入目录的问题堵住一大半。4. 从脚本到可交付的 rar加密 PDF、文件头校验与打包习惯4.1 加密 PDF 也能读打开密码与权限密码的区别PDF 加密分两类打开密码和权限密码。权限密码限制的是打印、复制、编辑等操作内容本身未必做了强加密pypdf 在不少情况下可以直接读出页数打开密码则会在解析对象流前拦截不带密码就会抛异常。批量统计时对每个文件先尝试无密码打开失败后再带配置的密码重试一次是一种常见的稳健做法。def count_pages_safe(path, passwordNone): from pypdf import PdfReader try: return len(PdfReader(str(path)).pages) except Exception: if password: return len(PdfReader(str(path), passwordpassword).pages) raise注意第一层 except 的捕获范围要收窄最好只捕PyPdfError不要捕所有异常。否则文件损坏也会被误判成密码文件多走一次带密码读取后抛出同一个异常排查时容易误导。批量场景下如果整个目录设置了统一密码就把密码通过--password参数传入如果只有个别文件有密码可以把密码放在一个pass.txt里让脚本逐行读取用 UTF-8 编码别用 GBK 存否则中文密码会踩编码坑。还要说明一句边界能读页数不等于能解密全文。页数属于元数据部分权限加密文件允许读取但打印、复制限制依然有效。不要在交付说明里写“加密 PDF 全能提取”这既会误导用户也会把工具引到你不希望的方向上。4.2 目录里混着假 PDF用文件头过滤而不是只信后缀只按.pdf后缀收集文件在真实目录里会踩两种坑一是 Linux 环境的大小写问题.PDF被 glob 漏掉二是有些假 PDF后缀正确但内容其实是 Office 私有格式或网页另存的文件。解决方式是在读取前加一个文件头校验合法 PDF 的文件头允许有前导空白去掉空白后以%PDF-开头。def is_pdf(path): with open(path, rb) as fh: head fh.read(1024).lstrip() return head.startswith(b%PDF-)读 1024 字节足够覆盖文件头lstrip()是为了兼容规范允许的前导空格。注意不要用seek(0)后读固定 5 字节的做法某些工具生成的 PDF 文件头前面有不可见字符直接读前 5 字节会误判。加上这层过滤之后pypdf 报 “EOF marker not found” 的情况会少很多同时 0 字节文件在stat().st_size为 0 时可以直接跳过单独写进日志而不是报错中断。如果你的目录里混着大量来自网页打印、虚拟打印驱动的 PDF这部分文件往往没有干净的页树文件头校验能过滤掉一部分但过滤不掉所有所以排错日志要保留完整路径。4.3 打包 rar 的三个习惯目录结构、校验和与说明文件压缩包不该只有一个孤零零的 exe那样既不方便排查也会让使用者怀疑来源。我通常按下面这个结构组织路径说明bin/PDFPageCounter.exe给文员双击运行src/scan_pdf_pages.py给工程师看和改requirements.txtpypdf 等依赖清单使用说明.txt运行方法、参数表、输出字段解释example/pages_sample.csv一份示例输出方便对字段使用说明里固定写一段“先解压到本地文件夹再双击 exe不要在压缩包内直接运行”。原因是rar 内的 exe 被资源管理器挂载为临时路径PyInstaller 单文件模式会在临时目录释放依赖杀毒软件对这个路径的敏感度更高解压到正式目录后误报率会明显下降。打完压缩包后顺手生成一份 MD5 校验值写到压缩包同级目录的.md5文件里发给对方后让对方核对能排除文件传输被截断的问题。给 rar 加密时密码用随机字符串并且通过另一个渠道告知对方别把密码写进“使用说明.txt”。否则压缩包一泄露密码和内容一起暴露加密就等于没做。这里不用太操心网上那些高级 rar 密码恢复工具只要密码不是纯数字短口令恢复成本就远超重新整理这批文件的时间。5. 页码统计的五个典型坑现象、原因、解决5.1 读出来的页数和 Acrobat 显示不一样现象脚本统计 87 页Acrobat 打开显示 88 页或者脚本和 qpdf 结果一致Acrobat 却差一页。原因页树根节点的 /Count 与实际页面叶子数不一致属于 PDF 文件本身的问题不是脚本算错。解决用 qpdf 交叉验证重点看文件结构是否报 “cant find page” 或类似警告然后以阅读器显示值为准因为最终装订和交付用的是 Acrobat。若差值稳定存在考虑用 qpdf 做一次修复qpdf sample.pdf fixed.pdfqpdf 重建对象结构后页树会变得更干净页数通常就能对齐。这个坑最影响信任度因为用户不会认为是文件的问题只会觉得你的工具不靠谱所以遇到差异时先解释文件结构再决定是否修复。5.2 扫描版大文件卡死批处理现象批处理跑到一个 500MB 的扫描 PDF脚本长时间不返回内存占用升到 2GB 以上。原因pypdf 解析全图片页时要对对象流做解压和扫描超大尺寸扫描图会让内存暴涨。解决给脚本加一个--max-size参数超过阈值的文件先跳过并单独记录到big_files.log读取时用线程池限制并发数不要把几百个文件一次性全开。更稳妥的做法是先把文件按大小升序排小文件快速跑完大文件留到最后用 pdfinfo 命令行单独处理再手动对账。批处理不是越快越好稳定的产出比多线程带来的几分钟加速值钱得多。5.3 中文文件名在 Excel 里变乱码现象CSV 用记事本打开正常Excel 双击打开后中文文件名一片“锟斤拷”。原因旧版 Excel 打开 CSV 默认按 GBK 解码UTF-8 无 BOM 的文件识别不了。解决写 CSV 时用encodingutf-8-sig写入 BOM老版 Excel 也能认。如果用户明确表示要直接用 Excel 处理更省事的做法是直接输出 xlsxfrom openpyxl import Workbook wb Workbook() ws wb.active ws.append([文件名, 页数, 文件大小, 路径]) for row in rows: ws.append(row) wb.save(pages.xlsx)openpyxl是额外依赖但换来的是用户不需要做任何编码转换对文员最友好。顺手说明一点utf-8-sig只在文件开头多三个字节对内容没有影响不用担心。5.4 统计了一堆临时目录里的副本现象用户说目录里只有 300 个 PDF脚本输出 800 个多出来的全是“备份”“tmp”“__MACOSX”里的重复件。原因递归扫描时没有排除隐藏目录和备份目录。解决加一个--exclude参数按路径部件匹配过滤而不是简单用字符串findexclude set(args.exclude.split(,)) files [f for f in files if not any(part in exclude for part in f.parts)]用f.parts匹配路径组件可以避免误伤正常目录。比如排除backup时路径backup_tools不会命中因为它是整个部件而不是子串。对账时把“有效页数”和“总页数含重复”分开统计在 CSV 里加一列“是否排除”不要直接删文件或改目录给自己留后悔药。5.5 rar 里的 exe 双击跑不起来或被杀毒删掉现象同事解压后双击 exeWindows Defender 报毒隔离或因缺少 VCRUNTIME140.dll 无法启动。原因PyInstaller 打包的程序特征较重启发式引擎容易误报缺 VC 运行库的机器则会报 DLL 缺失。解决交付双形态exe 和 Python 源文件一起放杀毒误报就加白名单或改用 Nuitka 打包降低特征强调先解压到正常目录再运行不要从 rar 内直接双击。另外提醒一句安全卫生工具代码不到一百行自己五分钟能构建一套没必要去不知名网站下载二次打包的版本里面被塞广告子程序的情况并不少见。6. 用交叉验证和两个进阶用法收住这批统计任务6.1 交叉验证pdfinfo 批量对账脚本输出是一份结果不能孤立地信。装好 poppler-utils 后用 pdfinfo 对同一批文件再读一遍页数把结果并排比对for f in *.pdf; do printf %s\t $f pdfinfo $f 2/dev/null | awk -F: /^Pages/{print $2} done pages_verify.tsv对比脚本输出和这份 tsv差异文件会立刻暴露。注意循环里文件名要加引号避免空格把路径拆成两段。如果两边的页数相差超过 1基本可以断定是源文件页树有问题而不是工具问题。6.2 技巧一按页数排序找异常文件把 CSV 按页数升序排列页数为 0 或 1 的文件多半是空文件或坏文件页数超过 500 的多半是扫描合集这类文件在转 word、转 markdown 之前要想清楚要不要先拆分。这个排序动作比逐个抽查快得多尤其适合上千文件的批量任务。6.3 技巧二把页码表变成工作量表格拿到页数列之后直接在 Excel 里加一列“建议处理方式”公式按页数区间拆分比如IF(B2200,先拆分,直接转档)。再算一列“打印张数”按双面打印估算就是ROUND(B2/2,0)。这一层表格可以直接派给文员当开工参考省掉中间人逐个问“这个文件要拆吗”的沟通成本。我现在的新习惯是任何批量 PDF 任务开始前先跑一遍页数统计把总页数写在命令窗口的标题栏上后面每一步出错都先怀疑输入文件再怀疑代码。这个顺序帮我省过不少冤枉路也希望帮到你。本文还有配套的精品资源点击获取