
什么是 anydoc?
anydoc 是一个快速的 Rust 库,用于将文档转换为整洁的 GitHub 风格 Markdown。它支持 Word、PowerPoint、Excel、OpenDocument、RTF、EPUB、CSV 和基于文本的 PDF 文件。该项目还提供命令行界面,以及适用于 Node.js、Python 和浏览器端 WebAssembly 的绑定。
该库专为接收多种文档格式、但需要统一 Markdown 表示形式的处理流水线而设计。每种受支持的办公文档格式都会被解析为共享文档模型,并通过同一个 Markdown 序列化器处理。这可确保不同格式中的表格、转义、标题、脚注、链接、列表及其他结构保持一致。
转换在本地运行,无需机器学习模型或外部服务。README 显示,在其基准测试中,每份文档的转换时间中位数低于 5 毫秒。基于文本的 PDF 通过 pdf-inspector 在本地处理。
anydoc 不执行 OCR。不含可读文本、仅由图像构成或扫描生成的 PDF 可能会被报告为不受支持。对于还需要 OCR 的工作流,该项目提到可使用托管式替代方案 Firecrawl Parse。
主要功能
- 一致的 Markdown:所有办公文档格式均使用共享文档模型和序列化器。
- 结构化输出:支持的结构包括标题和锚点、链接、内部引用、列表、表格、块引用、脚注、尾注、演讲者备注、行内代码和代码块。
- 保留格式:可表示粗体、斜体、删除线、任务列表、嵌套列表、原始编号、合并的表格单元格和表头行。
- 嵌入式资源:嵌入的图像和对象在 Markdown 中显示为替代文本,而其原始字节和媒体类型仍可通过文档模型获取。
- 基于内容的检测:PDF、RTF、OLE 和基于 ZIP 的文档格式根据其字节内容进行识别,而非仅依赖文件扩展名。
- 非阻塞绑定:Node.js 转换使用 libuv 线程池,而 Python 会在转换期间释放全局解释器锁。
- 类型化软件包:包含 TypeScript 类型和 Python 存根。
- Agent 集成:该项目包含一个 Agent Skill,用于教兼容的编程 Agent 使用 anydoc CLI。
支持的文件格式
- Word:
.doc、.docx和.docm - PowerPoint:
.ppt、.pps、.pot、.pptx、.pptm、.ppsx和.ppsm - Excel:
.xls、.xlsx、.xlsm和.xlsb - OpenDocument:
.odt、.ods和.odp - 其他格式:
.rtf、.epub、.csv和.pdf
选择安装方式
对于 Shell 脚本和一次性转换,请使用 CLI。当文档转换是应用程序的一部分时,可选择 Node.js 或 Python;原生集成可选择 Rust;需要直接在浏览器中转换文件时,则可选择 WebAssembly。
安装 CLI
你可以使用 npx 运行预构建的 CLI,无需永久安装:
npx @firecrawl/anydoc report.docx首次运行时会下载适用于当前平台的预构建二进制文件。要将 anydoc 作为永久可用的命令,请进行全局安装:
npm install -g @firecrawl/anydoc
anydoc --help为 Node.js 安装
npm install @firecrawl/anydoc为 Python 安装
pip install firecrawl-anydoc为浏览器 WebAssembly 安装
npm install @firecrawl/anydoc-wasm你也可以试用浏览器演示。该演示会在本地运行 WebAssembly 库,因此所选文件不会离开本机。
为 Rust 安装
cargo add anydoc安装 Agent Skill
要教兼容的 Agent 使用 anydoc CLI 转换文档,请安装随附的 Skill:
npx skills add firecrawl/anydocREADME 将 Claude Code、Codex、Cursor 和 OpenCode 列为兼容客户端。
使用 CLI 转换文件
将 Markdown 写入标准输出
将源文档作为第一个参数传入。生成的 Markdown 会输出到标准输出:
npx @firecrawl/anydoc report.docx当你想检查结果或将其通过管道传递给其他命令时,这种方式很方便。
将 Markdown 保存到文件
使用 -o 指定输出文件:
npx @firecrawl/anydoc slides.pptx -o slides.md转换来自标准输入的数据
使用 - 作为输入路径,从标准输入读取字节。CSV 的内容中没有可靠的签名,因此需要显式指定其格式:
npx @firecrawl/anydoc - --format csv < data.csv在 Node.js 中使用 anydoc
Node.js 软件包针对文件路径、字节数组和共享文档模型提供了不同的函数。
转换文件路径
import { toMarkdown } from '@firecrawl/anydoc';
const markdown = await toMarkdown('report.docx');
console.log(markdown);转换字节数据
当文件已上传、下载或读入内存时,请使用 toMarkdownBytes:
import { toMarkdownBytes } from '@firecrawl/anydoc';
const markdown = await toMarkdownBytes(bytes);对于具有可识别签名的格式,anydoc 会根据内容检测格式。CSV 没有此类签名,因此需要传入格式名称:
const markdown = await toMarkdownBytes(bytes, 'csv');保留文档模型和资源
如果你需要的不只是最终的 Markdown 字符串,可使用 toDocument 停留在共享文档模型阶段:
import { toDocument } from '@firecrawl/anydoc';
const document = await toDocument(bytes);文档模型包含已解析的结构和嵌入式资源。虽然 Markdown 表示形式使用资源的替代文本,但嵌入式资源的字节数据及其媒体类型仍然可用。
在 Python 中使用 anydoc
转换文件路径
import anydoc
markdown = anydoc.to_markdown("report.docx")
print(markdown)转换内存中的字节数据
import anydoc
markdown = anydoc.to_markdown_bytes(data)对于 CSV 等没有签名的输入,请显式指定其名称:
markdown = anydoc.to_markdown_bytes(data, "csv")访问文档模型
document = anydoc.to_document(data)当应用程序需要已解析的文档及其嵌入式资源,而不只是序列化后的 Markdown 时,这种方法非常有用。
在浏览器中转换文档
WebAssembly 软件包可在浏览器本地转换字节数组。调用转换函数之前,需要先初始化模块:
import init, { toMarkdownBytes, toDocument } from '@firecrawl/anydoc-wasm';
await init();
const markdown = toMarkdownBytes(bytes);与其他绑定一样,转换 CSV 时需要提供格式:
const markdown = toMarkdownBytes(bytes, 'csv');要保留文档模型和嵌入式资源,请使用 toDocument:
const document = toDocument(bytes);与前面展示的 Node.js API 不同,README 中的 WebAssembly 转换调用在初始化后是同步执行的。
在 Rust 中使用 anydoc
转换文件路径
let markdown = anydoc::to_markdown("report.docx")?;通过自动检测转换字节数据
let markdown = anydoc::to_markdown_bytes(&bytes, None)?;显式指定 CSV
let markdown = anydoc::to_markdown_bytes(
&bytes,
anydoc::Format::Csv,
)?;返回文档模型
let document = anydoc::to_document(&bytes, None)?;了解格式检测
anydoc 会检查 PDF 标头、RTF 起始组、OLE 流名称和 ZIP 软件包元数据等内容标记。这意味着,即使文件的标签或扩展名有误,只要能够根据字节识别其实际格式,仍然可以进行转换。
Rust 提供了用于检查字节数据、扩展名和路径的辅助函数:
Format::from_bytes(&bytes);
Format::from_extension("pptm");
Format::from_path(Path::new("report.odt"));第一个调用会返回检测到的格式,例如 Some(Format::Docx);如果没有匹配任何已知内容标记,则返回 None。Node.js 中提供了名为 formatFromBytes 等同类辅助函数,Python 中则提供了名为 anydoc.format_from_bytes 等同类函数。
为什么 CSV 需要特殊处理
CSV 是纯文本,没有能够唯一识别它的标准字节签名。转换未命名的 CSV 字节数据或标准输入时,请显式提供 csv。如果文件路径包含 .csv 扩展名,则可通过扩展名识别。
处理转换错误
如果无法生成有意义的 Markdown,转换会返回错误。Rust 通过 ConvertError 暴露这些情况:
- Unsupported:格式未知或无法转换,包括仅含图像的 PDF。
- Malformed:文件结构不可用,无法提取有意义的内容。
- Encrypted:文档已加密或受密码保护。
- ResourceLimit:转换超过了与解压缩、嵌套或节点数量相关的固定安全限制。
- MissingPart:缺少必需的文档组件。
- Io:无法读取传给
to_markdown的文件。
批处理流水线可以记录已加密或不受支持的文件,并继续处理其余输入:
match anydoc::to_markdown(path) {
Ok(markdown) => Some(markdown),
Err(error @ (ConvertError::Encrypted | ConvertError::Unsupported(_))) => {
unconverted.push((path, error));
None
}
Err(error) => return Err(error),
}在 Node.js 和 WebAssembly 中,可以通过 error.code 获取错误变体名称。对于每种转换错误变体,Python 都会抛出专用的 anydoc.ConvertError 子类;当文件无法读取时,则会抛出 OSError。
高级集成技巧
上传流水线优先使用字节转换
如果应用程序已从上传、对象存储或 HTTP 响应中获得文档字节,请使用 toMarkdownBytes 或 to_markdown_bytes。这样既不需要文件路径,也能让 anydoc 检查实际内容,而不是信任用户提供的文件名。
资源很重要时使用文档模型
Markdown 本身不包含嵌入对象的原始字节。当你需要存储、检查或单独处理嵌入式资源及其媒体类型时,请使用 toDocument 或 to_document。
单独处理扫描版 PDF
anydoc 的内置 PDF 支持面向基于文本的 PDF,不包含 OCR。应将仅含图像的 PDF 转交给支持 OCR 的工作流,而不是反复尝试在本地转换。
显式选择 CSV 格式
处理混合的内存输入时,请为 CSV 字节关联显式格式元数据。其他受支持的格式通常可以根据内容检测,但 CSV 需要名称、扩展名或显式格式参数。
利用运行时行为
Node.js 转换在线程池 libuv 上运行,不会阻塞事件循环。Python 会在转换期间释放 GIL,使其他 Python 线程能够继续运行。这些特性有助于将转换功能集成到并发应用程序中。
在下游使用一致的输出
由于各种办公文档格式共用一个序列化器,下游系统可以使用统一的 Markdown 结构,而不必为 Word、PowerPoint、Excel、RTF 和 OpenDocument 输入分别维护清理规则。因此,anydoc 尤其适合搜索索引、提取流水线,以及为语言模型准备结构化文本。
转换流水线的工作原理
- anydoc 检查文档字节或可用的文件名信息,以确定格式。
- 特定于格式的解析器读取 Word、PowerPoint、Excel、OpenDocument、RTF、EPUB 或 CSV 内容。
- 解析器创建一个共享文档模型,其中包含块、行内元素、表格、脚注和资源。
- 通用序列化器将该模型转换为 GitHub 风格 Markdown。
- PDF 通过 pdf-inspector 处理,并直接转换为 Markdown。
这种架构意味着,序列化器的一项改进可以同时惠及多种格式。例如,表格转义修复不会仅局限于某一个输入解析器。
性能与适用场景
该代码仓库报告了一项基准测试,涵盖 14 种已测试格式的 100 份真实文档。anydoc 覆盖了全部 14 种格式,转换时间中位数为 4.4 毫秒,并在该项对比中获得了 81 分的总体质量评分。质量评估维度包括完整性、结构、格式和整洁度。
基准测试结果取决于语料库、硬件和测试方法,因此应将随附的基准测试视为项目特定的证据,而不是对所有工作负载的保证。该项目所描述的最佳适用场景,是接收混合办公文档集合,并需要快速、结构化且一致的 Markdown 的处理流水线。
总结
anydoc 为 Rust、Node.js、Python、浏览器和命令行提供了统一的转换工作流。可以先使用 CLI 进行快速测试,再选择与应用程序匹配的绑定。对于上传的文件,请使用基于字节的转换;显式指定 CSV 格式;当嵌入式资源很重要时保留文档模型;并将加密、损坏、不受支持或扫描生成的文档作为单独情况处理。
