我把一个 Excel 样本转成 Markdown,工具正常退出,没有报错,速度也很快。
但结果里有一处很安静的变化:
Excel 显示:15.5%
Markdown 输出:0.155数字没丢,值也没有算错。做计算、排序或结构化抽取时,0.155 甚至更合适;做搜索、问答或面向人的报表摘要时,15.5% 又更直观。真正的风险不是保留了原始值,而是单位、显示规则和来源坐标没有一起交给下游。
这类问题比转换失败麻烦。失败会报警,语义退化往往只会继续流进系统。
不过,损失不一定是 Markdown 自己造成的。它可能发生在四层:解析器没有读出信息,中间模型没有承载,Markdown 序列化时选择不输出,或者下游只接收一份 Markdown,主动忽略了 assets、页级结果和 warnings。Markdown 本身完全能写 15.5% 和图片引用;这里讨论的是一条具体转换链做了哪些取舍。
一个很快的文档转换器
这次测试的是 GitHub 上的 firecrawl/anydoc(下文简称 anydoc),8 月初刚开源。它用 Rust 编写,可以在本地把 Word、PowerPoint、Excel、OpenDocument、RTF、EPUB、CSV 和文本型 PDF 转成 Markdown,也提供 Node.js、Python 和浏览器 WASM 入口。
它的主体设计很顺:除 PDF 外,Office、OpenDocument、RTF、EPUB 和 CSV 等格式先进入一套共享 document model,再由统一的 Markdown 渲染器输出。表格转义或脚注渲染修一次,走这条路径的格式都能复用。PDF 是例外:v0.1.8 由 pdf-inspector 直接生成 Markdown。
我在 macOS arm64 上固定到 v0.1.8 对应的源码提交,从头构建并跑了仓库测试。208 个单元测试、robustness 测试和 8 个非忽略的集成/快照测试通过;另有一个依赖未公开本地语料的测试被忽略。
随后我转换了仓库自带的五个小型测试文件。在这台机器上,它们都在毫秒级完成。
这只能说明它处理这些小型测试文件确实很快,不能外推到一份几百页、图片很多的真实报告,也不能证明冷启动、内存或并发表现。不过,“可以在本地运行、格式覆盖广”不是只写在 README 里的口号。
成功转换,不代表完整保留
再看开头的 Excel。
单元格底层保存的是 0.155,格式规则让人看到 15.5%。当前版本读取了底层值,却没有恢复显示格式;货币符号和千分位也有类似问题。项目里已经有一个修复 PR,但在我测试时还没有合并。
这不是简单的“排版不够漂亮”。对财务、运营和数据报表来说,显示格式本身就是语义的一部分。
它还可能改变下游的判断方式。用户搜索“增长率 15.5%”时,纯文本检索未必能命中 0.155;模型做摘要时,也可能把同一列里的比例、金额和普通小数当成同一种数值。本文没有做检索或问答的对照实验,这里说的是需要验证的风险,不是已经观察到的失败。更稳妥的转换结果不是二选一,而是同时保留底层值、显示值、格式规则和来源坐标。
PDF 的损失又不一样。测试样本里的文字基本都在,但多级列表和表格被明显扁平化。Markdown 里还能搜索到内容,却很难还原“谁属于哪一级、哪个值对应哪一列”。
图片则走了另一条路。anydoc 的内部 document model 能用 inline 位置和 AssetId 关联图片字节,我实测也从 DOCX 拿到了一个 PNG 和一个嵌入对象;但当前 Markdown 只留下替代文字,没有指向导出资产的引用。只接收 Markdown 的下游,仍然无法把图片内容接回原来的上下文。
扫描 PDF 也需要额外小心。纯扫描或没有可提取文字的 PDF 会报不支持;从源码看,文字与扫描页混合的 PDF 可能成功返回,同时跳过需要 OCR 的页面并只写 warn。因此服务端不能只看退出码。这个混合场景我没有实际构造,正式接入前应该单独测试。
所以“支持 PDF”“支持 Excel”只能说明入口存在,不能回答保真程度。更有用的问题是:你的业务允许它丢掉什么?

同一个“转换成功”,可能同时伴随显示值变化、图片位置丢失、分页扁平化或 OCR 分流。
还有一类文件,不只是难解析
只要这个转换入口对用户开放,传进来的就不一定是正常文档。
仓库附了几个专门测试资源上限的恶意样本。我实际跑了其中的 image bomb 和 zip bomb,两次都以 resource limit exceeded 退出,没有生成 Markdown。项目还对压缩包展开大小、XML 深度、表格跨度等场景做了限制和变异测试。
这一点很重要。文档解析器会处理压缩包、XML、二进制对象、外部链接和复杂容器;它不该被当成一个没有攻击面的字符串函数。
另一款同类工具 MarkItDown 也在文档开头提醒:它的转换 API 使用当前进程的文件和网络权限,不可信输入需要限制路径、URI scheme 和网络目标。这是 MarkItDown 的 API 边界,不代表 anydoc 会主动抓取外部 URL。
但隔离原则是共通的:对不可信上传,优先使用只接收字节流的窄接口,并放进最小文件、网络权限的独立进程或容器,再设置超时、内存和 CPU 限额。anydoc 内置的 resource limit 很有价值,但它只是其中一层,不等于完整沙箱。
我会先定义什么叫转换成功
如果只是给一批内部纯文本文档做关键词检索,anydoc 这种本地快速转换器很有吸引力。可一旦文档里有财务表格、扫描页、图片证据或精确页码,我不会让“成功生成 Markdown”成为唯一验收条件。
我会至少固定一小组真实样本,逐项回答:
- • 表格要保留底层值,还是用户看到的显示值?
- • 图片只要导出,还是必须保留原始位置和说明?
- • 答案需要回链到页码、工作表或幻灯片吗?
- • 扫描页、加密文件和超大文件应该失败,还是进入另一条处理链?
- • 转换发生退化时,下游能不能知道,而不是收到一份看起来正常的 Markdown?
如果要把这层做成长期服务,我还会让它返回一份很薄的“损失清单”。下面只是验收契约示意,不是 anydoc 现有 API:
markdown: report.md
assets_extracted: 2
warnings:
- code: xlsx_display_format_lost
stage: parser
source: { sheet: Summary, cell: B7 }
raw_value: 0.155
display_value: "15.5%"
- code: pdf_page_needs_ocr
stage: parser
source: { page: 4 }这样,下游可以选择拒绝、分流或降级展示,而不是把所有转换成功都当成同一种成功。
最后再用这些样本测试检索和回答。因为 RAG 的输入如果已经不满足任务契约,后面换更大的模型、调更细的提示词,也只是在认真处理一份缺少必要上下文的材料。
anydoc 仍然是个很新的项目,我不会因为它几天内获得很多 Star 就直接推荐上生产;项目自己的 benchmark 语料也没有随仓库公开,质量分不能当成独立评测。不过,它把一个值得认真对待的问题摆到了桌面上:统一格式很有价值,但统一之后留下了什么,必须由使用者自己验证。
回到最初那两个数字。15.5% 变成 0.155 时,程序没有撒谎,它只是没有告诉你自己省略了什么。
说明:本文与 Firecrawl 无商业合作。文中的构建、测试与样本转换均为实际执行;未测试 npm、Python、WASM 发布包、扫描 PDF、旧版 Office 格式和大型真实业务文档。本文由 AI 辅助整理、起草与审校,关键事实、命令和样本结果均已逐项核验;封面与流程图由生成式工具制作。
我是 overtrue,这里是「假装我会写代码」。
写代码,也写代码背后的取舍。
如果你有不同的文档解析实践,欢迎在评论区聊聊。
引用链接
1. anydoc 仓库与 README
https://github.com/firecrawl/anydoc
2. anydoc v0.1.8 Release
https://github.com/firecrawl/anydoc/releases/tag/v0.1.8
3. anydoc benchmark 方法说明
https://github.com/firecrawl/anydoc/blob/4e3089b1ed43404241a303109f81e2c7933040b2/bench/README.md
4. PR #72:XLSX 显示格式
https://github.com/firecrawl/anydoc/pull/72
5. Issue #62:PDF 逐页输出
https://github.com/firecrawl/anydoc/issues/62
6. Issue #63:嵌入图片位置
https://github.com/firecrawl/anydoc/issues/63
7. Issue #81:DOCX 重复编号
https://github.com/firecrawl/anydoc/issues/81
8. Microsoft MarkItDown 安全边界
https://github.com/microsoft/markitdown/blob/fd239d5d2be43d9b68329730206b9312c7d5a388/README.md#security-considerations
9. Hamel Husain、Shreya Shankar:如何评估 RAG 系统
https://hamel.dev/blog/posts/evals-faq/how-should-i-approach-evaluating-my-rag-system.html
夜雨聆风