Pythonライブラリ「notion-client」徹底解説!Notion APIを使いこなそう 🚀

プログラミング

はじめに:notion-clientとは?

Notionは、メモ、タスク管理、データベース、Wikiなど、様々な情報を一元管理できる非常に強力なツールです。近年、その柔軟性とカスタマイズ性の高さから、個人利用だけでなく、チームでの情報共有やプロジェクト管理にも広く活用されています。✅

このNotionの機能をプログラムから操作可能にするのがNotion APIです。そして、PythonからこのNotion APIを簡単かつ効率的に利用できるように設計されたライブラリが、今回ご紹介する`notion-client`です。

`notion-client` はNotion公式が提供・メンテナンスしているPython SDK(Software Development Kit)であり、Notion APIの複雑な仕様を意識することなく、直感的なPythonコードでNotionのページ作成、データ取得、更新、削除といった操作を実現できます。これにより、以下のようなことが可能になります:

  • 定型的なレポートページの自動生成
  • 外部サービス(例:カレンダー、タスク管理ツール)とのデータ同期
  • 特定のデータベースへの情報自動登録
  • Notionデータのバックアップや分析

このブログ記事では、`notion-client`の基本的な使い方から、具体的なコード例を交えながら、その詳細な機能について解説していきます。Pythonを使ってNotionをもっと便利に活用したいと考えている方は、ぜひ参考にしてください。😊

インストール方法

`notion-client`のインストールは非常に簡単です。Pythonのパッケージ管理ツールであるpipを使用します。ターミナル(コマンドプロンプト)を開き、以下のコマンドを実行してください。

pip install notion-client

もし、特定のバージョンをインストールしたい場合や、最新版にアップデートしたい場合は、以下のように実行します。

# 特定のバージョンをインストール
pip install notion-client==<バージョン番号>

# 最新版にアップデート
pip install --upgrade notion-client

これで、あなたのPython環境に`notion-client`が導入され、Notion APIを操作する準備が整いました。🎉

依存関係として`httpx`などのライブラリも同時にインストールされます。非同期処理を行いたい場合は、追加の依存関係が必要になることもありますが、基本的な同期処理であれば上記コマンドのみで十分です。

準備:NotionインテグレーションとAPIキーの取得

`notion-client`を使用するには、まずNotion側で「インテグレーション」を作成し、APIキー(Internal Integration Token)を取得する必要があります。これは、あなたのプログラムがNotionアカウントにアクセスするための「鍵」となります。

  1. インテグレーションの作成:
    • Notionのマイインテグレーションページにアクセスします。(Notionアカウントが必要です)
    • 「+ 新しいインテグレーションを作成する」ボタンをクリックします。
    • インテグレーションの名前(例: My Python Script)を入力します。
    • 関連付けるワークスペースを選択します。
    • 必要に応じてロゴやリダイレクトURIを設定しますが、基本的な利用では不要です。
    • 機能(Capabilities)を設定します。「コンテンツの読み取り」「コンテンツの挿入」「コンテンツの更新」「コメントの読み取り」「コメントの挿入」など、プログラムに許可したい操作に応じてチェックを入れます。ユーザー情報は通常不要であればチェックを外しておくと安全です。
    • 「送信」ボタンをクリックしてインテグレーションを作成します。
  2. APIキー(Internal Integration Token)の取得:
    • インテグレーションを作成すると、「シークレット」セクションに「内部インテグレーションシークレット」(Internal Integration Token)が表示されます。
    • 「表示」をクリックし、表示されたトークンをコピーします。このトークンは非常に重要なので、絶対に外部に漏らさないように厳重に管理してください。 🔑
    • このトークンをPythonスクリプト内で使用します。環境変数に設定するか、設定ファイルに保存するなど、安全な方法で管理することを強く推奨します。
  3. データベース/ページへのコネクト追加:
    • 作成したインテグレーションが特定のデータベースやページにアクセスできるように、権限を与える必要があります。
    • アクセスしたいNotionのデータベースまたはページの右上にある「・・・」(メニュー)をクリックします。
    • 「コネクトの追加」を選択します。
    • 検索窓に先ほど作成したインテグレーションの名前を入力し、表示されたインテグレーションを選択します。
    • これで、インテグレーション(あなたのプログラム)がそのデータベース/ページにアクセスできるようになります。

これらの準備が完了したら、いよいよPythonコードでNotionを操作していきます。

注意: APIキーはパスワードと同じくらい重要な情報です。コード内に直接書き込まず、環境変数や設定ファイル、シークレット管理ツールなどを使って安全に管理しましょう。

基本的な使い方:Clientオブジェクトの初期化

`notion-client`ライブラリの全ての操作は、`Client`オブジェクトを通して行われます。まず、この`Client`オブジェクトを初期化します。

初期化には、先ほど取得したAPIキー(Internal Integration Token)が必要です。環境変数 `NOTION_API_KEY` に設定しておくのが一般的です。

import os
from notion_client import Client

# 環境変数からAPIキーを取得
# 事前に export NOTION_API_KEY='YOUR_SECRET_TOKEN' のように設定しておく
api_key = os.environ.get("NOTION_API_KEY")

# APIキーが見つからない場合のエラーハンドリング
if not api_key:
    raise ValueError("APIキーが環境変数 'NOTION_API_KEY' に設定されていません。")

# Clientオブジェクトの初期化
notion = Client(auth=api_key)

print("Notion Clientの初期化が完了しました!👍")

もし環境変数を使わない場合は、以下のように直接文字列として渡すことも可能ですが、セキュリティ上の理由から推奨されません。

from notion_client import Client

# 直接APIキーを指定(非推奨)
# notion = Client(auth="secret_...")

# print("Notion Clientの初期化が完了しました!👍")

`Client`オブジェクトには、`auth`以外にもタイムアウト時間 (`timeout_ms`) やログレベル (`log_level`) などのオプションを指定できます。詳細は公式ドキュメントを参照してください。

これで、`notion`という変数(`Client`オブジェクトのインスタンス)を使って、様々なNotion APIのエンドポイントにアクセスできるようになりました。

データの取得 (Read Operations)

Notion APIを使って情報を取得する操作は非常に一般的です。`notion-client`を使えば、データベース、ページ、ブロックなどの情報を簡単に取得できます。

1. データベースの情報を取得する

特定のデータベースの情報を取得するには、`databases.retrieve()`メソッドを使用します。引数にはデータベースIDが必要です。

データベースIDは、Notionでデータベースを開いたときのURLに含まれています。例えば、`https://www.notion.so/your-workspace/abcdef1234567890abcdef1234567890?v=…` というURLの場合、`abcdef1234567890abcdef1234567890` の部分がデータベースIDです(ハイフンが含まれる場合もあります)。

import os
from notion_client import Client
import json # 結果を見やすく表示するため

api_key = os.environ.get("NOTION_API_KEY")
notion = Client(auth=api_key)

database_id = "YOUR_DATABASE_ID" # ここに取得したいデータベースのIDを入力

try:
    db_info = notion.databases.retrieve(database_id=database_id)
    print("データベース情報を取得しました!✨")
    # 取得した情報は辞書形式なので、見やすく表示
    print(json.dumps(db_info, indent=2, ensure_ascii=False))

    # データベースのタイトルを取得
    db_title = "".join([t['plain_text'] for t in db_info['title']])
    print(f"\nデータベース名: {db_title}")

    # プロパティ情報を表示
    print("\nプロパティ一覧:")
    for prop_name, prop_data in db_info['properties'].items():
        print(f"- {prop_name} ({prop_data['type']})")

except Exception as e:
    print(f"エラーが発生しました: {e}")

このコードを実行すると、指定したデータベースのタイトル、プロパティ定義(名前、型など)、アイコン、カバー画像などのメタデータが取得できます。

2. データベース内のページ(アイテム)を検索・取得する

データベースに含まれるページ(行データ)を取得するには、`databases.query()`メソッドを使用します。このメソッドは非常に強力で、様々な条件でフィルタリングやソートが可能です。

import os
from notion_client import Client
import json

api_key = os.environ.get("NOTION_API_KEY")
notion = Client(auth=api_key)

database_id = "YOUR_DATABASE_ID" # 対象のデータベースID

try:
    # データベース内の全ページを取得 (デフォルトでは最大100件)
    response = notion.databases.query(database_id=database_id)
    pages = response.get("results", [])
    print(f"{len(pages)}件のページを取得しました。")

    if pages:
        print("\n取得したページのタイトル一覧:")
        for page in pages:
            # ページのタイトルプロパティ名を取得 (多くの場合 'Name' や '名前')
            # 実際のプロパティ名に合わせて調整が必要
            title_prop_name = next(iter(page['properties'].get('Name', {}).get('title', []) or page['properties'].get('名前', {}).get('title', [])), {}).get('plain_text', 'タイトル不明')
            # 見つからない場合の代替タイトル
            if title_prop_name == 'タイトル不明':
                 # デフォルトのタイトルプロパティを探す試み
                 for prop_value in page['properties'].values():
                     if prop_value.get('type') == 'title':
                         title_text_list = prop_value.get('title', [])
                         if title_text_list:
                            title_prop_name = title_text_list[0].get('plain_text', 'タイトル不明')
                            break

            page_id = page['id']
            print(f"- {title_prop_name} (ID: {page_id})")

        # 最初のページのプロパティを詳しく表示
        print("\n最初のページのプロパティ詳細:")
        first_page = pages[0]
        print(json.dumps(first_page['properties'], indent=2, ensure_ascii=False))

    # ---- フィルタリングの例 ----
    print("\n--- フィルタリングの例 ---")
    # 'ステータス' プロパティ (Select型) が '完了' のページを検索
    try:
        filtered_response = notion.databases.query(
            database_id=database_id,
            filter={
                "property": "ステータス", # プロパティ名を指定
                "select": {
                    "equals": "完了" # Selectの値を指定
                }
            }
        )
        filtered_pages = filtered_response.get("results", [])
        print(f"'ステータス'が'完了'のページ: {len(filtered_pages)}件")
        for page in filtered_pages:
             # タイトル取得 (上記と同様)
             title_prop_name = next(iter(page['properties'].get('Name', {}).get('title', []) or page['properties'].get('名前', {}).get('title', [])), {}).get('plain_text', 'タイトル不明')
             if title_prop_name == 'タイトル不明':
                 for prop_value in page['properties'].values():
                     if prop_value.get('type') == 'title':
                         title_text_list = prop_value.get('title', [])
                         if title_text_list:
                            title_prop_name = title_text_list[0].get('plain_text', 'タイトル不明')
                            break
             print(f"- {title_prop_name}")

    except Exception as filter_e:
        print(f"フィルタリング中にエラー: {filter_e}")
        print("フィルタ条件 (プロパティ名 'ステータス' や値 '完了') がデータベースに存在するか確認してください。")


    # ---- ソートの例 ----
    print("\n--- ソートの例 ---")
    # '作成日時' プロパティで降順 (新しい順) にソート
    try:
        sorted_response = notion.databases.query(
            database_id=database_id,
            sorts=[
                {
                    "property": "作成日時", # ソート対象のプロパティ名
                    "direction": "descending" # descending (降順) or ascending (昇順)
                }
            ]
        )
        sorted_pages = sorted_response.get("results", [])
        print("'作成日時' で降順ソートした最初の5件:")
        for i, page in enumerate(sorted_pages[:5]):
             title_prop_name = next(iter(page['properties'].get('Name', {}).get('title', []) or page['properties'].get('名前', {}).get('title', [])), {}).get('plain_text', 'タイトル不明')
             if title_prop_name == 'タイトル不明':
                 for prop_value in page['properties'].values():
                     if prop_value.get('type') == 'title':
                         title_text_list = prop_value.get('title', [])
                         if title_text_list:
                            title_prop_name = title_text_list[0].get('plain_text', 'タイトル不明')
                            break
             created_time = page.get('created_time', '不明')
             print(f"{i+1}. {title_prop_name} (作成日時: {created_time})")
    except Exception as sort_e:
        print(f"ソート中にエラー: {sort_e}")
        print("ソート条件 (プロパティ名 '作成日時') がデータベースに存在するか確認してください。")


except Exception as e:
    print(f"エラーが発生しました: {e}")

databases.queryの`filter`や`sorts`パラメータには、様々な条件を指定できます。例えば、数値プロパティの範囲指定、日付プロパティでの絞り込み、複数の条件を組み合わせる(AND/OR)などが可能です。詳細はNotion APIのドキュメントや`notion-client`のドキュメントを確認してください。

ページネーションについて: `databases.query`は一度に最大100件のデータしか返しません。100件を超えるデータを取得したい場合は、レスポンスに含まれる`next_cursor`を使用して、次のページのデータを繰り返し取得する必要があります(ページネーション処理)。`notion-client`には、これを簡単に行うための高レベルな関数や、`has_more`フラグを確認しながらループ処理を実装する方法があります。

3. ページの内容(ブロック)を取得する

Notionのページは、テキスト、見出し、リスト、画像、データベースなど、様々な「ブロック」で構成されています。特定のページの内容(ブロックのリスト)を取得するには、`blocks.children.list()`メソッドを使用します。引数にはページIDが必要です。

ページIDは、データベース内のページを取得した際のレスポンスに含まれる`id`の値です。通常のページの場合は、URLから取得できます(例: `https://www.notion.so/Page-Title-abcdef1234567890abcdef1234567890` の `abcdef1234567890abcdef1234567890` の部分)。

import os
from notion_client import Client
import json

api_key = os.environ.get("NOTION_API_KEY")
notion = Client(auth=api_key)

# 例として、先ほどqueryで取得した最初のページのIDを使用
# page_id = pages[0]['id'] # pagesは databases.query の結果
page_id = "YOUR_PAGE_ID" # もしくは直接ページIDを指定

try:
    response = notion.blocks.children.list(block_id=page_id)
    blocks = response.get("results", [])
    print(f"ページ(ID: {page_id})から{len(blocks)}個のブロックを取得しました。📝")

    if blocks:
        print("\nブロックの内容:")
        for i, block in enumerate(blocks):
            block_type = block.get("type")
            block_id = block.get("id")
            print(f"\n--- ブロック {i+1} (Type: {block_type}, ID: {block_id}) ---")

            # 各ブロックタイプに応じて内容を取得・表示
            if block_type == "paragraph" and block.get("paragraph", {}).get("rich_text"):
                text = "".join([t.get("plain_text", "") for t in block["paragraph"]["rich_text"]])
                print(f"テキスト: {text}")
            elif block_type == "heading_1" and block.get("heading_1", {}).get("rich_text"):
                text = "".join([t.get("plain_text", "") for t in block["heading_1"]["rich_text"]])
                print(f"見出し1: {text}")
            elif block_type == "heading_2" and block.get("heading_2", {}).get("rich_text"):
                text = "".join([t.get("plain_text", "") for t in block["heading_2"]["rich_text"]])
                print(f"見出し2: {text}")
            elif block_type == "heading_3" and block.get("heading_3", {}).get("rich_text"):
                text = "".join([t.get("plain_text", "") for t in block["heading_3"]["rich_text"]])
                print(f"見出し3: {text}")
            elif block_type == "bulleted_list_item" and block.get("bulleted_list_item", {}).get("rich_text"):
                text = "".join([t.get("plain_text", "") for t in block["bulleted_list_item"]["rich_text"]])
                print(f"箇条書き: {text}")
            elif block_type == "numbered_list_item" and block.get("numbered_list_item", {}).get("rich_text"):
                text = "".join([t.get("plain_text", "") for t in block["numbered_list_item"]["rich_text"]])
                print(f"番号付きリスト: {text}")
            elif block_type == "to_do" and block.get("to_do", {}).get("rich_text"):
                text = "".join([t.get("plain_text", "") for t in block["to_do"]["rich_text"]])
                checked = block["to_do"].get("checked", False)
                print(f"ToDo: [{'x' if checked else ' '}] {text}")
            elif block_type == "code" and block.get("code", {}).get("rich_text"):
                language = block["code"].get("language", "plain text")
                code_text = "".join([t.get("plain_text", "") for t in block["code"]["rich_text"]])
                print(f"コード ({language}):\n```\n{code_text}\n```")
            # 他のブロックタイプ (toggle, quote, callout, image, etc.) も同様に処理可能
            else:
                # サポートしていないタイプやテキストがない場合は、タイプ名のみ表示
                print(f"タイプ '{block_type}' の詳細表示は未実装です。")
                # デバッグ用にJSON全体を表示
                # print(json.dumps(block, indent=2, ensure_ascii=False))

except Exception as e:
    print(f"エラーが発生しました: {e}")

この例では、主要なブロックタイプ(段落、見出し、リスト、ToDo、コード)の内容を取得して表示しています。Notionには他にも多くのブロックタイプが存在するため、必要に応じて処理を追加する必要があります。ブロックのデータ構造はタイプによって異なるため、詳細はAPIドキュメントを参照してください。

ここでもページネーションが適用され、一度に取得できるブロック数には上限があります。長いページの内容をすべて取得するには、`next_cursor`を使った繰り返し処理が必要です。

データの作成・更新 (Write Operations)

`notion-client`を使えば、Notionに新しい情報を追加したり、既存の情報を変更したりすることも可能です。✍️

1. 新しいページを作成する

データベースに新しいページ(アイテム)を追加するには、`pages.create()`メソッドを使用します。引数には、親となるデータベースのIDと、設定したいプロパティの値を指定します。

import os
from notion_client import Client
import json
from datetime import datetime # 日付プロパティ用

api_key = os.environ.get("NOTION_API_KEY")
notion = Client(auth=api_key)

parent_database_id = "YOUR_DATABASE_ID" # ページを追加したいデータベースのID

try:
    new_page_data = {
        "parent": { "database_id": parent_database_id },
        "properties": {
            # 'Name' または '名前' プロパティ (Title型) を設定
            # 実際のデータベースのタイトルプロパティ名に合わせてください
            "Name": {
            # "名前": { # もしプロパティ名が '名前' ならこちらを使う
                "title": [
                    {
                        "text": {
                            "content": "APIから作成した新しいタスク"
                        }
                    }
                ]
            },
            # 'ステータス' プロパティ (Select型) を設定
            "ステータス": {
                "select": {
                    "name": "未着手" # 存在する選択肢の名前を指定
                }
            },
            # '担当者' プロパティ (Person型) を設定 (User ID が必要)
            # ユーザーIDは事前に調べておく必要があります
            # "担当者": {
            #     "people": [
            #         {
            #             "object": "user",
            #             "id": "USER_ID_TO_ASSIGN"
            #         }
            #     ]
            # },
            # '期日' プロパティ (Date型) を設定
            "期日": {
                "date": {
                    "start": datetime.now().strftime("%Y-%m-%d") # 今日の日付
                    # "end": "2025-12-31" # 終了日も指定可能
                }
            },
            # 'メモ' プロパティ (Rich Text型) を設定
            "メモ": {
                "rich_text": [
                    {
                        "type": "text",
                        "text": {
                            "content": "これはPythonスクリプトから自動作成されたメモです。\n",
                            "link": None
                        },
                        "annotations": { # オプション: 太字、イタリックなど
                            "bold": False,
                            "italic": False,
                            "strikethrough": False,
                            "underline": False,
                            "code": False,
                            "color": "default"
                        },
                        "plain_text": "これはPythonスクリプトから自動作成されたメモです。\n",
                        "href": None
                    },
                    {
                        "type": "text",
                        "text": {
                            "content": "重要事項です!",
                            "link": None
                        },
                        "annotations": {
                            "bold": True,
                            "color": "red" # 色付けも可能
                        },
                        "plain_text": "重要事項です!",
                        "href": None
                    }
                ]
            }
            # 他のプロパティタイプ (Number, Checkbox, URL, Email, Phone, Multi-select, Relation, etc.) も同様に設定可能
        },
        # オプション: ページに初期コンテンツ (ブロック) を追加
        "children": [
            {
                "object": "block",
                "type": "heading_2",
                "heading_2": {
                    "rich_text": [{"type": "text", "text": {"content": "初期コンテンツの見出し"}}]
                }
            },
            {
                "object": "block",
                "type": "paragraph",
                "paragraph": {
                    "rich_text": [
                        {
                            "type": "text",
                            "text": {
                                "content": "これはページ作成時に追加された段落です。"
                            }
                        }
                    ]
                }
            }
        ]
    }

    created_page = notion.pages.create(**new_page_data)
    print("新しいページを作成しました!🎉")
    print(f"ページID: {created_page['id']}")
    print(f"ページURL: {created_page['url']}")
    # print(json.dumps(created_page, indent=2, ensure_ascii=False))

except Exception as e:
    print(f"ページの作成中にエラーが発生しました: {e}")
    print("指定したデータベースID、プロパティ名、プロパティの型、選択肢の値などが正しいか確認してください。")
    print("また、インテグレーションにコンテンツ挿入権限があるか確認してください。")

このコードは、指定したデータベースに新しいページを作成し、タイトル、ステータス(Select)、期日(Date)、メモ(Rich Text)の各プロパティを設定します。さらに、`children`パラメータを使って、ページ作成と同時に見出しと段落のブロックを追加しています。

プロパティのデータ構造は、プロパティの型によって異なります。例えば、`title`型は`title`リスト内にテキストオブジェクトを、`select`型は`select`オブジェクト内に選択肢の名前 (`name`) を、`date`型は`date`オブジェクト内に`start`や`end`の日付文字列(ISO 8601形式)を指定します。Rich Text型 (`rich_text`) は少し複雑で、テキストセグメントのリストとして表現されます。各セグメントにはテキスト内容 (`content`) と、オプションで書式設定 (`annotations`) やリンク (`link`) を含めることができます。

存在しないプロパティ名を指定したり、型に合わないデータを渡したりするとエラーになるため、対象データベースの構造をよく確認してからコードを記述する必要があります。

2. 既存のページのプロパティを更新する

既存のページのプロパティ値を変更するには、`pages.update()`メソッドを使用します。引数には更新したいページのIDと、変更したいプロパティとその新しい値を指定します。

import os
from notion_client import Client
import json

api_key = os.environ.get("NOTION_API_KEY")
notion = Client(auth=api_key)

page_id_to_update = "YOUR_PAGE_ID_TO_UPDATE" # 更新したいページのID

try:
    update_data = {
        "properties": {
            # 'ステータス' プロパティを '進行中' に変更
            "ステータス": {
                "select": {
                    "name": "進行中"
                }
            },
            # 'メモ' プロパティに追記 (既存の内容は上書きされるので注意!)
            # 追記したい場合は、一度ページを取得して既存テキストと結合する必要がある
            "メモ": {
                "rich_text": [
                    {
                        "type": "text",
                        "text": {
                            "content": "【更新】API経由でステータスを変更しました。"
                        }
                    }
                ]
            }
            # 他のプロパティも同様に更新可能
        }
        # アーカイブ (削除に近いが復元可能) する場合は以下を追加
        # "archived": True
    }

    updated_page = notion.pages.update(page_id=page_id_to_update, **update_data)
    print(f"ページ (ID: {page_id_to_update}) を更新しました!✔️")
    # print(json.dumps(updated_page['properties'], indent=2, ensure_ascii=False))

except Exception as e:
    print(f"ページの更新中にエラーが発生しました: {e}")
    print("指定したページID、プロパティ名、値などが正しいか確認してください。")
    print("また、インテグレーションにコンテンツ更新権限があるか確認してください。")

注意点: `pages.update()`でプロパティを更新する場合、指定したプロパティの値は完全に上書きされます。例えば、Rich Text型のプロパティに追記したい場合は、まず`pages.retrieve()`などで現在の内容を取得し、その内容と新しい内容を結合した上で`update`メソッドに渡す必要があります。

3. ページにブロックを追加する

既存のページに新しいコンテンツブロックを追加するには、`blocks.children.append()`メソッドを使用します。引数には親となるページ(またはブロック)のIDと、追加したいブロックのリストを指定します。

import os
from notion_client import Client
import json

api_key = os.environ.get("NOTION_API_KEY")
notion = Client(auth=api_key)

parent_page_id = "YOUR_PARENT_PAGE_ID" # ブロックを追加したいページのID

try:
    blocks_to_append = [
        {
            "object": "block",
            "type": "heading_3",
            "heading_3": {
                "rich_text": [{"type": "text", "text": {"content": "追記セクション"}}]
            }
        },
        {
            "object": "block",
            "type": "paragraph",
            "paragraph": {
                "rich_text": [
                    {
                        "type": "text",
                        "text": {
                            "content": "これはAPI経由で追記された段落です。 ",
                        }
                    },
                    {
                        "type": "text",
                        "text": {
                            "content": "リンクも追加できます。",
                            "link": {"url": "https://developers.notion.com/"}
                        },
                        "annotations": {
                            "underline": True
                        }
                    }
                ]
            }
        },
        {
            "object": "block",
            "type": "to_do",
            "to_do": {
                "rich_text": [{"type": "text", "text": {"content": "新しいToDoアイテム"}}],
                "checked": False
            }
        },
        {
            "object": "block",
            "type": "code",
            "code": {
                 "rich_text": [{
                     "type": "text",
                     "text": {
                         "content": "print('Hello from Notion API!')"
                     }
                 }],
                "language": "python" # コードの言語を指定
            }
        }
    ]

    response = notion.blocks.children.append(block_id=parent_page_id, children=blocks_to_append)
    print(f"ページ (ID: {parent_page_id}) に {len(blocks_to_append)} 個のブロックを追加しました!🧱")
    # print(json.dumps(response, indent=2, ensure_ascii=False))

except Exception as e:
    print(f"ブロックの追加中にエラーが発生しました: {e}")
    print("指定したページID、ブロックのデータ構造が正しいか確認してください。")
    print("また、インテグレーションにコンテンツ挿入権限があるか確認してください。")

このコードは、指定したページの末尾に、見出し3、段落(リンクと書式設定付き)、ToDoアイテム、Pythonコードブロックの4つのブロックを追加します。

ブロックのデータ構造は、`pages.create()`の`children`パラメータで指定したものと同様です。様々なブロックタイプを追加できます。

ブロックの更新や削除については、それぞれ`blocks.update()`や`blocks.delete()`メソッドを使用します。これらも同様にブロックIDを指定して操作します。

応用的な使い方と注意点

`notion-client`はここで紹介した基本的な操作以外にも、様々な機能を提供しています。

  • ページネーションの処理: 大量のデータを扱う場合、`databases.query`や`blocks.children.list`などのメソッドで返される結果をページネーション処理する必要があります。レスポンスに含まれる`next_cursor`と`has_more`を確認し、続きのデータを取得するループを実装します。
  • 検索 (`search`): ワークスペース全体から特定のキーワードを含むページやデータベースを検索する`search()`メソッドも利用できます。
  • ユーザー情報の取得 (`users`): ワークスペースのユーザー一覧を取得したり、特定のユーザー情報を取得したりできます (`users.list()`, `users.retrieve()`)。ただし、インテグレーションにユーザー情報の読み取り権限が必要です。
  • 非同期処理 (`AsyncClient`): 大量のAPIリクエストを効率的に処理したい場合や、Webフレームワーク(FastAPI, Django Channelsなど)と連携する場合は、非同期版のクライアント`AsyncClient`を使用できます (`from notion_client import AsyncClient`)。基本的なメソッド名は同期版と同じですが、`await`構文を使って呼び出します。
  • エラーハンドリング: Notion APIはレートリミット(短時間にリクエストを送りすぎると制限がかかる)があります。また、ネットワークエラーや無効なリクエストによるエラーも発生しえます。`notion_client.errors`には`APIResponseError`などのカスタム例外クラスが用意されているので、`try…except`ブロックで適切にエラーを捕捉し、必要に応じてリトライ処理(指数バックオフなど)を実装することが推奨されます。
  • リッチテキストの複雑さ: 特にリッチテキスト(`rich_text`)の扱いは少し複雑です。テキストの色、背景色、太字、イタリック、下線、取り消し線、コード記法、リンク、メンション(ユーザー、ページ、日付など)といった様々な要素を組み合わせる場合、正しいJSON構造を生成する必要があります。
  • APIのバージョン: Notion APIは継続的にアップデートされています。`notion-client`ライブラリもそれに追随しますが、利用する際はNotion APIのバージョン(リクエストヘッダー `Notion-Version` で指定、`notion-client`がデフォルトで設定)とライブラリのバージョンに注意し、必要に応じて更新やドキュメントの確認を行ってください。2022年頃からAPIのバージョン管理が導入され、互換性に影響する変更があり得るため、定期的なチェックが望ましいです。
ヒント: Notion APIや`notion-client`で複雑な操作を行う際は、まずNotion DevelopersのAPIリファレンスを参照し、どのようなデータ構造が必要かを確認するとスムーズです。また、`notion-client`のGitHubリポジトリにあるサンプルコードも非常に参考になります。

まとめ:Notion自動化の可能性

この解説では、Pythonライブラリ `notion-client` を使ってNotion APIを操作する方法について、基本的なインストールからデータの読み書き、応用的な使い方までを紹介しました。

`notion-client` を活用することで、以下のような様々なNotionの自動化や連携が実現可能になります:

  • 日報や週報のテンプレートを自動生成し、特定データベースに追加する。
  • Google Calendarの予定をNotionのタスクデータベースに同期する。
  • Webフォームからの問い合わせ内容をNotionの顧客管理データベースに登録する。
  • 特定の条件を満たすNotionデータベースのアイテムを検知し、Slackやメールで通知する。
  • Notion上のデータを定期的にCSVファイルなどにエクスポートしてバックアップする。

Notion APIと `notion-client` は、あなたのNotion活用をさらに一段階レベルアップさせるための強力な武器となります。ぜひこの記事を参考に、Notionの自動化に挑戦してみてください!きっと、これまで手作業で行っていた面倒なタスクを効率化し、より創造的な活動に時間を使うことができるようになるはずです。✨

Happy Notion Automating! 🤖

コメント

タイトルとURLをコピーしました