プログラムでおかえしできるかな

定年を過ぎて何かの役に立てないかなと始めた元SEのブログです

〖実践〗Notion API の使い方【Python】

このエントリーをはてなブックマークに追加

Python での Notion API の使い方を紹介します。
既存の Notion データベースにデータを追加、更新、削除します。

追加、更新する時に必要なデータベースのプロパティを分かりやすく引数にする方法も考えたので紹介します。

Notion データベースにプログラムでデータが入れられると使い勝手が上がりますよ。

追加される Notion データベースと追加するデータは次のリンクで紹介したものです。

アプリを使って自分のブログの管理をしたい方は、こちらを参照してください。

目次

Python で Notion API を操作する

Python で Notion API を操作するには、request パッケージでも対応が可能です。
しかし、notion-client パッケージ の方が使いやすいです。

notion-client は、次のように言っています。
 💬Notion API のシンプルで容易なクライアントである
 💬JavaScript SDKPython バージョンという位置づけなので、双方の使い方はよく似ている

Notion API リファレンスには、Python のサンプルとして request を使用した例が載っています。
notion-client を使用する場合、Notion API リファレンスのサンプルは、上記のこともあって、JavaScript を見た方が理解しやすいです。

◆notion-client パッケージの使い方

notion-client で Notion API を操作するには、Client クラスのインスタンスを作成して操作します。

 例:notion = Client(auth=self.AUTH)

◇Clientクラスのコンストラクタの引数

オプション デフォルト値 タイプ 説明
auth なし 文字列 Notionのインテグレーショントークンを指定
log_level logging.WARNING int インスタンスが生成するログの詳細度
デフォルトでは、ログはstdoutに出力
timeout_ms 60_000 int RequestTimeoutErrorを発行する前に待機するミリ秒数
base_url https://api.notion.com string APIリクエストを送信するためのルートURL
モックサーバーでテストする場合用
logger Log to console logging.Logger カスタムロガー指定可能

◇Notion データベースの読み込み

notion-client で Notion データベースを読み込む簡単な例です。
Client クラスのインスタンスに databases 属性(データベースオブジェクト)があります。
読み込みは、その databases 属性の query() メソッドを呼び出します。
呼び出しには、データベース ID をキーワード引数に指定します。
正常終了であれば、データベースの内容が辞書型で返ってきます。

notion.databases.query(
    **{
        'database_id' : ''  # データベースID
       }
)

◇Notion データベースの書き込み

notion-client で Notion データベースにデータを書き込む簡単な例です。
Client クラスのインスタンスに page 属性(ページオブジェクト)があります。
書き込みは、その page 属性の create() メソッドを呼び出します。
呼び出しには、データベース ID、プロパティ値をキーワード引数に指定します。

notion.pages.create(
    **{
        'parent': {'database_id': ''},  # データベースID
        'properties': {}  # ここにカラム名と値を記載 
    }
)

 ➡ propertiesに設定する辞書が難しい

◆Notion API 操作について

Notion API 操作についてまとめました。

これらの操作は、notion-client パッケージの Client クラスのインスタンスから行います。
Notion API には、 database object, page objects, block object が存在し、それを介して Notion と対話します。
インスタンスでは、ぞれぞれ databases, pages, blocks という属性が対応します。
何をどこにリクエストするのかということを気にしないで操作できます。

以降の表で下線が付いているものは今回使用したものです。
それ以外の操作はリファレンスからの抜粋です。
表の中の notioon は、Client クラスのインスタンスです。

◇Database object を介する操作

データベースオブジェクトは、データベース ID を指定して操作します。

Notion APIの記載 対応メソッド 内容
Query a database(POST) notion.databases.query() ▶問合せ▶データベースに含まれるページの配列を取得
フィルター条件と並べ替え基準を指定可能
Create a database(POST) notion.databases.create() ▶生成▶指定した親ページのサブページとしてデータベースを作成
指定したプロパティスキーマを使用
Update database(GET) notion.databases.update() ▶更新▶指定したパラメータで既存のデータベースを更新
Retrieve a database(GET) notion.databases.retrieve() ▶取得▶指定した ID で Database オブジェクトを取得

◇Page object を介する操作

ページオブジェクトは、Notion の一つのページに該当します。
一つのページには、プロパティ値が存在します。また、親が存在します。
親がデータベースの場合、プロパティ値はデータベースの表形式の1行に該当します。

ページオブジェクトは、ページ ID を指定して操作します。

Notion APIの記載 対応メソッド 内容
Retrive a page(GET) notion.pages.retrive() ▶取得▶指定した ID で Page オブジェクトを取得
引数:page_id
Create a page(POST) notion.pages.create() ▶生成▶指定したデータベースに新しいページを作成するか、既存のページの子として作成
引数:parent, properties, children, icon, cover
Update page(PATCH) notion.pages.update() ▶更新▶指定したページのページ プロパティ値で更新
指定されていないプロパティは変更されません
引数:page_id, properties, archived, icon, cover
Retrieve a pate property item(GET) notion.pages.properties.retrieve() ▶取得▶指定した page_id および property_id の property_item オブジェクトを取得

◇Block object を介する操作

ブロックオブジェクトは、Notion 内のコンテンツを表します。
ブロックには、テキスト、リスト、メディアなどがあります。
ページも一種のブロックです
ブロックオブジェクトは、ブロック ID を指定して操作します。

ブロック ID の取得方法が理解できていないので、「削除」以外は使っていません。
削除は、ページの削除で使用するので、ページ ID をブロック ID として使えたので使いました。
また、ページオブジェクトにも削除はあるのですが、ブロックオブジェクトを使うように指示されています。

Notion APIの記載 対応メソッド 内容
Retrieve a block(GET) notion.blocks.retrive() ▶取得▶指定した ID で Block オブジェクトを取得
Update a block(PATCH) notion.blocks.update() ▶更新▶ブロック タイプに基づいて、指定した block_id のコンテンツを更新
Retrieve block children(GET) notion.blocks.children.list() ▶取得▶指定した ID で、ブロックに含まれる子ブロック オブジェクトのページ分割された配列を返す
Append block children(PATCH) notion.blocks.children.append() ▶追加▶新しい子ブロックを作成し、指定した親 block_id に追加
Delete a block(DELETE) notion.blocks.delete() ▶削除▶指定した ID で Block オブジェクトを archived: true に設定
※ Notion UI アプリケーションでは、これによりブロックが「ゴミ箱」に移動され、引き続きアクセスして復元することができます
引数:block_id

◆Notion API の各種設定値

ここでは、Notion データベースに対して API を使用することを前提に説明します。

◇ページオブジェクト

データベースオブジェクトの Query() で問合せするとページオブジェクトの配列が返ります。

ページオブジェクトのプロパティの一部です。

プロパティ
object "page"固定
id id
create_time 作成日(世界標準時間)
last_edited_time 更新日(世界標準時間)
parent 親情報。{"type":"database_id", "database_id":???}
archived アーカイブ状態(削除する時に使用)
properties プロパティ。{プロパティ名:プロパティ値}

◇プロパティ値

ページオブジェクトにある proterties プロパティはオブジェクトで、キーはNotion データベースで定義したプロパティ名、値はそのプロパティの型に応じたオブジェクト(プロパティ値)になります。

▽サンプル

   "properties": {              # JSONにはコメントを付けられないのでPythonで記述しています
        "カテゴリ": {            # プロパティ名(キー) これ以降がプロパティ値
            "id": "UNHI",
            "type": "multi_select",   # 型名
            "multi_select": [        # 型に対する内容
                {
                    "id": "87ef771a-1e60-492a-b06a-2d8ed9dcd58d",
                    "name": "ブログ",
                    "color": "brown"
                },
                {
                    "id": "e869c74e-480b-43e4-a653-70f4f89406c4",
                    "name": "シニア",
                    "color": "purple"
                }
            ]
        },…
キー
id id
type 型名
"title", "rich_text", "number", "select", "multi_select", "date", "people", "files", "checkbox", "url", "email", "phone_number", "formula", "relation", "rollup", "created_time", "created_by", "last_edited_time", "last_edited_by", "status"
型に対する内容

◎型に対する内容の例

型ごとに異なるオブジェクトを持っています。

型名 設定例
"名前":プロパティの名前
title "名前":{
 "title":[{
  "type":"text",
  "text":{
   "content":"The title"
   }
  }]
 }
rich_text "名前":{
 "rich_text":[{
  "type":"text",
   "text":{"content":"xxx"}
  }, {
  "type":"text",
   "text":{"content":"xxx"}, "annotations":{"italic":true}
  }]
 }
number "名前":{
 "number":1234
 }
select "名前":{
 "select":{"name":"Option 1"}
 }
multi_select "名前":{
 "multi_select":[{"name":"B"},{"name":"C"}]
 }
date "名前":{
 "date":{
  "stat":"2021-04-25",
  "end":"2021-05-07"
 }
}
files "名前":{
 "files":[{
  "type":"external",
  "name":"Space Wallpaper",
  "external":{
   "url":"https://website.domain/images/space.png"
   }
  }]
 }
url "名前":{
 "url":"https://notion.so/notiondevs"
 }

◎time zomeについて

日付型のプロパティ値には、time_zome プロパティが存在します。
現在、Notion API で、このプロパティは機能しません。
何もしないと標準時で指定された日時として扱われます。

対応方法は、指定する日時に標準時との差を付加します。

 例:2022-07-10 15:30:41+09:00

◆データベースのプロパティに合ったプロパティ値の作り方

Notion API でデータベースの操作を行う場合、そのデータベースに定義されたプロパティに合ったプロパティを使って指示しなければなりません。

次節に取得した Notion データベースのプロパティを載せていますが、なかなか複雑です。
プロパティの型によってその属性(色など)もプロパティに含まれているためです。

プロパティの型にごとに設定するメソッドを作ろうかとも考えたのですが、型の数だけ作るのも大変そうだし、汎用的なメソッドにするのも難しそうなので、今回は止めました。

その代わり、直感的にわかりやすい方法を思いつきました。

それは、Notion API で取得した Notion データベースのプロパティを基にデータ更新に使えるプロパティの辞書を作る事です。

【手順】

  1. Notion データベースからページのプロパティのデータを取得する
  2. 取得したデータを JSON ファイルに出力する
    JSON ファイルにするのは辞書を可視化するため
  3. JSON ファイルをデータ更新に必要な要素だけにする
  4. 必要な要素だけになったものを Python の辞書に置き換える
  5. さらにメソッド化する

※以下、取得したデータを基に説明しますが、セキュリティ的に怪しいデータは「??????????」に変更してあります。ご了承ください。

◇取得したデータ

{
    "object": "list",
    "results": [
        {
            "object": "page",
            "id": "??????????",
            "created_time": "2022-05-09T06:42:00.000Z",
            "last_edited_time": "2022-07-12T01:12:00.000Z",
            "created_by": {
                "object": "user",
                "id": "??????????"
            },
            "last_edited_by": {
                "object": "user",
                "id": "??????????"
            },
            "cover": null,
            "icon": null,
            "parent": {
                "type": "database_id",
                "database_id": "??????????"
            },
            "archived": false,
            "properties": {
                "スター": {
                    "id": "%40RKt",
                    "type": "number",
                    "number": 5
                },
                "掲載日": {
                    "id": "F%3Eh%3D",
                    "type": "date",
                    "date": {
                        "start": "2021-05-13T10:28:00.000+09:00",
                        "end": null,
                        "time_zone": null
                    }
                },
                "アイキャッチ画像": {
                    "id": "LIyv",
                    "type": "files",
                    "files": [
                        {
                            "name": "https://cdn-ak.f.st-hatena.com/images/fotolife/??????????.png",
                            "type": "external",
                            "external": {
                                "url": "https://cdn-ak.f.st-hatena.com/images/fotolife/??????????.png"
                            }
                        }
                    ]
                },
                "ブックマーク": {
                    "id": "S%3ELZ",
                    "type": "number",
                    "number": 1
                },
                "カスタムURL": {
                    "id": "SKu%7C",
                    "type": "rich_text",
                    "rich_text": [
                        {
                            "type": "text",
                            "text": {
                                "content": "blog/gimp/shukusho",
                                "link": null
                            },
                            "annotations": {
                                "bold": false,
                                "italic": false,
                                "strikethrough": false,
                                "underline": false,
                                "code": false,
                                "color": "default"
                            },
                            "plain_text": "blog/gimp/shukusho",
                            "href": null
                        }
                    ]
                },
                "カテゴリ": {
                    "id": "UNHI",
                    "type": "multi_select",
                    "multi_select": [
                        {
                            "id": "??????????",
                            "name": "ブログ",
                            "color": "brown"
                        },
                        {
                            "id": "??????????",
                            "name": "GIMP",
                            "color": "pink"
                        },
                        {
                            "id": "??????????",
                            "name": "シニア",
                            "color": "purple"
                        }
                    ]
                },
                "最終更新日": {
                    "id": "XH%3Ex",
                    "type": "date",
                    "date": null
                },
                "タグ": {
                    "id": "bua%3D",
                    "type": "multi_select",
                    "multi_select": [
                        {
                            "id": "??????????",
                            "name": "GIMP",
                            "color": "brown"
                        }
                    ]
                },
                "リンク": {
                    "id": "vQ%60%3F",
                    "type": "url",
                    "url": "https://juu7g.hatenablog.com/entry/blog/gimp/shukusho"
                },
                "タイトル": {
                    "id": "title",
                    "type": "title",
                    "title": [
                        {
                            "type": "text",
                            "text": {
                                "content": "ブログに便利な画像の縮小、反転【GIMP】",
                                "link": null
                            },
                            "annotations": {
                                "bold": false,
                                "italic": false,
                                "strikethrough": false,
                                "underline": false,
                                "code": false,
                                "color": "default"
                            },
                            "plain_text": "ブログに便利な画像の縮小、反転【GIMP】",
                            "href": null
                        }
                    ]
                }
            }
        }
    ],
    "next_cursor": null,
    "has_more": false,
    "type": "page",
    "page": {}
}

先ほどプロパティ値の説明で使用した短い例で説明します。
idcolor などはデータの追加や更新には不要なデータなので削除します。

▽整理する前のサンプル

   "properties": {              # JSONにはコメントを付けられないのでPythonで記述しています
        "カテゴリ": {            # プロパティ名(キー)
            "id": "UNHI",         # 不要
            "type": "multi_select",   # 型名
            "multi_select": [        # 型に対する内容
                {
                    "id": "87ef771a-1e60-492a-b06a-2d8ed9dcd58d", # 不要
                    "name": "ブログ",
                    "color": "brown"  # 不要
                },
                {
                    "id": "e869c74e-480b-43e4-a653-70f4f89406c4", # 不要
                    "name": "シニア",
                    "color": "purple" # 不要
                }
            ]
        },…

▽整理した結果のサンプル

   "properties": {              # JSONにはコメントを付けられないのでPythonで記述しています
        "カテゴリ": {            # プロパティ名(キー)
            "type": "multi_select",   # 型名
            "multi_select": [        # 型に対する内容
                {
                    "name": "ブログ",
                },
                {
                    "name": "シニア",
                }
            ]
        },…

かなりスッキリします。

◇データ更新に必要なものだけにした結果

{
    "object": "list",
    "results": [
        {
            "properties": {
                "スター": {
                    "number": 5
                },
                "掲載日": {
                    "date": {
                        "start": "2021-05-13T10:28:00.000+09:00",
                        "end": null,
                        "time_zone": null
                    }
                },
                "アイキャッチ画像": {
                    "files": [
                        {
                            "name": "https://cdn-ak.f.st-hatena.com/images/fotolife/??????????.png",
                            "external": {
                                "url": "https://cdn-ak.f.st-hatena.com/images/fotolife/??????????.png"
                            }
                        }
                    ]
                },
                "カスタムURL": {
                    "rich_text": [
                        {
                            "text": {
                                "content": "blog/gimp/shukusho",
                                "link": null
                            }
                        }
                    ]
                },
                "カテゴリ": {
                    "multi_select": [
                        {
                            "name": "ブログ"
                        },
                        {
                            "name": "シニア"
                        }
                    ]
                },
                "タグ": {
                    "multi_select": [
                        {
                            "name": "GIMP"
                        }
                    ]
                },
                "リンク": {
                    "url": "https://juu7g.hatenablog.com/entry/blog/gimp/shukusho"
                },
                "タイトル": {
                    "title": [
                        {
                            "text": {
                                "content": "ブログに便利な画像の縮小、反転【GIMP】"
                            }
                        }
                    ]
                }
            }
        }
    ],
    "next_cursor": null,
    "has_more": false
}

Python でプロパティに変換

後はこのようなデータになるように、リテラルのところを変数に置き換えてデータ作成メソッドになるように修正します。

今回、更新用のデーは CSV ファイルを読み込んだ辞書型のデータを使います。
先ほどのデータの更新に不要な部分を取り除いた JSON データを基に Python の辞書型のデータを作ります。

いくつか注意点があります。

  • CSV ファイルを読み込んだデータはすべて文字列なのでプロパティが数値型の場合には数値に変換が必要
  • マルチセレクト用のデータはカンマ区切りの文字列("a,b")なので"name"をキーにした辞書のリスト([{"name":"a"}, {"name":"b"}])にする
  • タイムゾーンは動かないので日時に標準時との差を付加する
    2022-07-10 15:30:412022-07-10 15:30:41+09:00
  • 設定値がない場合はキーごと削除する

◎コード

"""
Notion用プロパティデータ作成
"""
import re

def set_properties(url:str="", title:str="", published:str="", updated:str="", 
                    bookmark:int=0, yellow:int=0, green:int=0, red:int=0, blue:int=0, purple:int=0, 
                    category:str="", eye_catch:str=""):
    """
    引数を値にしたNotionデータベースのプロパティ用辞書を作成する
    Args:
        any:    get_hatena_infoで出力したCSVの各カラム
    Returns:
        dict:   特定Notionデータベースのプロパティ用辞書
    """
    # カスタマイズURLはURLのentryより後ろだけ
    c_url = re.sub("^.+/entry/", "", url)
    # CSVを読み込んだデータはすべて文字列なので数値は変換が必要
    star_num = int(yellow) + int(green) + int(red) + int(blue) + int(purple)
    bookmark_num = int(bookmark)
    # マルチセレクト用のデータはカンマ区切りの文字列("a,b")なので"name"をキーにした辞書のリストにする
    c1 = [{"name":c} for c in category.split(",")]
    # t1 = [{"name":t} for t in tags]
    properties = {
                "スター": {
                    "number": star_num
                },
                "掲載日": {
                    "date": {
                        # タイムゾーンは動かないので日時に標準時との差を付加する
                        "start": published+"+09:00",
                        # "time_zone": "Asia/Tokyo"
                    }
                },
                "アイキャッチ画像": {
                    "files": [
                        {
                            "name": eye_catch,
                            "external": {
                                "url": eye_catch
                            }
                        }
                    ]
                },
                "ブックマーク": {
                    "number": bookmark_num
                },
                "カスタムURL": {
                    "rich_text": [
                        {
                            "text": {
                                "content": c_url,
                            }
                        }
                    ]
                },
                "カテゴリ": {
                    "multi_select": list(c1)
                },
                # "タグ": {
                #     "multi_select": list(t1)
                # },
                "リンク": {
                    "url": url
                },
                "タイトル": {
                    "title": [
                        {
                            "text": {
                                "content": title
                            }
                        }
                    ]
                },
            }
    # 設定がないキーは削除する
    if category == "":
        del properties["カテゴリ"] 
    if eye_catch == "":
        del properties["アイキャッチ画像"] 
    return properties

Python で実践

Python で Notion API を使ったプログラミングをしました。
データベースの更新や削除、情報取得やテストなどを作成したので紹介します。
全体のソースを見ると分かりますが、switch 変数を用意して処理を切り替えています。

説明するのは次の4つの処理です。

◇データベースの内容をJSONファイルに出力

Notion データベースを更新するプロパティ値を作るためにデータベースの中身を確認する必要がありました。

データベースオブジェクトの問い合わせの戻り値は辞書です。
辞書データを可視化するのによい方法を捜したところ、JSON のdump()メソッドが良いようです。
文字で JSON ファイルに出力されるので読めます。

▽呼び出し

        doc = notion_api.get_database() # データベースを取得
        notion_api.dump_results(doc)    # JSONファイルに出力

▽データベースオブジェクトの問合せ

    def get_database(self) -> dict:
        """
        データベースを取得
        Returns:
            dict:   データベースの内容
        """
        try:
            my_page = self.notion.databases.query(
                **{
                    "database_id": self.DATABASE_ID,
                }
            )
        except APIResponseError as error:
            # if error.code == APIErrorCode.ObjectNotFound:
            if error.code == APIErrorCode.ValidationError:
                # For example: handle by asking the user to select a different database
                print("データベースIDを確認してください")
            else:
                # Other error handling code
                print("インテグレーションのトークンを確認してください")
                # logging.exception(error.code)
        return my_page

JSON ファイルに出力

    def dump_results(self, doc:dict):
        """
        辞書をjson形式のファイルに出力
        Args:
            dict:   出力したい辞書
        """
        file_name = f"myDictionary_{datetime.now().strftime('%m%d')}.json"
        jf = open(file_name, "w", encoding="utf_8_sig")
        json.dump(doc, jf, ensure_ascii=False)
        jf.close()

◇データベースのデータ更新のテスト。辞書をリテラルで与えて更新

最終的には、CSV ファイルを読んでデータベースを更新します。
まずは、1件ずつ更新動作を確認するためにこちらの処理を用意しました。

データベースのプロパティに合ったプロパティ値の作り方」で説明したデータベースを更新する時のプロパティ値を作成するメソッドを呼び出します。
引数には辞書型のデータを自分で用意します。

▽呼び出し

        data = {"url":"https://xxx.hatenablog.com/entry/xxx", "title":"タイトル", 
            "published":"2022-06-01 21:50:15", "yellow":37, 
            "category":'シニア, 暮らし'
            }

        properties = notion_db_scm.set_properties(**data)           # プロパティ用辞書作成
        notion_api.append_page_2database(properties=properties)     # 追加

▽データの追加

    def append_page_2database(self, properties:dict):
        """
        データベースに引数の内容を追加
        Args:
            dict:   記事情報
        Returns:
            dict:   操作の結果
        """
        try:
            my_page = self.notion.pages.create(
                **{
                    "parent":{"database_id": self.DATABASE_ID},
                    "properties":properties
                }
            )
        except APIResponseError as error:
            if error.code == APIErrorCode.ObjectNotFound:
                # For example: handle by asking the user to select a different database
                print("データベースIDを確認してください")
            else:
                # Other error handling code
                print(f"target properties:{properties}")
                # logging.exception(error.code)
        return my_page

CSV ファイルを読んでデータベースを更新

読み込む CSV ファイルは次の記事のアプリで作成されるものです。
CSV ファイルのカラム構成は、次の記事で確認できます。

 📄『はてなブログのスターとブックマークの数を取得するアプリ【フリー】🔗』

CSV ファイルは、DictReader() メソッドを使用して辞書型のデータに読み込みます。
そのデータからプロパティ用辞書を作成します。

データベースの対しての操作は、記事の URL がデータベースに存在するかどうかで追加するか更新するかを切り分けます。

▽呼び出し

        if len(sys.argv) > 1:
            file_name = sys.argv[1]
        else:
            print(f"ファイルを指定してください")
            input("\n確認したらEnterキーを押してください")
            sys.exit()
        
        print(f"{file_name} の内容で更新します。")

        try:
            # CSV(1行目がカラム定義)を辞書として読んで
            with open(file_name, newline='', mode="r", encoding="utf-8-sig") as csvfile:
                spamreader = csv.DictReader(csvfile)
                for row in spamreader:
                    properties = notion_db_scm.set_properties(**row)        # プロパティ用辞書作成
                    # 更新データが既にデータベースにあるかをurlでチェック
                    doc = notion_api.get_page_by_url(row.get("url"))        # 問合せ(urlでフィルタリング)
                    if doc:
                        if len(doc["results"]) > 0:
                            page_id = doc["results"][0].get("id")
                            notion_api.update_page(page_id, properties)     # 更新
                            print(f"更新:{row.get('title')}")
                        else:
                            notion_api.append_page_2database(properties)    # 追加
                            print(f"追加:{row.get('title')}")
                    else:
                        # データベース読み込みに問題があったので中断
                        break
                print(f"処理終了")
        except Exception as e:
            print(f"エラーが発生しました:{e}")

▽データの問合せ(フィルタ指定)

    def get_page_by_url(self, url:str) -> dict:
        """
        データベースを取得
         データベースの「リンク」プロパティを引数でフィルタリングした結果
        Args:
            str:    記事のURL(データベースのキーとして扱う)
        Returns:
            dict:   データベースの内容
        """
        my_page = None
        try:
            my_page = self.notion.databases.query(
                **{
                    "database_id": self.DATABASE_ID,
                    "filter":{"or":[{
                        "property":"リンク",
                        "url":{"equals":url}
                    }]}
                }
            )
        except APIResponseError as error:
            # if error.code == APIErrorCode.ObjectNotFound:
            if error.code == APIErrorCode.ValidationError:
                # For example: handle by asking the user to select a different database
                print("データベースIDを確認してください")
            else:
                # Other error handling code
                print("インテグレーションのトークンを確認してください")
                # logging.exception(error.code)
        return my_page

フィルタの指定は、データベースの「リンク」プロパティのプロパティ値を見て記述します。
"リンク": {"url": "https://juu7g.hatenablog.com/entry/blog/gimp/shukusho"},
とあるので、property が「リンク」で、 url が url 変数と等しい (equals) を指定します。
フィルタの指定は、or を付ける必要はなかったと思います。
サンプルを基に作ったら、このようになってしまいました。

▽データの更新

    def update_page(self, page_id:str, properties:dict) -> dict:
        """
        データベースの既存のpage_idのデータに引数の内容を更新
        Args:
            str:    page ID
            dict:   記事情報
        Returns:
            dict:   操作の結果
        """
        try:
            my_page = self.notion.pages.update(
                **{
                    "page_id":page_id,
                    "properties":properties
                }
            )
        except APIResponseError as error:
            if error.code == APIErrorCode.ObjectNotFound:
                # For example: handle by asking the user to select a different database
                print("データベースIDを確認してください")
            else:
                # Other error handling code
                # logging.exception(error.code)
                pass
        return my_page

◇データベースのデータ削除

削除処理は、まず、データベースに問合せして全ページオブジェクトを取得します。
データの削除に必要なページオブジェクトの ID を取得します。 ログ出力用にページオブジェクトの「タイトル」プロパティの値を取得します。
ID とタイトルを関連付けて処理を繰り返し全ページオブジェクトを削除します。

▽問い合わせの結果データの先頭

{
    "object": "list",
    "results": [
        {
            "object": "page",
            "id": "??????????",

問合せの結果は、results キーの値が配列になり、ページオブジェクトが複数存在します。
ID はすぐ取れますが、タイトルはプロパティ値の中なのでオブジェクトが入れ子になっていて分かりにくいです。

▽呼び出し

        doc = notion_api.get_database()     # データベースを取得
        if len(doc["results"]) > 0:
            page_ids = [obj["id"] for obj in doc["results"] if obj["object"] == "page"] # 結果からpage_idを取得
            page_titles = [obj["properties"]["タイトル"]["title"][0]["text"]["content"] for obj in doc["results"] if obj["object"] == "page"] # 結果からタイトルを取得
            for page_id, page_title in zip(page_ids, page_titles):
                notion_api.delete_block(page_id)    # データベースから削除
                print(f"削除:{page_title}")

▽削除処理

    def delete_block(self, page_id:str) -> dict:
        """
        データベースから引数のデータを削除
        Args:
            str:    page ID
        Returns:
            dict:   操作の結果
        """
        try:
            my_page = self.notion.blocks.delete(
                **{
                    "block_id":page_id
                }
            )
        except APIResponseError as error:
            if error.code == APIErrorCode.ObjectNotFound:
                # For example: handle by asking the user to select a different database
                print("データベースIDを確認してください")
            else:
                # Other error handling code
                # logging.exception(error.code)
                pass
        return my_page

◇必要なパッケージ

  • notion-client
    【インストール】pip install notion-client
    【インポート】 from notion_client import Client

◇全体のソース

全体のソースはこちらから取得できます。
取得先:GitHub juu7g/Python-use-Notion-API

◆Notion インテグレーションの作成とデータベース ID の取得

Notion API を使うためには Notion 側に準備が必要です。

必要なのは次の2つです。どちらも本記事で説明しているソースを使う場合は、環境変数に設定します。

  • インテグレーションのトーク
  • Notion データベースのデータベース ID

まず、Notion のインテグレーションを作成し、そのインテグレーションを介してアクセスしたいデータベースをシェアします。

イメージ的にはオンラインストレージ(インテグレーション)を作成してその中のファイル(データベース)をシェアするのに近いでしょうか。

◇インテグレーションの作成

  1. Notion API サイトへアクセス
    (URL:https://developers.notion.com/
  2. ページ右上のリンク View my integrations をクリック
  3. 私のインテグレーション | Notion開発者』が開く
  4. 「新しいインテグレーションを作成する」をクリック
  5. 新しいインテグレーション設定画面が表示されます
  6. 「名前」にインテグレーションの名前を入力
    適当な名前を入力します
  7. 「関連ワークスペース」を選択
    個人使用の場合は一つしか選択肢がないと思います
  8. 「機能」を選択
    デフォルトの状態のままで大丈夫です
    私は「ユーザー情報なし」にだけ変更しました
  9. 「送信」をクリック
    インテグレーションが作成されます
  10. トークン」が表示されるのでコピー
    • 表示をクリック
    • コピーをクリック
      こちらを環境変数に登録します

◇データベースをシェア

次に、アクセスしたいデータベースをシェアします。
ここでのアクセスしたいデータベースというのは、「ブログ記事管理」データベースをコピーしたデータベースです。

  1. Notion のサイトでアクセスしたいデータベースを選択
  2. 画面右上の「共有」をクリック
  3. ダイアログが出るので「招待」をクリック
  4. インテグレーションの選択
    選択肢から作成したインテグレーションを選択
    選択後、再度「招待」をクリック
    「編集権限」が表示されればOK
  5. 「リンクをコピー」をクリック
    データベースのURLがコピーされます
  6. URL からデータベースIDを取得
    /の次から?の前まで)
    コピーしたURLをどこかにペーストして実施
    ★データベースURL:https://www.notion.so/ここから~ここまで?v=ここにも値があります
    ⇒データベースID:ここから~ここまで
    こちらを環境変数に登録します


環境変数の設定

本記事で説明しているソースを使って Notion データベースにアクセスするためには、環境変数に次のものを設定する必要があります。

環境変数の変数
py_notion_int_token Notion インテグレーショントーク
py_notion_db Notion データベース ID

環境変数設定画面の起動

環境変数環境変数設定画面を起動して追加します。

  1. スタートメニュー
  2. プログラムとファイルの検索に「環境変数」と入れる
  3. 検索結果に「環境変数を編集」が出るのでクリック
  4. 環境変数設定画面が出る
  5. 「新規」をクリック
  6. 変数名と変数値を入力してOKをクリックする

◆さいごに

Notion データベースを使い始めた時から Notion API は使ってみたいと思っていました。

いきなりではハードルが高そうに感じたので はてなフォトライフAPI から始めました。

データベース更新用のデータを取得するために はてなブログAPI も使いました。

そしてやっと Notion API を使うことができました。一歩一歩進んできたなと思います。

Notion API と notion-client の組合せは、仕様書を読み解くのが難しくて苦労しました。

特にプロパティ値の指定方法が理解できませんでした。
しかし、自分なりの理解しやすい方法を見つけてからは何とかなりました。

これから Notion API のプログラミングを始める方がこの記事で始めやすくなっていると幸いです。

あわせて読みたい 📄 Notionデータベースの作り方【Notionデータベース】🔗
📄 はてなブログのアイキャッチ画像のURLの取り方【Python】🔗

◇ご注意

本記事は次のバージョンの下で動作した内容を基に記述しています。

  • Notion 2022年7月のNotionサービス
  • Python 3.8.5
  • notion-client 1.0.0

ご利用に際しては、『免責事項』をご確認ください。
お気づきの点がございましたら『お問い合わせ』からお問い合わせください。

◆参考

投稿: 、更新: