Excelベースの申請書を Web フォームから自動入力する仕組みを作る
会社や行政とやり取りするとき、申請用紙・届出書・登録用紙などは、わがままを言ってもだいたい Excel ファイル で運用されています。
- フォーマットは相手側(役所・取引先)がガチガチに決まっている
- こちらが勝手に Web アプリや PDF に変えるわけにはいかない
- でも、毎回 Excel を開いてコピペするのはつらい
そこで今回は、
申請用紙そのものは Excel のままにしておきつつ、
Web のアンケートフォーム型 UI から入力して、自動で Excel に値を代入するツール
を Python + FastAPI + openpyxl で作ってみます。
ゴールイメージ
-
ユーザーは Web アプリにアクセスし、
会社名 / 住所 / 代表者名 / 日付 などをフォームに入力 -
送信ボタンを押すと、バックエンドで
- Excel テンプレートを読み込み
- 該当セルに値をセット
- 申請用 Excel ファイルを生成
-
ブラウザ上の「ダウンロード」ボタンから、
自動入力済みの Excel がそのままダウンロードできる
例えばこんなテンプレートですね。
Excel のレイアウトやフォーマットは、相手に合わせてそのまま維持できます。
全体構成
今回はシンプルに以下の構成にします。
-
フロントエンド
- 素の HTML + CSS + JavaScript
- フォームに入力 → JSON で API に送信
-
バックエンド
- Python + FastAPI
- Pydantic でフォーム入力を受け取る
- openpyxl で Excel テンプレートに値を代入
- 生成された Excel をダウンロード用に提供
ディレクトリ構成の一例はこんな感じです。backend/、frontend/と分けたほうが本格的ですが、まあこれくらいでやっちゃおうという意図です。
.
├─ main.py # FastAPI
├─ index.html
├─ script.js
├─ style.css
└─ template_application.xlsx # 申請用 Excel のテンプレート
1. バックエンド(FastAPI + openpyxl)
1-1. 必要なライブラリ
pip install fastapi uvicorn openpyxl pydantic
1-2. 入力データ用のモデル
フォームから受け取る値を Pydantic のモデルで定義します。
# main.py
from fastapi import FastAPI, HTTPException, Response
from pydantic import BaseModel
from openpyxl import load_workbook
from io import BytesIO
import os
app = FastAPI()
class FormData(BaseModel):
companyName: str
address: str
year: int
month: int
day: int
presidentName: str
1-3. マージセルに値を入れるヘルパー
申請用の Excel は、見た目の都合でセルが結合されていることが多いです。
そのまま値を入れると文字が上手く入らなかったり、とても小さくて不自然になったりするので、マージセル用の関数をひとつ用意します。
def set_merged_cell_value(ws, cell_range: str, value: str):
"""
結合セルの左上セルに値を入れ直すヘルパー
1. いったんセル結合を解除
2. 左上セルに値をセット
3. 同じ範囲を再度結合
"""
# 例: "A1:C1" -> "A1"
start_cell = cell_range.split(":")[0]
# 結合されている前提で一度解除
try:
ws.unmerge_cells(cell_range)
except ValueError:
# すでに解除済みでもエラーにならないようにしておく
pass
ws[start_cell] = value
ws.merge_cells(cell_range)
1-4. Excel に値を代入する処理
ここが今回の本題です。
あらかじめ用意した template_application.xlsx を開いて、
「どのセルに、フォームのどの項目を入れるか」をマッピングしていきます。
TEMPLATE_PATH = "template_application.xlsx"
OUTPUT_PATH = "created_application.xlsx"
def generate_excel(data: FormData) -> bytes:
if not os.path.exists(TEMPLATE_PATH):
raise HTTPException(status_code=500, detail="Excel template file not found")
wb = load_workbook(TEMPLATE_PATH)
ws = wb.active
# 会社名(例: B5:D6 が結合セルになっている想定)
set_merged_cell_value(ws, "B5:D6", data.companyName)
# 本店住所
set_merged_cell_value(ws, "B7:D9", data.address)
# 代表者氏名
set_merged_cell_value(ws, "B11:D12", data.presidentName)
# 設立日
establish_date = f"{data.year}年{data.month}月{data.day}日"
set_merged_cell_value(ws, "F17:H17", establish_date)
# Excel をメモリ上に保存(ファイルに書かずに bytes で返す)
buffer = BytesIO()
wb.save(buffer)
buffer.seek(0)
return buffer.getvalue()
実際には、自分の申請用テンプレートのセル範囲に合わせて
"B5:D6"などを調整してください。
1-5. API エンドポイント
フォームから JSON を受け取って Excel を生成し、その場で返す API を用意します。
@app.post("/generate-excel")
def generate_excel_endpoint(data: FormData):
excel_bytes = generate_excel(data)
headers = {
"Content-Disposition": 'attachment; filename="created_application.xlsx"',
"Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
}
return Response(content=excel_bytes, headers=headers)
開発サーバーの起動:
uvicorn main:app --reload
2. フロントエンド(HTML + JavaScript)
今回はフレームワークなしで、素の HTML/JS で実装します。
本番では React や Vue などに置き換えても問題ありません。
2-1. フォーム画面
<!-- index.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>申請フォームから Excel 自動生成</title>
<script defer src="script.js"></script>
</head>
<body>
<h1>会社設立 申請フォーム(Excel 自動生成)</h1>
<form id="applicationForm" onsubmit="submitForm(event)">
<div>
<label>会社名:</label>
<input id="companyName" required />
</div>
<div>
<label>本店住所:</label>
<input id="address" required />
</div>
<div>
<label>設立日:</label>
<input id="year" type="number" placeholder="2025" required /> 年
<input id="month" type="number" placeholder="1" required /> 月
<input id="day" type="number" placeholder="1" required /> 日
</div>
<div>
<label>代表者氏名:</label>
<input id="presidentName" required />
</div>
<button type="submit">Excel を生成してダウンロード</button>
</form>
</body>
</html>
2-2. フォーム送信の JavaScript
fetch で /generate-excel に対して POST し、返ってきた Excel をその場でダウンロードさせます。
// script.js
async function submitForm(event) {
event.preventDefault();
const payload = {
companyName: document.getElementById("companyName").value,
address: document.getElementById("address").value,
year: Number(document.getElementById("year").value),
month: Number(document.getElementById("month").value),
day: Number(document.getElementById("day").value),
presidentName: document.getElementById("presidentName").value
};
try {
const response = await fetch("http://localhost:8000/generate-excel", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
if (!response.ok) {
const text = await response.text();
throw new Error(text || "Failed to generate excel");
}
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "created_application.xlsx";
document.body.appendChild(a);
a.click();
a.remove();
window.URL.revokeObjectURL(url);
} catch (err) {
console.error(err);
alert("Excel の生成に失敗しました");
}
}
これで、
- フォームに入力
- 「Excel を生成してダウンロード」をクリック
- 申請用の Excel が自動でダウンロード
という体験ができます。
3. Excel テンプレートの作り方のコツ
ここまで読んで「セル範囲どうやって決めるの?」となると思うので、
テンプレート作成時のポイントも整理しておきます。
- 先に 相手から指定された Excel をコピーして
template_application.xlsxとして保存 - 入力したい項目(会社名・住所・氏名など)を書いているセルを確認
- 結合セルの場合は、Excel 上で「A1:C1」などの範囲を確認しておく
- コード側でその範囲に対して
set_merged_cell_value(ws, "A1:C1", value)を呼ぶ
個人的には、
- 「セル範囲 → 項目名 → コード上の key」をまとめた表を Notion や別シートで管理
- Excel 側のレイアウトが変わったときも、表を見ながらコードを修正
という運用にしておくと、あとから見ても分かりやすいです。
4. 応用: 住所の自動整形や Word への展開
この記事では「フォーム → Excel」の最小構成に絞りましたが、
実務では次のような拡張もよく欲しくなります。
- 入力された英語住所を Google Maps API で日本語の正式住所に変換してから Excel に入れる
- 代表者名を自動的にカタカナ表記に変換して別のセルに入れる
- Excel だけでなく、Word のテンプレート(.docx)にも同じ情報を差し込む
これらも基本は同じで、
- フロントから受け取った値を
Python 側で一度「変換済みの値」に加工 - その結果を Excel / Word のそれぞれのテンプレートに流し込む
という流れを増やしていくだけです。
こういうのは厄介...
入力欄がもはやセルではなく画像。つまり印刷して使うことが前提になっているフォーマットです。このような場合は、仕方がないのでフォーマットを作り直したほうが良いですね。

