Python | pandas.DataFrame で DataFrame → CSV/JSON/HTML 同時出力

Python
スポンサーリンク

pandas.DataFrame を受け取り、同じデータを CSV / JSON / HTML にまとめて出力する汎用関数を作ります。
使い勝手の良さ(出力フォルダ生成、タイムスタンプ、圧縮、エンコーディング、HTML の簡易スタイリング、ロギング連携など)に配慮した実装を下に置きます。コピーしてすぐ使えます。


完成コード(そのまま使える)

import os
import json
from datetime import datetime
from pathlib import Path
import logging
import pandas as pd

# --- 簡単なロガー設定(必要ならプロジェクトの logging に差し替えてください) ---
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")


def _safe_basename(name: str) -> str:
    """ファイル名に使えるように簡易サニタイズ(空白→_、危険文字削除)"""
    keep = "-_.() abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
    cleaned = "".join(c if c in keep else "_" for c in name)
    return "_".join(cleaned.split())


def save_dataframe_outputs(
    df: pd.DataFrame,
    basename: str = "report",
    outdir: str = "output",
    to_csv: bool = True,
    to_json: bool = True,
    to_html: bool = True,
    timestamp: bool = True,
    date_format: str | None = None,      # pandas.to_csv / to_json date_format parameter if needed
    index: bool = False,
    json_orient: str = "records",        # "records","split","index","columns","values"
    compress: bool = False,
    compress_format: str = "gzip",       # if compress True, e.g. 'gzip' or 'bz2' (pandas supports)
    encoding: str = "utf-8",
    html_table_class: str = "table table-striped table-bordered",
    html_title: str | None = None,
    pretty_json: bool = True
) -> dict:
    """
    DataFrame を CSV / JSON / HTML に同時出力するユーティリティ。

    Parameters
    ----------
    df : pd.DataFrame
        出力したい DataFrame
    basename : str
        出力ファイルのベース名(例: 'sales' -> sales.csv / sales.json / sales.html)
    outdir : str
        出力先フォルダ(存在しなければ作成される)
    to_csv, to_json, to_html : bool
        それぞれの形式を出力するか
    timestamp : bool
        True のときファイル名にタイムスタンプを付ける(例: report_20251105_172000.csv)
    date_format : str or None
        日付書式(必要に応じて pandas の to_csv/to_json に渡す)
    index : bool
        CSV/HTML に index を含めるか
    json_orient : str
        pandas.DataFrame.to_json の orient
    compress : bool
        圧縮して保存(CSV/JSONのみ対応)。pandas の to_csv/to_json の compression パラメータを使う。
    compress_format : str
        'gzip' や 'bz2' など。pandas がサポートするものを指定。
    encoding : str
        ファイルのエンコーディング
    html_table_class : str
        HTML 出力の table に付与するクラス(Bootstrap 風)
    html_title : str or None
        HTML にタイトルを付ける
    pretty_json : bool
        True のとき JSON を人間向けにインデント付きで書く(pandas.to_json ではなく json.dump を利用)

    Returns
    -------
    dict
        出力したファイルパスの辞書(存在しない出力は None)
    """
    # 準備
    outdir_path = Path(outdir)
    outdir_path.mkdir(parents=True, exist_ok=True)

    safe_name = _safe_basename(basename)
    ts = datetime.now().strftime("%Y%m%d_%H%M%S") if timestamp else ""
    base = f"{safe_name}_{ts}" if timestamp else safe_name

    results = {"csv": None, "json": None, "html": None}

    # --- CSV ---
    if to_csv:
        csv_name = f"{base}.csv"
        if compress:
            csv_name += f".{compress_format}"
            compression_arg = compress_format
        else:
            compression_arg = None
        csv_path = outdir_path / csv_name
        logging.info(f"Writing CSV -> {csv_path}")
        # pandas to_csv supports compression argument
        df.to_csv(csv_path, index=index, encoding=encoding, date_format=date_format, compression=compression_arg)
        results["csv"] = str(csv_path.resolve())

    # --- JSON ---
    if to_json:
        json_name = f"{base}.json"
        if compress:
            json_name += f".{compress_format}"
            compression_arg = compress_format
        else:
            compression_arg = None

        json_path = outdir_path / json_name
        logging.info(f"Writing JSON -> {json_path}")

        # pretty_json が True のときは pandas 的に一旦 dict/list にして json.dump を使う(ensure_ascii=False に注意)
        data_obj = None
        if pretty_json:
            # orient によって DataFrame からの変換方法を変える
            if json_orient == "records":
                data_obj = df.to_dict(orient="records")
            else:
                # それ以外は pandas にまかせて文字列を取得し、パースする
                # ただし pandas.to_json は ensure_ascii=True なので、ここでは pandas のUTF-8対応が必要な場合に注意
                # 簡単化のため、使用者が records を推奨
                data_obj = json.loads(df.to_json(orient=json_orient, date_format="iso"))
            # 書き込み(圧縮or通常)
            if compression_arg:
                # pandas の内部 compression を使えばよいが、pretty_json の場合ここでは gzip 例を実装
                if compression_arg == "gzip":
                    import gzip
                    with gzip.open(json_path, "wt", encoding=encoding) as f:
                        json.dump(data_obj, f, ensure_ascii=False, indent=2)
                else:
                    # 簡易的に非 gzip 圧縮は pandas に任せる(または拡張可)
                    with open(json_path, "w", encoding=encoding) as f:
                        json.dump(data_obj, f, ensure_ascii=False, indent=2)
            else:
                with open(json_path, "w", encoding=encoding) as f:
                    json.dump(data_obj, f, ensure_ascii=False, indent=2)
        else:
            # pretty_json False -> pandas に任せる(高速)
            df.to_json(json_path, orient=json_orient, force_ascii=not (encoding.lower().startswith("utf")), date_format=date_format, compression=compression_arg)
        results["json"] = str(json_path.resolve())

    # --- HTML ---
    if to_html:
        html_name = f"{base}.html"
        html_path = outdir_path / html_name
        logging.info(f"Writing HTML -> {html_path}")
        # DataFrame.to_html でテーブル本体を作る
        table_html = df.to_html(index=index, classes=html_table_class, justify="left", border=0, escape=False)
        # 簡易 HTML テンプレート(Bootstrap CSS を使いたければリンクを追加)
        title = html_title or safe_name
        html_doc = f"""<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>{title}</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- Optional: include Bootstrap CDN for nicer table styles -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
  <style>
    body {{ padding: 1rem; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Noto Sans JP', sans-serif; }}
    table {{ width: auto; }}
  </style>
</head>
<body>
  <h2>{title}</h2>
  {table_html}
</body>
</html>"""
        html_path.write_text(html_doc, encoding=encoding)
        results["html"] = str(html_path.resolve())

    logging.info("Export complete.")
    return results
Python

使い方(例)

import pandas as pd

# サンプル DataFrame を作る
df = pd.DataFrame({
    "名前": ["Alice", "Bob", "Charlie"],
    "年齢": [25, 31, 29],
    "入社日": pd.to_datetime(["2020-01-15", "2019-07-01", "2021-09-10"])
})

# ファイルを出力する(output/report_YYYYMMDD_HHMMSS.csv など)
results = save_dataframe_outputs(
    df,
    basename="employee_report",
    outdir="output/reports",
    to_csv=True,
    to_json=True,
    to_html=True,
    timestamp=True,
    index=False,
    json_orient="records",
    compress=False,
    html_title="社員一覧"
)

print("Saved files:")
print(results)
Python

実行すると output/reports/employee_report_YYYYMMDD_HHMMSS.csv / .json / .html が生成されます。戻り値 results はそれらの絶対パスを持つ辞書です。


補足・設計メモ

  • UTF-8 対応encoding="utf-8" にしているので日本語も安心(Excelで開くときは BOM の必要や Shift-JIS 変換を検討してください)。
  • JSON のフォーマットjson_orient="records"(リストの辞書)を推奨。API に渡すときや人間が読むときに扱いやすいです。
  • 圧縮compress=True にすると .csv.gz / .json.gz のように保存する実装が可能。上のコードは gzip を例にしていますが、さらに拡張可能です。
  • HTML の見た目:Bootstrap CDN を使う簡易テンプレートを入れてあります。社内レポートや共有ファイルとしてそのまま使えます。
  • ログlogging でどのファイルに書き出したかを出力します。プロジェクトでは logging.getLogger(__name__) を使うと良いです。
  • 拡張案
    • pandas.ExcelWriter を使って Excel(.xlsx)同時出力を追加
    • ファイルのローテーションや古い出力の自動削除
    • DataFrame を複数シートで Excel 出力(レポート生成)
Python
スポンサーリンク
シェアする
@lifehackerをフォローする
スポンサーリンク
タイトルとURLをコピーしました