📓

Notion API×PythonでNotionのデータベースのページに画像を送信する(AWS S3利用)

2024/09/07に公開

1.はじめに

この記事では、AWSのアカウントを持っていることを前提としています
今回の目標は以下のようなデータベースの作成である

手元にある画像をデータベースの子ページに貼り付けるのを最終的なゴールとしている

また、私は大学での研究で、結果の出力ファイルが大量になってしまいデータの整理が非常にめんどくさくなってしまったので、この方法を活用しました。

2.Notion側の設定

2-1. Notion Integrationの作成

https://www.notion.so/my-integrations

1. 上記のリンクから自分のNotionアカウントにログインする

2.

  1. 以下の画像内で赤く囲っている「新しいインテグレーション」をクリックする
  2. 以下の画像内で自分がAPIと連携させたいワークスペースを選択し、インテグレーションを作成する ※種類は「内部」で大丈夫
  3. 設定が完了すると以下の画像のような設定画面が表示される
    赤く囲っている「内部インテグレーションシークレット」をコピーして手元に保存する
    ※後ほどAPIをPython内で利用する際に必要となるAPIのアクセスキーとなる
     

2-2. Notion Databaseに上記のIntegrationを接続

1.

1.Notion内でAPIを用いて操作したいページに行く
2.右上の3点リーダーをクリック
3.プルダウンメニューから「コネクト」⇒「接続先」をクリック
4.表示されたコネクトのリストから先ほど作成したIntegrationを選択する

2.Notionのデータベースを作成し、データベースのリンクを取得


上記のリンクはhttps://www.notion.so/<DBのID>?v=yyyyの形になっており、この<DBのID>を後ほど利用する

以上でNotion側の設定は完了
※データベースのプロパティなどは適切に設定しておく必要はある

3.AWS側の設定

3-1. S3にアクセスするためのIAMユーザーを作成する

  1. 管理用IAMユーザーでコンソールにログインする
  2. IAMを開き、ユーザーを選択し、ユーザーを作成する
  3. ユーザーへのアクセス許可を設定する
    この時、ポリシーを直接アタッチするを選択し、「AmazonS3FullAccess」のみ選択する
    ※今回はS3への書き込みと読み出しを行うので、その権限が与えられる必要がある
    そのため、これを選択する(これが適切であるかは少し不安である)
  4. これ以後は、そのまま「次へ」を押し、ユーザーを作成する
  5. IAMユーザーの一覧ページから先ほど作成したIAMユーザーを選択し、アクセスキーを作成する
  6. 作成完了後は以下のようにシークレットアクセスキーとアクセスキーが表示される
    csvファイルに出力することも可能
    この2つのキーも以後利用するので保存しておく

    以上でS3にアクセスするIAMユーザーを作成できた

3-2. 画像を保存する用のS3バケットを作成する

1. S3を開きバケットを作成する
基本的にはデフォルトのままでいい

※ Noiton内で画像を表示させるためには、設定時に以下のように選択を行う

そして、以下のようにパケットポリシーを設定する

    {
    	"Version": "2012-10-17",
    	"Statement": [
    		{
    			"Sid": "PublicReadGetObject",
    			"Effect": "Allow",
    			"Principal": "*",
    			"Action": "s3:GetObject",
    			"Resource": "arn:aws:s3:::<S3バケット名>/*"
    		}
    	]
    }

これでAWS側の設定は完了

4.Python側の設定+コード

https://github.com/NyoRyorN/NotionDB_UploadImage_withS3
コードは上記に記載
pythonのバージョンは3.11.8を利用

.envファイルの中身に記載する必要のある事項

NOTION_API_KEY = 最初に作成したNotionのIntegrationのAPIKEY
NOTION_API_URL = "https://api.notion.com/v1/pages"
NOTION_DB_ID = Notion's データを新たに追加したいデータベースの ID 

AWS_S3_BUCKET_NAME = 先ほど作成したS3のバケット名
AWS_ACCESS_KEY_ID : 先ほど作成したIAMユーザーのアクセスキー
AWS_SECRET_ACCESS_KEY : 先ほど作成したIAMユーザーのシークレットアクセスキー

csvファイルの中身

name,type1,type2,height,weight,imagepath
Eevee,Normal,,0.3,6.5,ibui.png
Quagsire,Water,Ground,1.4,76.6,nuoh.png
Meowth,Normal,None,0.4,4.2,nyars.png

※imagepathがimagesフォルダ内の対応する画像のファイル名になっている

notion_db.py

import os, glob
from notion_client import Client
from dotenv import load_dotenv
import boto3
from botocore.exceptions import NoCredentialsError
import pandas as pd

def upload_image_to_s3(file_path, s3_file_name):
    """
    画像をS3にアップロードする
    
    Parameters
    ----------
    file_path : str
        ローカルでの画像ファイルのパス
    s3_file_name : str
        S3にアップロードする際のファイル名
    """
    # S3に画像をアップロードための設定
    s3 = boto3.client('s3',
                      aws_access_key_id     = os.getenv("AWS_ACCESS_KEY_ID"),
                      aws_secret_access_key = os.getenv("AWS_SECRET_ACCESS_KEY"))
    
    # S3のバケット名を取得
    bucket_name = os.getenv("AWS_S3_BUCKET_NAME")

    # 画像をS3にアップロード
    try:
        # ファイルをアップロードし、そのアップロードされたファイルのURLを取得
        s3.upload_file(file_path, bucket_name, s3_file_name)
        url = f"https://{bucket_name}.s3.amazonaws.com/{s3_file_name}"
        return url
    # ファイルが見つからない場合
    except FileNotFoundError:
        print("The file was not found")
        return None
    # 認証情報がない場合
    except NoCredentialsError:
        print("Credentials not available")
        return None

def main():
    # 環境変数の読み込み
    load_dotenv()
    
    # Notion APIの設定
    token = os.getenv("NOTION_API_KEY")
    client = Client(auth=token)
    database_id = os.getenv("NOTION_DB_ID")

    # 各データに設定するアイコンを宣言
    # 今回は仮にビリヤードのキューのアイコンを設定
    emoji = "🎱"

    # データが入っているcsvファイルを読み込む
    df = pd.read_csv("pokemon.csv", header=0)

    # 各データに対して画像をS3にアップロードしてURLを取得
    for i in range(len(df)):
        s3_file_name = df["imagepath"][i]
        image_url = upload_image_to_s3(os.path.join("images", s3_file_name), s3_file_name)
        
        # 画像のアップロードに失敗した場合は処理を終了
        if image_url == "":
            print("Failed to upload image to S3")
            exit()

        # Notionにページを作成
        try:
            # ページを作成
            response = client.pages.create(
                **{
                    "parent": { "database_id": database_id },
                    "icon":{
                        "emoji": emoji
                    },
                    # ページのプロパティを指定する
                        # データベースと同じプロパティ名を指定する
                        # 異なるプロパティ名を指定するとエラーが発生する
                        # 中身がNanの場合にもエラーが発生するので注意
                    "properties": {
                        "Name": {# タイトルのプロパティ名
                            "title": [# タイトルのプロパティを宣言するときはtitleを指定する
                                {
                                    "text": {# タイトルのプロパティの中身はtext型
                                        "content": df["name"][i] 
                                    }
                                }
                            ]
                        },   
                        "Type1": {"select": {"name": df["type1"][i]}},# セレクトボックスのプロパティ名
                        # セレクトボックスのプロパティの中身はselect型でType2に関しては値がない場合があるのでNoneを設定
                        "Type2": {"select": {"name": df["type2"][i] if pd.notnull(df["type2"][i]) else "None"}},
                        "Height": {
                            "number": float(df["height"][i]),# 数値型のプロパティ名
                        },
                        "Weight": {
                            "number": float(df["weight"][i]),# 数値型のプロパティ名
                        },
                    },  
                    # ページ内にコンテンツを追加する場合はchildrenを指定する
                    "children" : [
                        # toc block 目次ブロック
                        {
                            "object": "block",
                            "type": "table_of_contents",
                            "table_of_contents" : {}
                        },
                        # heading 1 見出し1ブロック
                        {
                            "object": "block",
                            "type": "heading_1",
                            "heading_1" : {
                                "rich_text": [
                                    {
                                        "text": {
                                            "content": df["name"][i]
                                        }
                                    }
                                ]
                            }
                        },
                        #Image 画像ブロック
                        {
                            "object": "block",
                            "type": "image",
                            "image": {
                                "type": "external",
                                "external": {
                                    "url": image_url # 画像のURLを指定
                                    # 外部リンクの場合はexternalを指定する
                                    # 内部リンクの場合はfileを指定する
                                    # ローカルファイルのパスを指定することはできない
                                }
                            }
                        }
                    ]
                }
            )
            print(response)
        except Exception as e:
            print(e)

main()

まとめ

どなたかの参考になると嬉しいです。

もし、不適切な点があればご指摘いただきたいです。
S3関連の設定で少しセキュリティ面で不適切なところがあるかもしれません...

※今回のコードは、GithubCopilotの力を借りて作成しました

参考ページ

https://zenn.dev/karaage0703/articles/9e58dec9eeaa86
https://zenn.dev/ysksatoo/articles/66fd26893a6cdd
https://zenn.dev/st43/articles/8e2f9c48761e59
https://qiita.com/harukikaneko/items/b004048f8d1eca44cba9
https://developers.notion.com/reference/page
https://qiita.com/Yusuke_Pipipi/items/b44cb8442932019c52c9
https://juu7g.hatenablog.com/entry/Python/blog/notion-api

Discussion