一份 Markdown 文件就能让 Claude 获得专业 PDF 处理能力——本文带你从需求分析到代码实现,完整走一遍。文末附完整源码获取方式。


你是否遇到过这些场景?扫描版 PDF 里的文字无法搜索复制、几十页的合同要逐行翻找关键条款、上百份 PDF 需要逐个提取表格数据……如果有一个 AI 助手,一句话就能帮你搞定这些事呢?

什么是 Claude Skill?

Claude Skill 可以理解为给 Claude AI 安装的"专业插件"——它不需要写 App、不需要后端服务,一份 Markdown 文件就能让 Claude 获得专项能力。你只需要用自然语言描述你的需求,它就能自动识别、路由、执行。

本文能带给你什么

今天,我以自己开发的 PDF 智能阅读助手 Pro(已迭代至 v1.2.1)为例,从需求分析到代码实现,带你完整走一遍 Claude Skill 的开发流程。读完你将获得:

  • 🧠 一套 Skill 设计方法论:触发词设计、决策树路由、工作流编排

  • 🛠️ 一个可直接使用的 PDF 处理工具:覆盖文本提取、OCR、表格、加密、批量处理

  • 🚀 从设计到发布的全流程经验:包含安全防护、错误处理、版本迭代

废话不多说,我们开始。


一、需求分析:PDF 阅读的 7 大场景

在动手写代码之前,先梳理一下用户面对 PDF 时的真实痛点。我把需求归纳为 7 个核心场景:

场景 用户痛点 技术挑战
📄 纯文本提取 PDF 文字无法直接复制、编辑 多页拼接、编码兼容
🔍 扫描件 OCR 图片型 PDF 完全不可搜索 OCR 引擎选型(轻量 vs 高精度)
📊 表格提取 手动抄表费时易错 表格边界检测、结构化输出
🔐 加密 PDF 忘记密码、供应商加密文件 多策略解密、用户交互
📦 大文件处理 200 页 PDF 一次读取内存爆炸 分页流式、增量分析
📝 Markdown 导出 想保留标题/目录等结构 格式推断、目录生成
🔄 批量处理 几十份文件逐个处理效率低 并发控制、进度反馈

6 个功能型场景 + 1 个调度型场景(批量处理),共同构成一个完整的 PDF 处理工具矩阵。

设计原则:决策树路由

面对 7 种不同的场景,用户不可能记住所有命令。我设计了一套决策树路由——用户只需用自然语言描述需求(比如"帮我提取这份 PDF 里的表格"),Skill 自动判断文件类型、选择合适的处理引擎,一条龙完成。这个设计我们在第二章展开。


二、架构设计:触发词 + 决策树 + 工作流编排

2.1 触发词设计

触发词决定了用户在什么情况下能"唤醒"你的 Skill。我按照三个维度设计了触发词矩阵:

口语化表达(覆盖日常使用):

PDF阅读  读PDF  提取PDF  PDF总结  PDF转文本  PDF转Markdown

专业场景(覆盖特定需求):

合同提取  论文摘要  识别扫描件  PDF表格  PDF摘要  PDF关键词
PDF问答  发票提取  简历解析

批处理场景(覆盖效率需求):

批量处理PDF  PDF批量  PDF导出

设计原则:触发词要宽不要窄。用户说"帮我读下这个 PDF"和"提取这份 PDF 的文字",在 Skill 看来是同一个需求——都要走文本提取工作流。宁可多注册几个触发词,也不要让用户遇到"不知道怎么触发"的尴尬。

2.2 决策树路由

当 Skill 被触发后,如何决定走哪个工作流?核心就是这个决策树:

7 条路径,一次触发自动路由,用户不需要关心内部逻辑。这就是决策树设计的价值——把复杂度留给自己,把简洁留给用户

2.3 工作流编排三原则

每个工作流在设计时遵循三条原则:

原则 说明 示例
独立可组合 每个工作流可独立调用,也可串联 先 OCR → 再总结,或直接提取
自动降级 高精度方案不可用时自动切换 marker-pdf 不可用 → pytesseract → pymupdf fallback
统一入口 用户无需关心内部路由 一句话自动分发到正确工作流

这三条原则确保 Skill 在不同环境下都能正常运行,不会因为某个依赖缺失就全线崩溃。


三、核心实现:5 个关键代码模块精讲

下面进入实战环节。我从完整的 700 行 Skill 中精选了 5 个核心模块,逐一拆解。

3.1 文本提取引擎

场景:用户上传一份 PDF,需要提取全部文字内容进行搜索、分析或存档。

import fitz  # pymupdf
​
def extract_text_pdf(filepath, max_pages=None):
    filepath = safe_path(filepath)  # 安全校验(见第四章)
    doc = fitz.open(filepath)
    text_parts = []
    pages = min(len(doc), max_pages or float('inf'))
    for i in range(int(pages)):
        text = doc[i].get_text()
        if text.strip():
            text_parts.append(f"[第{i+1}页]\n{text}")
    doc.close()
    return "\n\n".join(text_parts)

为什么选 pymupdf? 三个理由:

  • 轻量:安装包 ~50MB,不依赖系统库

  • 跨平台:Windows/Linux/macOS 均可运行

  • 功能全面:除了文本提取,还支持元数据读取、页面渲染、加密检测

设计亮点[第{i+1}页] 标注不是可有可无的装饰——它让 LLM 在分析时能精确定位内容来源,比如"合同第 3 页第 2 段提到……"

3.2 智能 OCR 引擎

场景:扫描版 PDF 本质上是图片,需要 OCR 才能提取文字。

def smart_ocr(filepath):
    # 第一级:pytesseract(轻量,沙箱可用)
    try:
        text = ocr_tesseract(filepath)
        if text and len(text) > 100:
            return text, "pytesseract"
    except Exception:
        pass
​
    # 第二级:marker-pdf(高精度,需 8GB+ RAM)
    try:
        import marker
        text, _ = ocr_marker(filepath)
        if text:
            return text, "marker-pdf"
    except (ImportError, MemoryError):
        pass
​
    # 第三级:pymupdf 直接提取(终极降级)
    return pymupdf_fallback(filepath), "pymupdf_fallback"

三级降级策略是这套 OCR 系统最核心的设计:

级别 引擎 精度 资源需求 适用环境
1 pytesseract ⭐⭐⭐ ~200MB RAM 所有环境(含沙箱)
2 marker-pdf ⭐⭐⭐⭐⭐ 8GB+ RAM 服务器/高配机器
3 pymupdf fallback ~50MB RAM 终极兜底

设计理念:不假设用户环境,能用最好的就用最好的,不行就降级,但绝不让用户看到报错页面。

3.3 加密 PDF 解密器

场景:供应商发来的合同有密码保护,或者用户自己忘了密码。

def handle_encrypted(filepath, password=None):
    doc = fitz.open(filepath)
    if not doc.needs_pass:
        return doc, "no_password"
​
    # 策略1:用户提供的密码
    if password and doc.authenticate(password):
        return doc, "decrypted"
​
    # 策略2:从文件名猜测(如 合同_123456.pdf)
    basename = os.path.basename(filepath)
    pwd_match = re.search(r'[_-](\d{4,8})', basename)
    if pwd_match:
        doc2 = fitz.open(filepath)
        if doc2.authenticate(pwd_match.group(1)):
            return doc2, "auto_decrypted"
        doc2.close()
​
    # 策略3:常见密码字典
    for pwd in ["123456", "password", "000000", "1234", "admin"]:
        doc3 = fitz.open(filepath)
        if doc3.authenticate(pwd):
            return doc3, f"decrypted_with_{pwd}"
        doc3.close()
​
    raise PermissionError("所有解密策略失败,请手动提供密码")

密码策略优先级:用户提供 → 文件名猜测 → 常见密码字典 → 报错请求用户输入。注意这里不做暴力破解——既是为了安全边界,也是因为 pymupdf 不提供那种接口。

3.4 LLM 分析模板

场景:文本提取只是第一步,真正的价值在于——让 LLM 帮你理解和分析内容。

我针对 4 种高频需求设计了结构化 Prompt 模板:

📋 合同提取

从合同PDF提取JSON:{"合同标题","签约方","合同金额","签署日期",
"有效期","违约责任","终止条件","保密条款","争议解决"}
内容:{text[:12000]}

📄 论文摘要

提取:1.标题 2.作者 3.研究问题 4.方法论 5.主要发现 6.创新点 7.局限性
内容:{text[:10000]}

🧾 发票提取

从发票PDF提取JSON:{"发票号码","开票日期","销售方","购买方",
"金额合计","税额","价税合计"}
内容:{text[:8000]}

👤 简历解析

从简历PDF提取JSON:{"姓名","学历","毕业院校","工作年限",
"当前职位","技能标签","工作经历":[]}
内容:{text[:10000]}

模板设计技巧

  1. JSON Schema 约束:强制 LLM 输出结构化数据,方便后续程序处理

  2. 截断策略{text[:8000]} 不是随便截的——合同通常前几页就有核心条款,发票信息在第一页就能找全

  3. Token 预算:8000 字符 ≈ 2000 token,留足余量给 LLM 的回复

3.5 批量处理调度器

场景:用户有一个文件夹,里面躺着 50 份 PDF,需要全部处理。

from concurrent.futures import ThreadPoolExecutor, as_completed

def batch_process(folder, pattern="*.pdf", mode="extract", max_workers=2):
    files = sorted(glob.glob(os.path.join(folder, pattern)))
    results = {"total": len(files), "success": 0, "failed": 0}

    with ThreadPoolExecutor(max_workers=max_workers) as ex:
        futures = {ex.submit(process_one, f): f for f in files}
        for future in as_completed(futures):
            fp, info = future.result()
            if info["status"] == "ok":
                results["success"] += 1
            else:
                results["failed"] += 1
            # 实时进度条 + ETA 估算
            print(f"\r[{pct:.0f}%] OK:{results['success']} "
                  f"FAIL:{results['failed']} ETA:{eta:.0f}s")

    return results

设计考量

  • max_workers=2:不是越多越好。PDF 处理是 IO 密集型,2 个并发足够饱和磁盘读取,再多反而增加内存压力

  • 进度条 + ETA:批量处理最怕"黑盒等待"。实时反馈让用户知道还剩多久,大幅减少焦虑

  • 部分成功不过滤:一个文件失败不影响其他文件,最终报告给出完整成败明细


四、安全与容错:让 Skill 更健壮

4.1 路径安全校验

所有涉及文件路径的操作,都先过一道安全检查。我实现了一个 6 层防御的 safe_path() 函数:

def safe_path(filepath, base_dir=None):
    # 第1层:过滤危险字符(; | & $ ` 等命令注入字符)
    dangerous = [';', '|', '&', '$', '`', '(', ')', '{', '}', '<', '>']
    for ch in dangerous:
        if ch in filepath:
            raise ValueError(f"路径包含非法字符: {repr(ch)}")

    # 第2层:禁止通配符和空字节
    if '\x00' in filepath or '*' in filepath or '?' in filepath:
        raise ValueError("路径包含通配符或空字节")

    # 第3层:规范化路径(解析 ../ 防止目录穿越)
    normalized = os.path.normpath(os.path.abspath(filepath))

    # 第4层:限制在基准目录内
    if base_dir and not normalized.startswith(base_dir):
        raise ValueError(f"路径逃逸: {filepath}")

    # 第5层:文件扩展名白名单
    ext = os.path.splitext(normalized)[1].lower()
    if ext not in ['.pdf', '.md', '.json', '.txt']:
        raise ValueError(f"不支持的文件类型: {ext}")

    # 第6层:文件存在性检查
    if ext == '.pdf' and not os.path.exists(normalized):
        raise FileNotFoundError(f"文件不存在: {normalized}")

    return normalized

为什么需要 6 层? 每一层防御不同攻击面:命令注入(层1-2)、路径穿越(层3-4)、类型攻击(层5-6)。单层防御总有绕过方式,纵深防御才是可靠方案。

4.2 错误速查表

一个好用的工具,用户遇到问题时应该能自助排查。我在 Skill 里内置了一张错误速查表:

错误 原因 解决
FileNotFoundError 文件不存在/路径非法 检查路径,自动走 safe_path 校验
FileDataError 损坏/非 PDF 格式 确认文件格式正确
PermissionError PDF 已加密 走工作流 D,提供密码
MemoryError 文件太大/内存不足 走工作流 E 分页,降低并发数
ModuleNotFoundError 缺少依赖 pip install 对应库
tesseract not found 未装 OCR 引擎 按平台安装指南安装
RuntimeError: marker 环境不支持 marker-pdf 自动降级到 pytesseract

设计理念:错误信息不是给开发者看的日志——是给用户看的行动指南。每条都包含「原因」和「解决」,用户不需要懂技术也能按步骤操作。

4.3 大文件流式处理

处理 200 页以上的 PDF,一次性加载会导致内存溢出。解决方案:生成器模式。

def process_large(filepath, chunk_size=20):
    doc = fitz.open(filepath)
    total = len(doc)
    for start in range(0, total, chunk_size):
        end = min(start + chunk_size, total)
        chunk = ""
        for i in range(start, end):
            chunk += f"[第{i+1}页]\n{doc[i].get_text()}"
        yield chunk, start, end, total  # 生成器:按需产出,不是一次性返回
    doc.close()

yield 是关键——它不是一次性返回所有内容,而是每次只产出 20 页。LLM 可以分批处理,内存占用始终保持在可控范围。对于 500 页的 PDF,内存占用从 ~2GB 直接降到 ~100MB。


五、发布与迭代:从 1.0 到 1.2.1

5.1 版本演进

一个好用的 Skill 不是一次写成的。以下是我的迭代记录:

版本 新增内容 迭代动机
1.0 基础文本提取 + LLM 分析 MVP:先跑通核心链路
1.1 表格提取 + Markdown 导出 用户反馈:需要结构化输出
1.2 发票/简历模板、OCR 多引擎 场景扩展:从通用到垂直领域
1.2.1 路径安全校验、加密完整流程、智能 OCR 降级 安全加固 + 容错增强

迭代心得:1.0 不要追求完美。先把最小可用版本发布出去,从真实使用反馈中找迭代方向。我的 OCR 三级降级、密码多策略尝试,都是在用户遇到实际问题后才加上去的。

5.2 Skill 发布 Checklist

最后,总结一下发布一个 Skill 之前的三个检查项——我的血泪教训浓缩:

✅ 一、触发词覆盖率验证

用至少 20 句不同的自然语言命令测试,确保 Skill 能被正确触发。比如:

  • "帮我读一下这个 PDF" ✅

  • "提取合同里的金额" ✅

  • "这份扫描件帮我识别一下" ✅

  • "把这个文件夹里所有 PDF 转成 Markdown" ✅

✅ 二、依赖声明清晰

在 Skill 开头放一张依赖速查表,让用户一目了然:

功能 安装命令 环境要求
纯文本 pip install pymupdf 所有环境
OCR(推荐) pip install pytesseract pdf2image ~100MB 磁盘
OCR(高精度) pip install marker-pdf 8GB RAM

✅ 三、错误速查表

用户遇到报错时,错误信息应该直接告诉他怎么解决,而不是扔给他一个异常堆栈。详见 4.2 节。


结语

我们从需求分析出发,设计了 7 大场景的决策树路由,实现了 5 个核心代码模块,加入了 6 层安全防护和完整的错误容错体系,最终迭代到了 v1.2.1。

回顾全文,构建一个高质量 Claude Skill 的关键不是堆代码,而是:

  1. 理解场景 —— 搞清楚用户在什么情况下会用到你的 Skill

  2. 设计路由 —— 让用户用最简单的方式触发,Skill 内部自动分发

  3. 拥抱降级 —— 不假设用户环境,优雅地处理各种异常情况

  4. 迭代进化 —— 先发布 MVP,从反馈中找方向


完整 Skill 源码(700+ 行,含全部 7 个工作流)已开源,关注我即可获取。 后续我会持续分享更多 AI Agent 开发实战内容,包括:

  • 《Skill 的 TDD 开发:如何用测试驱动写出零 bug 的 Agent 指令》

  • 《Claude Skill 决策树设计模式》

  • 《AI Agent 安全防护实战》

你遇到过哪些 PDF 处理的痛点?或者你对 Skill 开发有什么疑问?欢迎在评论区交流 👇


本文由 PDF Reader Pro v1.2.1 作者原创,转载请注明出处。

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐