一、为什么开发这个技能?
1.1 痛点场景
作为一个重度依赖知识库的 AI 助手用户,我经常遇到这样的问题:
- 📄 PDF 文档:技术论文、产品手册、行业报告都是 PDF 格式,无法直接传递给 LLM
- 📊 PPT 演示:培训材料、会议记录以幻灯片形式存在,内容提取困难
- 📝 Word 文档:合同、方案、笔记是.docx 格式,需要转换为纯文本
- 🖼️ 图片内容:截图、图表、手写笔记中的文字需要 OCR 识别
- 在线转换工具:隐私风险、格式丢失、批量处理困难
- 本地脚本:每次都要重新运行,无法与对话系统集成
- 商业 API:成本高、调用限制、依赖外部服务
1.2 为什么选择 OpenClaw Skill?
OpenClaw 的 Skill 系统提供了完美的解决方案:
| 特性 | 价值 |
|---|---|
| 标准化接口 | 统一的输入/输出格式,易于集成 |
| 可组合性 | 可以与其他 Skill 串联使用 |
| 本地执行 | 数据不出本地,隐私安全 |
| 一次开发多处用 | 在任意 OpenClaw 会话中调用 |
二、技术选型与架构设计
2.1 功能需求
核心功能:
- PDF → Markdown (含表格/图片提取)
- PPTX → Markdown (按幻灯片分页)
- Word → Markdown (保留标题/列表结构)
- 图片 → Markdown (OCR 文字识别)
非功能需求:
- 单文件最大支持:50MB
- 处理速度:10 页/分钟
- 格式保留率:>90%
- 支持批量处理
2.2 技术栈选择
| 组件 | 选型 | 理由 |
|---|---|---|
| PDF 解析 | pymupdf + pdf2image |
性能好、支持表格提取 |
| PPT 解析 | python-pptx |
官方库、结构清晰 |
| Word 解析 | python-docx |
成熟稳定、社区活跃 |
| OCR 引擎 | paddleocr |
中文识别准确率高 |
| Markdown 生成 | 自研模板引擎 | 灵活控制输出格式 |
2.3 架构设计
┌─────────────────────────────────────────────────────┐
│ OpenClaw Gateway │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ Document Converter Skill │
│ ┌─────────────────────────────────────────────┐ │
│ │ SKILL.md (元数据) │ │
│ │ - 触发词:文档转换、PDF 转 Markdown... │ │
│ │ - 输入:文件路径/URL │ │
│ │ - 输出:Markdown 文本 + 提取的图片 │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────┐ │
│ │ converter.py (核心逻辑) │ │
│ │ - DocumentConverter 类 │ │
│ │ - convert() 主入口 │ │
│ │ - _convert_pdf/_convert_pptx 等子方法 │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────┐ │
│ │ utils/ (工具函数) │ │
│ │ - ocr.py: OCR 处理 │ │
│ │ - markdown.py: MD 生成 │ │
│ │ - image.py: 图片提取与保存 │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────┐
│ 输出目录 (knowledge/) │
│ - {文档名}.md │
│ - {文档名}_images/ (提取的图片) │
└─────────────────────────────────────────────────────┘
三、核心实现
3.1 Skill 元数据 (SKILL.md)
---
name: document-converter
description: |
文档转换技能 - 支持 PDF/PPT/Word/图片 转 Markdown
自动提取文字、表格、图片,生成结构化 Markdown 文档
trigger_keywords:
- "文档转换"
- "PDF 转 Markdown"
- "PPT 转换"
- "Word 转 Markdown"
- "图片转文字"
- "OCR"
Document Converter Skill
使用方法
单个文件转换
python converter.py --input path/to/file.pdf批量转换
python converter.py --input-dir ./docs/ --output-dir ./knowledge/
输出格式
- Markdown 文件:
knowledge/{文档名}.md
- 提取图片:
knowledge/{文档名}_images/
3.2 核心转换逻辑
class DocumentConverter:
"""文档转换器基类"""
def __init__(self, output_dir: str = "knowledge"):
self.output_dir = Path(output_dir)
self.output_dir.mkdir(exist_ok=True)
def convert(self, input_path: str) -> dict:
"""统一转换入口"""
path = Path(input_path)
if not path.exists():
raise FileNotFoundError(f"文件不存在:{input_path}")
# 根据扩展名选择转换器
converters = {
'.pdf': self._convert_pdf,
'.pptx': self._convert_pptx,
'.docx': self._convert_word,
'.png': self._convert_image,
'.jpg': self._convert_image,
}
converter = converters.get(path.suffix.lower())
if not converter:
raise ValueError(f"不支持的文件格式:{path.suffix}")
return converter(path)
def _convert_pdf(self, path: Path) -> dict:
"""PDF 转 Markdown"""
import fitz # PyMuPDF
doc = fitz.open(path)
md_content = []
for page_num, page in enumerate(doc, 1):
# 提取文字
text = page.get_text()
md_content.append(f"## 第{page_num}页\n\n{text}\n")
# 提取图片
images = page.get_images()
for img_idx, img in enumerate(images):
xref = img[0]
base_image = doc.extract_image(xref)
image_bytes = base_image["image"]
# 保存图片
img_path = self._save_image(image_bytes, path.stem, page_num, img_idx)
md_content.append(f"!图片{img_idx+1}\n")
# 写入 Markdown 文件
output_md = self.output_dir / f"{path.stem}.md"
with open(output_md, 'w', encoding='utf-8') as f:
f.write('\n'.join(md_content))
return {
"status": "success",
"output_file": str(output_md),
"pages": len(doc)
}
3.3 OCR 处理(图片转文字)
def ocr_image(image_path: str) -> str:
"""使用 PaddleOCR 识别图片文字"""
from paddleocr import PaddleOCR
ocr = PaddleOCR(use_angle_cls=True, lang='ch')
result = ocr.ocr(image_path, cls=True)
text_lines = []
if result and result[0]:
for line in result[0]:
text = line[1][0] # 文字内容
confidence = line[1][1] # 置信度
if confidence > 0.6: # 过滤低置信度结果
text_lines.append(text)
return '\n'.join(text_lines)
3.4 Markdown 格式化
def format_as_markdown(content: dict) -> str:
"""将解析结果格式化为 Markdown"""
md = []
# 标题
md.append(f"# {content['title']}\n")
# 元数据
if 'metadata' in content:
md.append("来源: " + content['metadata'].get('source', '未知'))
md.append("处理时间: " + content['metadata'].get('processed_at', ''))
md.append("")
# 正文
md.append("## 内容\n")
for section in content['sections']:
md.append(f"### {section['title']}\n")
md.append(section['text'])
md.append("")
# 表格
if 'tables' in content:
md.append("## 表格\n")
for table in content['tables']:
md.append(format_table_as_md(table))
md.append("")
return '\n'.join(md)
四、踩坑记录与解决方案
4.1 编码问题(Windows 特有)
问题:Windows 命令行输出中文乱码[ERROR] 无法写入文件:gbk codec can't encode character
# 方法 1:设置环境变量
import os
os.environ['PYTHONUTF8'] = '1'
方法 2:显式指定编码
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
方法 3:PowerShell 用户
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
4.2 大文件内存溢出
问题:处理 100+ 页 PDF 时内存暴涨 解决方案:# 逐页处理,避免一次性加载
def _convert_pdf_large(self, path: Path):
doc = fitz.open(path)
for page_num in range(len(doc)):
page = doc.load_page(page_num)
text = page.get_text()
# 每 10 页写入一次,释放内存
if page_num % 10 == 0:
self._flush_to_disk()
page.close()
doc.close()
4.3 表格格式丢失
问题:PDF 表格提取后变成纯文本,行列结构丢失 解决方案:def extract_tables(page):
"""使用 pdfplumber 提取表格"""
import pdfplumber
with pdfplumber.open(page) as pdf:
tables = pdf.pages[0].extract_tables()
for table in tables:
# 转换为 Markdown 表格
md_table = []
for row_idx, row in enumerate(table):
cells = [str(cell).strip() if cell else '' for cell in row]
md_table.append('| ' + ' | '.join(cells) + ' |')
if row_idx == 0:
md_table.append('| ' + ' | '.join(['---'] * len(cells)) + ' |')
yield '\n'.join(md_table)
五、使用示例
5.1 单个文件转换
cd skills/document-converter
python converter.py --input ~/Downloads/技术报告.pdf
[INFO] 正在处理:技术报告.pdf
[INFO] 检测到格式:PDF (15 页)
[INFO] 提取文字:23,456 字符
[INFO] 提取图片:8 张
[OK] 转换完成!
- Markdown: knowledge/技术报告.md
- 图片目录:knowledge/技术报告_images/
5.2 批量转换
python converter.py --input-dir ~/Downloads/ --output-dir ./knowledge/ --pattern "*.pdf"
5.3 在 OpenClaw 对话中使用
用户:帮我把这份 PDF 转成 Markdown
助手:[调用 document-converter skill]
✅ 转换完成!文件已保存到 knowledge/xxx.md
六、性能基准
| 文档类型 | 大小 | 处理时间 | 输出大小 |
|---|---|---|---|
| PDF (文字) | 10MB, 50 页 | ~8 秒 | 120KB |
| PDF (扫描版) | 15MB, 50 页 | ~45 秒 | 150KB + 图片 |
| PPTX | 5MB, 30 页 | ~5 秒 | 80KB |
| DOCX | 2MB, 20 页 | ~2 秒 | 50KB |
| 图片 (OCR) | 2MB | ~3 秒 | 5KB |
七、后续优化方向
- [ ] 支持更多格式(Excel、EPUB、MOBI)
- [ ] 增加 LLM 后处理(自动摘要、关键信息提取)
- [ ] 支持输出到 Obsidian/Notion 等知识库
- [ ] 添加进度条和断点续传
- [ ] 支持在线 URL 直接转换
八、相关资源
- OpenClaw 文档: https://docs.openclaw.ai
- Skill 源码:
skills/document-converter/ - PyMuPDF 文档: https://pymupdf.readthedocs.io/
- PaddleOCR: https://github.com/PaddlePaddle/PaddleOCR
本文基于实际项目开发经验整理,代码已开源到 OpenClaw 技能市场。