文章 · 2024-10-06

复杂结构文档解析:Keynote 转换与自定义 Reader 双路径实践

路径 A:自定义 Reader 直接解析 .key 文件

Keynote .key 文件并非文本格式。该格式采用苹果的专有压缩二进制——本质上是一个包含多种资源的 ZIP 压缩包:图像、视频,以及使用 Snappy 压缩的 Protobuf (.iwa) 二进制数据,存储文档元数据、文本和所有结构定义。这意味着标准文本解析库完全无效;提取内容需要专门的工具或库来解包并读取内部结构。

开源项目 keynote-parser 提供了一个可行的方案。它将 Keynote 文件解压并解码成可读的文本形式(通常为 YAML),适合提取。具体来说,keynote-parser 将 .key 文件解包为文件夹结构,包含解析后的 YAML 文件,你可以从中提取幻灯片文本、演讲者备注、表格内容和其他数据。基本工作流如下所示:

# 安装 keynote-parser 工具
pip install keynote-parser

# 将 Keynote 文件解包为文件夹结构(生成 ./MySlides/ 目录)
keynote-parser unpack MySlides.key

执行后,生成名为 MySlides/ 的文件夹,包含解析的 YAML 和资源。每张幻灯片可能对应一个 Slide-XXXX.yaml,记录对象属性和文本。编写脚本读取这些 YAML 文件,提取所需的文本和结构。对于表格,在 YAML 中定位单元格文本;对于形状和流程图,提取文本标签(但形状之间的线条连接可能无法直接转换为纯文本)。

实现自定义 Reader 时,可将提取的内容组装为结构化文本(Markdown 或用于表格的自定义标记)或生成用于索引的文本块。LangChain 或 LlamaIndex 等框架通过继承其基类支持自定义文档加载器。在 LangChain 中,可以子类化 BaseLoader 创建一个 KeynoteLoader,其 load() 方法实现解析逻辑:

from langchain.docstore.document import Document
from langchain.document_loaders import BaseLoader
import subprocess, os

class KeynoteLoader(BaseLoader):
    def __init__(self, file_path: str):
        self.file_path = file_path

    def load(self) -> list[Document]:
        # 使用 keynote-parser 将 .key 解包
        output_dir = os.path.splitext(self.file_path)[0]  # 去掉扩展名作为文件夹名
        subprocess.run(["keynote-parser", "unpack", self.file_path, "-o", output_dir])
        text_chunks = []
        # 遍历解包后的目录,读取所有 YAML 文件中的文本内容
        for root, dirs, files in os.walk(output_dir):
            for fname in files:
                if fname.endswith(".yaml"):
                    text_chunks.append(extract_text_from_yaml(os.path.join(root, fname)))
        # 将提取的文本合并为一个字符串,或根据需要拆分
        full_text = "\n".join(text_chunks)
        # 封装为 Document 对象返回
        return [Document(page_content=full_text, metadata={"source": self.file_path})]

extract_text_from_yaml 函数(由你实现)解析 YAML 并检索文本字符串,排除样式和其他无关元数据。此加载器随后可处理 .key 文件。索引时,你从幻灯片描述、表格数据和其他元素获得原生文本内容,无需中间转换。

优点: 路径 A 避免了转换期间的信息丢失。理论上,它提取更丰富的结构化信息——表格保留行列对应关系,幻灯片备注可访问(如果存储在 .key 文件中),甚至流程图文本标签也可检索。自定义解析还允许特殊处理:用 Markdown 语法标记表格文本以保留结构,便于后续答案生成。

挑战: 路径 A 需要工程投入。虽然 keynote-parser 处理格式解码,但你仍需理解其输出结构才能正确提取内容。该方法依赖特定 Keynote 格式定义;新版本可能需要解析器更新。不过,截至 2025 年,格式演变频率不高,现有工具支持当前版本。路径 A 适合对解析质量要求高且具备开发能力的团队,能最大化定制复杂内容的处理。

路径 B:Keynote 转 PDF 再用成熟解析工具

路径 B 更直接、更通用:批量将 Keynote 文件转换为 PDF,然后用成熟的文档解析工具提取信息。PDF 解析库和服务擅长版面分析、文本提取和表格识别。Keynote 本身提供 PDF 导出功能;可通过 AppleScript 或 Keynote 命令行工具自动化批量转换。

从 Keynote 自动导出 PDF

在 macOS 上,AppleScript 提供对 Keynote 的脚本控制。编写脚本打开每个 .key 文件并在后台执行导出到 PDF 的操作。以下是导出单个 Keynote 文档的简单示例:

-- 假设 Keynote 已安装在本机
set keynoteFile to POSIX file "/路径/至/文件/MySlides.key" as alias
set exportPath to POSIX file "/路径/至/导出/MySlides.pdf" as text

tell application "Keynote"
    open keynoteFile
    -- 将当前打开的文档导出为 PDF
    export document 1 to file exportPath as PDF
    close document 1
end tell

这调用了 Keynote 的导出命令将第一个文档保存为 PDF。你可以通过终端 osascript 批量处理目录中的所有 .key 文件:

# 假设 current_dir 下有多个 .key 文件
for f in *.key; do
  echo "Converting $f to PDF..."
  osascript -e "tell application \"Keynote\" to export document 1 of (open POSIX file \"$(pwd)/$f\" as alias) to POSIX file \"$(pwd)/${f%.key}.pdf\" as PDF"
done

shell 脚本使用一行 AppleScript 打开并导出每个 Keynote 文件(注意:$(pwd) 展开为当前目录的完整路径;${f%.key}.pdf 将扩展名替换为 .pdf)。无需手动操作,高效地批量转换 Keynote 文件到 PDF。应用几个实用考虑:

获得 PDF 文件后,其余部分是标准 PDF 解析。

解析 PDF 内容

众多工具高效解析 PDF 文档内容。常见选择包括:

以 Unstructured 为例:

from unstructured.partition.pdf import partition_pdf

pdf_file = "MySlides.pdf"
elements = partition_pdf(filename=pdf_file)
# elements 是文档元素列表,可包含 Title, NarrativeText, Table 等不同类型
text_segments = [elem.text for elem in elements if hasattr(elem, "text")]
full_text = "\n".join(text_segments)
print(full_text[:500])  # 打印前500字符预览

此代码使用 partition_pdf 将 PDF 分解成元素列表,然后连接文本。对于表格元素,Unstructured 通常将其转换为 Markdown 表格语法以保留结构。可按元素类型进一步处理——为内容添加幻灯片标识符或将每张幻灯片作为单独 Document 以保持分段索引。

使用 LangChain 的加载器,代码更简洁:

from langchain.document_loaders import PyPDFLoader

loader = PyPDFLoader("MySlides.pdf")
docs = loader.load()  # 将PDF按页面等切分成多个Document
print(docs[0].page_content[:200])  # 打印第一个Document的一部分文本

LangChain 的加载器将 PDF 拆分为一个或多个 Document(通常按页拆分,或按自定义策略),可直接用于向量嵌入和索引。要将每张幻灯片保持为单独段落,导出时选择一张幻灯片为一页,则每页成为一个 Document。

解析质量取决于 PDF 内容表达方式。 大多数幻灯片文本在 PDF 中仍可提取(可选中复制),解析工具检索所有文本。流程图将文本标签转译为纯文本,但失去箭头关系和语义连接。包含流程节点的幻灯片产生提取的节点标题和描述;"指向"关系消失。表格通常作为 PDF 中的单元格文本出现;解析工具可能输出连续文本行或结构化表格(来自高级服务)。路径 B 快速提取文本但表达结构简单,丧失版式信息。对于 RAG 应用,文本检索比结构更重要,此方法足以满足大多数检索需求。

实现提示: 为向量数据库后处理结果,在解析阶段添加元数据。包括幻灯片编号、标题或标签数据(如"来自第 X 页表格"),以便检索结果追溯源或渲染上下文。使用 LlamaIndex,直接将解析文本传入索引;使用向量数据库 + LangChain,将 Document 列表存入数据库并为每个 Document 附加元数据,如 {"source": "slides1.pdf", "page": 2}

常见误区与注意事项

为索引解析像 Keynote 演示文稿这样的复杂文档会遇到可预见的陷阱:

实现策略

对于包含复杂结构(流程图、表格、备注)的 Keynote 文档,构建高质量索引以支持 RAG 应用需在解析阶段下功夫。两条实践路径出现:其一定制一个 reader 直接解析 .key 内部并提取原始内容;其二转换为通用 PDF 并应用成熟解析工具。各适应不同场景——自定义解析产生更丰富的结构化信息但实现成本高;PDF 转换快速、可靠,利用现有工具。

对于数据工程和 AI 应用开发,混合方法最优:对特别重要或复杂的文件投入精力定制解析逻辑(路径 A)以确保关键内容不丢失并保留结构;对大批标准文件使用转换和解析工作流(路径 B)以获得良好的成本效益。避免常见误区,尊重工具能力和转换细节,大幅改进复杂文档的可解析性和检索质量。

有了本指南,你可以自信地处理 Keynote 文档解析。从导出脚本编写到解析器实现,提供了具体示例和经验教训。选择适合你项目的方法,持续改进解析结果,为后续 RAG 应用建立坚实的数据基础。

© 2026 Yuxu Ge ·