はじめに
Google Cloud Storage(以下 GCS)を初めて触ると、多くの人が同じポイントでつまずきます。
それが「GCS のフォルダって実際何者?」
そして「list_blobs するとフォルダ名が勝手に付くの何?」 という問題です。
私自身、Cloud Run から GCS の zip ファイル一覧を取得しようとした際に、01/aaa.zipという “フォルダっぽい文字列” が返ってきて「なんで?」となりました。
Consoleでは確かに「01」というフォルダの中に aaa.zip があるように見えるのに、GCS から戻ってくるのは フォルダ名込みの “文字列”。
この記事では、GCS のフォルダの正体(プレフィックス)と list_blobs の動作原理を、初心者が混乱しないよう丁寧に解説します。
Cloud Run、Cloud Functions、ETL 処理でファイルを扱う人には特に役立つはずです。
GCS にフォルダは存在しない
GCS にフォルダは存在しません。
Console 上でフォルダがあるように見えるのは、
GCS がオブジェクト名に含まれる文字列 / を利用して階層風に表示しているだけです。
例えば Console でこう見えたとしても:
01/
└ aaa.zip
実際のオブジェクトは 01/aaa.zipという一つの文字列(キー)。
フォルダではなく、ただの名前です。
GCS が採用している「プレフィックス構造」の発想
AWS S3 と同様、GCS は オブジェクトストレージであり、
「キー文字列を並べて管理するだけ」というシンプルな構造です。
なので以下のように見えるものは、
| 見た目 | 実体 |
|---|---|
| フォルダ 01 の中に aaa.zip | 01/aaa.zip という 1ファイル |
この理解がないと list_blobs の挙動に戸惑います。
list_blobs が返すのは “実際のオブジェクト名そのまま”
実例で見てみます。
from google.cloud import storage
client = storage.Client()
bucket = client.bucket("my-bucket")
for blob in bucket.list_blobs():
print(blob.name)
出力例:
01/aaa.zip
01/bbb.csv
images/dog.png
フォルダのように見える部分も含めて、そのまま返るのがポイント。
つまり、list_blobs が勝手にフォルダ名を付けているわけではなく、
GCS 内部のオブジェクト名が「最初からそういう文字列」なのです。
prefix を指定するとフォルダっぽく絞り込める
フォルダ選択したい場合、バケットにフォルダ指定はできません。
代わりに prefix で絞り込みます。
blobs = bucket.list_blobs(prefix="01/")
for blob in blobs:
print(blob.name)
出力:
01/aaa.zip
01/bbb.csv
01/sub/ccc.txt
ポイント:
"01" ではなく "01/" と書くこと
→ "01" にすると "0100/file.txt" などもヒットして事故りがち
ファイル名だけ欲しい時は basename が便利
01/aaa.zipではなくaaa.zipだけ欲しい場合、os.path.basenameを使います。
import os
for blob in bucket.list_blobs(prefix="01/"):
filename = os.path.basename(blob.name)
print(filename)
出力:
aaa.zip
bbb.csv
ccc.txt
Cloud Run で zip 解凍したり BigQuery ロード時にログを整理する時に超便利。
Cloud Run でよくある実戦コード例(zip 限定で一覧化)
Cloud Run ジョブ内で、特定フォルダ(prefix)にある zip だけ処理したいケース。
せいにちが実際に使っていた構造に近い形にしています。
from google.cloud import storage
import os
def list_zip_files(prefix: str):
client = storage.Client()
bucket = client.bucket("my-bucket")
blobs = bucket.list_blobs(prefix=prefix)
results = []
for blob in blobs:
if blob.name.endswith(".zip"):
results.append({
"full_path": blob.name,
"filename": os.path.basename(blob.name),
"size": blob.size,
"updated": blob.updated
})
return results
# 実行
zip_files = list_zip_files("01/")
for f in zip_files:
print(f)
出力例:
{
"full_path": "01/aaa.zip",
"filename": "aaa.zip",
"size": 21044,
"updated": "2025-12-10T10:01:22"
}
このように prefix で絞りつつ、オブジェクト名(full_path)と実際のファイル名(filename)を分けて扱うのがベスト。
フォルダを作ったり削除したりできるように見える理由
GCS Console では「フォルダ作成」ボタンや「フォルダ削除」操作が存在します。
しかし、実体はこうです:
フォルダ作成
→ "some-folder/" という 0バイトオブジェクトを作っているだけ
フォルダ削除
→ "some-folder/" から始まるすべてのオブジェクトを削除している
つまり、あくまで UI が“フォルダっぽく見せている”だけ。
なので、コードからフォルダ操作をしようと考える必要はありません。
常に “文字列の prefix を操作している” と考えればOK。
よくあるつまずきポイントまとめ
初心者がよく混乱するポイントをまとめると:
❌ フォルダが実体として存在する
→ 実際は / を含むオブジェクト名の区切りを UI が階層に見せているだけ
❌ list_blobs がフォルダ名を付けてくる
→ 内部オブジェクトが “はじめからそういう文字列” だから
❌ バケットにフォルダを指定して取得できる
→ prefix で絞る("01/" など)
❌ "01" と "01/" は同じ
→ "01" は "0100" も当たる。事故りやすい。
まとめ
GCS の本質はここに凝縮されています。
GCS は階層構造を持たない、フラットなキー文字列の世界。
フォルダは存在せず、prefix で疑似的に表現しているだけ。
この理解があると、Cloud Run / Cloud Functions / BigQuery ETL のコードが一気に整理されます。
特に大量ファイル処理や prefix 分割運用をする場合、
“フォルダ” というイメージから脱却することでミスが激減します。
今後、GCS で複数ファイルを自動処理したり、
プレフィックスごとにバッチを回すような運用にも応用できます。