メインコンテンツへ移動
AIチュートリアル

anydocでOffice文書をMarkdownに変換

anydocをインストールして、Word、PowerPoint、Excel、OpenDocument、RTF、EPUB、CSV、PDFファイルを一貫性のあるGitHub Flavored Markdownに変換する方法を学びます。このチュートリアルでは、CLI、言語バインディング、ブラウザ向けWebAssembly、形式の検出、ドキュメントアセット、エラー処理、実用的な統合のヒントについて解説します。

anydocでOffice文書をMarkdownに変換

anydocとは?

anydocは、ドキュメントをクリーンなGitHub-Flavored Markdownに変換する高速なRustライブラリです。Word、PowerPoint、Excel、OpenDocument、RTF、EPUB、CSV、テキストベースのPDFファイルに対応しています。また、コマンドラインインターフェースと、Node.js、Python、ブラウザベースのWebAssembly向けバインディングも提供しています。

このライブラリは、複数のドキュメント形式を受け取り、統一されたMarkdown表現を必要とするパイプライン向けに設計されています。対応する各オフィス形式は共通のドキュメントモデルに解析され、同じMarkdownシリアライザーを通して処理されます。これにより、表、エスケープ、見出し、脚注、リンク、リストなどの構造を形式間で一貫して扱えます。

変換は、機械学習モデルや外部サービスを使わずにローカルで実行されます。READMEのベンチマークによると、ドキュメント1件あたりの変換時間の中央値は5ミリ秒未満です。テキストベースのPDFは、pdf-inspectorを通じてローカルで処理されます。

anydocはOCRを実行しません。読み取り可能なテキストを含まない画像のみのPDFやスキャンPDFは、未対応として報告される場合があります。OCRも必要なワークフロー向けのホステッドな代替手段として、プロジェクトではFirecrawl Parseが案内されています。

主な機能

  • 一貫したMarkdown: すべてのオフィス形式で共通のドキュメントモデルとシリアライザーを使用します。
  • 構造化された出力: 見出しとアンカー、リンク、内部参照、リスト、表、引用、脚注、文末脚注、発表者ノート、インラインコード、コードブロックなどの構造に対応しています。
  • 書式の保持: 太字、斜体、取り消し線、タスクリスト、入れ子のリスト、元の番号付け、結合された表セル、ヘッダー行を表現できます。
  • 埋め込みアセット: 埋め込み画像やオブジェクトはMarkdownでは代替テキストとして表示されますが、生のバイトデータとメディアタイプはドキュメントモデルから引き続き利用できます。
  • コンテンツベースの検出: PDF、RTF、OLE、ZIPベースのドキュメント形式を、ファイル拡張子だけに頼らずバイトデータから識別します。
  • ノンブロッキングバインディング: Node.jsの変換ではlibuvのスレッドプールを使用し、Pythonでは変換中にGlobal Interpreter Lockを解放します。
  • 型付きパッケージ: TypeScriptの型定義とPythonのスタブが含まれています。
  • エージェント連携: 対応するコーディングエージェントにanydoc CLIの使い方を教えるAgent Skillが含まれています。

対応ファイル形式

  • 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

インストール方法を選ぶ

シェルスクリプトや一度限りの変換には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をインストールする

対応するエージェントにanydoc CLIでドキュメントを変換する方法を教えるには、付属のスキルをインストールします。

npx skills add firecrawl/anydoc

READMEでは、対応クライアントとして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スレッドも実行を継続できます。これらの特性は、変換を並行アプリケーションに統合する際に役立ちます。

下流で一貫した出力を使用する

オフィス形式は1つのシリアライザーを共有するため、下流システムでは、Word、PowerPoint、Excel、RTF、OpenDocumentの入力ごとに個別のクリーンアップルールを維持する代わりに、1つのMarkdown構造を利用できます。そのためanydocは、検索インデックス作成、抽出パイプライン、言語モデル向けの構造化テキストの準備に特に適しています。

変換パイプラインの仕組み

  1. anydocは、ドキュメントのバイト列または利用可能なファイル名情報を調べ、形式を判定します。
  2. 形式固有のパーサーが、Word、PowerPoint、Excel、OpenDocument、RTF、EPUB、またはCSVの内容を読み取ります。
  3. パーサーは、ブロック、インライン要素、表、脚注、アセットを含む共有ドキュメントモデルを作成します。
  4. 共通のシリアライザーが、そのモデルをGitHub Flavored Markdownに変換します。
  5. PDFはpdf-inspectorを介して処理され、Markdownに直接変換されます。

このアーキテクチャにより、シリアライザーの改善を複数の形式に同時に反映できます。たとえば、表のエスケープに関する修正は、単一の入力パーサーだけに限定されません。

パフォーマンスと最適な用途

リポジトリでは、14のテスト済み形式にまたがる、実際のドキュメント100件以上を対象としたベンチマークが報告されています。anydocは14形式すべてをカバーし、変換時間の中央値4.4ミリ秒を達成し、その比較で総合品質スコア81を獲得しました。品質は、完全性、構造、書式、クリーンさについて評価されました。

ベンチマーク結果はコーパス、ハードウェア、手法によって異なるため、付属のベンチマークはすべてのワークロードに対する保証ではなく、プロジェクト固有の根拠として利用してください。このプロジェクトが示す最適な用途は、オフィスドキュメントの混在したコレクションを受け取り、高速で構造化された一貫性のあるMarkdownを必要とするパイプラインです。

まとめ

anydocは、Rust、Node.js、Python、ブラウザー、コマンドラインにまたがる1つの変換ワークフローを提供します。まずCLIで簡単なテストを行い、次にアプリケーションに適したバインディングを選択してください。アップロードされたファイルにはバイトベースの変換を使用し、CSVの形式を明示し、埋め込みアセットが重要な場合はドキュメントモデルを保持してください。また、暗号化、破損、未対応、スキャン済みのドキュメントは別個のケースとして処理してください。