为什么你需要关注 anydoc

给大模型喂文档时,格式转换质量直接影响 RAG 效果。Word 里的表格错位、PPT 的层级丢失、PDF 的段落断裂——每一个格式问题都会让向量检索的精度大打折扣。过去我们不得不把 pandoc、python-docx、LibreOffice 等多个工具拼在一起,才能勉强覆盖主流办公格式,维护成本高、输出不一致。

anydoc 是 Firecrawl 团队用 Rust 从零打造的文档转换库,一个 API 调用搞定 14 种格式,中位数转换时间仅 4.4 毫秒。它已在 GitHub 斩获 12000+ Star,正在成为 RAG/LLM 工具链中文档预处理的新标准。

支持的 14 种格式

anydoc 的格式覆盖面是目前所有开源转换工具中最广的:

格式类别 支持的扩展名
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 .pdf

注意 .doc(旧版二进制格式)和 .docx(新版 XML 格式)都支持——这在同类工具中非常少见。大多数工具只支持 .docx,遇到老版 .doc 就束手无策。

性能实测:4.4ms 的秘密

anydoc 的官方基准测试在 100 份真实文档上对比了 7 款工具(包括它自己),覆盖全部 14 种格式:

工具 支持格式 中位数耗时 质量评分 完整性 结构 格式 清洁度
anydoc 14/14 4.4ms 81 87 79 78 81
libreoffice 12/14 1129.5ms 40 59 42 40 24
unstructured 8/14 572.9ms 63 76 59 51 63
markitdown 6/14 134.8ms 65 78 66 60 52
pandoc 5/14 102.1ms 56 74 57 56 38
docling 4/14 513.6ms 57 60 60 57 51
mammoth 1/14 52.5ms 70 84 71 75 51

几个关键发现:

  1. 速度碾压:anydoc 的 4.4ms 是第二名 pandoc(102.1ms)的 1/23,是 LibreOffice(1129.5ms)的 1/257
  2. 质量最高:在所有被评测的格式上,anydoc 的得分都是最高的
  3. 覆盖最全:唯一支持全部 14 种格式的工具
  4. Rust 优势:纯 Rust 实现,无 ML 模型依赖,无外部服务调用

质量评分由 Claude Sonnet 5 作为 LLM 裁判,对两份输出进行盲评对比(以 LibreOffice 渲染的前 6 页图片为基准),每个工具对每对比较进行两次评判以消除位置偏差,共计 482 次评判。

逐格式对比:anydoc 全面领先

下面的数据展示了各工具在相同格式上的直接对比(满分 100):

格式 anydoc libreoffice unstructured markitdown pandoc docling mammoth
.doc 87 57 67 - - - -
.docx 88 56 53 71 68 71 70
.pptx 74 24 - 66 - 52 -
.xlsx 72 30 66 55 - 47 -
.rtf 88 53 46 - 45 - -
.odt 80 51 68 - 60 - -
.epub 77 - 72 72 52 - -

mammoth 在 .docx 上得分 70 看似不错,但它只支持一种格式;anydoc 的 81 分是横跨全部 14 种格式的平均值,含金量完全不同。

安装与快速上手

anydoc 提供 Rust 核心 + Node.js / Python / WebAssembly 绑定,以及开箱即用的 CLI。

CLI 方式(零安装)

# 直接用 npx 运行,首次会自动下载预编译二进制
npx @firecrawl/anydoc report.docx               # 输出到 stdout
npx @firecrawl/anydoc slides.pptx -o slides.md  # 输出到文件
npx @firecrawl/anydoc - --format csv < data.csv # 从 stdin 读取

Python 集成

pip install firecrawl-anydoc
import anydoc

# 从文件路径转换
markdown = anydoc.to_markdown("report.docx")
print(markdown)

# 从字节转换(自动检测格式)
with open("report.docx", "rb") as f:
    data = f.read()
markdown = anydoc.to_markdown_bytes(data)

# 指定格式(CSV 等无魔数的格式需要)
markdown = anydoc.to_markdown_bytes(csv_data, "csv")

# 获取文档模型(包含嵌入图片等资源)
document = anydoc.to_document(data)

Node.js 集成

npm install @firecrawl/anydoc
import { toMarkdown, toMarkdownBytes, toDocument } from '@firecrawl/anydoc';

// 从文件路径
const md = await toMarkdown('report.docx');

// 从字节
const mdFromBytes = await toMarkdownBytes(bytes);

// 获取文档模型(含嵌入资源)
const doc = await toDocument(bytes);

Rust 原生使用

cargo add anydoc
// 从文件路径
let markdown = anydoc::to_markdown("report.docx")?;

// 从字节(自动检测格式)
let markdown = anydoc::to_markdown_bytes(&bytes, None)?;

// 指定格式
let markdown = anydoc::to_markdown_bytes(&bytes, anydoc::Format::Csv)?;

与 LangChain / LlamaIndex 搭配实战

在 RAG 管道中,anydoc 最典型的用法是作为文档加载的前置步骤,将各种格式统一转为 Markdown 后再做切片和向量化。

搭配 LangChain

import anydoc
from langchain.text_splitter import MarkdownTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import FAISS

# 1. 用 anydoc 统一转 Markdown
def load_documents(file_paths):
    docs = []
    for path in file_paths:
        md = anydoc.to_markdown(path)
        docs.append({"content": md, "source": path})
    return docs

# 2. Markdown 切片
splitter = MarkdownTextSplitter(chunk_size=1000, chunk_overlap=100)
raw_docs = load_documents(["report.docx", "slides.pptx", "data.xlsx"])

chunks = []
for doc in raw_docs:
    chunks.extend(splitter.split_text(doc["content"]))

# 3. 向量化入库
embeddings = OpenAIEmbeddings()
db = FAISS.from_texts(chunks, embeddings)
db.save_local("./faiss_index")

搭配 LlamaIndex

import anydoc
from llama_index.core import Document, VectorStoreIndex

# anydoc 转换
file_paths = ["contract.docx", "presentation.pptx", "budget.xlsx"]
documents = []
for path in file_paths:
    md = anydoc.to_markdown(path)
    documents.append(Document(text=md, metadata={"source": path}))

# LlamaIndex 索引
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()
response = query_engine.query("合同中的违约条款是什么?")
print(response)

RAG 场景下的最佳实践

1. 优先使用 to_document() 获取嵌入资源

当文档包含图片时,to_document() 返回的文档模型会保留图片的字节数据和 media type,你可以选择: - 提取图片 alt text 作为文本内容 - 将图片单独送入多模态模型(如 GPT-4V)做 OCR - 保留图片 URL 引用(如果图片有外部链接)

2. 利用格式自动检测

anydoc 通过文件内容的魔数(magic bytes)检测格式,而非依赖文件扩展名。这意味着即使文件被错误命名(比如 .pdf 实际是 .docx),anydoc 也能正确识别并转换。

# 检测格式
fmt = anydoc.format_from_bytes(data)  # 返回 Format 枚举
print(fmt)  # Format.DOCX

3. 批量处理时注意 GIL

Python 绑定会释放 GIL,因此多线程批量转换不会互相阻塞:

import concurrent.futures
import anydoc

def convert(path):
    return anydoc.to_markdown(path)

with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
    results = list(executor.map(convert, file_paths))

4. 扫描件 PDF 的处理

anydoc 内置了 pdf-inspector,能自动判断 PDF 是文本型还是扫描型。对于文本型 PDF,直接本地提取;对于扫描型 PDF,建议搭配 Firecrawl 的 Parse API(带 OCR 能力)使用。

局限性与注意事项

  1. 扫描件 PDF 能力有限:anydoc 本身不做 OCR,扫描型 PDF 需要外部 OCR 服务。Firecrawl 的 Parse API 可以补充这一点。
  2. 复杂排版可能有偏差:对于多栏排版、复杂图文混排的文档,转换结果可能不如专业排版工具精确。anydoc 的目标是 LLM 可读的结构化文本,而非像素级还原。
  3. 旧版 .doc 格式:虽然支持,但 .doc(OLE2 二进制格式)的解析复杂度高于 .docx,转换质量可能略低。
  4. 无 ML 增强:anydoc 刻意不依赖 ML 模型,这意味着它不会像 docling 或 marker 那样利用深度学习做版面分析。优势是速度快、无 GPU 依赖;劣势是对复杂版面的理解力有限。
  5. 浏览器 WASM 版本:WebAssembly 版本可在浏览器中本地运行,文件不离开用户设备,适合隐私敏感场景,但大文件转换可能受浏览器内存限制。

总结评价

anydoc 解决了一个很实际的痛点:在 RAG/LLM 管道中,用一个轻量、快速、格式全覆盖的工具替代过去需要拼凑四五个库才能完成的文档转换工作。

优势: - 14 种格式全覆盖,开源工具中唯一 - 4.4ms 中位数转换速度,比第二名快 23 倍 - 质量评分全面领先,输出一致性高 - 纯 Rust 实现,无外部依赖,无 GPU 需求 - 提供 Python/Node.js/Rust/WASM 四种绑定

适合谁: - 构建 RAG 管道的开发者,需要处理用户上传的各种格式文档 - 需要统一文档输出格式的团队 - 对转换速度有要求的实时应用场景 - 不想维护多个转换工具依赖的开发者

GitHub 地址: firecrawl/anydoc(12000+ Star)

常见问题(FAQ)

Q1: anydoc 和 pandoc 有什么区别? pandoc 是通用文档格式转换工具,支持输入输出格式极多但每种格式的转换深度有限。anydoc 专注于将办公文档转为 LLM 友好的 Markdown,在覆盖的 14 种格式上转换质量更高、速度快 23 倍,且专为 RAG 场景优化。

Q2: anydoc 需要 GPU 或外部服务吗? 不需要。anydoc 是纯 Rust 实现,无 ML 模型依赖,无外部 API 调用。本地运行,单文件转换中位数 4.4ms。

Q3: anydoc 能处理扫描型 PDF 吗? anydoc 内置 pdf-inspector 可以处理文本型 PDF。对于扫描型 PDF(图片型),建议搭配 Firecrawl Parse API(带 OCR 能力)使用。

Q4: anydoc 的输出格式是什么样的? 统一输出为 GitHub Flavored Markdown(GFM),包含标题层级、表格、列表、代码块、链接等结构。所有 14 种格式的输出风格一致。

Q5: anydoc 可以在浏览器中运行吗? 可以。anydoc 提供 WebAssembly 版本(@firecrawl/anydoc-wasm),文件在浏览器本地转换,不上传到服务器,适合隐私敏感场景。