【NotionAPI】【GAS】Notionのテーブルをスプレッドシートに反映
はじめに
こんにちは!大ちゃんの駆け出し技術ブログです。
社内業務にてNotionのテーブルをスプレッドシートに連携させる実装をすることを頼ました。しかし、調べてみてもそのような記事は見当たらず、NotionAPIもGASも使ったことがなかったのでだいぶ苦労しました。ですので、こちらの記事にて実装した内容を共有しておきたいと思います。
使用技術
- GAS(Google Apps Script)
- Notion API
最終実装
こちらの記事では最終的に下記のような実装ができるようになります。
※ spreadsheetからNotionへのテーブルの反映機能は実装していません。
↓(転記!!!)
NotionAPIの準備
まずGASを使用してNotionからテーブルの情報を取得する方法を実装します。そのための準備としてNotionから情報を取得できるように設定します。
テーブル作成
まずはNotionで適当なテーブルページを作成します。必ず自分が管理者であるワークスペースで作成してください。
私は下記のようなページを作成しました。
今回の実装ではタイトルである「Name」カラム、マルチタグの「Tags」カラム、日付の「Date」カラムをスプレッドシートに移します。
Integration secretの作成
Integrationとは?
Integrationと聞いてピンと来ないかもしれませんが、ここでの意味はNotionAPIのAppと考えていいと思います。Slack Appみたいなものです。
下記公式手順に沿ってAPIを連携させるためのキーなどを作成していきます。
Start building with the Notion API
- https://www.notion.so/my-integrationsにアクセス
- 基本情報を入力
- 「送信」をクリック
- 遷移したらトークンをコピーして控えておく
テーブルに連携
作成したIntegrationとテーブルを紐付けます。
- 先ほど作成したテーブルの右上の「Share」ボタンをクリック
- フォームをクリック
- モーダルが表示されるので先ほど作成したIntegrationを選択
- 「Invite」をクリック
これによって作成したIntegrationからテーブルに情報を取得する準備ができました!
GASでNotionのデータにアクセス
プロジェクトの作成
適当なスプレッドシートを作成し、「拡張機能」→「Apps Script」をクリックします。
定数の定義
今回の実装をする上で値が一定であるものを定数として最初に定義しておきます。
// API用トークン const integrationSecret = "secret_xxxxxxxxxxxxxxxxxxx"; // 書き込むスプレッドシートのID const spreadsheetId = "xxxxxxxxxxxxxxxxxxxxxxx"; // Notionのバージョン const notionVer = "2021-08-16"; // スプレッドシートのシート名とそれに対応するNotionのテーブルのID const object = { // スプレッドシートの名前 sheetName: " シート1", // NotionのテーブルのID dbId: "xxxxxxxxxx", } // 空値 const blankValue = "";
先ほどIntegration作成時にコピーしたトークンをペースト
- スプレッドシートのID
用意したスプレッドシートのIDを入力します。IDの調べ方は作成したシートのURLの下記該当箇所に書かれています。
https://docs.google.com/spreadsheets/d/xxxxxxxxxxxxxxxxxxxxxxxxxxxx/edit#gid=0
- Notionのバージョン
こちらは使用しているNotionのバージョンを記載します。理由はヘッダーにバージョンを指定しないと下記エラーが起こってしまいAPIリクエストが失敗するからです。
Notion-Version header should be defined, instead was `undefined`.
- スプレッドシートのシート名とそれに対応するNotionのテーブルのID
転記先のスプレッドシートの名前をsheetNameプロパティに記載します。また、NotionのテーブルIDはNotionのテーブルのページURLのxxxxxxxxxxの部分に書かれています。
https://www.notion.so/xxxxxxxxxx?v=・・・・・・
- 空値
共通の空値としてblankValueを定義しておきます。
Notionの情報を取得
いよいよNotionの情報を取得します。
まず今回の実装は
- Notionからデータを取得
- スプレッドシートに転記
のため、メソッドを二段階に分けるかと思います。よって前もって二段階に分ける想定で下記のようにgetNotionDatabase(Notionからデータを取得)とsetValuesToSheets(スプレッドシートに転記)を実行するnotionToSheetsメソッドを定義します。これが最終的に実行するスクリプトです。
function notionToSheets() { const res = getNotionDatabase(object); setValuesToSheets(res, object); }
getNotionDatabaseメソッドの中身を記述します。テーブルの中身を取得するための方法は公式に書かれていました。
Start building with the Notion API
公式を参考にメソッドを定義すると以下のようになります。
function getNotionDatabase(obj) { const headers = { Authorization: `Bearer ${integrationSecret}`, "Content-Type": "application/json", "Notion-Version": `${notionVer}`, }; const options = { headers: headers, method: "post", muteHttpExceptions: true, }; let res = UrlFetchApp.fetch(`https://api.notion.com/v1/databases/${obj.dbId}/query`, options); return JSON.parse(res); }
UrlFetchApp.fetchメソッドはGASのAPIアクセス時に使えるメソッドです。
Class UrlFetchApp | Apps Script | Google Developers
これでデータは取得はできたはずです。確認したい人はGASのデバッグ機能を使って確認してみましょう。
GASでスプレッドシートに転記
取得したデータをスプレッドシートに転記するsetValuesToSheetsメソッドを記述していきます。長いので先に書いてから上から説明していきます。
function setValuesToSheets(res, obj) { propertyArray = Object.keys(res.results[0].properties); const valueArrays = [...Array(res.results.length)].map((v, i) => { return propertyArray.map((prop) => { return translateNotionData(res.results[i].properties[prop]); }); }); // プロパティ配列を先頭に挿入 valueArrays.unshift(propertyArray); // 参考リンク[https://qiita.com/cazimayaa/items/224daebe536799e5a8a2#getrangerow-column-numrows-numcolumns] const spreadSheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(obj.sheetName); const sheetRange = { row: 1, column: 1, numRows: valueArrays.length, numColumns: propertyArray.length, }; const range = spreadSheet.getRange( sheetRange.row, sheetRange.column, sheetRange.numRows, sheetRange.numColumns ); range.setValues(valueArrays); }
まず一行目ですが、これは列の全てのカラムを取得しています。今回自分が作成したテーブルでいうと「Date」、「Tags」、「Name」を取得しています。
propertyArray = Object.keys(res.results[0].properties);
次の行は非常にわかりにくいです。mapメソッドが二重になっています。
const valueArrays = [...Array(res.results.length)].map((v, i) => { return propertyArray.map((prop) => { return translateNotionData(res.results[i].properties[prop]); }); });
まず、 [...Array(res.results.length)].map((v, i)
の部分ですが、res.resultsにはNotionのテーブルのレコードごとのデータが格納されています。その数だけ繰り返し処理を行い、全てのレコードを加工しています。次にpropertyArray.map((prop)
の部分ですが、後述した通りpropertyArrayはカラムとなります。そのカラムの数だけカラムを加工する繰り返し処理を行います。translateNotionData(res.results[i].properties[prop])の部分ですが、res.results[i].properties[prop]には各レコードの各カラムの値が入ります。今回の実装でいうと「Date」、「Tags」、「Name」の値が渡されています。
translateNotionDataを下記に定義します。
function translateNotionData(data) { if (data.type === "title") { return getTitleData(data); } else if (data.type === "multi_select") { return getMultiSelectData(data); } else if (data.type === "date") { return getDateData(data); } else { return blankValue; } }
NotionAPIのカラムの値にはdata
オブジェクトが渡されその中のtypeプロパティがそのカラムの属性を表しています。データの種類に合わせて加工しているということです。繰り返しますが今回は種類が「Date」、「Tags」、「Name」の3種類にしか対応していません。
function getTitleData(data) { return data.title.length ? data.title[0].plain_text.trim() : blankValue; } function getDateData(data) { if (data.date) { return data.date.end ? `${data.date.start}|${data.date.end}` : `${data.date.start}`; } else { return blankValue; } } function getMultiSelectData(data) { if (data.multi_select.length) { return data.multi_select .map((select) => { return select.name; }) .join(", "); } else { return blankValue; } }
setValuesToSheetsメソッドに戻ります。
以下の記載ではvalueArrays(テーブルの全てのレコード)にカラムの配列を先頭に挿入しています。
// プロパティ配列を先頭に挿入 valueArrays.unshift(propertyArray);
次にシートのどの部分に転記するかを定義します。これは記述してある参考リンクを参照いただけるとわかりやすいかと思います。
// 参考リンク[https://qiita.com/cazimayaa/items/224daebe536799e5a8a2#getrangerow-column-numrows-numcolumns] const spreadSheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(obj.sheetName); const sheetRange = { row: 1, column: 1, numRows: valueArrays.length, numColumns: propertyArray.length, };
sheetRangeでは一番左上の端からレコードの数(valueArrays.length) × カラムの数(propertyArray.length)の範囲で転記させます。
いよいよ最後に転記するメソッドです。
const range = spreadSheet.getRange( sheetRange.row, sheetRange.column, sheetRange.numRows, sheetRange.numColumns ); range.setValues(valueArrays);
定数rangeに転記する範囲を定義します。そしてrangeを使ってsetValuesメソッドを使用すると転機ができます。
最終コード
最終的にできたコードは下記になります。
// API用トークン const integrationSecret = "secret_xxxxxxxxxxxxxxxxxxx"; // 書き込むスプレッドシートのID const spreadsheetId = "xxxxxxxxxxxxxxxxxxxxxxx"; // Notionのバージョン const notionVer = "2021-08-16"; // スプレッドシートのシート名とそれに対応するNotionのテーブルのID const object = { // スプレッドシートの名前 sheetName: " シート1", // NotionのテーブルのID dbId: "xxxxxxxxxx", } // 空値 const blankValue = ""; function notionToSheets() { const res = getNotionDatabase(object); setValuesToSheets(res, object); } function getNotionDatabase(obj) { let data = obj.data; const headers = { Authorization: `Bearer ${integrationSecret}`, "Content-Type": "application/json", "Notion-Version": `${notionVer}`, }; const options = { headers: headers, method: "post", muteHttpExceptions: true, }; let res = UrlFetchApp.fetch(`https://api.notion.com/v1/databases/${obj.dbId}/query`, options); return JSON.parse(res); } function setValuesToSheets(res, obj) { propertyArray = Object.keys(res.results[0].properties); const valueArrays = [...Array(res.results.length)].map((v, i) => { return propertyArray.map((prop) => { return translateNotionData(res.results[i].properties[prop]); }); }); // プロパティ配列を先頭に挿入 valueArrays.unshift(propertyArray); // 参考リンク[https://qiita.com/cazimayaa/items/224daebe536799e5a8a2#getrangerow-column-numrows-numcolumns] const spreadSheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName(obj.sheetName); const sheetRange = { row: 1, column: 1, numRows: valueArrays.length, numColumns: propertyArray.length, }; const range = spreadSheet.getRange( sheetRange.row, sheetRange.column, sheetRange.numRows, sheetRange.numColumns ); range.setValues(valueArrays); } function translateNotionData(data) { if (data.type === "title") { return getTitleData(data); } else if (data.type === "multi_select") { return getMultiSelectData(data); } else if (data.type === "date") { return getDateData(data); } else { return blankValue; } } function getTitleData(data) { return data.title.length ? data.title[0].plain_text.trim() : blankValue; } function getDateData(data) { if (data.date) { return data.date.end ? `${data.date.start}|${data.date.end}` : `${data.date.start}`; } else { return blankValue; } } function getMultiSelectData(data) { if (data.multi_select.length) { return data.multi_select .map((select) => { return select.name; }) .join(", "); } else { return blankValue; } }
終わりに
NotionAPI、GAS共に初めて使用しましたがドキュメントは非常にわかりやすかったのですぐにAPI情報を取得することができました。
難点としてはやはりカラムのデータを加工する際にデータの種類に合わせて処理が必要なことですね。。これはどうやって対応すればいいのかわかりませんでした。よりいい方法がありましたら教えてください🙇♂️
以上、大ちゃんの駆け出し技術ブログでした!