10 人が閲覧(直近 30 日) Go

Excelizeとは?GoでExcelを読み書きする使い方とv2.11.0の変更点・10万行の実測

Excelizeとは?GoでExcelを読み書きする使い方とv2.11.0の変更点・10万行の実測

Excelize(エクセライズ)は、Go言語でExcelファイル(XLSX)を作成・編集・読み込みするためのライブラリです。C言語のライブラリやExcel本体を必要とせず、Linuxのサーバーやコンテナの中で帳票を生成できます。2026年10月時点の最新版は2026年7月7日(日本時間)公開のv2.11.0で、Go 1.25.0以上が必須になり、グラフのタイトル指定に破壊的変更が入り、脆弱性3件が修正されました。

この記事では、v2.11.0で書き込みと読み込みのコードを動かし、10万行のブックを通常APIとStreamWriterで書いたときのメモリ使用量を測った結果をもとに、使い方と移行時の注意点を整理します。

まとめ:Excelize v2.11.0を使う前に押さえる要点

  • 導入は go get github.com/xuri/excelize/[email protected]。v2.11.0はGo 1.25.0以上が必須で、GOTOOLCHAIN=local でGo 1.24に固定した環境ではv2.10.1が上限
  • v2.10.1以前の脆弱性3件(CVE-2026-54063ほか)はv2.11.0で修正済み。ただし2026年10月8日時点で、v2.11.0も影響を受けるアドバイザリが16件公開されており、修正版のv2.11.1は未リリース
  • 利用者がアップロードしたファイルを開くなら、別プロセスで時間とメモリに上限を付けて処理する
  • v2.10系から上げると、グラフの Title を []RichTextRun で書いたコードはコンパイルエラーになる。ChartTitle で包み直す
  • 10万行×10列の書き込みは、通常APIで最大常駐メモリ約790MiB、StreamWriterで約71MiB(実測)。大量行を昇順に出力し、同じシートで通常APIによる編集を必要としない場合はStreamWriterを選ぶ
  • 読み込みも GetRows の約95MiBに対し、Rows イテレータは約14MiB(実測)。処理時間はほぼ同じなので、大きいファイルはイテレータで読む
  • Node.jsやブラウザからは excelize-wasm(npm 0.1.3)で同じAPIが使える。ただし初回に約4.1MBのWasmを読み込む

Excelizeとは:Pure GoでXLSXを読み書きするライブラリ

Excelizeは、Microsoft Excel 2007以降で使われるOffice Open XML(ECMA-376)形式のファイルを読み書きするGoライブラリです。対応形式はXLAM・XLSM・XLSX・XLTM・XLTXの5種類で、Excel 97-2003形式の.xlsは扱えません。XLSXの実体は、シートやスタイルをXMLで記述したファイルをZIPでまとめたものです。後述の10万行のブック(3.9MB)を展開すると、9個のファイルで合計47MBありました。

ライセンスはBSD-3-Clauseで、商用製品に組み込めます。GitHubのリポジトリは2026年10月時点で qax-os/excelize に移っており、スター数は約2.1万です。ただし、Goのimportパスは従来どおり github.com/xuri/excelize/v2 です。公式ドキュメント(xuri.me/excelize)は日本語を含む12言語で公開されています。

セルの値・数式・スタイル・結合セルに加えて、グラフ、ピボットテーブル、スライサー、条件付き書式、画像の挿入、パスワード付きブックの読み書きにも対応しています。CalcCellValue を使えば、Excelを起動せずにGo側で対応する数式を計算できます。Excelのすべての関数・計算機能を再現するものではないため、利用する数式の対応状況と計算結果を確認してください。

インストールと基本の書き込み・読み込み

go getでの導入とGo 1.25要件

モジュールを作り、バージョンを指定して取得します。

go mod init example.com/report
go get github.com/xuri/excelize/[email protected]

v2.11.0の go.mod には go 1.25.0 と書かれています(v2.10.1は go 1.24.0)。手元のGoが1.24でも、GOTOOLCHAIN が既定の auto なら、要件を満たす新しいツールチェーンへ自動で切り替わり、必要に応じて取得されます。選ばれる版はGo 1.25とは限りません。CIで GOTOOLCHAIN=local に固定している場合はビルドが止まるので、先にGo本体を上げてください。切り替えの仕組みはGOTOOLCHAINとは|Go Toolchainでバージョンを自動切り替えする仕組みと設定方法で解説しています。

書き込みの最小コード:行の書き込み・スタイル・列幅の自動調整

次のコードは、表を書き込んで見出し行に色を付け、列幅を自動調整し、合計の数式を入れて保存します。v2.11.0とGo 1.26.5で実行し、E2 = 405 と出力されることを確認しました。

package main

import (
	"fmt"
	"log"

	"github.com/xuri/excelize/v2"
)

func main() {
	f := excelize.NewFile()
	defer f.Close()

	rows := [][]interface{}{
		{"商品", "4月", "5月", "6月"},
		{"ノートPC", 120, 135, 150},
		{"モニター", 80, 72, 95},
	}
	for i, r := range rows {
		cell, _ := excelize.CoordinatesToCellName(1, i+1)
		if err := f.SetSheetRow("Sheet1", cell, &r); err != nil {
			log.Fatal(err)
		}
	}
	f.SetCellFormula("Sheet1", "E2", "SUM(B2:D2)")

	head, _ := f.NewStyle(&excelize.Style{
		Font: &excelize.Font{Bold: true},
		Fill: excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"DDEBF7"}},
	})
	f.SetCellStyle("Sheet1", "A1", "D1", head)
	f.AutoFitColWidth("Sheet1", "A:D") // v2.11.0で追加

	v, _ := f.CalcCellValue("Sheet1", "E2")
	fmt.Println("E2 =", v) // E2 = 405
	if err := f.SaveAs("report.xlsx"); err != nil {
		log.Fatal(err)
	}
}

1行ずつまとめて書くなら SetSheetRow が便利です。SetCellValue で1セルずつ書く方法もあります。AutoFitColWidth はv2.11.0で追加された関数で、2017年に起票されたissue #92への対応です。v2.10系以前では文字数から幅を計算し、SetColWidth で指定する必要がありました。

読み込み:GetRowsとRowsイテレータの使い分け

f.GetRows("Sheet1") はシート全体を [][]string で返すので、数百行の設定ファイルを読むならこれで十分です。行数が多いファイルや、ユーザーがアップロードしたファイルのようにサイズが読めないものは、Rows イテレータで1行ずつ処理します。

f, err := excelize.OpenFile("report.xlsx")
if err != nil {
	log.Fatal(err)
}
defer f.Close()

rows, err := f.Rows("Sheet1")
if err != nil {
	log.Fatal(err)
}
for rows.Next() {
	cols, err := rows.Columns()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(cols) // 1行ずつ処理し、全行をメモリに載せない
}
iterErr := rows.Error()
closeErr := rows.Close()
if iterErr != nil {
	log.Fatal(iterErr)
}
if closeErr != nil {
	log.Fatal(closeErr)
}

どちらもセルの値を文字列で返し、表示形式(日付や通貨)を適用できる場合は適用後の値になります。また、行末の空セルは省かれるため、行ごとにスライスの長さが揃わない点に注意してください。生の値が必要な場合は、OpenFile の Options で RawCellValue: true を指定します。

10万行で測ったメモリと速度:StreamWriterとRowsの効果

大量行を扱うときの判断材料として、数値と文字列が半々の10万行×10列を書き込み・読み込みし、処理時間と最大常駐メモリ(/usr/bin/time -l の maximum resident set size)を2回ずつ測りました。環境はIntel Core i9-9880H(2.3GHz)、macOS 26.6.2、Go 1.26.5、Excelize v2.11.0です。読み込みの比較には、両APIともStreamWriterで生成した同じstream.xlsxを使用しました。以下の掲載コードは2列の使用例で、表の10列の測定コードとは異なります。表は2回の平均です。

処理 API 時間 最大常駐メモリ
書き込み SetSheetRow(通常) 13.2秒 約790MiB
書き込み StreamWriter 3.3秒 約71MiB
読み込み GetRows 8.7秒 約95MiB
読み込み Rowsイテレータ 8.8秒 約14MiB

書き込みでは、StreamWriterが通常APIの約1/11のメモリで、約4倍速く終わりました。通常APIはシート全体をメモリ上の構造体として保持してから保存するため、行数に比例してメモリが増えます。今回の通常APIの測定値は100MB前後のメモリ上限を大幅に超えています。ただし、コンテナやサーバーレス環境での必要量は、列数・文字列・書式・同時実行数も含めて実際の帳票で測定してください。

sw, err := f.NewStreamWriter("Sheet1")
if err != nil {
	log.Fatal(err)
}
for i := 1; i <= 100000; i++ {
	cell, _ := excelize.CoordinatesToCellName(1, i)
	if err := sw.SetRow(cell, []interface{}{i, fmt.Sprintf("item-%d", i)}); err != nil {
		log.Fatal(err)
	}
}
if err := sw.Flush(); err != nil { // Flushを忘れるとシートが空のまま
	log.Fatal(err)
}
if err := f.SaveAs("large.xlsx"); err != nil {
	log.Fatal(err)
}

StreamWriterには公式ドキュメントに明記された制約が3つあります。行番号は昇順で書くこと、最後に Flush を呼ぶこと、同じシートで通常APIと混ぜて書かないことです。メモリ上のデータが16MBを超えると一時ファイルに逃がす仕組みのため、書き込み中にセルの値を読み返すこともできません。列幅は、行を書き込む前に sw.SetColWidth で指定してください。Flush の後に AutoFitColWidth を呼ぶとエラーは返りませんが、保存して開き直すと列幅は既定の9.14に戻っていました。SetRow の前に SetColWidth(1, 1, 34) を呼んだ場合は、保存後も34のままです。

読み込みでは時間に差がほとんどなく、メモリだけが約1/7になりました。v2.11.0のリリースノートでも、暗号化されていないブックを読むときのRowsイテレータのメモリ使用量を最大85%削減したとされています。全行をスライスにためる必要がない処理なら、イテレータを選ばない理由はありません。

v2.11.0の変更点とv2.10系からの移行

破壊的変更:グラフのTitleのChartTitle型への変更

v2.11.0では AddChart・AddChartSheet・AddShape の型が変わりました。v2.10.1の書き方のまま上げると、次のコンパイルエラーになります。

cannot use []excelize.RichTextRun{…} (value of type []excelize.RichTextRun)
as excelize.ChartTitle value in struct literal

Paragraph フィールドに移せば直ります。

// v2.10.1まで
Title: []excelize.RichTextRun{{Text: "月別販売数"}},

// v2.11.0から
Title: excelize.ChartTitle{
	Paragraph: []excelize.RichTextRun{{Text: "月別販売数"}},
},

あわせて、図形の Line が ShapeLine 型から LineOptions 型に変わり、ChartDashType→LineDashType、ChartLineType→LineType、ChartLine→LineOptions と改名されました。いずれもコンパイル時に検出されるので、実行してから壊れていることに気づく心配はありません。新しい ChartTitle では、タイトルの塗り・枠線・位置に加えて、セルを参照する数式(Formula)も指定できます。

修正された脆弱性3件とgovulncheckでの確認

v2.11.0では次の3件が修正されています。いずれもv2.10.1以前が影響を受け、細工したXLSXに対してGetRowsやGetCellValueなどの読み取りAPIを呼び出すと、メモリ枯渇やpanicを引き起こす可能性があります。

CVE Go脆弱性ID 深刻度 内容
CVE-2026-54063 GO-2026-5960 High(7.5) 行番号の検証漏れでメモリを無制限に確保
CVE-2026-59161 GO-2026-6453 High GetRowsの行数上限をすり抜けて確保量を操作
CVE-2026-59162 GO-2026-6452 Medium 負の共有文字列インデックスでpanic

3件ともGoの脆弱性データベースに登録されているので、govulncheck で検出できます。脆弱な関数が実際に呼ばれているかまで判定してくれるので、影響の有無を確かめてから上げられます。

go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
go get github.com/xuri/excelize/[email protected]
go mod tidy

Go 1.24から上げられない事情があってv2.10.1に留まる場合は、信頼できない入力を開かない運用で補うしかありません。到達可能性で絞り込む仕組みはgovulncheckとは|Goの脆弱性を到達可能性で検出する仕組みと使い方を参照してください。

追加機能:列幅の自動調整・ピボットテーブル・数式計算

先に紹介した AutoFitColWidth のほかに、ピボットテーブルの値フィールドに「計算の種類」を設定する ShowValuesAs、ピボットテーブルとスライサーで選択済みの項目を指定する SelectedItems が加わりました。CalcCellValue は、シートをまたぐ3D参照(Sheet1:Sheet3!A1 の形)、チルダによるワイルドカードのエスケープ、暗黙的なインターセクションに対応しています。性能面では ColumnNumberToName のメモリ割り当てが約90%減りました。また、グラフシートがExcel OnlineやWPS Officeで表示されない不具合も直っています。

アップロードされたExcelを開くときの上限設定

展開後の合計サイズの上限 UnzipSizeLimit は、既定で16GBです。XLSXはZIPなので、数MBのファイルが展開後に数十倍の大きさになります。Webアプリでアップロードを受け付けるなら、業務で想定する最大サイズに合わせて下げておきます。

func openUpload(r io.Reader) (*excelize.File, error) {
	return excelize.OpenReader(r, excelize.Options{
		UnzipSizeLimit:    200 << 20, // 展開後の合計を200MiBで打ち切る(既定は16GB)
		UnzipXMLSizeLimit: 16 << 20,  // これを超えるシートXMLは一時ファイルへ
	})
}
// 上限を超えると次のエラーが返る
// unzip size exceeds the 10485760 bytes limit(10MiBに設定したとき)

上限を10MiBにして前述の3.9MBのブック(展開後47MB)を開くと、シートを読む前に OpenReader がエラーを返しました。ファイルを開く入口で止まるので、ハンドラ側ではこのエラーを413や400の応答に変換すれば済みます。

v2.11.0にも残る未修正のアドバイザリ

展開サイズの上限で防げるのは、ファイルを開く段階の膨張だけです。GitHubのセキュリティアドバイザリには、v2.11.0公開後に報告され、v2.11.0も影響範囲に含むものが2026年10月8日時点で16件公開されています。このうち5件は修正版を2.11.1としていますが、2.11.1はまだタグが打たれておらず、残りは修正版の記載がありません。

アドバイザリ 深刻度 起点となる処理
GHSA-jw42-f3rr-4cc3 High GetRows・Rowsイテレータが長時間CPUを占有
GHSA-jrfj-fhj2-jjvm High 暗号化ブックのOpenFileでCPUを消費
GHSA-x2q3-8cjh-766f High 暗号化ブックの解析で過大なメモリ確保
GHSA-wp2g-vpjj-g53r High 相互参照する配列数式のCalcCellValueでスタック溢れ
GHSA-mx22-3794-2vpv Medium ピボットキャッシュの読み取りでpanic

CPUの占有やスタック溢れは recover でも UnzipSizeLimit でも止められず、同じプロセスで処理するとサーバー全体を巻き込みます。利用者のファイルを開く処理は、タイムアウトとメモリ上限を付けた別プロセス(ジョブ用コンテナや os/exec で起動する子プロセス)に分け、異常時は強制終了できるようにしてください。自社で生成したファイルしか開かない用途なら、ここまでの対策は不要です。2.11.1が出たら、govulncheck で影響を確かめたうえで更新します。

Excelの「重複するスタイル」警告とNewStyleの動作

Excelでブックを開いたときに「このブックには、パフォーマンスが低下する可能性のある重複するスタイルが多数含まれています」と表示されることがあります。Excelizeで帳票を作る側として気になるのは、セルごとに NewStyle を呼んでいるとスタイルが増え続けるのではないか、という点です。

結論として、同じ定義なら増えません。v2.11.0の NewStyle は、登録済みのスタイル(styles.xml の cellXfs)から、フォント・塗り・罫線・表示形式・配置・保護がすべて一致するものを探し、あればそのIDを返します。同じ定義で1万回呼んで返ったIDは1種類でした。スタイルが増えるのは、行ごとに色を少しずつ変えるなど、定義そのものが毎回違う場合です。それでも、IDは使い回せるよう、ループの外で一度だけ NewStyle を呼ぶ書き方が読みやすく、無駄もありません。

excelize-wasm・Python版・C#版:Go以外から使う選択肢

同じ作者が、ExcelizeをWebAssemblyにコンパイルしたexcelize-wasm(npm)、Python版のexcelize(PyPI)、C#版のExcelizeCs(NuGet)を公開しています。2026年10月時点の版は、excelize-wasmが0.1.3、Python版が0.1.0(Python 3.9以上)、ExcelizeCsが0.0.6です。

excelize-wasmは、Node.js 12以上、Deno 1.0以上、主要ブラウザ(Chrome 57以上、Safari 11以上など)で動きます。関数名はGo版と同じで、戻り値の形は関数によって異なり、GetRowsは { error, result }、WriteToBufferは { error, buffer }、NewFileやOpenReaderはファイルオブジェクトを返します。Node.js 26.5.0で次のコードを実行し、書いたブックを読み戻せることを確認しました。

const { init } = require('excelize-wasm');
const fs = require('fs');

init('./node_modules/excelize-wasm/excelize.wasm.gz').then((excelize) => {
  const f = excelize.NewFile();
  f.SetCellValue('Sheet1', 'A1', '売上');
  f.SetCellValue('Sheet1', 'B1', 100);
  const { buffer, error } = f.WriteToBuffer();
  if (error) throw new Error(error);
  fs.writeFileSync('w.xlsx', buffer);
  console.log(excelize.OpenReader(fs.readFileSync('w.xlsx')).GetRows('Sheet1'));
  // { error: null, result: [ [ '売上', '100' ] ] }
});

注意したいのはサイズです。excelize.wasm.gz は約4.1MBあり、Node.jsでの初期化に約0.9秒かかりました。ブラウザでは初期表示をWasmの取得・初期化待ちにしない構成を選びます。利用頻度が低ければボタン押下時に読み込み、頻繁に使う画面では初期表示後にバックグラウンドで読み込む方法もあります。数百行のCSV相当の出力なら、サーバー側のGoで生成して返すほうが軽く済みます。

Go以外の選択肢との比較と、Excelizeが向かない場面

Goで競合になりやすかった tealeg/xlsx は、GitHubでアーカイブ(読み取り専用)になっています。ただし、tealeg/xlsxの公式READMEはCodebergへの移行とv4への更新を案内しており、GitHubのアーカイブを開発終了と同一視できません。グラフやピボットテーブル、大量行のストリーミング処理が必要なら、Excelizeを候補にできます。

一方で、次の場面ではExcelizeを選ぶ必要がない、または選べません。

  • .xls(Excel 97-2003形式)を読む必要がある:Excelizeは非対応。変換してから読むか、別の手段が必要
  • マクロ(VBA)を実行したい:XLSMの読み書きはできても、マクロは実行されない
  • 出力先が単なる一覧表で、Excelで開ければよい:encoding/csv で十分で、依存を増やす理由がない
  • .NETの業務システムに組み込む:C#ならClosedXMLなど.NET向けのライブラリが第一候補になる(ClosedXMLとは?C#でExcelを操作する使い方・できないこと・ライセンス【0.105.1】)

よくある質問

Excelizeは商用利用できますか?

できます。ライセンスはBSD-3-Clauseで、著作権表示とライセンス文を残せば、商用製品への組み込みや改変も可能です。

Excelizeで.xlsファイルは読めますか?

読めません。対応形式はXLAM・XLSM・XLSX・XLTM・XLTXの5種類で、Excel 97-2003形式の.xlsは対象外です。

Go 1.24のままExcelize v2.11.0を使えますか?

v2.11.0の go.mod は go 1.25.0 を要求します。GOTOOLCHAIN=auto(既定)なら要件を満たす新しいツールチェーンへ自動で切り替わりますが、local に固定している環境ではビルドできません。v2.10.1はGo 1.24で動きますが、脆弱性3件が未修正です。

数式を入れたセルの計算結果をGoで取得できますか?

CalcCellValue で取得できます。SetCellFormula は数式を書き込むだけなので、Go側で値が必要なときは続けて CalcCellValue を呼びます。本文の例では SUM(B2:D2) に対して405が返りました。

excelize-wasmはブラウザだけで動きますか?

動きます。サーバーを用意せずにブラウザ内でXLSXを生成・解析できます。ただし約4.1MBのWasmファイルを配信する必要があり、初回の読み込みに時間がかかります。

関連記事

お気に入りに入れた記事の一覧

この記事は以下の記事からリンクされています

資料請求

今日のトレンド記事 直近 24 時間で、いつもより多く読まれている記事

  1. 2026.10.09 テックブログ IDCFクラウド(IDCフロンティア)不正アクセス・ランサムウェア:影響先・復旧・データは戻るか
  2. 2026.10.08 テックブログ 大阪公立大学のランサムウェア被害と仮想化基盤の停止|全授業休講に至った経緯とバックアップを守る設定
  3. 2024.11.08 テックブログ OpenAPI GeneratorでJavaコードを自動生成する方法|CLI導入からSpring・ライブラリ選択まで
  4. 2026.10.09 テックブログ ニッスイのサイバー攻撃で日水物流の入出荷停止|委託先クラウド障害に荷主が備える手順
  5. 2026.10.09 テックブログ 京王電鉄のランサムウェア被害とグループ共通基盤:決済・ポイント・予約が止まった範囲と遮断の初動

RELATED POSTS 関連記事

目次