Главная / Статьи / anydoc: конвертация 14 форматов документов в Markdown за миллисекунды

anydoc: конвертация 14 форматов документов в Markdown за миллисекунды

Разбираем anydoc от Firecrawl на Rust: как быстро он переводит docx, pptx, xlsx и PDF в Markdown на свежем тестовом наборе, что ломается и как им пользоваться.

1196 слов

Firecrawl выложил в open source конвертер документов anydoc, и цифры у него впечатляют. Перегнать docx через LibreOffice перед тем, как отдать модели, может занять больше секунды на файл, и это без учёта ручных шагов. anydoc опускает медиану до 4,4 миллисекунды на 14 распространённых форматах и всегда выдаёт один и тот же вариант Markdown в стиле GitHub.

Что это такое

Это не очередной конвертер из серии «и так сойдёт». Весь пайплайн переписан на чистом Rust. Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, CSV и PDF раскладываются в одну общую модель документа, а результат пишет один сериализатор. Поэтому старый .doc 2003 года и .pptx, выгруженный вчера, выглядят одинаково: уровни заголовков, объединённые ячейки, сноски и списки задач ведут себя идентично. Всё работает локально, не обращается ко внешним сервисам и не использует моделей машинного обучения.

Принято считать, что для разбора документов нужна цепочка склеенных инструментов или поход в облако. Но такая скорость и качество достижимы на собственной машине. anydoc уже работает внутри эндпоинта Parse у самого Firecrawl, где лишний шаг OCR нужен только сканированным PDF. Текстовые PDF и офисные файлы обрабатываются локально за один проход.

Проверяем заявления на свежем корпусе

Бенчмарк разработчика воспроизвести нельзя. В README сказано, что его 100 файлов нельзя распространять и в репозитории их нет. Поэтому собрали новый тестовый набор: по 40 файлов на формат, каждый меньше 25 МБ.

Вышло 206 файлов, из них успешно сконвертировались 194.

Главное обещание — медиана меньше 5 мс — выполняется только для csv и xlsx. Медиана для docx составила 6,4 мс, для pptx и pdf — по 22. Самый медленный pptx занял 346 мс: это был большой файл в выборке, где попадались экземпляры больше двухсот мегабайт. Цифра 5 мс — медиана по всему смешанному корпусу, и её тянут вниз построчные форматы вроде csv.

На практике тысяча файлов docx обрабатывается примерно за 6 секунд, а тысяча файлов PowerPoint — больше чем за двадцать. И то и другое быстро, но планировать стоит по более медленным цифрам.

Восемь из двенадцати сбоев случились не по вине инструмента. Эти восемь файлов вернули malformed, поэтому каждый проверили командой file. Два файла .docx оказались один data, другой JSON data, а два .xlsx и четыре .pptx — просто data. Настоящих офисных документов среди них не было. anydoc определяет содержимое по формату, а не по расширению, так что обмануть его не вышло. Если убрать восемь подделок, реальная доля успеха — 194 из 198, то есть 98%.

Тут есть и незапланированный бонус. Прогоните его по библиотеке документов, соберите всё, что вернуло malformed, и получите готовый список повреждённых или неправильно подписанных файлов. Создавали его не для этого, но работает.

Почему это важно для LLM-пайплайнов

Когда отдаёшь модели договор, первая проблема — понять, в каком он на самом деле формате. Расширение говорит .docx, а внутри может оказаться изменённый ZIP. У CSV нет сигнатуры, его приходится объявлять вручную. Поменяешь инструмент — поменяются и правила вывода: таблицы иногда превращаются в списки, сноски пропадают, а отступы вложенных списков разъезжаются.

Для сравнения: медиана у LibreOffice выше 1100 миллисекунд, и все 14 форматов он не покрывает. unstructured, markitdown и pandoc справляются каждый только с частью диапазона, а их оценки качества не поднимаются выше примерно 65. На той же пачке из 100 реальных документов anydoc получил 81 балл в целом: 87 за полноту, 79 за структуру, 78 за форматирование и 81 за чистоту. И это примерно в 250 раз быстрее.

Это не только лабораторная цифра. 500 файлов docx обрабатываются за 1,7 секунды — достаточно быстро, чтобы встроить конвертер прямо в цикл агента. Раньше пришлось бы строить асинхронную очередь и ждать колбэков. Теперь даже синхронные вызовы кажутся мгновенными.

Старые файлы .xls и .odp с неправильными именами всё равно распознавались по заголовку файла. CSV нужна явная подсказка: без сигнатуры инструмент его иначе пропускает. Такие мелочи лучше знать до того, как строить на нём что-то своё.

Как это устроено внутри

Каждый файл начинается с определения содержимого: PDF — по заголовку, RTF — по группам, ZIP-контейнеры — по mimetype. После опознания отдельный парсер заполняет единую модель Document с блоками, inline-элементами, таблицами, сносками и встроенными ресурсами. Завершает работу единственный сериализатор GitHub-Flavored Markdown.

Именно поэтому объединённые ячейки таблиц, якоря заголовков, зачёркивание, блоки кода и заметки докладчика подчиняются одним и тем же правилам. Изображения и объекты превращаются в Markdown с alt-текстом, а сырые байты остаются в модели, чтобы их можно было достать позже.

PDF проходят через pdf-inspector, который берётся только за файлы с текстовым слоем. Чистый скан возвращает Unsupported, а не делает вид, что прочитан, а зашифрованный файл даёт понятную ошибку. Такая честность в любом случае лучше, чем «конвертация по мере сил».

Поскольку это чистый Rust, нет накладных расходов интерпретатора Python и временных файлов на диске. Биндинг для Node использует пул потоков libuv, так что event loop не блокируется, а биндинг для Python отпускает GIL. Так и получается медиана в 4,4 мс.

Кто-то назовёт парсер на Rust оверинжинирингом. Другие упаковывают его в навык агента, чтобы Claude или Cursor превращали файл в Markdown, как только он появится. У обоих лагерей есть свои доводы.

Как пользоваться

Самый прямой путь — командная строка:

npx @firecrawl/anydoc report.docx

Вывод идёт в терминал. Добавьте -o slides.md, чтобы записать файл. Чтобы прочитать CSV из stdin:

npx @firecrawl/anydoc - --format csv < data.csv

В Node:

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

const md = await toMarkdown('report.docx');
const mdFromBytes = await toMarkdownBytes(bytes);
const mdCsv = await toMarkdownBytes(bytes, 'csv');

Версия для Python похожа:

import anydoc

md = anydoc.to_markdown("report.docx")
md_bytes = anydoc.to_markdown_bytes(data)
md_csv = anydoc.to_markdown_bytes(data, "csv")

Есть и сборка для браузера, @firecrawl/anydoc-wasm, так что файлы вообще не покидают машину. Для Rust выполните cargo add anydoc.

С агентами ещё проще. После npx skills add firecrawl/anydoc тулчейн сам вызывает конвертер, как только появляется документ.

Ждите чистый Markdown с сохранёнными таблицами, списками и заголовками. В сложных документах иногда теряется оформление пограничных случаев — это компромисс парсера, а не падение.

Ограничения, которые стоит учесть

Проект пока на стадии 0.1.x. Сильно повреждённые файлы или слишком большие встроенные объекты теоретически могут вызвать ResourceLimit. В продакшене обрабатывайте возвращаемые ошибки по категориям, а зашифрованные и неподдерживаемые файлы логируйте отдельно, чтобы один плохой файл не уронил весь пайплайн.

Если конвертацию можно сделать локально, обойдитесь без похода в облако, а сканы оставьте эндпоинтам с OCR. Скорость и качество уже достаточно хороши, так что остаётся вопрос, как это впишется в ваш собственный пайплайн. Проект живёт на github.com/firecrawl/anydoc.