メインコンテンツへスキップ
見出し画像

私が実務で手癖になっているコード8本(Python / GAS / VBA / SQL)

    金曜の22時、私は同じCSVを4回開き直していた

    客先の会議室、蛍光灯が半分落ちた状態だった。
    やることは単純で、支店から集まった売上CSVを1本のテーブルにまとめるだけ。
    ファイルは14個。
    1個目は普通に開いた。
    2個目で文字化けした。
    3個目はカンマではなくタブ区切りだった。
    4個目はヘッダーが3行目から始まっていて、上2行に「※このファイルは触らないでください」と書いてあった。

    私はそこから1時間半、encoding= の引数を書き換えては実行し、書き換えては実行していた。
    帰りの電車の中で思ったのは「今日の1時間半は、来月も再来月も同じ形で発生する」ということだった。

    その週末に書いたのが、この記事の1本目のコードだ。

    ここに出すのは、そういう夜を何度か越えた結果として、私のスニペット置き場から消えずに残っている8本になる。
    きれいなコードではない。
    ベストプラクティスから外れているものもある。
    ただ、私は今も実案件でこれを貼り付けて動かしている。

    残っているコードには、必ず「それで殴られた記憶」がくっついている

    この記事の値段と、その決め方

    有料部分の価格は 1,980円 (タイムセール後)にした。

    理由は単純で、ここに載せた8本を自力でゼロから書き、しかも「本番で1回コケてから直す」ところまで到達するのに、私は合計で15時間以上を使っている。
    文字コードの総当たり順序、GASの実行ログの持ち方、VBAの結合セルの罠、SQLの日付の穴。
    どれも「書けること」ではなく「どこで壊れるかを知っていること」に時間が溶けた部分だ。
    時間単価をいくらに見るかは人それぞれだが、私は15時間の踏み抜きを1,980円で渡すのは妥当な線だと考えている。

    なお、これを買えば作業時間がどれだけ減る、といった保証は一切しない。
    環境も業務も人によって違う。
    私が渡せるのは、私の手癖と、その手癖ができた理由だけだ。

    1. 文字コードと区切り文字を諦めて総当たりするCSVローダー(Python)

    冒頭の金曜の夜が、これを常備するきっかけになった。
    支店ごとに使っているツールが違えば、出てくるCSVの文字コードは揃わない。
    経理システムから出たものは Shift_JIS 系、BIツールから出たものは UTF-8、誰かが Excel で開いて上書き保存したものは BOM 付き UTF-8。
    私はここで、月に3〜4時間を毎月きれいに溶かしていた。

    一般には文字コード判定に chardet や charset-normalizer を使うのが定石とされる。
    私はそれを外して、総当たりの try で回している
    理由は、判定ライブラリが「それらしい答え」を返してきたときに、それが正解かどうかを検証する手間が結局残るからだ。
    日本語混じりの短いCSVで euc-jp と cp932 を取り違えられたことがあり、そのときの原因追跡に半日かかった。
    候補を自分で並べて、上から順に「読めて、列数が足りて、置換文字が出ていない」ものを採用するほうが、私にとっては挙動が読める。

    # 動作確認環境: Python 3.11 / pandas 2.2 以降(3.0 系でも確認)
    # 使用ライブラリ: pandas (BSD 3-Clause License)
    # 事前準備: pip install pandas
    # 必要権限: 対象ファイルの読み取り権限のみ
    
    from pathlib import Path
    import pandas as pd
    
    # 順番が命。UTF-8系を必ず先に置く
    ENCODING_CANDIDATES = ("utf-8-sig", "utf-8", "cp932", "euc-jp")
    SEP_CANDIDATES = (",", "\t", ";", "|")
    
    
    def read_table_bruteforce(path, min_cols=2, verbose=True):
        """文字コードと区切り文字を総当たりして、最初に成立した組み合わせで読む。"""
        path = Path(path)
        if not path.exists():
            raise FileNotFoundError(path)
    
        tried = []
        for enc in ENCODING_CANDIDATES:
            for sep in SEP_CANDIDATES:
                try:
                    df = pd.read_csv(
                        path,
                        encoding=enc,
                        sep=sep,
                        dtype=str,              # 数値変換は後段でやる
                        keep_default_na=False,  # 空欄をNaNにしない
                        engine="python",
                    )
                except (UnicodeDecodeError, pd.errors.ParserError, ValueError) as e:
                    tried.append(f"{enc} / {sep!r} -> {type(e).__name__}")
                    continue
    
                if df.shape[1] < min_cols:
                    tried.append(f"{enc} / {sep!r} -> 列数 {df.shape[1]} で不足")
                    continue
    
                header_text = "".join(str(c) for c in df.columns)
                if "�" in header_text:
                    tried.append(f"{enc} / {sep!r} -> 置換文字を検出")
                    continue
    
                df.attrs["source_encoding"] = enc
                df.attrs["source_sep"] = sep
                if verbose:
                    print(f"[read] {path.name} enc={enc} sep={sep!r} shape={df.shape}")
                return df
    
        raise ValueError(
            "どの組み合わせでも読めなかった: {}\n{}".format(path, "\n".join(tried))
        )
    
    
    if __name__ == "__main__":
        df = read_table_bruteforce("sample.csv")
        print(df.head())
        print(df.attrs)

    ここで一番危ないのは、cp932 を候補の先頭に置いてしまうことだ。
    UTF-8 のバイト列は cp932 でも例外を出さずに「読めてしまう」ことがあり、その場合は文字化けした文字列がそのまま後工程に流れる。
    例外が飛ばないぶん、気づくのが翌週の集計ミーティングになる。
    だから ENCODING_CANDIDATES は必ず UTF-8 系を先頭にする。
    それでも化けるケースに備えて、ヘッダー文字列に置換文字 � が混ざっていないかを見ている。

    私はここを必ず書き換えてから使う

    min_cols を、そのファイルで期待される列数マイナス1くらいまで上げる。
    デフォルトの2のままだと、区切り文字を取り違えたまま「1列だけど2列に見える」状態を拾ってしまうことがある。
    あとは案件ごとに ENCODING_CANDIDATES を削る。
    候補が少ないほど誤爆が減るので、その現場で出てこない文字コードは消す。

     
     
    現役エンジニア:世界最高峰の技術と論理性で利益直結のデジタル資産&自動化を完全実装 🔵データアーカイブ 🟢自動化ツール 🟣データ分析 🟠ビジネスツール 🔴管理シート 🟡データ集 👑限定特典付 ▶https://note.com/minato74_/membership/join

    あなたへのおすすめ