Notion API からページの本文を取得するためにBlockを理解する

feature-image

Notion の API であれこれしたいと思ったとき、まずは APIリファレンスを見ますね。Notion において記事(あるいはノート)を指す名称は「ページ(Page)」と呼ばれています。

そしてこのページを取得するにあたって、Notion API はこれまでの一般的(初歩的な)な REST-API 知識から想像していたものとは大きく異なっていましたので、実際に試して学んだことを記載します。

この記事は、これから初めてNotion API を操作する方の理解が捗るよう「Notionでページの本文を取得するにはどういう操作感になるのか」という目的で説明します。

APIレスポンスの実例

例えば Notion で書いた、こんな本文があるとします。

「記事から本文を取得いしたい」と思ったら、どういうREST-APIのレスポンスを想像しますか?

WordPress REST-API のケース

例えばこれが WordPress なら、このようなAPIレスポンスが返ってきます。

/* 一部抜粋 */
{
  "id": 99,
  "date": "2022-01-27T20:07:42",
  "date_gmt": "2022-01-27T11:07:42",
  "modified": "2022-01-27T20:07:59",
  "slug": "hello-wordpress",
  "status": "publish",
  "type": "post",
  "title": {
    "rendered": "H1 \u306f\u308d\u30fc\u3001Notion\uff01"
  },
  "content": {
    "rendered": "<h1>H1 \u306f\u308d\u30fc\u3001WordPress\uff01<\/h1>\n<p>\u8a18\u4e8b\u306e\u672c\u6587\u3002<\/p>\n<p>\u3053\u3093\u306b\u3061\u306f\u3053\u3093\u306b\u3061\u306f\u3002<\/p>\n",
    "protected": false
  }
}

そうそう、まさにイメージ通り。 response["content"]["rendered"] に記事の全文が入っていますね。

Notion API のケース

では Notion API のレスポンスはどうでしょうか。たった4行(最終行は空白行)の本文を取得するためのAPIレスポンスを全文貼り付けます。

/* 各要素のIDは適当にマスクしています */
{
  "object": "list",
  "results": [
    {
      "object": "block",
      "id": "00000000-1111-2222-3333-9313be548dfd",
      "created_time": "2022-01-27T10:55:00.000Z",
      "last_edited_time": "2022-01-27T10:56:00.000Z",
      "has_children": false,
      "archived": false,
      "type": "heading_1",
      "heading_1": {
        "text": [
          {
            "type": "text",
            "text": {
              "content": "H1 はろー、Notion!",
              "link": null
            },
            "annotations": {
              "bold": false,
              "italic": false,
              "strikethrough": false,
              "underline": false,
              "code": false,
              "color": "default"
            },
            "plain_text": "H1 はろー、Notion!",
            "href": null
          }
        ]
      }
    },
    {
      "object": "block",
      "id": "00000000-1111-2222-3333-9a9968d52b62",
      "created_time": "2022-01-27T10:55:00.000Z",
      "last_edited_time": "2022-01-27T10:55:00.000Z",
      "has_children": false,
      "archived": false,
      "type": "paragraph",
      "paragraph": {
        "text": [
          {
            "type": "text",
            "text": {
              "content": "記事の本文。",
              "link": null
            },
            "annotations": {
              "bold": false,
              "italic": false,
              "strikethrough": false,
              "underline": false,
              "code": false,
              "color": "default"
            },
            "plain_text": "記事の本文。",
            "href": null
          }
        ]
      }
    },
    {
      "object": "block",
      "id": "00000000-1111-2222-3333-81694983f0c1",
      "created_time": "2022-01-27T10:55:00.000Z",
      "last_edited_time": "2022-01-27T10:55:00.000Z",
      "has_children": false,
      "archived": false,
      "type": "paragraph",
      "paragraph": {
        "text": [
          {
            "type": "text",
            "text": {
              "content": "こんにちはこんにちは。",
              "link": null
            },
            "annotations": {
              "bold": false,
              "italic": false,
              "strikethrough": false,
              "underline": false,
              "code": false,
              "color": "default"
            },
            "plain_text": "こんにちはこんにちは。",
            "href": null
          }
        ]
      }
    },
    {
      "object": "block",
      "id": "00000000-1111-2222-3333-fbb07b9a6b14",
      "created_time": "2022-01-27T10:55:00.000Z",
      "last_edited_time": "2022-01-27T10:55:00.000Z",
      "has_children": false,
      "archived": false,
      "type": "paragraph",
      "paragraph": {
        "text": []
      }
    }
  ],
  "next_cursor": null,
  "has_more": false
}

な、長い…。

response["results"] が配列になっていて、各要素がブロックと呼ばれるオブジェクトの構造になっています。雑に言えば本文の1行が1ブロックみたいな捉え方です。何故こんな面倒くさいことになっているのかというと、1ブロックごとに「テキスト」だったり「画像」だったり、あるいは「YouTube動画」や「Tweetの埋め込み」というように意味が付与できることで Notion の柔軟な表現力が実現できているわけですね。

厳密に、1ブロックに複数行を記載することもできます。例えるなら、エクセルのセル内で Alt + Enter を押すことで改行できるのと同じ感覚です。

ページを取得するために必要な知識

特定のページに含まれる本文を取得したい場合、「Retrieve block children」APIを利用します。

https://developers.notion.com/reference/get-block-children

「Retrieve a block」のAPIを見てしまいそうになりますが、これは特定の単一のブロックを取得するものです。前述の通りページ本文は複数行のブロックで構成されていますので、ページのID(pageId)を指定してその配下に連なる Children を「Retrieve block children」で取得する関係にになります。

GET https://api.notion.com/v1/blocks/{32桁のID ハイフン濃霧はどちらでも良い}/children HTTP/1.1

なお、箇条書きなどネストする場合は、ブロックの更に配下に Children を持つことになるので、複数回のAPIリクエストが必要になります。将来楽になることに期待ですね。

ブロックの構造と種類

前述のレスポンス例から察することができる通り、全ての配列要素をループ処理で確認しなければページの本文を作ることができません。

また、各ブロックの種類( type )ごとに構造が若干異なるので、それぞれの理解と分岐処理が必要です。ここでいう種類というのは、「ヘッダ要素」「普通のテキスト」「箇条書き」「画像」「コードブロック」・・・というように大量にあります。

どの程度の種類があるかは、公式のリファレンスを参照してください。2022/01/28時点でも、30種類ほどのブロックが提供されていますね。

https://developers.notion.com/reference/block ―Block object

これらをゼロから実装するのは面倒ですので、まずは利用したい言語に存在するライブラリを探してみるのがよいでしょう。ポイントとなるのは、「どの言語でやりたいか」「どのフォーマットに変換したいか」の2点です。

Typescript で Markdown 書式に変換したい場合に採用したライブラリを次の記事で紹介していますのでご参考まで。

Notionの本文をMarkdownに変換するライブラリの現状
Notionの本文をMarkdownに変換するライブラリの現状
https://fand.jp/notion/the-library-converts-the-concept-body-to-markdown-format/
NotionのAPIから取得したページの本文をMarkdownに変換するための方法やライブラリを調査しました。

コードブロックの紹介(余談)

Markdown フォーマットデコードを書くときには、```language このような書式で言語向けのハイライトを利用している方も多いと思います。Notion API から取得するデータには Notion アプリで設定した言語の情報がちゃんと取得できますので安心してください。気が利きますね。

/* 以下サンプルは公式リファレンスからの引用 */
{
  "type": "code",
  //...other keys excluded
  "code": {
    "text": [{
      "type": "text",
      "text": {
        "content": "const a = 3"
      }
    }],
    "language": "javascript"
  }
}

まとめ

Notion API からページの本文を取得するための考え方を説明しました。

APIへのアクセスさせできてしまえばこっちの勝利!とはならず、更にそのデータを加工するひと手間が必要ですが、Notion の柔軟な表現力ゆえですので頑張って使いこなしましょう。

参考資料

Notion のデータモデルの解説

公式ブログにて「ページ」と「ブロック」の関係を説明されています。図も用いられているのでイメージしやすいです。

Exploring Notion's Data Model: A Block-Based Architecture | Notion

Exploring Notion's Data Model: A Block-Based Architecture | Notion

Notion’s data model enables the product’s most foundational component: blocks. Through blocks, we allow users more flexibility over their …