S3上のファイル容量を取得して、Python で集計する

# はじめに
PoC を担当しているのですが、S3 上の既存ファイルの容量を調査する必要がありました。
具体的には、S3上のファイルサイズの全体感を掴みたいので、
**S3上の全てのファイル容量を調べて、最大値、最小値、中央値、平均値を出したい**という内容でした。

ネット上にはS3上の情報を取得する記事は数があったのですが、集計まで行なっている記事は少なかったため、記録として残しておきます。

# 環境

```
❯ wmic os get caption
Caption
Microsoft Windows 10 Pro

❯ aws --version
aws-cli/2.2.11 Python/3.8.8 Windows/10 exe/AMD64 prompt/off

❯ python --version
Python 3.10.0
```

# AWS CLI でファイルサイズを取得する
## 設定
AWS CLI のインストールと設定がお済みでない方は、以下のページなどを参考に行なっていただければと思います。

- [公式ドキュメント](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-quickstart.html)
- [AWS CLIのインストールから初期設定メモ](https://qiita.com/n0bisuke/items/1ea245318283fa118f4a)

```aws configure```と打って以下のように設定値が表示されるようになったら、準備完了です。

```
❯ aws configure
AWS Access Key ID [****************AAAA]:
AWS Secret Access Key [****************BBBB]:
Default region name [ap-northeast-1]:
Default output format [json]:
```

## S3コマンド
まずは、AWS CLI のS3コマンドを試してみます。

```
aws s3 ls s3://${BUCKET}/${OBJECT_PATH}/ --recursive --human --sum
```

```
❯ aws s3 ls s3://BucketName/ObjectPath/ --recursive --human --sum
2021-11-02 10:22:46    1.7 MiB ObjectPath/テスト1.pdf
2021-11-13 22:16:34    4.5 MiB ObjectPath/テスト2.pdf
2021-11-29 13:11:12    8.6 MiB ObjectPath/テスト3.pdf
...
```

- recursive: 再帰的にリストを出力する
- human: ファイルサイズに単位を付与して出力する
- sum: サマリー (オブジェクト個数と合計サイズ) を出力する

単にファイル容量を確認したいだけなら、これで問題ありません。
しかし、何かしらのフォーマットの出力にはこのS3コマンドは対応していません。

大量のデータを集計をする以上、json など何かしらのフォーマットで扱いたいです。
そこで、S3apiコマンドを使います。

## S3apiコマンド
簡単に言うと、S3コマンドのパワーアップバージョンです。
詳しくは、[こちらの記事](https://www.fenet.jp/aws/column/aws-beginner/946/)が参考になります。
バケット単位の取得で、S3コマンドのときの```--human```オプションは使えないので、単位は byte での出力となります。

```
aws s3api list-objects-v2 --bucket ${BUCKET}
```
上記のコマンドでS3上のファイルの全情報を json で出力してもいいのですが、今回は容量の情報だけが必要です。
そのため、ファイル容量の一覧を抽出して配列に入れて、適当なテキストファイルに出力してみます。
抽出には、```--query```オプションを使います。

```
❯ aws s3api list-objects-v2 --bucket BucketName --query "Contents[].Size" >> s3_search.txt
```

```s3_search.txt
[
    100960,
    1656243,
    9890,
    ...
]
```
サイズを取得して、扱いやすいように配列としてフォーマット化することができました。
ここまでくれば、あとはスクリプトを書いていい感じに集計すれば完了です。

# Python でスクリプトを作成する
Python を選んだ理由は、以下の2つです。

- データ集計系のライブラリが充実している
- 素早く書いて実行できる

S3apiコマンドで出力した配列のテキストファイルを使ってもいいのですが、せっかくなのでスクリプト上でファイル容量の取得まで行います。

まず、Boto3（AWS SDK for Python）[Boto3](https://aws.amazon.com/jp/sdk-for-python/) をインストールします。
これで、S3apiコマンドをPython上で呼び出せます。

```
pip install boto3
```
準備は整いましたので、あくまで一例ながら以下のように書いてみました。

```python
import boto3
import statistics

# S3apiを叩いてファイル情報を取得
s3_client = boto3.client('s3')
response_contents = s3_client.list_objects_v2(
    Bucket = 'bucket_name'
).get('Contents')

# ファイルサイズを配列として取得
size_list = []
for rc in response_contents:
    size = rc.get('Size')
    size_list.append(size)

# 集計して出力
print('最大値：{}、最小値：{}、中央値：{}、平均値：{}'
    .format(max(size_list), min(size_list), 
    statistics.median(size_list), statistics.mean(size_list))
)
```
出力結果は、以下のような感じです。

```
最大値：4194340、最小値：98.0、中央値：361.0、平均値：10286.569
```
ただし、上記のコードでは一度のAPI呼び出しで取得できる**1000件を上限にしている**点に注意してください。
これは、今回のようなストレージのデータを取得するような API は、レスポンスが膨大な量になることが多いためです。
このような制限を、ページネーションといいます。

長くなってしまうのでここでは詳しく触れませんが、以下の記事などを参考に少し手を加える必要があります。
[AWS S3で1000件以上のオブジェクトを操作する方法](https://qiita.com/ShotaOnishi/items/b7adb4b09221bb01c576)
# おわりに
大学以来、久々に Python を触りました。そういえば、業務では初めてです。
普段は C# をメインで書いているので、こういうのをサクッと書くのにやっぱり Python 向いてるなと思いました。
こういうスクリプト考える作業、とても好きです。

# 参照

- https://dev.classmethod.jp/articles/aws-cli-count-s3-filesize/
- https://inokara.hateblo.jp/entry/2020/12/03/000341
- https://www.fenet.jp/aws/column/aws-beginner/946/
- https://serverfault.com/questions/84815/how-can-i-get-the-size-of-an-amazon-s3-bucket