PDF 工具箱
DocumentsPDF 工具箱 is an Agent Skill for documents published in the maka-agent/maka-agent repository on GitHub: a SKILL.md instruction file an AI agent can load to do this task.
需要对 PDF 做任何操作时使用——提取文本/表格、合并拆分、旋转、加密解密、填写表单、PDF 与图片互转。只要用户提到 .pdf 文件或想生成 PDF 就触发。
How to use this skill
Bring this guide into your coding agent with a prompt tailored to the tool you use.
- Open your project in Codex.
- Copy and paste the prompt below.
- Review files and risks before approving any action.
Inspect the Agent Skill at https://github.com/maka-agent/maka-agent/blob/HEAD/apps/desktop/resources/bundled-skills/pdf-toolkit/SKILL.md and all supporting files. Report provenance, permissions, scripts, dependencies, and security risks. Propose an exact installation into .agents/skills/pdf-工具箱, but wait for my approval before writing files or running scripts.
Copying this prompt does not install or run the skill. Review third-party files before use.
PDF 工具箱
目标
为本地 PDF 文件提供一站式处理能力。所有操作都通过 Bash 调用 python3 加少量成熟的第三方库完成,脚本自包含、可直接执行。maka 市场只支持单文件 SKILL.md,因此这里不依赖任何外部脚本文件——需要运行的 python 代码全部内联在下面的工作流里,你把它写入临时 .py 文件后执行,或用 python3 -c / heredoc 运行。
能力清单
| 操作 | 推荐库 | 说明 |
|---|---|---|
| 读取/提取文本 | pdfplumber | 保留版面布局逐页取文 |
| 提取表格 | pdfplumber | extract_tables(),可导出 Excel/CSV |
| 合并多个 PDF | pypdf | 顺序拼接为一个文件 |
| 拆分 PDF | pypdf | 按页/按范围切成多个文件 |
| 旋转页面 | pypdf | 90/180/270 度 |
| 加密/解密 | pypdf | 设置或去除打开密码 |
| 读取元数据 | pypdf | 标题、作者、页数等 |
| 填写表单 | pypdf | 写入 AcroForm 字段 |
| 生成新 PDF | reportlab | 从文本/表格排版生成 |
| PDF 转图片 | pdf2image (需 poppler) | 每页渲染为 PNG |
| 图片转 PDF | Pillow | 多张图合成一个 PDF |
第 0 步:依赖检测与按需安装
动手前先探测环境,缺什么装什么,避免报 ModuleNotFoundError。
# 探测核心库是否可用;缺失的会被打印出来
python3 - <<'PY'
import importlib
mods = {
"pypdf": "pypdf",
"pdfplumber": "pdfplumber",
"reportlab": "reportlab",
"PIL": "Pillow",
"pdf2image": "pdf2image",
}
missing = [pip for imp, pip in mods.items() if importlib.util.find_spec(imp) is None]
print("MISSING:", " ".join(missing) if missing else "(none)")
PY
按当前任务只安装真正需要的包(不要一次性全装):
# 例:只需读文本 + 合并,就装这两个
python3 -m pip install --quiet --disable-pip-version-check pypdf pdfplumber
各能力对应的最小依赖:
- 文本/表格提取 →
pdfplumber - 合并/拆分/旋转/加密/元数据/填表单 →
pypdf - 生成新 PDF →
reportlab - PDF 转图片 →
pdf2image(另需系统 poppler:macOSbrew install poppler,Debian/Ubuntuapt-get install poppler-utils) - 图片转 PDF →
Pillow
表格导出 Excel 时再加 pandas openpyxl。
如果 pip install 因网络失败,先降级方案:文本提取可用系统自带/易装的 pdftotext(poppler-utils),合并可用 qpdf。发现这类外部命令可用时优先用,省去装 python 库。
# 无 python 库时的兜底
pdftotext -layout input.pdf output.txt # 提取文本,保留版面
qpdf --empty --pages a.pdf b.pdf -- merged.pdf # 合并
qpdf input.pdf --pages . 1-5 -- part.pdf # 取 1-5 页
qpdf --password=PWD --decrypt enc.pdf dec.pdf # 解密
工作流
下面每个片段都写成可直接运行的样子。建议把脚本写到临时文件再执行,例如
python3 /tmp/pdf_job.py,便于传参和复用。文件路径请始终使用绝对路径。
1. 提取文本
import sys, pdfplumber
src = sys.argv[1]
with pdfplumber.open(src) as doc:
for i, page in enumerate(doc.pages, 1):
text = page.extract_text() or ""
print(f"===== 第 {i} 页 =====")
print(text)
只要某几页可加范围判断(enumerate 里过滤页码)。若结果为空且文件是扫描件(图片型 PDF),文本提取会拿不到内容——此时需走 OCR,见「边界」一节。
2. 提取表格并导出
import sys, pdfplumber, pandas as pd
src, out_xlsx = sys.argv[1], sys.argv[2]
frames = []
with pdfplumber.open(src) as doc:
for page in doc.pages:
for tbl in page.extract_tables():
if tbl and len(tbl) > 1:
frames.append(pd.DataFrame(tbl[1:], columns=tbl[0]))
if frames:
pd.concat(frames, ignore_index=True).to_excel(out_xlsx, index=False)
print(f"已导出 {len(frames)} 张表到 {out_xlsx}")
else:
print("未检测到表格")
不想装 pandas 时,直接 print(tbl) 或用标准库 csv 写文件即可。
3. 合并多个 PDF
import sys
from pypdf import PdfReader, PdfWriter
*inputs, output = sys.argv[1:] # 最后一个参数是输出路径
writer = PdfWriter()
for path in inputs:
for page in PdfReader(path).pages:
writer.add_page(page)
with open(output, "wb") as f:
writer.write(f)
print(f"已合并 {len(inputs)} 个文件 -> {output}")
4. 拆分 PDF
import sys, os
from pypdf import PdfReader, PdfWriter
src, out_dir = sys.argv[1], sys.argv[2]
os.makedirs(out_dir, exist_ok=True)
reader = PdfReader(src)
base = os.path.splitext(os.path.basename(src))[0]
for i, page in enumerate(reader.pages, 1):
w = PdfWriter()
w.add_page(page)
dst = os.path.join(out_dir, f"{base}_p{i}.pdf")
with open(dst, "wb") as f:
w.write(f)
print(f"共拆分为 {len(reader.pages)} 个单页文件到 {out_dir}")
按范围拆分时,把 reader.pages 换成切片(如 reader.pages[0:5])并写入一个 writer。
5. 旋转页面
import sys
from pypdf import PdfReader, PdfWriter
src, out, deg = sys.argv[1], sys.argv[2], int(sys.argv[3]) # deg: 90/180/270
reader, writer = PdfReader(src), PdfWriter()
for page in reader.pages:
page.rotate(deg) # 顺时针
writer.add_page(page)
with open(out, "wb") as f:
writer.write(f)
print(f"已将全部页面旋转 {deg} 度 -> {out}")
6. 加密 / 解密
import sys
from pypdf import PdfReader, PdfWriter
# 加密:python3 job.py enc in.pdf out.pdf 用户密码
# 解密:python3 job.py dec in.pdf out.pdf 原密码
mode, src, out, pwd = sys.argv[1:5]
reader = PdfReader(src)
if mode == "dec":
if reader.is_encrypted:
reader.decrypt(pwd)
writer = PdfWriter()
for page in reader.pages:
writer.add_page(page)
if mode == "enc":
writer.encrypt(pwd)
with open(out, "wb") as f:
writer.write(f)
print(f"{mode} 完成 -> {out}")
7. 读取元数据
import sys
from pypdf import PdfReader
r = PdfReader(sys.argv[1])
m = r.metadata or {}
print("页数:", len(r.pages))
print("标题:", getattr(m, "title", None))
print("作者:", getattr(m, "author", None))
print("主题:", getattr(m, "subject", None))
print("创建工具:", getattr(m, "creator", None))
print("是否加密:", r.is_encrypted)
8. 填写 PDF 表单
先探明表单字段名,再按名赋值。
import sys
from pypdf import PdfReader
# 第一步:列出字段
fields = PdfReader(sys.argv[1]).get_fields() or {}
for name, f in fields.items():
print(f"{name} ({f.get('/FT')}) 当前值={f.get('/V')}")
import sys, json
from pypdf import PdfReader, PdfWriter
# 第二步:写入。data.json 形如 {"姓名":"张三","日期":"2026-07-11"}
src, out, data_json = sys.argv[1], sys.argv[2], sys.argv[3]
data = json.loads(open(data_json, encoding="utf-8").read())
reader = PdfReader(src)
writer = PdfWriter()
writer.append(reader)
for page in writer.pages:
writer.update_page_form_field_values(page, data)
with open(out, "wb") as f:
writer.write(f)
print(f"已填写 {len(data)} 个字段 -> {out}")
复选框的值通常是 /Yes 或字段自带的导出值,不确定时先用第一步列出字段再赋值。
9. 生成新 PDF
简单文本用 canvas,多页排版用 platypus。
from reportlab.lib.pagesizes import A4
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
from reportlab.lib.styles import getSampleStyleSheet
doc = SimpleDocTemplate("/tmp/report.pdf", pagesize=A4)
styles = getSampleStyleSheet()
story = [
Paragraph("报告标题", styles["Title"]),
Spacer(1, 12),
Paragraph("这里是正文内容。" * 10, styles["Normal"]),
]
doc.build(story)
print("已生成 /tmp/report.pdf")
注意:reportlab 内置字体不含 Unicode 上下标字符(如 ₂ ² 等),会渲染成黑块。需要上下标时用 Paragraph 的 <sub> / <super> 标签,例如 Paragraph("H<sub>2</sub>O", styles["Normal"])。中文若出现方块,需注册中文字体(如系统 STHeiti / 思源黑体)再用。
10. PDF 转图片
import sys, os
from pdf2image import convert_from_path
src, out_dir = sys.argv[1], sys.argv[2]
os.makedirs(out_dir, exist_ok=True)
pages = convert_from_path(src, dpi=150)
for i, img in enumerate(pages, 1):
img.save(os.path.join(out_dir, f"page_{i}.png"), "PNG")
print(f"已渲染 {len(pages)} 页为 PNG 到 {out_dir}")
pdf2image 依赖系统 poppler,若报错提示找不到 pdftoppm,先 brew install poppler(macOS)。
11. 图片转 PDF
import sys
from PIL import Image
*imgs, out = sys.argv[1:] # 最后一个是输出 pdf
frames = [Image.open(p).convert("RGB") for p in imgs]
frames[0].save(out, save_all=True, append_images=frames[1:])
print(f"已把 {len(frames)} 张图合成 -> {out}")
执行约定
- 先
Read或ls确认输入 PDF 存在、拿到绝对路径。 - 跑第 0 步依赖检测,只安装当前任务需要的库。
- 把对应片段写入临时
.py(放到系统临时目录),用Bash执行并传绝对路径参数。 - 操作完成后向用户报告输出文件位置和关键结果(页数、导出条数等)。
边界
- 扫描件/图片型 PDF 提取不到文本,需要 OCR。可先用第 10 步把页面转成图片,再用 OCR(如
pytesseract,需系统tesseract)识别;OCR 属重依赖,先跟用户确认再装。 - 不覆盖数字签名、复杂 XFA 动态表单、PDF/A 归档合规等高级场景——遇到时说明限制。
- 加密相关操作只做技术处理,不用于绕过你无权访问的受版权保护文档。
- 所有写操作输出到新文件,不原地覆盖源 PDF;确需覆盖时先明确告知用户。
- 处理超大文件(数百页)时注意内存,可分批处理。