3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Excelベースの申請書を Web フォームから自動入力する仕組みを作る

会社や行政とやり取りするとき、申請用紙・届出書・登録用紙などは、わがままを言ってもだいたい Excel ファイル で運用されています。

  • フォーマットは相手側(役所・取引先)がガチガチに決まっている
  • こちらが勝手に Web アプリや PDF に変えるわけにはいかない
  • でも、毎回 Excel を開いてコピペするのはつらい

そこで今回は、

申請用紙そのものは Excel のままにしておきつつ、
Web のアンケートフォーム型 UI から入力して、自動で Excel に値を代入するツール

を Python + FastAPI + openpyxl で作ってみます。


ゴールイメージ

  1. ユーザーは Web アプリにアクセスし、
    会社名 / 住所 / 代表者名 / 日付 などをフォームに入力

  2. 送信ボタンを押すと、バックエンドで

    • Excel テンプレートを読み込み
    • 該当セルに値をセット
    • 申請用 Excel ファイルを生成
  3. ブラウザ上の「ダウンロード」ボタンから、
    自動入力済みの Excel がそのままダウンロードできる

例えばこんなテンプレートですね。

image.png

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 の生成に失敗しました");
  }
}

これで、

  1. フォームに入力
  2. 「Excel を生成してダウンロード」をクリック
  3. 申請用の Excel が自動でダウンロード

という体験ができます。


3. Excel テンプレートの作り方のコツ

ここまで読んで「セル範囲どうやって決めるの?」となると思うので、
テンプレート作成時のポイントも整理しておきます。

  1. 先に 相手から指定された Excel をコピーして template_application.xlsx として保存
  2. 入力したい項目(会社名・住所・氏名など)を書いているセルを確認
  3. 結合セルの場合は、Excel 上で「A1:C1」などの範囲を確認しておく
  4. コード側でその範囲に対して set_merged_cell_value(ws, "A1:C1", value) を呼ぶ

個人的には、

  • 「セル範囲 → 項目名 → コード上の key」をまとめた表を Notion や別シートで管理
  • Excel 側のレイアウトが変わったときも、表を見ながらコードを修正

という運用にしておくと、あとから見ても分かりやすいです。


4. 応用: 住所の自動整形や Word への展開

この記事では「フォーム → Excel」の最小構成に絞りましたが、
実務では次のような拡張もよく欲しくなります。

  • 入力された英語住所を Google Maps API で日本語の正式住所に変換してから Excel に入れる
  • 代表者名を自動的にカタカナ表記に変換して別のセルに入れる
  • Excel だけでなく、Word のテンプレート(.docx)にも同じ情報を差し込む

これらも基本は同じで、

  1. フロントから受け取った値を
    Python 側で一度「変換済みの値」に加工
  2. その結果を Excel / Word のそれぞれのテンプレートに流し込む

という流れを増やしていくだけです。


こういうのは厄介...

入力欄がもはやセルではなく画像。つまり印刷して使うことが前提になっているフォーマットです。このような場合は、仕方がないのでフォーマットを作り直したほうが良いですね。

image.png

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?