0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【GCS基礎】フォルダっぽく見えるけど実体はない?prefix構造と list_blobs の挙動まとめ

Posted at

はじめに

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 で複数ファイルを自動処理したり、
プレフィックスごとにバッチを回すような運用にも応用できます。

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?