【Notion API】Google Apps Scriptでデータベースからページの中身を取得する

Code

はじまり

リサちゃん
リサちゃん

お、遅い・・・

135ml
135ml

ページが増えてくると、表示するのがな・・・

Notionのページが増えてくると表示が重くなる・・・。

Notionでは、データベース上に沢山ページを作成することが出来ます。

しかし、どんどんページが増えてくると、そのページをブラウザ上で表示する処理が重たくなってきます・・・。

そこで今回は、Notionのページの情報をGoogleスプレッドシートで表示する方法を紹介します。

Googleスプレッドシートで情報を表示する方が、Notionのサイトでページを表示するよりも速く表示できます。そのために、「Notion API」を「Google Apps Script(以下、GAS)」で叩いて、Notionのページを取得していきたいと思います。

その他、Notionのページを新規作成、更新をGASで行う方法も取り扱います。

Notion APIとは

Notion APIは、Notionのデータをプログラムから操作するためのインターフェースです。

これを使うことで、JavaScriptなどのプログラミング言語から、Notionのページを作成・更新・削除したり、情報を取得したりすることができます。例えば、Googleフォームの回答を自動でNotionのデータベースに転記したり、Notionの情報を元にスプレッドシートを更新したり、その日に登録されたNotionのページの一覧を自分のGmailに送ったり、といったことが可能になります。

Start building with the Notion API
Connect Notion pages and databases to the tools you use every day, creating powerful workflows.

準備するもの

今回は、Google Apps Script(以降、GAS)を使って、NotionのAPIを利用していきます。

GASは、JavaScriptベースのプログラミング言語なので、今回のコードの殆どの部分は、Node.js、Deno、Bun等のランタイムでも利用できるかと思います。

以下のものが、今回のプログラムの実行に必要になります。

  • Notionのアカウント: APIを利用するためには、Notionのアカウントが必要です。
  • Notion Integration: Notionのワークスペースで、APIを利用するためのIntegrationを作成し、トークンを取得します。
  • Google Apps Scriptのプロジェクト: 新規プロジェクトを作成するか、既存のプロジェクトを使用します。

Notionのアカウント

アカウントを持っていない場合は、以下から作ります。

Wiki、ドキュメント、プロジェクトがつながるコネクテッドワークスペース | Notion (ノーション)
1人でもチームでも使える、オールインワンのワークスペース。毎日使うアプリケーションを、ひとつにまとめます。

Notion Integration

自分のNotionアカウント内にあるページを、外部からプログラミング言語で操作するためには、Notionのワークスペースで「Integration」というものを作成し、トークンを取得する必要があります。このトークンが、APIへの認証に利用されます。

まずは、Notionの開発者向けページにアクセスします。

Notion – The all-in-one workspace for your notes, tasks, wikis, and databases.
A new tool that blends your everyday work apps into one. It's the all-in-one workspace for you and your team

上記の開発者向けページが切れている場合、以下の手順で開発者向けページにアクセスしてみて下さい。

  1. Notionのワークスペースでサイドバーを開いて、「Settings」をクリック。
  2. 表示されたウインドウ内のサイドバーにある「Connections」をクリック。
  3. 「Connections」の下方にある、「Develop or manage integrations」をクリック。
  4. 「New Integration」からIntegrationを追加していきます。
  5. 自分が使っているNotionのワークスペースを選んだりして、Integrationを追加します。
  6. Integrationが作成されたら、そのIntegrationの「Internal Integration Secret」をメモします。これをGASのコード内で使います。
  7. Notionのワークスペースに戻って、先程の「Connections」のウインドウを開いて、自分が先程作成したIntegrationが追加されていることを確認します。
  8. 次に、データベースの画面を開きます。すると、画面右上の横3点リーダーをクリックして開かれるメニューの下方に、自分が先程作成したIntegrationが追加されていることが確認できます。

「Connections」のウインドウを開いて、自分が先程作成したIntegrationが追加されている場合、そのIntegrationの横3点リーダーをクリックすると、「Copy internal integration token」やら「Manage in developer portal」といった選択肢が追加されているConnectionを確認できます。

上記の手順でも辿り着けなかったら以下のNotion公式ページから、手順を参照してみて下さい。(僕が2024-11-05時点で以下のページの手順で辿り着けなかったので、上記の手順を残しました・・・のですが・・・。)

Notion API integrations – Notion Help Center
With Notion's API, you'll be able to create custom internal integrations. Some of our partners may also require an inter...

Google Apps Scriptのプロジェクト

GASを使うために、GASのプロジェクトを作成する必要があります。

プロジェクト作成の手順は以下の記事をご参考下さい。

Notion APIの基本

これで、Notion APIを使う準備は出来たので使っていきましょう。

アクセストークンで認証する

先程、Notionのワークスペースなどで「Internal Integration Secret」や「Copy internal integration token」をクリックしてメモしたトークンを使います。

トークンをコード内に貼るのは良くないですが、これで一応コードを試すことは可能です。出来れば、アクセストークンがそのまま書いてあるコードをGitなどに載せたくないですね。

function getAccessToken() {
  // Notion Integrationで取得したトークンをここに設定
  const accessToken = "your_access_token";
  // Git等に載せる時はコッチ。トークンはスクリプトプロパティに入れる。
  const accessToken = PropertiesService.getScriptProperties().getProperty("NOTION_API_ACCESS_TOKEN");
  return accessToken;
}

リクエストの構造

Notion APIへリクエストを送る時は基本的にこんな感じの形で送ります。エンドポイントによって、メソッド等が違ったりしますが、それは後述します。

これは、「Create a page」エンドポイントへのリクエストの例です。

function createPage(databaseId, properties) {
  const url = "https://api.notion.com/v1/pages";
  const options = {
    "method" : "post",
    "headers" : {
      "Authorization": "Bearer " + getAccessToken(),
      "Notion-Version": "2022-06-28",
      "Content-Type": "application/json",
    },
    "payload" : JSON.stringify({
      "parent": {"database_id": databaseId},
      "properties": properties
    })
  };
  const response = UrlFetchApp.fetch(url, options);
  return JSON.parse(response.getContentText());
}

リクエストして返ってきたレスポンスresponseを解析する際に、response.getContentText()でJSONを取得して、JSON.parse(response.getContentText())だとオブジェクトを取得できます。

ページやデータベースのID

参照するページやデータベースのIDを知りたい時は、そのページやデータベースをブラウザ上で開いてURLを確認します。そしたら、以下の部分がそれぞれのIDに当たります。

例:URLが「https://www.notion.so/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx?v=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy&p=zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz&pm=s」だった場合。

  • ページID:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  • データベースID:zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
  • ちなみに、yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyは、データベースのビューIDです。

さらにちなみに、xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxという風にハイフンが付いているIDは、「UUID」というものになります。ソッチを使ってリクエストしなければならない場合もあります。

Notionのページ周りの構造に関して

まずは、Notion APIを触る前に、Notionで利用する「データベース」、「ページ」、「ブロック」という要素を知っておきましょう。Notionにおけるデータ構造はそれらの要素で構成されています。

データベース

データベースは、Notionにおける最も基本的な構造の1つです。Excelのスプレッドシートのような役割を果たし、複数のページをまとめ、特定のテーマやプロジェクトに関する情報を一元管理する場所です。

  • プロパティ: 各ページに共通する属性(例えば、タイトル、作成日、ステータスなど)を定義します。
  • ビュー: データベース内のページを様々な角度から表示するための仕組みです。一覧表示、カレンダー表示、ボード表示など、様々なビューを切り替えることができます。

ページ

ページは、データベース内の個々のレコードです。データベースの行に相当し、具体的な情報が記述されます。

  • プロパティ値: データベースで定義されたプロパティに対応する値を持ちます。
  • ブロック: ページの内容を構成する最小単位です。テキスト、画像、リスト、コードブロックなど、様々な種類のブロックを組み合わせて、ページを作成します。

ブロック

ブロックは、ページの内容を構成する最小単位です。テキスト、画像、リスト、コードブロックなど、様々な種類のブロックがあります。ブロックは、ページ内で自由に配置したり、入れ子にしたりすることができます。

  • テキストブロック: 文章を入力するブロックです。
  • 見出しブロック: 見出しを作成するブロックです。Heading 1~Heading 3まであります。
  • リストブロック: リストを作成するブロックです。Bulleted ListやNumbered Listが該当します。
  • コードブロック: コードを記述するブロックです。
  • 画像ブロック: 画像を挿入するブロックです。
  • データベースブロック: 別のデータベースを埋め込むブロックです。
  • その他: ToDoリスト、チェックボックス、分割線など、様々な種類のブロックがあります。

データ構造の全体像

Notionのデータ構造は、これらの要素が組み合わさって構成されています。

  • データベース: 複数のページをまとめる容器
  • ページ: データベース内の個々のレコード
  • ブロック: ページの内容を構成する最小単位

例えば、「タスク管理」というデータベースを作成し、各タスクをページとして作成します。各ページには、「タスク名」、「担当者」、「期限」などのプロパティを設定し、テキストブロックやチェックボックスブロックを使ってタスクの詳細を記述します。

また、「ページ」は「ルートブロック」と同義です。つまりは「ページ」は「ブロック」でもあるのです。このことを知っておくと、Notion APIの「Retrieve block children」エンドポイントを使用する際に役に立ちます。

データベースの中にあるページの内容を取得する

それでは、基本的な部分を押さえた上で、Notionのデータベースの中にある任意のページの内容を取得していきます。

「Query a database」エンドポイント

それでは、Notion APIの「Query a database」のエンドポイントで、データベースの中にあるページの一覧を取得します。APIの公式リファレンスは下記のリンクから見れます。

Start building with the Notion API
Connect Notion pages and databases to the tools you use every day, creating powerful workflows.

リクエストの構造はこんな感じです。muteHttpExceptionsをtrueにすると、レスポンスがエラーで帰ってきた時に原因を見つけやすくなります。

function queryDatabase(databaseId) {
  const url = "https://api.notion.com/v1/databases/" + databaseId + "/query";
  const options = {
    "muteHttpExceptions": true,
    "method" : "post",
    "headers" : {
      "Notion-Version": "2022-06-28",
      "Content-Type": "application/json",
      "Authorization": "Bearer " + getAccessToken()
    },
    "payload" : JSON.stringify(
      {
        "filter": {
          "or": [
            {
              "property": "Name",
              "title": {
                "contains": "タスク"
              }
            }
          ]
        },
        "sorts": [
          {
            "property": "Created time",
            "direction": "descending"
          }
        ],
        "page_size": 100
      }
    )
  };
  const response = UrlFetchApp.fetch(url, options);
  console.log(response.getContentText());
  return JSON.parse(response.getContentText());
}

const databaseId = PropertiesService.getScriptProperties().getProperty("DATABASE_ID");
console.log(queryDatabase(databaseId));

この「Query a database」のエンドポイントで取得できるページは、1回のリクエストごとに100ページまでの上限が定められています。

そのため、101個以上のページを取得するためには、直前のリクエストから返ってきたレスポンスの中にある「has_more」フィールドを見て、trueになっているかどうかを確認します。trueになっていれば更にリクエストして追加のページを取得できますので、「next_cursor」フィールドの中にあるページIDをメモして、次のリクエスト時に「start_cursor」フィールドを追加して、追加のページを取得します。

function queryDatabase(databaseId) {
  const endpoint = "https://api.notion.com/v1/databases/" + databaseId + "/query";
  const options = {
    "muteHttpExceptions": true,
    "method" : "post",
    "headers" : {
      "Notion-Version": "2022-06-28",
      "Content-Type": "application/json",
      "Authorization": "Bearer " + getAccessToken()
    },
    "payload" : JSON.stringify(
      {
        "filter": {
          "or": [
            {
              "property": "Name",
              "title": {
                "contains": "タスク"
              }
            }
          ]
        },
        "sorts": [
          {
            "property": "Created time",
            "direction": "descending"
          }
        ],
        "page_size": 100,
        "start_cursor": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
      }
    )
  };
  const response = UrlFetchApp.fetch(endpoint, options);
  console.log(response.getContentText());
  return JSON.parse(response.getContentText());
}

const databaseId = PropertiesService.getScriptProperties().getProperty("DATABASE_ID");
console.log(queryDatabase(databaseId));

そして、受け取れるレスポンスは、JSON形式だとこんな感じになります。

{
  "object": "list",
  "results": [
    {
      "object": "page",
      "id": "your_page_id",
      "created_time": "2023-11-22T03:14:00.000Z",
      "last_edited_time": "2023-11-22T03:15:00.000Z",
      "archived": false,
      "in_trash": false,
      "properties": {
        // 各プロパティの値
      },
      "public_url": null
    },
    // ... 他のページ
  ],
  // ページネーションに関する情報
}

各プロパティの値は、例えば下記のように格納されています。長いJSONは読みにくいので、上記のものと見比べて確認してみて下さい。

{
  "object": "list",
  "results": [
    {
      "object": "page",
      "id": "your_page_id",
      "created_time": '"2023-02-15T21:07:00.000Z",
      "last_edited_time": "2023-11-22T03:15:00.000Z",
      "archived": false,
      "in_trash": false,
      "properties": {
        "Name": {
          "title": [
            {
              "text": {
                "content": "タスク1"
              }
            }
          ]
        },
        "Status": {
          "select": {
            "name": "完了"
          }
        }
      },
      "public_url": null
    }
  ],
  "next_cursor": null,
  "has_more": false
}

「Retrieve a page」エンドポイント

次に、Notion APIの「Retrieve a page」のエンドポイントで、先程データベースから取得したページの一つからプロパティを取得します。このエンドポイントは、ページの中身だけを取得する場合は不要です。

APIの公式リファレンスは下記のリンクから見れます。

Start building with the Notion API
Connect Notion pages and databases to the tools you use every day, creating powerful workflows.

リクエストの構造はこんな感じです。

function retrievePage(pageId) {
  const endpoint = "https://api.notion.com/v1/pages/" + pageId;
  const options = {
    "muteHttpExceptions": true,
    "method" : "get",
    "headers" : {
      "Notion-Version": "2022-06-28",
      "Content-Type": "application/json",
      "Authorization": "Bearer " + getAccessToken()
    }
  };
  const response = UrlFetchApp.fetch(endpoint, options);
  return JSON.parse(response.getContentText());
}

const pageId = "PAGE_ID";
console.log(retrievePage(pageId));

そして、受け取れるレスポンスはこんな感じになります。

{
  "object": "page",
  "id": "pageId",
  "created_time": "2023-04-05T13:34:26.000Z",
  "last_edited_time": "2023-04-05T13:34:26.000Z",
  "has_children": false,
  "parent": {
    "type": "database_id",
    "database_id": "database_id"
  },
  "archived": false,
  "properties": {
    // プロパティの定義
  },
  // その他の属性
}

「Retrieve block children」エンドポイント

次に、Notion APIの「Retrieve block children」のエンドポイントで、先程データベースから取得したページの一つから内容を取得します。このエンドポイントは、ページのプロパティだけを取得する場合は不要です。

APIの公式リファレンスは下記のリンクから見れます。

Start building with the Notion API
Connect Notion pages and databases to the tools you use every day, creating powerful workflows.

リクエストの構造はこんな感じです。コード内でblockIdと記述している理由は、「ブロックID」は「ルートブロック(ページ)のID」を指しているためです。

function retrieveBlockChildren(blockId) {
  const url = "https://api.notion.com/v1/blocks/" + blockId + "/children";
  const options = {
    "muteHttpExceptions": true,
    "method" : "get",
    "headers" : {
      "Notion-Version": "2022-06-28",
      "Content-Type": "application/json",
      "Authorization": "Bearer " + getAccessToken()
    }
  };
  const response = UrlFetchApp.fetch(url, options);
  return JSON.parse(response.getContentText());
}

const pageId = "PAGE_ID";
console.log(retrieveBlockChildren(pageId));

そして、受け取れるレスポンスはこんな感じになります。ブロックもページの時と同様に、一度のリクエストで100個までしか取得できません。

{ "object": "list",
  "results": 
   [ { "object: "block",
       "id: "62b4bdff-571a-4be9-823f-e6f8a12f420e",
       "parent: [Object],
       "created_time": "2023-04-05T13:34:26.000Z",
       "last_edited_time": "2023-04-05T13:34:26.000Z",
       "created_by": [Object],
       "last_edited_by": [Object],
       "has_children": false,
       "archived": false,
       "in_trash": false,
       "type": 'paragraph',
       "paragraph": [Object]
  } ],
  "next_cursor": null,
  "has_more": false,
  "type": "block",
  "block": {},
  // その他の属性
}

なので、「has_more」がtrueであれば、次のリクエストにクエリパラメータ「start_cursor」を含めて、レスポンスの「next_cursor」のブロックIDを記載します。この時のブロックIDは、UUIDである必要があるので、ハイフンを含めなければなりません。

function retrieveBlockChildren(blockId, startId="") {
  let url = "https://api.notion.com/v1/blocks/" + blockId + "/children";
  if(startId !== ""){
    url = "https://api.notion.com/v1/blocks/" + blockId + "/children?start_cursor=" + startId;
  }
  const options = {
    "muteHttpExceptions": true,
    "method" : "get",
    "headers" : {
      "Notion-Version": "2022-06-28",
      "Content-Type": "application/json",
      "Authorization": "Bearer " + getAccessToken()
    }
  };
  const response = UrlFetchApp.fetch(url, options);
  return JSON.parse(response.getContentText());
}

const pageId = "PAGE_ID";
const startId = "BLOCK_UUID";
console.log(retrieveBlockChildren(pageId));

その他のAPIエンドポイント

先程は、データベースからページのプロパティおよび内容を取得するまでに必要なエンドポイントを並べましたが、それ以外の処理を行うエンドポイントもあります。

「Create a page」エンドポイント

Notion APIの「Create a page」のエンドポイントで、指定したデータベースの中に新しいページを追加できます。APIの公式リファレンスは下記のリンクから見れます。

Start building with the Notion API
Connect Notion pages and databases to the tools you use every day, creating powerful workflows.

リクエストの構造はこんな感じです。

function createPage(databaseId, properties) {
  const url = "https://api.notion.com/v1/pages";
  const options = {
    "muteHttpExceptions": true,
    "method" : "post",
    "headers" : {
      "Authorization": "Bearer " + getAccessToken(),
      "Notion-Version": "2022-06-28",
      "Content-Type": "application/json",
    },
    "payload" : JSON.stringify({
      "parent": {"database_id": databaseId},
      "icon": {
        "type": "emoji",
        "emoji": "🥬"
      },
      "properties": properties
    })
  };
  const response = UrlFetchApp.fetch(url, options);
  return JSON.parse(response.getContentText());
}

const databaseId = "your_database_id";
const properties = {
  "Name": {
    "title": [
      {
        "text": {
          "content": "テストですよ"
        }
      }
    ]
  },
  "Tags": {
    "multi_select": [
      {
        "name": "GoogleAppsScript"
      }
    ]
  }
}
console.log(createPage(databaseId, properties));

そして、受け取れるレスポンスはこんな感じになります。

{
  "object": "page",
  "id": "your_page_id",
  "created_time": "2023-11-22T03:14:00.000Z",
  "last_edited_time": "2023-11-22T03:14:00.000Z",
  "cover": null,
  "icon": { "type": "emoji", "emoji": "🥬" },
  "properties": {
    // 作成したページのプロパティ
  }
}

ページの作成に失敗した場合のレスポンスは下記のような感じになります。

{
  "object": "error",
  "status": 400,
  "message": "Invalid request"
}

「Update page properties」エンドポイント

Notion APIの「Update page properties」のエンドポイントで、ページのプロパティの値を変更することが出来ます。APIの公式リファレンスは下記のリンクから見れます。

Start building with the Notion API
Connect Notion pages and databases to the tools you use every day, creating powerful workflows.

リクエストの構造はこんな感じです。

function patchPageProperties(pageId, properties) {
  const url = "https://api.notion.com/v1/pages/" + pageId;
  const options = {
    "muteHttpExceptions": true,
    "method" : "patch",
    "headers" : {
      "Authorization": "Bearer " + getAccessToken(),
      "Notion-Version": "2022-06-28",
      "Content-Type": "application/json",
    },
    "payload" : JSON.stringify({
      "properties": properties
    })
  };
  const response = UrlFetchApp.fetch(url, options);
  return JSON.parse(response.getContentText());
}

const pageId = "your_page_id";
const properties = {
  "Name": {
    "title": [
      {
        "text": {
          "content": "タスクなのですよ"
        }
      }
    ]
  },
  "Tags": {
    "multi_select": [
      {
        "name": "GoogleAppsScript"
      }
      , {
        "name": "JavaScript"
      }
    ]
  }
}
console.log(patchPageProperties(pageId, properties));

そして、受け取れるレスポンスはこんな感じになります。

{
  "object": "page",
  "id": "your_page_id",
  "created_time": "2023-11-22T03:14:00.000Z",
  "last_edited_time": "2023-11-22T03:14:00.000Z",
  // 更新されたページの他のプロパティ
}

ページの更新に失敗した場合のレスポンスは下記のようになります。

{
  "object": "error",
  "status": 400,
  "message": "Invalid request"
}

Notionページの一覧をスプレッドシートに載せたらこうなる。

Notionのページの一覧を、Googleスプレッドシートに載せたらこんな感じになります。この状態からまたさらに色々と出来そうな感じがしてきます。

まとめ

今回は、NotionのAPIにリクエストして、ページを一覧で取得してページの内容を取得する方法を紹介しました。

以下、本記事のまとめです。

  • Notion APIでNotionのページを取得したり更新できる。
  • Notion APIを使うためには、Integrationというものを作成して、Integration Tokenを取得する必要がある。
  • Notionにおけるデータ構造は、「データベース」、「ページ」、「ブロック」という要素で構成されている。
  • Notionのページを取得するためには、以下の流れで行う。
    • 「Query a database」エンドポイントからページIDを取得する。
    • 「Retrieve a page」エンドポイントで、ページIDを使って、ページのプロパティを取得できる。
    • 「Retrieve block children」エンドポイントで、ページIDを使って、ページの中身を取得できる。
  • 「Create a page」エンドポイントでページを新規作成したり、「Update page properties」エンドポイントでページを更新することが出来る。
  • Google Apps ScriptでNotion APIを叩けば、Googleスプレッドシートに一覧表示できる。

これで、Notionのページに対する作業を、Google Apps Scriptで自動化出来るようになりました。Notionで日記を付けていれば毎日同じページを追加したり、Notionのページを日々バックアップすることが出来るようになります。自分の中のデータベースをどんどん大きくしていきましょう。

おしまい

リサちゃん
リサちゃん

Notionのページをスプレッドシートで管理できるぞ~

135ml
135ml

ページのプロパティを俯瞰できますね。

以上になります!

コメント

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