ByteScope

Excel 活頁簿(xlsx)檔案格式

Office Open XML 格式的 Microsoft Excel 活頁簿。

MIME 類型
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
副檔名
.xlsx
分類
文件

Magic number(檔案簽章)

0x00000000504B0304PK··'PK' — it is a ZIP

ZIP 內含 '[Content_Types].xml' 以及一個 'xl/' 目錄。

關於 Excel 活頁簿(xlsx)格式

.xlsx 是一個 Open Packaging Conventions 套件——一包 XML 元件組成的 ZIP——版面配置是為表格資料設計的。儲存格住在 xl/worksheets/sheet1.xml 之類的地方,但字串多半不住在那裡:重複的文字會被集中到 xl/sharedStrings.xml,再用索引引用,所以一份有十萬個相同標籤的活頁簿壓完幾乎不佔空間。樣式也用同樣手法集中在 xl/styles.xml。檔案開頭那幾個位元組完全看不出它是試算表;只有 ZIP 目錄裡的項目名稱看得出來。

檔頭結構,一個位元組一個位元組看

位移長度欄位意義
04Local file header signature50 4B 03 04,ZIP 的本地檔案標頭。活頁簿、Word 文件、簡報、電子書和 Android 套件在這個位移完全無法分辨——這個格式是疊在 ZIP 之上的容器慣例,不是它自己的簽章。
42Version needed to extract最低讀取端版本乘以 10,小端序。deflate 是 20。元件超過 65535 個、或整體超過 4 GiB 的活頁簿會把它推到 45 走 ZIP64,內嵌媒體非常大的時候才會遇到。
62General purpose bit flag位元 3 會把 CRC 與大小移到壓縮資料後面的 data descriptor,串流式寫入端就是這麼做的。位元 11 宣告項目名稱是 UTF-8——這件事有意義,因為工作表元件名稱都是 ASCII,內嵌媒體的檔名卻常常不是。
82Compression method所有 XML 元件都是 8,也就是 deflate。試算表 XML 是整個 OOXML 家族裡最好壓的內容:一張全是數字儲存格的工作表,展開是一大堆囉唆的標記,deflate 之後通常剩不到十分之一。
102Last modified timeMS-DOS 打包的時間,解析度兩秒、沒有時區。Excel 會把元件的時間戳記正規化,所以活頁簿真正的編輯歷程在 docProps/core.xml,不在這裡。
122Last modified dateMS-DOS 打包的日期,從 1980 起算。值得和活頁簿自己的日期系統對照著看,後者是從 1900 起算天數(也可以選 1904)——同一個檔案裡兩個互不相干的紀元。
144CRC-32這個元件未壓縮內容的檢查碼。Excel 載入時會驗,所以傳輸中壞掉的活頁簿會乾脆地失敗,而不是打開之後裡面的數字是錯的。
184Compressed size這個元件的 deflate 位元組數。FF FF FF FF 代表交給 ZIP64 額外欄位;大到需要 ZIP64 的活頁簿,通常在別的地方早就出問題了。
224Uncompressed size元件的原始長度。它和壓縮後大小之間的落差,正是共用字串設計獲利的地方——集中起來的那張表展開後是儲存大小的好幾倍。
262File name length第一個項目是 [Content_Types].xml 時(每個 OOXML 寫入端都這樣吐),這裡是 13 00,也就是 19 個位元組。這是第一個帶有「這檔案到底是什麼型別」資訊的欄位。
282Extra field lengthExcel 寫出來通常是 0。被指令碼重新打包過的活頁簿常常會多出 Unix 權限或時間戳記的額外欄位,功能上沒有任何差別,但檔案的雜湊值會變。
3019First entry name[Content_Types].xml。它裡面有一個 Override 元素把 /xl/workbook.xml 對應到結尾是 spreadsheetml.sheet 的內容型別——那個字串就是「這個套件是活頁簿而不是文件或簡報」的權威宣告。
variablevariablexl/workbook.xml讓這個套件成為試算表的元件。它的位移不固定,要透過中央目錄去找。它列出每張工作表與對應的關聯 id,再經由 xl/_rels/workbook.xml.rels 解析到真正的 xl/worksheets/sheetN.xml 元件。
-224End of central directory signature50 4B 05 06,沒有壓縮檔註解時位於倒數第 22 個位元組。讀取端要列出活頁簿有哪些工作表,所需的一切都是從這裡走出去的,所以最後 22 個位元組比前 30 個重要。
-64Offset of the central directory目錄開始的位置,是從檔案開頭起算的絕對位置。只要在套件前面塞任何位元組,它就失效了,而 Excel 的反應是直接宣告檔案讀不了,不會去找真正的位移在哪。

怎麼跟長得像的格式分開

容易搞混的怎麼分
Word 文件(docx)本地檔案標頭每個欄位都一樣,位移 30 也一樣,因為兩種套件都以 [Content_Types].xml 開頭。分界在檔尾的目錄清單:活頁簿是 xl/workbook.xml 加一棵 xl/ 樹,文件是 word/document.xml 加一棵 word/ 樹。看副檔名不能代替這件事——位元組說它是什麼,Excel 就當它是什麼。
xls它的前輩,而且是完全不同的生物:Compound File Binary 容器,開頭 D0 CF 11 E0 A1 B1 1A E1,裡面有一個叫 'Workbook' 的串流。兩者都能用 Excel 打開、都被叫做「Excel 檔」,而列與欄的上限差距極大——二進位格式是 65536 乘 256,.xlsx 是 1048576 乘 16384,這就是全世界會轉檔的實務理由。
csv實務上常被混為一談,位元組層級則從來不會:CSV 是純文字、完全沒有簽章,.xlsx 是 ZIP。真正會咬人的是語意差異——CSV 只帶值,活頁簿帶的是值加上公式、型別、格式與多張工作表,所以「匯出成 CSV」是把檔案裡大部分東西丟掉,「用 Excel 開 CSV」則會憑空發明出原本不存在的型別。
xlsm啟用巨集的活頁簿,標頭部分和 .xlsx 逐位元組相同,唯一的差別是多一個 xl/vbaProject.bin 元件,以及不同的 ContentType 宣告。既然看得到的差別只有副檔名、而改名不用錢,郵件過濾器要判斷這一對就非得把套件拆開看不可。

常見的壞法

  • end-of-central-directory 記錄不見了。下載被截斷時,每個工作表元件都完好、壓縮也正確,Excel 還是拒絕開檔,因為它是從檔尾開始列舉的。從 50 4B 03 04 往後切出各個項目、重建目錄,通常每張工作表都救得回來。
  • 套件前面被塞了位元組。代理伺服器的錯誤頁或一個亂入的 BOM 卡在 50 4B 03 04 前面,整個檔案就偏移了,中央目錄裡每個絕對位移都錯了同樣的量。活頁簿完整無缺;把前面的垃圾刪掉就復原了。
  • 文字模式傳輸。deflate 資料流裡的換行被轉換過,標頭、項目名稱與大小全都讀得漂漂亮亮,所以套件列得出來,然後在解壓縮某張工作表時 CRC 檢查失敗。ZIP 結構在那之前完全沒有徵兆。
  • 手改過的元件重新打包錯了。工作表就是純 XML,會想直接改它很正常;在 XML 宣告前面加了 BOM、改了項目名稱的大小寫、或把資料夾也加成目錄項目,任何一項都會讓 Excel 拒收——而每個壓縮工具都說這個套件很健康。
  • xl/sharedStrings.xml 壞掉。儲存格文字是集中存放、用索引引用的,所以這一個元件損壞不會只讓某個儲存格變空白——它會讓整份活頁簿的字串索引全部位移,工作表打開之後,正確的數字全配上錯誤的標籤。這是這個格式最會誤導人的失效模式。

這個格式現在還在用嗎

.xlsx 套件是「稍微有點結構的表格資料」的通用交換格式,而它的覆蓋率主要是商業事實而不是技術事實:財務、營運與政府單位就是用它發布資料,所以每套資料工具都被迫要能讀。就它的定位而言,技術上算合理——開放的 ISO 標準、可壓縮、元件是 XML 所以救得回來,而且擺脫了 .xls 那個讓大型匯出根本做不了的列數上限。但拿它當資料處理格式就很糟:解析要先解壓縮、再串流讀幾 MB 的 XML;日期是以 1900 為紀元的序號存的,還刻意保留了從 Lotus 沿襲下來的閏年 bug;長的數字型識別碼會被默默轉成浮點數,再用科學記號重新格式化。任何分析導向的用途,簡單交換用 CSV、規模大就用 Parquet,兩者讀取速度都快上好幾個數量級。.xlsx 真正的首選場合,是要交給人打開、人來編輯的成品:公式、多張工作表、格式與圖表全都保得住,換成 CSV 一樣都留不下來。

在這個站上打開 Excel 活頁簿(xlsx)檔

這些全部在你的瀏覽器裡跑,檔案不會離開你的電腦。

相關格式

容易和 Excel 活頁簿(xlsx)搞混的格式,可以比對它們的開頭位元組。

格式開頭位元組副檔名
Word 文件(docx)50 4B 03 04.docx
PowerPoint 簡報(pptx)50 4B 03 04.pptx
ZIP50 4B 03 04.zip
OLE2 / CFBD0 CF 11 E0 A1 B1.doc

常見問題

檔案的 magic number 是什麼?

檔案開頭的前幾個位元組,不管檔名寫什麼,它標明的是真正的格式。PNG 一定以 89 50 4E 47 開頭,ZIP 則是 50 4B 03 04。作業系統、瀏覽器和 file 指令讀的都是這幾個位元組,而不是相信副檔名 — 這就是為什麼把檔案改名並不會改變它實際上是什麼。

我要怎麼自己檢查 magic number?

在 macOS 或 Linux 上,xxd -l 16 檔名 會印出前 16 個位元組;在 Windows 上,PowerShell 的 Format-Hex -Path 檔名 -Count 16。或者把檔案丟進本站的檔案類型辨識工具,它會在本機讀取位元組並告訴你是什麼格式,不會上傳任何東西。

為什麼兩種格式的 magic number 會一樣?

因為其中一種是建立在另一種之上。docx、xlsx、pptx、apk、jar 與 epub 骨子裡都是 ZIP 壓縮檔,所以全都以 ZIP 的簽章開頭;要分辨它們得看裡面的項目。Java class 檔和 macOS 的通用二進位檔都以 CA FE BA BE 開頭,靠緊接其後的欄位區分。

有完全沒有 magic number 的檔案嗎?

有。XML、HTML、JSON、SVG、CSV 這類以文字為基礎的格式沒有固定的位元組簽章,所以改用結構來辨識:開頭的 <?xml 宣告、<svg 元素,或是 { 後面接著帶引號的鍵。這種偵測是合理推測而非確定,因此本參考頁會把這些格式標示為結構偵測。

magic number 相符就代表檔案安全嗎?

不代表。它告訴你的是格式,不是內容是否無害 — 一個貨真價實的 PDF 或 docx 仍然可能夾帶惡意內容。辨識真實類型是有用的第一道檢查,尤其在副檔名和位元組不一致的時候,但它不是惡意軟體掃描。

手上有檔案可能是 Excel 活頁簿(xlsx)?用檔案類型辨識工具查出來 — 它在本機讀取 magic number,不上傳任何東西。或者瀏覽所有格式。