【データ可視化/Streamlit】S3バケットの画像を署名付きURL機能を使ってStreamlit上に表示する方法

# 前置き
こんにちは、データエンジニアの山口です！

現在業務で、Streamlitでデータ可視化アプリを作成しており、
今後アプリ上にS3バケット内の画像を表示できるようにもしたいなと考えております。
なので、今回はその練習をしてみることにしました！

# 実装する手順の目次
以下のような流れで、実施テストを行いました。

1. S3に画像をアップロード
1. アクセスキーの取得
    * IAMユーザーを作成する
    * S3のアクセス権限を持たせる
    * アクセスキーを作成する
1. Streamlitでアプリを作る
    * コードの作成をする
    * `$ streamlit run`コマンドを実行する

# 実装に取り掛かります！
それでは、**実装する手順**に記載した手順で実装を進めていきます！

## 1. S3に画像をアップロード
まずは、Streamlitに表示したい画像をS3バケット内に用意をします！


Streamlitに表示したい適当な画像を用意して、
`yamaguchi-streamlit-test`バケットへアップロードします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/883573/27dd4015-429b-799e-5f93-9db5ce71e20f.png)



## 2. アクセスキーの取得
AWSのアクセスキーを使ってStreamlitからS3へのアクセスを可能にしていきます！
今回はIAMユーザーを作成して、そのユーザーにS3へのアクセス権限を渡し、そこからアクセスキーを取得します。



### 2-1. IAMユーザーを作成する
AWSのコンソール画面の`IAM > ユーザー > ユーザーの作成`からIAMユーザーの作成をします。
今回は`yamaguchi-streamlit-test`というユーザーを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/883573/9a269197-4cf0-b8bd-2c4e-ff8e8b008ce7.png)

### 2-2. S3FullAccessを持たせる
IAMユーザーにS3のアクセス権限(ポリシー)を持たせます。
今回は`AmazonS3FullAccess`を持たせることにします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/883573/d657211e-4e37-06d4-f4fc-7b468b72207f.png)

### 2-3. アクセスキーを作成する
作成したIAMユーザーのアクセスキーを取得していきます。
`IAM > ユーザー > [作成したIAMユーザー名]`から、**セキュリティ認証情報**タブをクリックして、
**アクセスキーを作成**をクリックします。

![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3838333537332f61623035623162642d336339622d383565322d613132662d6661656635633338383738342e706e67.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/883573/f6f98c20-a648-ff71-a9d8-ed91a5fd3c98.png)


今回はStreamlitをローカルで動かすので、ユースケースを**ローカルコード**としてアクセスキーを作成してみます。

AWS上にアプリをデプロイしてアプリを動かしたい場合は、
**AWSコンピューティングサービスで実行されるアプリケーション**を選択するのか、
またはアクセスキーを使うべきなのか要検討だなと思いました。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/883573/2ff99fc6-a667-5afe-5743-2988c0667eec.png)

これで一通り設定が終わりなので、**アクセスキーを作成**をクリックします！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/883573/871e455f-6402-a4c2-71aa-2718113ba86c.png)

以下のような画像が表示されるので、**アクセスキー**と**シークレットアクセスキー**をメモしておきます。
<font color = "red">後ほどここで取得した情報を`/.streamlit/secrets.toml`に記載します。</font>
![スクリーンショット 2024-02-24 9.34.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/883573/fead61f3-1a82-4f48-81e0-dd3e28049400.png)

## 3.Streamlitでアプリを作る


### 3-1. コードの作成をする
まずはこのようなディレクトリの構成を作ります。
```
├── yamaguchi-streamlit-test/
│   └── .streamlit/
│       └── secrets.toml
└── yamaguchi-streamlit-test.py
```
そして、各ファイルには以下のようなコードを書きます。
```.streamlit.secrets.toml
[aws_credentials]
aws_access_key_id = "[上記で取得したアクセスキー]"
aws_secret_access_key = "[上記で取得したシークレットアクセスキー]"
region_name = "[S3バケットがあるリージョン名]"
```
```yamaguchi-streamlit-test.py
import os
import boto3
import streamlit as st

# AWSの認証情報を取得する関数
def get_aws_config():
    if os.path.exists('./.streamlit/secrets.toml'):
        aws_connection_params = {
            "aws_access_key_id": st.secrets["aws_credentials"]["aws_access_key_id"],
            "aws_secret_access_key": st.secrets["aws_credentials"]["aws_secret_access_key"],
            "region_name": st.secrets["aws_credentials"]["region_name"]
        }
    return aws_connection_params


# 署名付きURLを生成する関数
def generate_presigned_url(bucket_name, object_name, expiration=3600):
    response = s3_client.generate_presigned_url('get_object',
                                                Params={'Bucket': bucket_name,
                                                        'Key': object_name},
                                                ExpiresIn=expiration)
    return response

# S3のバケット名
bucket_name = 'yamaguchi-streamlit-test'
# S3のクライアントを生成
aws_config = get_aws_config()
s3_client = boto3.client('s3',
                         aws_access_key_id=aws_config['aws_access_key_id'],
                         aws_secret_access_key=aws_config['aws_secret_access_key'],
                         region_name=aws_config['region_name'])


# S3バケット内の画像の一覧の署名付きURLを生成
image_url1 = generate_presigned_url(bucket_name, 'bear1.jpg')
image_url2 = generate_presigned_url(bucket_name, 'bear2.jpg')


# Streamlitで画像を横並びで表示
st.title('S3 Image Viewer')
col1, col2 = st.columns(2)
with col1:
    st.image(image_url1, caption='Bear1', use_column_width=True)
with col2:
    st.image(image_url2, caption='Bear2', use_column_width=True)

```


### 3-2. `$ streamlit run`コマンドを実行する
最後に`yamaguchi-streamlit-test`ディレクトリにて、
`$ streamlit run yamaguchi-streamlit-test.py`コマンドを実行すると画像を表示することができました！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/883573/e70c4835-1b0b-e67a-2258-002e120935fb.png)


# まとめ
無事問題なく実装することができました！

今回は画像2枚だけを表示するだけで済みましたが、
業務で実装する場合は大量に画像を表示する必要があるので、そのためのコードも考えていかなきゃなと思いました。



GENDAデータチームではプロダクトのデータ解析や機械学習プロジェクトを推進できるデータサイエンティストを募集しています。
興味を持っていただけましたら、是非ご連絡いただけると幸いです！

https://hrmos.co/pages/genda/jobs/1700006587045969926



# 参考記事

https://docs.streamlit.io/knowledge-base/tutorials/databases/aws-s3

https://qiita.com/yamasakk/items/3060d22faeed8e05ebe4

https://docs.streamlit.io/knowledge-base/tutorials/databases/aws-s3

https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_credentials_access-keys.html

https://zenn.dev/alivelimb/articles/20220503-aws-accesskey-sec