Azure AI Document Intelligence で Word(.docx)を解析すると、PDF ではページ単位の結果が得られるのに Word は「常に 1 ページ分」しか返ってこない――この戸惑いを、仕様の背景から整理し、検索インデックス設計まで踏み込んだ現実解(変換/自前分割/セマンティック分割)を具体的手順・コード例つきでまとめます。
Azure AI Document Intelligence × Word(.docx)で起こる現象
Word 文書を prebuilt-document/prebuilt-layout/read などで解析すると、レスポンス内の pages が 1(または 3,000 文字ごと程度で増える論理ページ)に見えることがあります。PDF は物理ページ+座標(bounding box)が返るため、ページ単位の切り分けが容易ですが、Word は「リフロー」前提のフォーマットであり、物理的なページ区切りを持たないため、現在の API ではページ対応が制限されています。
要点(TL;DR)
- 仕様どおり:Word は「約 3,000 文字 ≒ 1 ページ」の論理ページで扱われる。境界座標・
pageRange・linesは未対応。 - PDF は別物:物理ページ+座標が返るのでページ分割しやすい。
- 回避策:①PDF へ変換して解析、②OpenXML/Word API でページ推定、③文字数ベースの擬似ページ、④ページに依存しないセマンティック分割。
- 2025年11月時点:Word の「ページ番号」を直接検出する機能の公式ロードマップは未公表。
PDF と Word(DOCX)の機能差まとめ
| 観点 | Word(.docx) | |
|---|---|---|
| ページ概念 | 物理ページ | 論理ページ(約 3,000 文字単位) |
| 座標(bounding box) | 取得可 | 未対応 |
pageRange 指定 | 利用可 | 未対応 |
lines / 行オブジェクト | 取得可(モデルによる) | 未対応 |
| テーブル抽出 | 可(座標あり) | 可(ただし座標なし) |
| 使うべきモデル例 | prebuilt-layout, prebuilt-document | prebuilt-document(論理ページ) |
なぜ「1 ページ」なのか:原因・仕様の背景
Word はレイアウトエンジンにより表示時に改ページが決まる「リフロー」型です。これに対し、PDF はレンダリング済みなのでページと座標が固定です。現行の SDK/REST API(例:2024‑02‑29-preview、2023‑10‑31-preview、v4.0 GA など)では、Word のページは 約 3,000 文字を 1 単位とする論理ページで扱われ、座標・pageRange・lines は未対応です。そのため、複数ページの .docx でもレスポンス上は「1 ページ(または 3,000 文字ごとに増える程度)」に見えます。
レスポンスのイメージ(DOCX)
{
"modelId": "prebuilt-document",
"apiVersion": "2024-02-29-preview",
"content": "(文書全文)",
"pages": [
{
"pageNumber": 1,
"spans": [{ "offset": 0, "length": 2987 }]
// 座標情報はなし(Word は未対応)
}
],
"paragraphs": [
{ "spans": [{ "offset": 0, "length": 15 }], "role": "title" },
{ "spans": [{ "offset": 16, "length": 120 }], "role": "body" }
// …
],
"tables": [ /* 座標なしの表構造 */ ]
}
PDF 解析のイメージ(比較用)
{
"modelId": "prebuilt-layout",
"pages": [
{
"pageNumber": 1,
"width": 8.5,
"height": 11,
"unit": "inch",
"words": [ { "content": "Azure", "polygon": [/* 座標 */] } ],
"lines": [ /* 行・座標あり */ ],
"tables": [ /* セル座標あり */ ]
},
{ "pageNumber": 2, /* … */ }
]
}
検索インデックス設計への影響
「ページ単位でチャンク化して索引化したい」ニーズは強いですが、Word の仕様を踏まえると次の方針になります。
- 物理ページへの厳密なマッピングが必要:PDF へ事前変換が最も確実。
- Word のまま処理:OpenXML/Word API でページ推定またはセマンティック分割に切り替える。
- 暫定対応:3,000 文字 ≒ 1 ページの擬似ページで割り切る。
解決策・回避策の比較
| 方法 | 概要 | メリット | デメリット | おすすめ度 |
|---|---|---|---|---|
| A. 事前に PDF へ変換して解析 | Word → PDF 変換後に Document Intelligence を実行 | 物理ページ・座標が取れる/既存ロジック流用 | 変換コスト/動的要素の喪失 | ◎(ページ厳格要件) |
| B. Word API/OpenXML SDK でページ分割 | Word 組版に近い区切りを自前推定 | UI の「ページへジャンプ」を再現しやすい | 実装・運用が難しい/レンダラー必要 | ○(オンプレ/社内ツール) |
| C. 文字数ベースで擬似ページ | 3,000 文字 ≒ 1 ページでチャンク化 | 最短で実装可/コスト低 | 実ページと一致しない | △(暫定策) |
| D. セマンティック分割へ方針転換 | 見出し・段落・表など構造でチャンク化 | 検索品質向上が見込める/汎用 | ページジャンプ不可/UI設計要 | ◎(長期安定) |
実装レシピ A:PDF へ変換してから解析
もっとも堅い方法です。既存の PDF 向けパイプラインと同じ前提で組めます。
処理フロー
- DOCX を PDF に変換(Microsoft 365 / Graph の変換 API、もしくはサーバーサイドの Office レンダラーやヘッドレス LibreOffice 等)。
- 変換済み PDF を
prebuilt-layout(またはprebuilt-document)で解析。 - ページ座標に基づいてチャンク化し、インデックスに投入。
解析リクエスト(REST、概念例)
POST https://<resource-name>.cognitiveservices.azure.com/
documentintelligence/documentModels/prebuilt-layout:analyze?api-version=2024-02-29-preview
Content-Type: application/json
Ocp-Apim-Subscription-Key: <key>
{
"urlSource": "https:///converted.pdf"
}
レスポンスの pages[n].words[].polygon/tables を利用すれば、座標ベースの正確なページ分割・ハイライトが可能です。
インデックス設計(例:Azure AI Search)
| フィールド | 型 | 用途 |
|---|---|---|
docId | Edm.String(key) | 文書の一意キー(ハッシュ推奨) |
chunkId | Edm.String | ページ番号+セクション情報など |
pageNumber | Edm.Int32 | 物理ページ番号 |
content | Edm.String | チャンク本文 |
contentVector | Collection(Edm.Single) | 埋め込みベクトル |
bbox | Edm.String | ハイライト用座標(JSON) |
headingPath | Edm.String | 見出し階層(パンくず) |
実装レシピ B:OpenXML/Word API でページ境界を推定
Word の「手動改ページ(<w:br w:type="page"/>)」や「段落書式のページ前改ページ(w:pPr/w:pageBreakBefore)」、セクション区切り(w:sectPr)を利用し、自前で「擬似ページ」を算出します。サーバー側でレンダリング(Office/LibreOffice)を行う場合は、より実際のページに近い結果が得られます。
OpenXML でページ区切りを拾う C# 例(抜粋)
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
var breaks = new List(); // コンテンツ内オフセットのリスト
using (var doc = WordprocessingDocument.Open(path, false))
{
var main = doc.MainDocumentPart.Document.Body;
int offset = 0;
foreach (var p in main.Elements())
{
// ページ前改ページ(段落書式)
var pPr = p.ParagraphProperties;
if (pPr?.PageBreakBefore != null) breaks.Add(offset);
foreach (var r in p.Elements<Run>())
{
foreach (var br in r.Elements<Break>())
{
if (br.Type?.Value == BreakValues.Page) breaks.Add(offset);
}
// テキスト長をオフセットに足す
var txt = string.Concat(r.Elements<Text>().Select(t => t.Text));
offset += txt.Length;
}
offset += Environment.NewLine.Length;
}
}
// breaks を基点に自前ページ配列を作成
この方法は 「Word の実表示にかなり近い」擬似ページを作れますが、等幅でないフォント・余白設定・禁則などの組版差は残ります。高精度に寄せるならサーバー側レンダリング(Word/LibreOffice)で PDF 化 → A へ寄せるのが現実的です。
実装レシピ C:3,000 文字ごとの擬似ページ
仕様に合わせて、約 3,000 文字を 1 単位と見なす簡易アプローチです。段落の途中で切れないよう軽く工夫します。
Python(概念)
def chunk_by_chars(text: str, size: int = 3000, overlap: int = 200):
chunks = []
start = 0
n = len(text)
while start < n:
end = min(start + size, n)
# できれば段落単位の区切りを後方探索
sep = text.rfind("\n\n", start, end)
if sep != -1 and sep - start > size * 0.6:
end = sep
chunks.append(text[start:end])
start = max(end - overlap, 0)
return chunks
「厳密なページ一致」は捨てる代わりに、実装の速さ・安定性を取ります。UI は「ページ」ではなく「チャンク番号+見出し」でナビゲーションするのが相性良しです。
実装レシピ D:セマンティック分割(おすすめ)
ページ番号に依存せず、見出し・段落・表・箇条書きなどの論理構造やトピックでチャンク化します。長文の再検索(RAG, QA)ではページよりも意味単位の方が精度が高いケースが多いです。Word でも paragraphs 情報(役割・スタイル)を利用して分割可能です。
セマンティック分割のアルゴリズム例
paragraphsを走査し、heading/title ロールを境にセクション単位へ分割。- 各セクション内で
tables/箇条書きを保持しつつ、3,000〜4,000 文字未満へ再分割(オーバーラップ 200〜300 文字)。 - 見出し階層(H1 > H2 > …)を
headingPathとしてメタデータ化。 - ベクトル埋め込みとキーワード索引を併用し、UI でハイライト。
擬似コード(TypeScript 風)
type Para = { text: string; role?: string };
function semanticChunks(paragraphs: Para[], max = 3200, overlap = 256) {
const chunks = [];
let buf = "", headingPath: string[] = [];
const flush = () => {
if (!buf.trim()) return;
chunks.push({ content: buf, headingPath: headingPath.join(" > ") });
buf = "";
};
for (const p of paragraphs) {
if (p.role === "title" || p.role === "heading") {
if (buf.length > 0) flush();
headingPath.push(p.text.trim());
continue;
}
if ((buf + "\n\n" + p.text).length > max) {
const tail = buf.slice(-overlap);
flush();
buf = tail + p.text;
} else {
buf += (buf ? "\n\n" : "") + p.text;
}
}
flush();
return chunks;
}
UI/UX 設計のヒント
- ページジャンプが必須:A(PDF 変換)で座標を取り、ビューワ(PDF.js 等)と連携。
- ページに依存しないナビ:D(セマンティック分割)で「見出しツリー+チャンク」ナビを提供。
- ハイブリッド:Word は D、PDF は A で処理し、同一 UI に統合。
- ハイライト:
contentのオフセット(spans)を保存し、ヒット箇所の前後 N 文字を抜き出してプレビュー。
実務アーキテクチャ例
- 取り込み:Blob ストレージへ DOCX アップロード(メタに
contentTypeを保持)。 - 前処理:DOCX の場合は
- 方針 A:コンテナ(LibreOffice)または SaaS 変換で PDF 化。
- 方針 B:OpenXML で改ページ・見出し抽出。
- 方針 D:Document Intelligence の
paragraphsを主軸に分割。
- 解析:Document Intelligence(
prebuilt-documentかprebuilt-layout)。 - チャンク化:A はページ、B/C/D は擬似ページ・セマンティック。
- 索引:キーワード+ベクトルの二段構え(Azure AI Search)。
- 配信:ビューワと連携(PDF はページ、Word は見出しでジャンプ)。
運用チェックリスト
- DOCX は座標・
pageRange非対応であることを前提に設計しているか。 - ページ厳格要件のユースケースでは PDF 変換の SLA とコストを見積もったか。
- セマンティック分割時の「最大長」「オーバーラップ」「見出し階層」を明示的に管理しているか。
- UI に「ページ」依存のないナビゲーション(見出しツリー/検索ハイライト/文脈プレビュー)があるか。
- エラー時のフォールバック(変換失敗→C で暫定索引など)を用意したか。
よくある落とし穴と対処
| 落とし穴 | 症状 | 対処 |
|---|---|---|
DOCX の pageRange が効かない | 指定しても 1 ページ分しか返らない | 仕様。PDF に変換するか、擬似ページで対応 |
| 行オブジェクトが取れない | lines が空/未出力 | 仕様。段落(paragraphs)を利用し論理単位で扱う |
| 表のセル座標がない | セルの位置ハイライト不可 | PDF 解析へ切替、または表全体をテキスト化し文脈でハイライト |
| 手動改ページを復元したい | ページ表示とズレる | OpenXML で改ページ要素(w:br)等を解析して擬似ページ化 |
コード断片(解析の呼び出しとポーリング)
解析は非同期です。operation-location をポーリングして結果を取得します。
// JavaScript(概念)
// 1) analyze リクエスト
const res = await fetch(
"https://<resource>.cognitiveservices.azure.com/documentintelligence/documentModels/prebuilt-document:analyze?api-version=2024-02-29-preview",
{
method: "POST",
headers: {
"Ocp-Apim-Subscription-Key": process.env.DI_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({ urlSource: "https://<blob>/file.docx" })
}
);
// 2) operation-location を取得してポーリング
const op = res.headers.get("operation-location");
let result;
for (;;) {
await new Promise(r => setTimeout(r, 1500));
const r2 = await fetch(op, { headers: { "Ocp-Apim-Subscription-Key": process.env.DI_KEY }});
result = await r2.json();
if (result.status === "succeeded" || result.status === "failed") break;
}
// 3) result.pages / paragraphs / tables を用いて分割
品質向上の実戦テクニック
- 見出しスコアリング:文字サイズ・太字・全角カナ・番号(1. / 1.1 / 第○章)を特徴量に、区切り点を強化。
- 表の扱い:1 セルを 1 チャンクにせず、表全体を 1 チャンクにして見出しと関連付ける。
- オーバーラップ:300〜500 文字の重ねを推奨。検索ヒット後の文脈欠落を防止。
- 重複排除:ヘッダー/フッター/署名ブロックを取り除く(特に契約書)。
- 監査性:
docId+chunkId+sourceOffset(spans.offset)を保存し、誰でも再現できるように。
パフォーマンスとコストの観点
- バッチ変換:A 案(PDF 化)はキューイングでスループットを確保。小容量ファイルは複数束ねてスループット最適化。
- サイズ閾値:Word のみ・短文は D(セマンティック)で即索引、長文・図表が多い文書は A(PDF)へ。
- 差分更新:ハッシュでコンテンツ差分を検出し、変更チャンクだけ再解析。
セキュリティとコンプライアンス
- 機微情報(個人・機密条項)は前処理でマスキング(正規表現+ルール+辞書)。
- インデックス化前にアクセスレベル(部署・ラベル)をメタデータで紐づけ、検索時フィルタリング。
- 変換サービングのログに原文を残さない。監査はハッシュとメタのみ。
今後の見通し(2025年11月時点)
Word 文書の物理ページ番号検出・座標付与について、現時点で公開ロードマップはありません。Issue/フィードバックで要望を挙げつつ、A(PDF 化)・D(セマンティック分割)中心の設計にしておくのが堅実です。将来 Word のページ対応が拡充された場合も、セマンティック分割はそのまま価値を持つため、後方互換の高い投資になります。
まとめ
- 現状仕様:Word はページを返さない(論理ページのみ)。PDF は物理ページ+座標が返る。
- 要件が「ページ厳格」なら、まずはA:PDF 変換→解析が最短で確実。
- Word のままなら、B:OpenXML 推定かD:セマンティック分割を選択。C(3,000 文字)は暫定策に。
- 検索 UXはページ番号依存から脱却し、見出し・段落・表を軸に再設計すると成果が出やすい。
付録:推奨フローの具体化
- 変換コストを許容できる:A(PDF 変換)が最も確実。既存の PDF パイプラインがそのまま生きる。
- 変換が難しい:業務・運用制約に合わせて B(OpenXML)または D(セマンティック)。
- 短期リリース:C(3,000 文字擬似ページ)で立ち上げ、後から A/D へ移行。

コメント