はじめに
普段Pythonでopenpyxlやpandasを使ってExcelを触っていて、ふと「このライブラリたち、内部でいったい何をやってるんだろう?」と気になったことはありませんか?
私は気になりました。気になって調べたら、思った以上にシンプルで、しかも応用範囲が広い話だったので記事にまとめます。
対象読者
- Pythonなどから
.xlsxファイルを操作するライブラリを使っている中級エンジニア- Office文書の「中身」を一度ちゃんと知っておきたい人
- 「ClaudeがなぜExcelを編集できるのか」が気になる人
結論
-
.xlsxの正体はZIPで圧縮されたXMLファイルの集合 - これはOPC (Open Packaging Conventions) という業界標準パターンで、
.docx/.pptx/.epub/.apk/.jarも全部同じ仕組み - ZIPが選ばれたのは「圧縮率」「複数ファイルの集約」「特許フリー」「ランダムアクセス」の4点が揃っていたから
- 中身がXMLテキストだから、Claudeなどの言語モデルでも編集できる
拡張子を .zip に変えてみる
百聞は一見にしかず、です。手元の.xlsxファイルをコピーして、拡張子を.zipに変えてみてください。普通に解凍できます。
cp sample.xlsx sample.zip
unzip sample.zip -d sample_extracted
中身はこんな構造になっています。
sample.xlsx/
├── [Content_Types].xml ← 各ファイルのMIMEタイプ定義
├── _rels/
│ └── .rels ← パッケージ全体のリレーション
├── docProps/
│ ├── app.xml ← アプリ情報(作成者、Excelバージョン等)
│ └── core.xml ← メタデータ(タイトル、更新日時等)
└── xl/
├── workbook.xml ← シート一覧、定義名など
├── styles.xml ← セル書式(フォント、罫線、色)
├── sharedStrings.xml ← 文字列の共通プール
├── _rels/
│ └── workbook.xml.rels ← workbookからの参照関係
└── worksheets/
├── sheet1.xml ← Sheet1の中身(セルの値)
└── sheet2.xml ← Sheet2の中身
私は初めてこれを見たとき、率直に「あ、こんなにシンプルなのか」と思いました。Excelファイルというと何か魔法のバイナリを想像していたのですが、実態はただのテキストファイルの寄せ集めだったのです。
中身の正体は Open Packaging Conventions (OPC)
このZIP+XMLという形式、Microsoftが思いつきで作った独自仕様ではありません。OPC (Open Packaging Conventions) という規格として整理されていて、ISO/IEC 29500で標準化されています1。
そして驚くべきことに、同じ仕組みを使っているファイル形式は他にもたくさんあります。
| 拡張子 | 中身 |
|---|---|
.xlsx / .xlsm
|
Excel |
.docx |
Word |
.pptx |
PowerPoint |
.odt / .ods / .odp
|
LibreOffice (OpenDocument) |
.epub |
電子書籍 |
.jar / .war
|
Java アーカイブ |
.apk |
Android アプリ |
.ipa |
iOS アプリ |
全部ZIPです。手元の.apkや.epubの拡張子を.zipに変えてみると本当に解凍できます。私もこの記事を書きながら試してみて、ちゃんと開けたときは少し感動しました。
つまり「ZIPに構造を決めてアプリの保存形式にする」というのは、ソフトウェア業界ではごく普通のパターンなんです。Microsoftが独自に編み出したわけではなく、すでに実績のあった手法を踏襲した結果と言えます。
※厳密に言うと、全部同じ仕組みというのは少し乱暴みたいですが、今回はざっくり理解が目的なので割愛します。
なぜZIPが選ばれたのか
ではなぜ、Office文書の保存形式にZIPが選ばれたのでしょうか。理由は4つあります。
1. 圧縮率が高い
XMLは見ての通り、タグの分だけ冗長です。セル1個でも<c r="A1" t="s"><v>0</v></c>のような記述が必要で、これが何万セル分も並びます。
ZIPの圧縮アルゴリズム(Deflate)はXMLとの相性が抜群で、繰り返しパターンを効率的に圧縮します。実測で元サイズの10〜20%程度まで縮むことも珍しくありません。
2. 複数ファイルを1つにまとめられる
Excelファイルは「ワークブック定義」「各シート」「スタイル」「文字列プール」など、機能ごとにファイルを分離したい事情があります。これを1つの.xlsxとしてユーザーに届けるには、アーカイブの仕組みが必要です。
3. 特許フリー・枯れた技術
ZIPは1989年から存在する仕様で、ライブラリも豊富、ライセンス問題もありません。新しいフォーマットの基盤として採用するうえで、これ以上ない安心感です。
4. ランダムアクセス可能
ZIPは中央ディレクトリ構造を持つため、「シート3だけ読みたい」という部分アクセスが可能です。tarだと頭から走査が必要なので、この差は大きいです。
sharedStrings.xml という地味だが賢い工夫
中身を覗いていて私が「お、これは賢い」と感じたのがsharedStrings.xmlの仕組みです。
セルに"東京"という文字列を100個書いても、実体はsharedStrings.xmlに1回だけ格納され、各セルはそのインデックス番号を参照する設計になっています。
<sst>
<si><t>東京</t></si>
<si><t>大阪</t></si>
</sst>
<c r="A1" t="s"><v>0</v></c> <!-- "東京"を参照 -->
<c r="A2" t="s"><v>1</v></c> <!-- "大阪"を参照 -->
t="s"がshared string参照、t="n"(または属性なし)が数値です。数値はインライン、文字列は参照プールから引くという設計になっています。
openpyxlでExcelを読むとき、内部ではこのsharedStrings.xmlをパースしてメモリに展開し、各セルの<v>タグの値をキーとして文字列を引き直しています。大きなファイルでread_only=Trueモードが推奨されるのも、このプール構造を一気にメモリに乗せないためです。
.xlsx / .xlsm / .xlsb の違い
ついでに、Excelの保存形式の親戚たちも整理しておきます。
| 拡張子 | 構造 | 特徴 |
|---|---|---|
.xlsx |
ZIP+XML | 標準形式、マクロなし |
.xlsm |
ZIP+XML + xl/vbaProject.bin
|
マクロ付き、バイナリのVBAが追加される |
.xlsb |
バイナリ形式 | XMLではない。巨大ファイルで高速だが汎用ツールから扱いにくい |
業務でPythonから扱うときは基本.xlsxで十分ですが、サイズが数百MBを超えてくると.xlsbへの変換が現実的な選択肢になります。ただし.xlsbはopenpyxlでは読めないので、pyxlsbなどの専用ライブラリが必要です。
「ZIPに名前を付けただけ」の限界
ここまで読むと「じゃあ拡張子変えれば何でも.xlsxとして開けるの?」と思いますよね。私も最初そう思いました。
でも、そうはいきません。中身のXMLが厳密な仕様(ISO/IEC 29500、約6000ページ)に従っている必要があります。
-
[Content_Types].xmlが必須 -
xl/workbook.xmlのスキーマに従う - リレーションシップ(
.rels)で参照関係を正しく記述 - セルの座標、データ型、参照の整合性
なので正確には「ZIPをコンテナとして使い、その中身の構造を仕様で定めたもの」です。コンテナとしてのZIPは汎用品、中身のフォーマットは専用品、という二層構造になっています。
.xlsxの中のXMLを手で書き換えると、整合性が崩れてExcelが「ファイルが壊れています」と言ってくることがあります。書き換える場合は必ずバックアップを取り、[Content_Types].xmlと.relsの整合性に注意してください。
だからClaudeは.xlsxを編集できる
ここが個人的に一番おもしろいと感じた点です。
.xlsxがZIP+XMLという構造である事実は、LLM時代になって新しい意味を持ち始めたと思っています。
考えてみてください。もし.xlsxが旧形式の.xlsのような独自バイナリだったら、ClaudeのようなLLMがExcelファイルを編集するには、
- バイナリ仕様を完全に理解し
- オフセット計算をミスなくこなし
- チェックサムを正しく計算する
…という、言語モデルが本質的に苦手とする処理が必要になります。
でも実際には、.xlsxの中身は人間が読めるXMLテキストです。Claudeから見れば、ZIPを解凍してXMLを編集して、ZIPに戻すだけ。これは「テキスト処理」の延長線上にあります。
# Claudeが実際にやっていること(概念図)
import zipfile
import shutil
# 1. ZIPとして解凍
with zipfile.ZipFile('input.xlsx', 'r') as z:
z.extractall('workdir')
# 2. XMLをテキストとして編集
with open('workdir/xl/worksheets/sheet1.xml', 'r') as f:
content = f.read()
content = content.replace('<v>old_value</v>', '<v>new_value</v>')
with open('workdir/xl/worksheets/sheet1.xml', 'w') as f:
f.write(content)
# 3. ZIPに戻す
shutil.make_archive('output', 'zip', 'workdir')
shutil.move('output.zip', 'output.xlsx')
実際にはClaude Code等の内部ではopenpyxlのようなライブラリを呼んでいることが多いですが、根本にあるのは「XMLテキストの編集」という、LLMが得意な作業です。
2000年代にMicrosoftがOOXMLを策定したとき、彼らは「相互運用性のため」と説明しました。EUからの圧力もありました。でも結果として、その決定は20年後にLLMがOffice文書を扱えるようになるという、誰も予想していなかった副産物を生んだのです。
技術選択の長期的な影響って、こういうところに現れるんだなと、調べていてしみじみ感じました。
まとめ
要点を整理します。
-
.xlsxの正体はZIP圧縮されたXMLファイルの集合 - これはOPCという業界標準パターン。
.docx/.pptx/.epub/.apk/.jarも同じ - ZIPが採用されたのは「圧縮率」「集約」「特許フリー」「ランダムアクセス」の4点
-
sharedStrings.xmlによる文字列プール化が処理速度の鍵 - 中身がXMLテキストだから、LLMでも編集できる
データエンジニアの実用的な含意としては、
- 壊れたExcelの救出: 解凍してXMLを直接編集すれば、Excelで開けないファイルから値を救出できることがある
- 大量生成の高速化: openpyxlを介さず、テンプレートZIPの一部だけ差し替える方法で高速化が可能
- Git管理は厳しい: ZIPなのでバイナリ扱いになり、差分が取れない。テキスト管理したいならCSVに分離するのも手
ファイル形式の歴史と中身を知ると、なぜこの設計なのかが腑に落ちます。私は今後Excelを触るとき、ちょっと違う目で見られそうです。
もし手元に.xlsxがあったら、ぜひ拡張子を.zipに変えて中身を見てみてください。実際にやってみると一気に解像度が上がります。
-
ISO/IEC 29500: Information technology — Document description and processing languages — Office Open XML File Formats ↩
