PDF 批量解锁与文本抽取
⚠️ 合规红线(执行前必读)
| 文件状态 | 是否处理 | 说明 |
|---|---|---|
| 未加密 | ✅ 原样复制/直接抽取 | — |
| Owner 加密(能打开,但不能复制/打印/修改) | ✅ 解除限制 | 内容未被真正加密,权限只是"标记" |
| User 密码,用户已提供密码 | ✅ --password 解开 |
合法授权场景 |
| User 密码,无密码 | ❌ 停止 | 绝不暴力破解、绝不绕过 |
原理:Owner 加密只在文件里写了"不许复制/打印"的权限标记,解密密钥就在文件内;User 密码才是真正的访问控制,绕过它需要暴力破解——本工具不提供、也不会提供该能力。
执行要求:
1. 仅处理本人拥有或已获授权的文件;
2. 敏感文件(审计底稿、合同等)必须本地离线处理,禁止上传任何在线解锁网站;
3. 首次处理建议先 --scan 或 --dry-run,确认范围后再实跑。
何时使用
- 用户说"PDF 能打开但复制不了 / 打印不了 / 改不了"
- 一批 PDF 需要逐个去掉只读限制,手动用 GUI 工具太慢
- 需要从受限 PDF 里批量提取文字(配合
extract_text.py)
✋ 阶段 0:执行前必须让用户决定的两件事
不要替用户默认这两项。 它们决定文件会不会被覆盖、产物放哪里,属于用户财产处置权。
在跑解锁命令前,先用一次提问把两个选项问清(可用多选/二选一形式呈现),再据此拼命令行。
问题 1:源文件怎么处理?
| 选项 | 参数 | 行为 | 何时推荐 |
|---|---|---|---|
| 保留源文件(默认·最安全) | --source-policy keep |
原文件不动,解锁版另存 | 首次处理、重要资料、还需留档 |
| 替换并备份 | --source-policy backup |
原地替换,原文件先备份到 原文件备份/原名.pdf.bak |
想让原位置直接可用,又想留退路 |
| 直接替换,不保留 | --source-policy replace |
原地覆盖,不可逆 | 已确认不需要原件、磁盘紧张 |
> 说明:replace 是原子写(先写 .tmp 再 os.replace),过程中断不会损坏原文件;
> 但替换成功后原件不可恢复,所以拿不准就选 backup。
问题 2:解锁后的文件放哪里?
| 选项 | 参数 | 输出位置 |
|---|---|---|
| 默认:源目录下新建「已解锁」 | 不加参数 | <源目录>/已解锁/(目录模式保持原相对结构;单文件为 <文件所在目录>/已解锁/xxx.pdf) |
| 改默认子目录名 | --dir-name 已解密 |
<源目录>/已解密/ |
| 完全自定义路径 | -o D:\我的输出 |
指定目录(保持相对结构);单文件时可直接给目标 .pdf 路径 |
| 备份目录改名 | --backup-dir D:\备份 |
仅 backup 策略下生效 |
> 用中文目录名没问题;如需纯英文环境,--dir-name unlocked 一键切回。
> 注意:当源文件策略是 backup/replace 时,输出即源位置,问题 2 自然不适用(脚本会忽略输出目录)。
提问后可参考的话术
这批 PDF 解锁前先确认两点:
1) 原文件要保留吗?[保留(推荐)/ 替换但先备份 / 直接替换]
2) 解锁版放哪?[默认在源目录新建「已解锁」(推荐)/ 我来指定路径]
若用户未明确表态 → 一律按最保守组合执行:keep + 默认「已解锁」目录,然后再告知结果并询问是否要改成原地替换。
Agent 决策树
用户反映 PDF 受限
↓
⓪ 先问清两件事:源文件保留/备份/替换?输出放默认「已解锁」还是自定义路径?
↓
① 先 --scan 诊断(只读取,不改动文件)
↓
② 按状态分流:
├─ unencrypted → 无需解锁,直接复制/抽取
├─ owner_locked → ✅ 可解锁
├─ user_password_required → ⛔ 停止并告知用户;
│ 若用户主动提供密码 → --password
└─ broken → 报错,建议重新获取文件
↓
③ 解锁:按⓪的选择加 --source-policy / -o --dir-name
↓
④ 校验:--verify(回读确认未加密 + 页数一致)
↓
⑤ 要文字 → scripts/extract_text.py(pdfplumber 优先)
用法
脚本位于 scripts/,带 PEP 723 元数据,可用 uv run,也可直接 python 跑(需 pip install pypdf,抽取文本再加 pdfplumber)。
# 诊断(推荐第一步,不改任何文件)
python scripts/unlock.py -i ./加密PDF --scan
# 预览将处理哪些文件
python scripts/unlock.py -i ./加密PDF --dry-run
# 解锁:默认 —— 保留源文件,输出到 源目录/已解锁
python scripts/unlock.py -i ./加密PDF
# 只处理文件名含 2022 的
python scripts/unlock.py -i ./加密PDF --filter 2022
# 改默认子目录名 / 完全自定义输出目录
python scripts/unlock.py -i ./加密PDF --dir-name 已解密
python scripts/unlock.py -i ./加密PDF -o D:\输出
python scripts/unlock.py -i a.pdf -o D:\out\a_unlocked.pdf
# 源文件策略:替换并先备份(推荐折中)
python scripts/unlock.py -i ./加密PDF --source-policy backup --verify
# 源文件策略:直接替换,不保留(不可逆;--inplace 是其兼容别名)
python scripts/unlock.py -i ./加密PDF --source-policy replace --verify
# 已知打开密码的合法授权文件
python scripts/unlock.py -i a.pdf --password 已知密码
# 机器可读报告(供 Agent 解析)
python scripts/unlock.py -i ./加密PDF --json report.json
# 批量抽取文本
python scripts/extract_text.py -i ./加密PDF -o ./文本
python scripts/extract_text.py -i ./加密PDF -o 汇总.md --merge
主要参数
| 参数 | 默认 | 说明 |
|---|---|---|
-i/--input |
必填 | PDF 文件或目录 |
-o/--output |
源目录/已解锁 |
自定义输出目录;单文件时可给目标 .pdf 路径 |
--dir-name |
已解锁 |
默认输出子目录名(中英文均可) |
--source-policy |
keep |
源文件处置:keep保留 / backup替换并备份 / replace替换不保留 |
--backup-dir |
源目录/原文件备份 |
仅 backup 生效;备份文件后缀 .bak |
--inplace |
关 | [兼容别名] 等价 --source-policy replace |
--password |
无 | 已知的 User 密码 |
--scan |
关 | 只诊断,不改动文件 |
--filter |
无 | 文件名关键字过滤 |
--no-recursive |
关 | 不递归子目录 |
--exclude |
无 | 额外排除的目录名(可重复) |
--json |
无 | JSON 报告路径 |
--verify |
关 | 回读校验 + 页数比对 |
--dry-run |
关 | 预览 |
--overwrite |
关 | 覆盖已存在输出 |
退出码:0 全部成功 / 1 部分失败 / 2 输入错误 / 3 遇到需 User 密码的文件
状态含义(--scan 输出)
| 状态 | 含义 |
|---|---|
unencrypted |
未加密,无需解锁 |
owner_locked |
权限限制,可解除 ✅ |
user_password_required |
需要打开密码,本工具不处理 ⛔ |
user_password_supplied |
用提供的密码解开 ✅ |
broken |
损坏或不是 PDF |
踩坑提示(实测)
- 文本抽取务必用 pdfplumber:pypdf 的
extract_text()对部分 CID 字体中文 PDF 会输出乱码(嵌入字体缺 ToUnicode 映射),但 pdfplumber 正常——看到乱码别误判为文件损坏或解锁失败。 - 输出目录会自动排除:目录模式下输出目录若位于输入目录内,扫描时自动跳过,不会自扫描或同名覆盖。
此外默认还会排除名为已解锁/unlocked/原文件备份的目录——
避免上次的产物被下次运行重复处理(实测踩过:默认跑完生成已解锁/,再换-o跑一次就把里面的副本又扫进来了)。
需要额外排除用--exclude 目录名;把输出目录本身作为-i输入仍可正常处理。 - 原地替换是原子操作:先写
.tmp再os.replace,中途中断不会损坏原文件;但替换成功后原件不可恢复,故提供backup策略(备份为原名.pdf.bak,后缀不是.pdf,不会被批量扫描误伤)。 - 大文件:pypdf 流式 clone,百 MB 级文件秒级完成,但加
--verify会回读一次、略慢。
文件结构
pdf-unlock-batch/
├── SKILL.md # 本文件(Agent 入口)
├── README.md # 人类文档:原理 / 安装 / FAQ / 合规
├── LICENSE
├── scripts/
│ ├── unlock.py # 核心:诊断 + 解锁(仅依赖 pypdf)
│ └── extract_text.py # 可选:批量抽文本(依赖 pdfplumber)
└── tests/
└── test_pipeline.py # 真自测(硬断言,无跳过式假通过)
与其他工具的配合
- 替代 GUI 工具逐个点:讯读等适合单文件,本技能适合整目录批量,且本地离线、可审计。
- 配合 markitdown:解锁后的 PDF 可直接转 Markdown,进入后续分析流程。
- 配合 OCR:扫描件(纯图片)抽取为空时,需走 OCR(如 Umi-OCR),本技能不内置。
完成度校验
- 成功/失败分类计数清晰,失败文件打印具体原因;
--verify回读确认未加密且页数一致;--json输出结构化结果供程序消费。
作者:李鹏洋(方少恒)· QQ:455922144