Azure File Share 上のCSVをPython(pandas)で加工し、結果をそのまま同じFile Share内にCSVとして保存したいのに、df.to_csv()だとローカルにしか出力できず困る――そんなときの実装パターンを、最小コードから運用のコツまでまとめます。
やりたいことと、つまずく理由
要件を整理すると次の流れです。
- Azure File Share に置かれたCSVを読み込む
- Pythonで
pandas.DataFrame(df)として加工する - 加工後の結果を、再度 Azure File Share 内に CSVファイルとして保存する
ここで多くの方が最初に書くのが、次のようなコードです。
df.to_csv("output.csv")
しかし df.to_csv("output.csv") は「ローカルパスに書き出す」動作です。Azure File Share はローカルのファイルシステムではないため、単にパスを書いただけではFile Shareに保存されません。File Shareに保存するには、SMBでマウントしてOSに“ローカルドライブ”として見せるか、もしくはAzure Storage File Share SDK(REST API)でアップロードする必要があります。
Azure File ShareにCSVを保存する2つの方法
| 方法 | 概要 | 向いているケース | 注意点 |
|---|---|---|---|
| SMBでマウントしてローカルパス扱い | File Shareをネットワークドライブとしてマウントし、df.to_csv("Z:/share/output.csv")のように書く | Windows Server上で運用していて、SMBマウントが前提の環境 | 実行環境にマウント設定が必要。コンテナやサーバレスでは難しいことが多い |
| Python SDKでアップロード | DataFrameをメモリ上でCSV化し、upload_file()でFile Shareへ書き込む | Azure Functions / コンテナ / CIなど、ローカルディスクに依存したくない環境 | SDKのクライアント階層や上書き・文字コードを理解しておくと安定する |
この記事では、より汎用性が高い「Python SDKでアップロード」の方法を中心に解説します。
今回の基本アイデア:メモリにCSVを書き出してからアップロード
ポイントはとてもシンプルです。
df.to_csv()の出力先を「ファイルパス」ではなく、メモリ上のバッファ(io.StringIO)にする- バッファからCSV文字列を取り出し、
bytes(UTF-8)に変換する - Azure File Share SDK の
upload_file()で、そのバイト列をFile Shareにアップロードする
必要ライブラリと前提
Azure File Share に対してPythonから操作するには、azure-storage-file-share を使うのが定番です。公式ドキュメントでは、必要要件として Python 3.9 以降が挙げられています。
インストールは次の通りです。
pip install azure-storage-file-share pandas
クライアント階層を理解すると迷わない
Azure File Share SDKは、操作対象に応じてクライアントが分かれています。これを押さえると「どこで upload するのか」が一気に明確になります。
| クライアント | 役割 | よく使うメソッド例 |
|---|---|---|
ShareServiceClient | ストレージアカウント(File service)全体 | get_share_client() / from_connection_string() |
ShareClient | 特定のファイル共有(share) | get_directory_client() |
ShareDirectoryClient | 特定ディレクトリ | get_file_client() / upload_file() |
ShareFileClient | 特定ファイル | download_file() / upload_file() |
最小実装:directory_client がある前提で Output.csv を作成する
質問の状況(すでに df と directory_client がある前提)なら、次のコードが最小で分かりやすい形です。
import io
# すでに df, directory_client がある前提
# 1. DataFrameをメモリ上の文字列バッファにCSV形式で書き出す
output = io.StringIO()
df.to_csv(output, index=False) # index=False で行番号は出力しない
# 2. 文字列をバイト列(UTF-8)に変換
csv_bytes = output.getvalue().encode("utf-8")
# 3. 出力先ファイルのクライアントを取得
output_file_name = "Output.csv"
output_file_client = directory_client.get_file_client(output_file_name)
# 4. Azure File Share にアップロード
# 既存ファイルを上書きしたい場合(SDK/環境によっては対応状況に差が出ることがあります)
output_file_client.upload_file(csv_bytes, overwrite=True)
この形にしておけば、ローカルディスクに Output.csv を作らず、生成したCSVデータをそのままAzure File Shareへ保存できます。
なぜ io.StringIO() と encode("utf-8") が効くのか
df.to_csv() は「パスに保存」だけではない
to_csv() は通常「ファイルパス」を渡すとディスクに出力しますが、出力先に“ファイルのように振る舞うオブジェクト”を渡すこともできます。そこで io.StringIO() を使うと、CSVをメモリ上に書き出せます。
StringIOではpandas側のencoding指定が効かない場合がある
pandasの仕様として、path_or_buf に非バイナリのファイルオブジェクト(たとえば StringIO)を渡したとき、to_csv(encoding=...) の指定はサポートされないと明記されています。そのため、自分で .encode("utf-8") して bytes にするのが確実です。
Azure File Shareの upload_file() は bytes/str/stream を受け取れる
Azure File Share SDKの upload_file() は、bytes / str / ストリーム(IO)など複数の形式を受け取れます。さらに encoding の既定値はUTF-8です。ただし運用上は「文字コードを固定したい」「環境差を減らしたい」ことが多いので、今回のように bytes を明示的に渡す形が安全です。
上書きでハマらないための実践パターン
実務で多いのが「同名ファイルを毎日作り直す」「同じファイル名に上書きしたい」といったケースです。ところが、SDKのバージョンや実行環境の違いで overwrite=True がうまく効かない(またはエラーになる)ことがあります。そんなときは、存在確認→削除→アップロードの手順が堅いです。
ファイルがあれば削除してからアップロード(確実)
import io
output = io.StringIO()
df.to_csv(output, index=False)
csv_bytes = output.getvalue().encode("utf-8")
file_name = "Output.csv"
file_client = directory_client.get_file_client(file_name)
# 既存があるなら削除(存在しない場合は何もしない)
if file_client.exists():
file_client.delete_file()
# 新規作成としてアップロード
file_client.upload_file(csv_bytes)
同じディレクトリ内の削除なら、ShareDirectoryClient.delete_file(file_name=...) を使う選択肢もあります。どちらでも構いませんが、運用コードでは「どのクライアントを持っているか」で書きやすい方を選ぶとよいです。
読み込み→加工→書き戻しまでの完全サンプル
ここからは、File Share上のCSVを読み込み、pandasで加工して、同じFile Shareへ書き戻すまでを一気通貫で示します。ローカルに一時ファイルを作らない構成なので、コンテナやサーバレスでも再現しやすいです。
サンプルコード(接続文字列で認証する例)
import os
import io
import pandas as pd
from azure.storage.fileshare import ShareServiceClient
# 環境変数から接続文字列を取得(コードにベタ書きしない)
CONN_STR = os.environ["AZURE_STORAGE_CONNECTION_STRING"]
# 対象の共有名・ディレクトリ・ファイル名
SHARE_NAME = "myshare"
DIRECTORY_PATH = "data" # ルート直下なら "" でもOK
INPUT_FILE = "input.csv"
OUTPUT_FILE = "Output.csv"
# 1) サービスクライアント
service = ShareServiceClient.from_connection_string(CONN_STR)
# 2) 共有 → ディレクトリ → ファイル
share_client = service.get_share_client(SHARE_NAME)
directory_client = share_client.get_directory_client(DIRECTORY_PATH)
# 3) File Share上のCSVをダウンロードしてDataFrameへ
input_file_client = directory_client.get_file_client(INPUT_FILE)
download = input_file_client.download_file()
csv_bytes = download.readall()
df = pd.read_csv(io.BytesIO(csv_bytes))
# 4) 加工(例:欠損値を埋め、列名を整える)
df = df.fillna("")
df.columns = [c.strip() for c in df.columns]
# 5) DataFrameをメモリでCSV化
buffer = io.StringIO()
df.to_csv(buffer, index=False)
out_bytes = buffer.getvalue().encode("utf-8")
# 6) 既存があれば削除してからアップロード(安定運用)
out_client = directory_client.get_file_client(OUTPUT_FILE)
if out_client.exists():
out_client.delete_file()
out_client.upload_file(out_bytes)
認証は接続文字列以外にも、SASトークンや共有キー、Azure AD(azure-identity)など複数の方式があります。運用ポリシーに合わせて選べます。
運用で差がつくポイント
文字コードは「用途で決め打ち」すると事故が減る
CSVの“正解の文字コード”は、利用者と利用ツールで変わります。特に日本語環境では、Excelで開くかどうかで方針が変わるので、チーム内で基準を決めておくのがおすすめです。
| 文字コード | 特徴 | メリット | 注意点 |
|---|---|---|---|
| UTF-8 | Web/クラウドで標準的 | 多言語に強い。PythonやLinux系で扱いやすい | Excelの設定によっては文字化けすることがある |
| UTF-8-SIG | 先頭にBOMを付けるUTF-8 | Excelで開いても文字化けしにくい | 厳密にBOM非許容の処理系では注意(Web系では稀) |
| CP932(Shift_JIS系) | 日本語Windows/Excel互換寄り | Excelで開く前提なら安定 | 機種依存文字や絵文字などで表現できない文字がある |
UTF-8-SIGで出したい場合は、次のようにBOM付きでエンコードします。
csv_bytes = buffer.getvalue().encode("utf-8-sig")
改行コード(\n / \r\n)を意識すると取り込みが安定する
to_csv() の改行はOS依存(Linuxなら \n、Windowsなら \r\n)になることがあります。外部ツールが改行に敏感なときは、lineterminator を指定して固定すると事故が減ります。
df.to_csv(buffer, index=False, lineterminator="\n")
大きなCSVは「コピー回数」を減らすとメモリが楽になる
StringIO -> getvalue() -> encode() は分かりやすい反面、データ量が大きいと「文字列」「バイト列」の両方を一時的に保持しがちです。サイズが大きい場合は、BytesIO と TextIOWrapper を使って“書いた瞬間にエンコード”する形が有効です。
import io
byte_buf = io.BytesIO()
# TextIOWrapperに書くと、UTF-8にエンコードされた結果がbyte_bufにたまります
text_buf = io.TextIOWrapper(byte_buf, encoding="utf-8", newline="")
df.to_csv(text_buf, index=False)
text_buf.flush()
text_buf.detach() # wrapperを切り離し(closeでbyte_bufまで閉じないため)
byte_buf.seek(0, io.SEEK_END)
length = byte_buf.tell()
byte_buf.seek(0)
file_client = directory_client.get_file_client("Output.csv")
file_client.upload_file(byte_buf, length=length)
File Shareのファイルは最大1TiBまで扱えるため、パイプライン次第ではかなり大きなCSVも保存できます。ただし、DataFrame化自体がメモリを使うので、処理全体のボトルネック(DataFrameサイズ・変換コスト)も合わせて見直すと効果的です。
認証情報はコードに書かない
接続文字列やアカウントキーをソースコードに直書きすると、漏えい時の影響が大きくなります。少なくとも環境変数、できればKey VaultやマネージドID(Azure AD)など、運用に合った方式に寄せましょう。公式ドキュメントでも、SASや共有キーなど複数のクレデンシャル方式が紹介されています。
よくあるエラーと対処
最後に、Azure File Share×PythonでCSVを扱うときに遭遇しがちなトラブルをまとめます。
| 症状 | 原因の例 | 対処 |
|---|---|---|
| アップロード時に「既に存在する」系のエラー | 同名ファイルが残っている/overwrite指定が効かない | exists()で確認し、delete_file()してからアップロードする |
| 404(Not Found) | 共有名・ディレクトリパスが違う/ディレクトリが未作成 | Azure Portalでパスを再確認。必要なら事前にディレクトリを作成する |
| 認証エラー | 接続文字列やSASの期限切れ/権限不足 | 期限・権限(read/write/list)を見直す。運用ではKey VaultやManaged Identityも検討 |
| 文字化け | ExcelでUTF-8を期待通りに解釈していない | UTF-8-SIGやCP932に切り替える。どのツールで開くかを決めて統一する |
まとめ
Azure File Share内にCSVを作成(保存)するコツは、ローカルパスに書く発想を捨てて、DataFrameをメモリ上でCSV化し、そのままSDKでアップロードすることです。io.StringIO+encode("utf-8") で最小構成を作り、上書きは「削除→アップロード」に寄せると、環境差に強い実装になります。あとは文字コードと改行を“運用で決め打ち”しておけば、日次バッチでもトラブルが減っていきます。

コメント