Notion API × Pythonでダッシュボードを自動更新|フリーランス向け実装ガイド

毎朝Notionを開いて、昨日の売上・タスク進捗・稼働時間を手入力していませんか?そのルーティン、Pythonスクリプト1本で丸ごと自動化できます。

フリーランスや個人事業主にとって、Notionは最高の情報管理ツールです。でも「データを見るためのデータ入力」に毎日時間を取られているとしたら、それは本末転倒ですよね。この記事では、Notion APIとPythonを組み合わせて、売上集計・タスク完了率・稼働ログを自動でダッシュボードに反映させる仕組みを、実装コードつきで丁寧に解説します。

目次

フェーズ1:なぜ手動更新は限界なのか——課題の整理

フリーランスが陥りがちな「Notion手動更新地獄」の実態

Notionでタスク管理・売上管理をしているフリーランスの方にありがちなのが、毎朝の「更新タイム」です。昨日完了したタスクにチェックを入れて、請求書の金額を売上DBに転記して、稼働時間を集計して……。1回あたり5〜15分の作業でも、月に換算すると2〜5時間が「入力作業」に消えていきます。

しかもこの作業、ミスが起きやすい。数字の転記ミスや更新忘れが積み重なると、ダッシュボードの数字が信頼できなくなり、「結局Excelのほうが楽かも」という気持ちになってしまいます。せっかく整えたNotionのワークスペースが、形骸化してしまうのはもったいないですよね。

自動化すべき3つのユースケース

Notion APIで自動化の効果が大きいのは、次の3つのユースケースです。

  • 売上ファネル集計:案件DBから月次売上・平均単価・成約件数を自動集計してダッシュボードに反映する
  • タスク完了率の可視化:タスクDBのステータスを集計し、今月の完了率をリアルタイムで確認できるようにする
  • AI稼働ログの記録:外部ツールやAPIの利用ログをNotion DBに自動書き込みし、稼働時間・コストを一元管理する

これらはいずれも「既存DBのデータを読み取って集計し、別のページに書き込む」という同じ構造を持っています。つまり、1つの仕組みを作れば、複数のユースケースに応用できるのです。

Notion APIが解決できる範囲と限界

Notion API(アプリケーション・プログラミング・インターフェース:外部プログラムからNotionを操作するための窓口)は非常に強力ですが、いくつかの制限も理解しておく必要があります。

まずレート制限(Rate Limit:APIへのリクエスト回数の上限)について。Notion APIは1秒あたり3リクエストまでという制限があります。大量のページを一度に処理する場合は、適切なスリープ処理を挟む必要があります。また、Notion APIはポーリング型(一定間隔で問い合わせる方式)のため、「データが変わった瞬間に検知する」というリアルタイム性はありません。毎朝1回の定期実行という使い方が現実的で、それで十分なケースがほとんどです。

参考として、Notion APIをPythonで活用する際の基礎的な解説として PythonでNotion APIを実行する方法 | TEMPブログ も合わせてご確認ください。

フェーズ2:Notion API連携の設計——インテグレーション作成とDB構造

Notion Integrationの作成手順

まず、NotionとPythonスクリプトをつなぐための「インテグレーション(Integration:外部サービスとの連携設定)」を作成します。手順は以下のとおりです。

  1. https://www.notion.so/my-integrations にアクセスし、「新しいインテグレーション」をクリックします。
  2. インテグレーション名(例:「Dashboard Updater」)とワークスペースを選択して作成します。
  3. 作成後に表示される「Internal Integration Token(内部インテグレーショントークン)」をコピーします。これがAPIキーになります。このキーは絶対に公開しないでください。
  4. 次に、連携したいNotionデータベースを開き、右上の「…」メニューから「接続先」→作成したインテグレーションを選択して接続を許可します。

この「接続許可」の手順を忘れると、スクリプト実行時に「Object not found」エラーが出ます。連携するDBすべてに対して許可設定が必要な点に注意してください。

ダッシュボード用DBの設計指針

自動更新の仕組みを作る前に、Notion側のDB設計を整理しておくことが重要です。スクリプトが書き込む「ダッシュボードページ」には、以下のようなプロパティを用意しておくとスムーズです。

プロパティ名 用途
月間タスク数 数値(Number) 当月のタスク総数
完了タスク数 数値(Number) ステータスが「完了」のタスク数
タスク完了率(%) 数値(Number) 完了率(パーセント)
月間売上合計 数値(Number) 当月の売上合計金額
案件数 数値(Number) 当月の成約件数
平均案件単価 数値(Number) 1案件あたりの平均金額
最終更新日時 日付(Date) スクリプトが最後に実行された日時

プロパティ名はスクリプト側のコードと完全一致させる必要があります。スペースや全角・半角の違いでエラーになるため、コピー&ペーストで合わせることをおすすめします。

Pythonライブラリの選定:requests直叩き vs notion-client SDK

Notion APIをPythonから呼び出す方法は大きく2つあります。

requestsライブラリで直接叩く方法は、追加インストールが最小限で済みますが、ヘッダーの設定やエラーハンドリングを自分で書く必要があります。シンプルな1回限りのスクリプトには向いていますが、複数DBを扱う本格的な自動化には向きません。

notion-client SDK(公式Pythonクライアント)は、Notionが公式に提供するPythonライブラリです。認証・エラーハンドリング・ページネーション処理が整理されており、コードの可読性が高くなります。定期実行スクリプトとして長期運用するなら、こちらを強く推奨します。

詳細な実装例については NotionのデータをPythonで操る技術:API連携で実現する情報管理の自動化へ – uepon日々の備忘録 も参考になります。

フェーズ3:Pythonスクリプトの実装——取得・集計・書き込みの全コード

いよいよ実装です。ここからは手を動かしながら読み進めてください。コードはすべてコピーして使えます。

フェーズ3:実装手順

Step 1:環境構築と.envファイルの設定

必要なライブラリをインストールし、APIキーと各種IDを環境変数で管理します。

pip install notion-client python-dotenv
# .env
NOTION_TOKEN=secret_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
TASK_DB_ID=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
SALES_DB_ID=yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
DASHBOARD_PAGE_ID=zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz

各IDの確認方法:Notionでデータベースを開いてURLを見ると、notion.so/xxxxxxxx?v=... のような形式になっています。?v=より前の32文字がデータベースIDです。ダッシュボードページのIDも同様にURLから取得できます。.envファイルは.gitignoreに追加して、Gitリポジトリに含めないよう必ず注意してください。

Step 2:Notion DBからタスク・売上データをクエリ取得し月次集計する

フィルタとページネーションを使い、当月分のデータを全件取得して集計します。notion-clientのquery()はデフォルト100件上限のため、has_moreフラグでループ処理します。

import os
import logging
from datetime import datetime, timezone
from dotenv import load_dotenv
from notion_client import Client
from notion_client.errors import APIResponseError

load_dotenv()
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger(__name__)

notion = Client(auth=os.environ["NOTION_TOKEN"])

def get_current_month_range() -> tuple[str, str]:
    now = datetime.now(timezone.utc)
    start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
    if now.month == 12:
        end = now.replace(year=now.year + 1, month=1, day=1, hour=0, minute=0, second=0, microsecond=0)
    else:
        end = now.replace(month=now.month + 1, day=1, hour=0, minute=0, second=0, microsecond=0)
    return start.isoformat(), end.isoformat()

def query_all_pages(database_id: str, filter_obj: dict) -> list[dict]:
    results = []
    cursor = None
    while True:
        try:
            params = {"database_id": database_id, "filter": filter_obj, "page_size": 100}
            if cursor:
                params["start_cursor"] = cursor
            response = notion.databases.query(**params)
            results.extend(response["results"])
            if not response.get("has_more"):
                break
            cursor = response.get("next_cursor")
        except APIResponseError as e:
            logger.error(f"Notion API error during query: {e}")
            break
    logger.info(f"Fetched {len(results)} pages from DB: {database_id}")
    return results

def aggregate_monthly_data() -> dict:
    start_date, end_date = get_current_month_range()

    task_filter = {
        "and": [
            {"property": "作成日時", "date": {"on_or_after": start_date}},
            {"property": "作成日時", "date": {"before": end_date}},
        ]
    }
    task_pages = query_all_pages(os.environ["TASK_DB_ID"], task_filter)

    total_tasks = len(task_pages)
    completed_tasks = sum(
        1 for p in task_pages
        if p.get("properties", {}).get("ステータス", {}).get("status", {}).get("name") == "完了"
    )

    sales_filter = {
        "and": [
            {"property": "請求日", "date": {"on_or_after": start_date}},
            {"property": "請求日", "date": {"before": end_date}},
        ]
    }
    sales_pages = query_all_pages(os.environ["SALES_DB_ID"], sales_filter)

    total_revenue = sum(
        p.get("properties", {}).get("金額", {}).get("number") or 0
        for p in sales_pages
    )
    deal_count = len(sales_pages)
    avg_deal = round(total_revenue / deal_count, 2) if deal_count > 0 else 0.0

    summary = {
        "total_tasks": total_tasks,
        "completed_tasks": completed_tasks,
        "completion_rate": round(completed_tasks / total_tasks * 100, 1) if total_tasks > 0 else 0.0,
        "total_revenue": total_revenue,
        "deal_count": deal_count,
        "avg_deal_value": avg_deal,
    }
    logger.info(f"Aggregation result: {summary}")
    return summary

Step 3:集計結果をダッシュボードページに自動書き込みする

patchリクエスト(pages.update)でダッシュボードページの数値プロパティを上書きします。プロパティ名はNotion側の設定と完全一致させる必要があります。エラー時はログに詳細を残してスクリプトを継続します。

def update_dashboard(summary: dict) -> None:
    page_id = os.environ["DASHBOARD_PAGE_ID"]
    properties = {
        "月間タスク数": {"number": summary["total_tasks"]},
        "完了タスク数": {"number": summary["completed_tasks"]},
        "タスク完了率(%)": {"number": summary["completion_rate"]},
        "月間売上合計": {"number": summary["total_revenue"]},
        "案件数": {"number": summary["deal_count"]},
        "平均案件単価": {"number": summary["avg_deal_value"]},
        "最終更新日時": {"date": {"start": datetime.now(timezone.utc).isoformat()}},
    }
    try:
        notion.pages.update(page_id=page_id, properties=properties)
        logger.info(f"Dashboard updated successfully: page_id={page_id}")
    except APIResponseError as e:
        logger.error(f"Failed to update dashboard: {e}")

def main():
    logger.info("Starting dashboard update script...")
    summary = aggregate_monthly_data()
    update_dashboard(summary)
    logger.info("Dashboard update complete.")

if __name__ == "__main__":
    main()

コード全体の流れをまとめると、①.envから認証情報を読み込み、②当月の日付範囲を計算し、③タスクDBと売上DBをクエリ取得(100件超はページネーションで対応)、④集計して辞書に格納、⑤ダッシュボードページをpatchで更新、という5ステップです。

DBのプロパティ名に日本語を使っている場合も問題なく動作しますが、Notionの管理画面で確認したプロパティ名と1文字も違わず一致させることが重要です。「タスク完了率(%)」のような括弧・記号も含めて完全一致が必要です。

Notionデータベースへのページ作成・更新の詳細については PythonによるNotionデータベースのページを自動的に作成 – Zenn も参考にしてください。

フェーズ4:定期実行と応用拡張——cronで毎朝自動更新を完成させる

動作確認の手順

定期実行を設定する前に、まずローカル環境でスクリプトが正しく動くことを確認しましょう。以下のチェックポイントを順番に確認してください。

  1. インテグレーションの接続許可:タスクDB・売上DB・ダッシュボードページの3つすべてにインテグレーションが接続されているか確認します。
  2. 環境変数の確認:python -c "import os; from dotenv import load_dotenv; load_dotenv(); print(os.environ.get('NOTION_TOKEN', 'NOT FOUND'))" を実行してトークンが読み込まれているか確認します。
  3. スクリプトの実行:python dashboard_updater.py を実行し、ログに「Dashboard update complete.」が表示されることを確認します。
  4. Notionページの確認:ダッシュボードページを開き、数値プロパティが更新されていることを目視確認します。

エラーが出た場合は、ログのAPIResponseErrorメッセージを確認してください。Could not find databaseはDB IDの誤りまたは接続許可漏れ、Validation errorはプロパティ名の不一致が原因であることがほとんどです。

cronで毎朝8時に自動実行する(Mac/Linux)

cron(クロン:Unixシステムのジョブスケジューラ)を使って、毎朝8時に自動実行する設定をします。ターミナルで crontab -e を実行して、以下の行を追加してください。

# 毎朝8時にダッシュボードを自動更新(パスは実際の環境に合わせて変更)
0 8 * * * /usr/bin/python3 /Users/yourname/projects/notion-updater/dashboard_updater.py >> /Users/yourname/projects/notion-updater/cron.log 2>&1

ポイントは3つです。①Pythonのフルパスを指定する(which python3で確認できます)、②スクリプトのフルパスを指定する、③ログファイルにリダイレクトしておくことで実行結果を後から確認できるようにする、です。

macOSの場合、cronがフルディスクアクセスを持っていないと失敗することがあります。「システム設定」→「プライバシーとセキュリティ」→「フルディスクアクセス」にcronを追加しておきましょう。

Windowsでのタスクスケジューラ設定

Windowsの場合はタスクスケジューラを使います。「タスクスケジューラ」を開き、「基本タスクの作成」から以下のように設定します。

  • トリガー:毎日、開始時刻を8:00に設定
  • 操作:プログラムの開始 → python.exeのフルパスを指定
  • 引数の追加:C:\projects\notion-updater\dashboard_updater.py(スクリプトのフルパス)
  • 開始(オプション):スクリプトが存在するフォルダのパス

応用アイデア:さらに便利にする3つの拡張

基本の仕組みが動いたら、以下の応用拡張を試してみてください。

① Slack通知との連携:スクリプトの最後にSlack Incoming Webhookへのリクエストを追加すると、更新完了と同時に集計サマリーをSlackに通知できます。「売上合計:○○円、完了率:○○%」といったメッセージを朝一番に受け取れるので、Notionを開かなくても状況把握が可能になります。

② Google Sheetsからのデータ取り込み:gspreadライブラリを使ってGoogle Sheetsのデータを取得し、Notionに書き込む形にすると、クライアントから共有されたスプレッドシートの数値を自動でNotionに集約できます。請求管理をGoogleスプレッドシートで行っているフリーランスの方に特に有効です。

③ 複数DBの一括更新:今回の構成を複数のプロジェクトDBに対応させるには、DB IDのリストをループで処理する形に拡張します。プロジェクトごとに別のダッシュボードページを持っている場合も、設定ファイル(YAMLやJSON)でDB IDとページIDのペアを管理することで、コードを変えずに対応できます。

ハナの所見

ハナがこの自動化の仕組みを実際に試してみて感じたのは、「技術的なハードルより、設計の整理が9割」ということです。コード自体はそれほど難しくありません。むしろ、Notionのプロパティ名を統一する、DBの構造を事前に決める、という「設計フェーズ」をきちんとやっておくかどうかで、実装のスムーズさが大きく変わります。

フリーランスの方が最初につまずくのは、「どのDBを自動化すべきか」の優先順位づけです。ハナのおすすめは、まず売上DBだけを対象に動かしてみること。タスク数・完了率・売上の3指標を一度に自動化しようとすると、デバッグが複雑になります。1つのDBで動作確認が取れてから、対象を増やしていくアプローチが確実です。

また、このスクリプトを長期運用するうえで重要なのが、Notionのプロパティ名を変えないこと。チームで使っているワークスペースでは誰かがプロパティ名を変更してしまうことがあります。スクリプトが突然動かなくなったときは、まずプロパティ名の確認から始めましょう。

このような自動化スクリプトを書けるようになると、次のステップとして「取得したデータをグラフ化する」「異常値を検知してアラートを出す」といった応用が広がります。APIで取得したデータの集計・可視化スキルをより深く学びたい方には、体系的なPython学習リソースが役立ちます。

📚 Pythonによるビジネスデータサイエンス 第1巻 データ分析

APIで取得したデータを集計・可視化する際のPython実践スキルを体系的に学べるため、ダッシュボード自動化の次のステップとして最適です。

この記事はハナ編集部が執筆しました。

>

なお、Pythonを使った自動化スクリプトの実装パターンについては、note自動投稿をPlaywright×Pythonで実現する記事も参考になります。

>

PR・広告
🛠 この記事の内容、プロに頼むこともできます
「自分で作る時間がない」ならスキルマーケットのココナラで。業務自動化・スクリプト開発の出品者が多数います。

ココナラで探す →

Pythonを使った自動化の実装パターンについては、note自動投稿をPlaywright×Pythonで実現する事例も参考になります。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

はじめまして、「白米元気」と申します。

ノースキルで副業をスタートし、2ヶ月で月10万円を達成。
その後も毎日ChatGPTとにらめっこしながら、
「どうやったら仕組みで稼げるのか?」を考え続けてきました。

そんな中出会ったのが「LLM無職」です。
AIと仕組みを作り、AIに仕事をさせる。
副業や働き方そのものを実験していく——そんな挑戦をしています。

このブログでは、わたしのLLM無職への道のりの途中で
AIを活用した具体的な方法や工夫、日々の実践内容を紹介。
ときどき家族の話もまじえながら、
読んでくれた方が「なんかおもしろそう!」と思えるような、
リアルで実験的な情報をお届けしていきます。

コメント

コメントする

目次