💾

NotionのDBのページ数をgithub actionsで定期的に監視しNotionで可視化する

に公開

はじめに

筆者は日頃から個人でnotionを利用しています。
本記事は、notionに記載した知識の量を把握できるようにするために、ページ数の可視化に取り組んだ記録です。

完成イメージ
完成イメージ

前提として筆者のnotionの運用について記載します。
もともとScrapboxという、ページ同士をリンクを用いて関連付けるメモサービスを使っていたため、Notionでもそれに似た運用を目指しています。
浅い階層にいくつかのデータベース(以下:DB)を作成し、その要素としてページを追加していく運用をしています。

// データベースの構造
suzuhiki-memo
|- 記録DB - ページ
|- 語彙DB - ページ
|- 作品DB - ページ
︙

この構造のnotionプロジェクトを対象に、各DBのページ数をカウントして、チャート表示用のDBに書き込み、それをnotion上で可視化する方法について記載します。

作業

可視化を実現するためには大きく4つのステップがあります。

  1. NotionのAPIを利用できるようにする
  2. 対象とするDBからDBの要素数を取得して、可視化用のDBに保存する
  3. Github Actionsを用いて定期実行できるようにする
  4. Notionのチャート機能を利用して可視化

以下でそれぞれについて説明します。

1. NotionのAPIを利用できるようにする

トークンを取得してNotion APIにアクセスできるようにします。

まず、プロジェクトを作る必要があります。
今回は勉強も兼ねてGo言語を利用しました。

公式サイトからダウンロードします。
https://go.dev/doc/install

筆者はMacのhomebrewからインストールしました。
https://zenn.dev/y16ra/articles/251c3770365689

notion-db-counterというリポジトリをgithubに作成し、git cloneでローカルに持ってきます。

cloneしたディレクトに移動しgo mod initからgo言語のプロジェクトを作成します。

go mod init notion-db-counter

環境変数を定義する.envファイルの雛形(.env.template)を作成します。

.env.template
# please run source .env
export NOTION_TOKEN=<YOUR_NOTION_TOKEN>
export WRITE_DB_ID=<YOUR_WRITE_DB_ID>

このファイルをコピーして、名前を.envにします。
githubに環境変数がアップロードされないように、.gitignore.envを追加します。

Notion Tokenの取得

https://www.notion.com/ja/help/create-integrations-with-the-notion-api#内部インテグレーションの作成
上記記事にしたがって、任意の名前のインテグレーションを作成します。

内部インテグレーションシークレットの項目にあるトークンを、.env<YOUR_NOTION_TOKEN>の部分に貼り付けます。

権限は以下のように設定します。
notionの権限設定

忘れずにnotionワークスペースのページにこのインテグレーションを接続しておきます。
対象となる全てのDBより階層が上(親)であるページで右上のボタンから接続を選択し、先程作成したインテグレーションを登録します。
インテグレーションの接続

2. 対象とするDBからDBの要素数を取得して、可視化用のDBに保存する

可視化用DBを環境変数に登録する

先ほどインテグレーションを設定したページより下の階層に可視化用の可視化DBを作成します。
DBの名前は何でもかまいません。

目標とするDB構造は以下の図のようになります。
これに従って、title、date、label、lengthのカラムを持つように可視化DBを編集して下さい。
目標とするDB構造
目標とするDB構造

可視化DBのデータベースIDをURLから取得し、.env<YOUR_WRITE_DB_ID>に登録して下さい。
データベースIDはURLに以下のように含まれています。

https://www.notion.so/<データベースID>?...

可視化対象のDBをcsvファイルとして保存する

監視対象のDBをcsvファイルの形で記載します。
db_ids.csvを以下のように作成してください。

db_name, db_id
記録DB,<db id>
語彙DB,<db id>
楽曲DB,<db id>
作品DB,<db id>

db_nameは可視化の際に、ラベル名として利用されます。
db_idは先程の可視化DBと同じように取得して貼り付けてください。

なお、ディレクトリ構造は以下の図の通りとなります。
ディレクトリ構造

APIを利用するgolangのコードを用意する

以下のようなコードを作成しました。
詳細には説明しません。

main.go
package main

import (
	"bytes"
	"encoding/csv"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"
)

func main() {
	db_ids := map[string]string{}
	db_id_data, err := readCSV("db_ids.csv")
	if err != nil {
		fmt.Println("Error reading db_ids.csv:", err)
		os.Exit(1)
	}
	for i, record := range db_id_data {
		if i == 0 {
			// ヘッダーをスキップ
			continue
		}

		// record[0] is db_name, record[1] is db_id
		db_ids[record[0]] = record[1]
	}
	fmt.Println("Database IDs loaded")
	fmt.Println(db_ids)

	notion_token := os.Getenv("NOTION_TOKEN")
	if notion_token == "" {
		fmt.Println("Error: NOTION_TOKEN environment variable is not set")
		os.Exit(1)
	}
	fmt.Println("Notion token loaded")

	db_name_data_counts := map[string]int{}
	for db_name, db_id := range db_ids {
		start_cursor := ""
		data_counts := 0
		for true {
			// リクエストの作成
			url := fmt.Sprintf("https://api.notion.com/v1/databases/%s/query", db_id)
			var body []byte
			if start_cursor != "" {
				body = []byte(fmt.Sprintf(`{"start_cursor": "%s"}`, start_cursor))
			}
			req, _ := http.NewRequest("POST", url, bytes.NewBuffer(body))
			req.Header.Set("Authorization", "Bearer "+notion_token)
			req.Header.Set("Notion-Version", "2022-06-28")
			req.Header.Set("Content-Type", "application/json")

			client := &http.Client{}
			resp, err := client.Do(req)
			if err != nil {
				fmt.Printf("Error sending request for %s: %v\n", db_name, err)
				break
			}
			defer resp.Body.Close()

			// レスポンスの読み取り
			response, err := io.ReadAll(resp.Body)
			if err != nil {
				fmt.Printf("Error reading response for %s: %v\n", db_name, err)
				os.Exit(1)
			}

			// レスポンスのパース
			var responseData map[string]interface{}
			err = json.Unmarshal(response, &responseData)
			if err != nil {
				fmt.Printf("Error parsing response for %s: %v\n", db_name, err)
				os.Exit(1)
			}

			// エラーレスポンスのチェック
			if status, ok := responseData["status"].(float64); ok && status != 200 {
				message, _ := responseData["message"].(string)
				fmt.Printf("Error: API request failed for %s\nStatus: %v\nMessage: %s\n", db_name, status, message)
				os.Exit(1)
			}

			// resultsの長さをdata_countsに加算
			if results, ok := responseData["results"].([]interface{}); ok {
				data_counts += len(results)
			}

			// start_cursorの処理
			if has_more, ok := responseData["has_more"].(bool); ok && has_more {
				start_cursor = responseData["next_cursor"].(string)
				fmt.Println("DB name:", db_name, "Next cursor:", start_cursor)
			} else {
				db_name_data_counts[db_name] = data_counts
				fmt.Println("DB name:", db_name, "No next cursor break")
				break
			}
		}
	}

	fmt.Println("DB data counts fetched:", db_name_data_counts)

	// データをnotionページに書き込み
	write_db_id := os.Getenv("WRITE_DB_ID")
	if write_db_id == "" {
		fmt.Println("Error: WRITE_DB_ID environment variable is not set")
		os.Exit(1)
	}

	fmt.Println("Writing to Notion...")

	for db_name, data_counts := range db_name_data_counts {
		// 現在の日付を取得
		currentDate := time.Now().Format("2006-01-02")

		// リクエストボディの作成
		body := fmt.Sprintf(`{
			"parent": { "database_id": "%s" },
			"properties": {
				"title": {
					"title": [
						{
							"text": {
								"content": ""
							}
						}
					]
				},
				"label": {
					"rich_text": [
						{
							"text": {
								"content": "%s"
							}
						}
					]
				},
				"length": { "number": %d },
				"date": {"date": {"start": "%s"}}
			}
		}`, write_db_id, db_name, data_counts, currentDate)

		// リクエストの作成
		url := "https://api.notion.com/v1/pages"
		req, _ := http.NewRequest("POST", url, bytes.NewBuffer([]byte(body)))
		req.Header.Set("Authorization", "Bearer "+notion_token)
		req.Header.Set("Notion-Version", "2022-06-28")
		req.Header.Set("Content-Type", "application/json")

		// リクエストの送信
		client := &http.Client{}
		resp, err := client.Do(req)
		if err != nil {
			fmt.Printf("Error sending request for %s: %v\n", db_name, err)
			os.Exit(1)
		}
		defer resp.Body.Close()

		// レスポンスの読み取り
		response, err := io.ReadAll(resp.Body)
		if err != nil {
			fmt.Printf("Error reading response for %s: %v\n", db_name, err)
			os.Exit(1)
		}

		// レスポンスのパース
		var responseData map[string]interface{}
		err = json.Unmarshal(response, &responseData)
		if err != nil {
			fmt.Printf("Error parsing response for %s: %v\n", db_name, err)
			os.Exit(1)
		}

		// エラーレスポンスのチェック
		if status, ok := responseData["status"].(float64); ok && status != 200 {
			message, _ := responseData["message"].(string)
			fmt.Printf("Error: API request failed for %s\nStatus: %v\nMessage: %s\n", db_name, status, message)
			os.Exit(1)
		}

		fmt.Printf("Successfully created page for %s\n", db_name)
	}
}

func readCSV(filePath string) ([][]string, error) {
	file, err := os.Open(filePath)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	reader := csv.NewReader(file)
	records, err := reader.ReadAll()
	if err != nil {
		return nil, err
	}

	return records, nil
}

以下のコマンドをterminalで実行することでテストできます。
可視化用DBに要素が追加されれば成功です。

source .env #環境変数の適用
go run main.go

実行が確認できればcommitしてpushしてください。

3. Github Actionsを用いて定期実行できるようにする

2で作ったコードを定期実行できるようにします。
今回はGithub Actionsを利用します。

notion-db-counter/.github/workflows/notion.ymlを作成し、以下のように記載してください。

notion.yml
name: Run Notion DB counts

on:
  schedule:
    - cron: '0 20 * * *'    # 定期実行の設定(毎朝5:00)
  workflow_dispatch:        # 手動実行も可能にする

jobs:
  run-go:
    runs-on: ubuntu-latest
    environment: actions

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up Go
        uses: actions/setup-go@v4
        with:
          go-version: '1.24'

      - name: Run Go script
        env:
          NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
          WRITE_DB_ID: ${{ secrets.WRITE_DB_ID }}
        run: go run main.go

環境変数のgithubへの登録

.envに記載した環境変数はローカルでのみ利用しているため、github actionsには別で設定する必要があります。

以下の記事を参考に、Environment Secretを作成してください。
https://qiita.com/ak2ie/items/4fbcdf74e7760c49c1af

githubの環境変数の設定画面
githubの環境変数の設定画面

この状態でgithubのActionsタブより手動で実行できます。
実行中にエラーが出ず、Actionsの画面に✅が表示され、notionの可視化DBにデータが追加されていれば成功です。

githubActionsの画面

4. Notionのチャート機能を利用して可視化

NotionのDBの表示形式の一つである、チャートを利用して可視化します。
以下の画像のように新しいチャートビューを作成してください。
チャートを作成

以下のように設定すると折れ線グラフとしてDB内のページ数の変化を記録できます。
チャートの設定
チャートの設定
チャート設定の「その他のスタイルオプション」
チャート設定の「その他のスタイルオプション」

おわりに

NotionのDBの要素数をNotionのチャート機能を用いて可視化する試みについて紹介しました。
今回はNotion APIのデータベース関連のものを利用しましたが、検索関連のAPI (api.notion.com/v1/search) などを利用するとより柔軟な可視化が可能だと思います。

https://developers.notion.com/reference/intro

みなさんのお役に立てれば幸いです。

GitHubで編集を提案

Discussion