MENU

GAS×ChatGPT×Notion連携|議事録を自動整理してDBに保存

GAS×ChatGPT×Notion連携|議事録を自動整理してDBに保存

会議メモをNotionに整理する作業は地味に時間がかかる。形式がバラバラだと後から検索もしにくい。GAS×ChatGPT×Notion APIを連携させれば、生の会議メモを貼り付けるだけで、決定事項とTODOが抽出された議事録がNotionデータベースに自動登録される。

ここでは、その仕組みをゼロから構築する手順を説明する。


目次

この記事でできること

完成すると、以下の流れが自動で走る。


1. スプレッドシートに会議メモを貼り付け
        ↓
2. ChatGPTが議事録を整形(決定事項・TODO抽出)
        ↓
3. Notion APIでデータベースに自動登録
        ↓
4. 整形された議事録がNotionで閲覧可能

活用例:

  • 定例会議の議事録を自動整理
  • 決定事項とTODOを構造化して保存
  • チームのナレッジベースを自動構築

事前準備

必要なもの

項目 詳細 取得方法
Googleアカウント スプレッドシート、GAS用 お持ちのもの
OpenAI APIキー ChatGPT連携用 platform.openai.com
Notion APIキー Notion連携用 後述
NotionデータベースID 保存先DB 後述

Notion API設定

1. インテグレーションを作成

  • Notion Developers にアクセス
  • 新しいインテグレーションをクリック
  • 名前を入力(例: 議事録Bot)
  • ワークスペースを選択
  • 送信をクリック
  • 表示されたInternal Integration Tokenをコピー

[画像: Notion インテグレーション作成画面]

2. データベースを作成・共有

  • Notionで新規データベースを作成
  • 以下のプロパティを設定する:
プロパティ名 種類 用途
会議名 タイトル 会議の名前
日付 日付 会議日
参加者 テキスト 参加者リスト
決定事項 テキスト 決定事項
TODO テキスト アクションアイテム
ステータス セレクト 未処理/処理済
  • データベースページ右上の三点メニュー → コネクトを追加
  • 作成したインテグレーションを選択

[画像: Notionデータベース構成]

3. データベースIDを取得

データベースのURLから取得する。


https://www.notion.so/ワークスペース名/[ここがデータベースID]?v=xxx

32文字の英数字部分がデータベースIDにあたる。


実装手順

STEP 1: スプレッドシートを準備

A列(会議名) B列(日付) C列(参加者) D列(議事メモ) E列(処理済)
定例MTG 2026-02-01 山田,田中,鈴木 (生の議事メモ) FALSE

STEP 2: GASコードを設置


// ===================================================
// 設定
// ===================================================
const CONFIG = {
  OPENAI_API_KEY: PropertiesService.getScriptProperties().getProperty('OPENAI_API_KEY'),
  NOTION_API_KEY: PropertiesService.getScriptProperties().getProperty('NOTION_API_KEY'),
  NOTION_DATABASE_ID: PropertiesService.getScriptProperties().getProperty('NOTION_DATABASE_ID'),
  SHEET_NAME: '議事録',
  MODEL: 'gpt-4o-mini'
};

// ===================================================
// メイン処理: 議事録を整理してNotionに登録
// ===================================================
function processMinutesToNotion() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet()
    .getSheetByName(CONFIG.SHEET_NAME);
  const data = sheet.getDataRange().getValues();

  for (let i = 1; i < data.length; i++) {
    // 未処理の行のみ対象
    if (data[i][4] === true || data[i][4] === 'TRUE') continue;
    if (!data[i][3]) continue; // 議事メモが空

    const meeting = {
      name: data[i][0],
      date: data[i][1],
      participants: data[i][2],
      rawMinutes: data[i][3]
    };

    // 1. ChatGPTで議事録を整理
    const structured = structureMinutes(meeting.rawMinutes);

    // 2. Notionに登録
    createNotionPage(meeting, structured);

    // 3. 処理済みフラグを設定
    sheet.getRange(i + 1, 5).setValue(true);

    console.log(`${meeting.name} の議事録をNotionに登録しました`);
  }
}

// ===================================================
// ChatGPTで議事録を構造化
// ===================================================
function structureMinutes(rawMinutes) {
  const prompt = `以下の会議メモを整理してください。

【出力形式】
## 議題
(箇条書きで議題を列挙)

## 決定事項
(決定された内容を箇条書き)

## TODO
(誰が・何を・いつまでに、の形式で箇条書き)

## 次回への申し送り
(次回会議で確認すべき事項)

---
会議メモ:
${rawMinutes}`;

  return callChatGPT(prompt);
}

// ===================================================
// ChatGPT API呼び出し
// ===================================================
function callChatGPT(prompt) {
  const url = 'https://api.openai.com/v1/chat/completions';

  const payload = {
    model: CONFIG.MODEL,
    messages: [
      { role: 'system', content: 'あなたは議事録を整理するアシスタントです。' },
      { role: 'user', content: prompt }
    ],
    max_tokens: 1500,
    temperature: 0.3
  };

  const options = {
    method: 'post',
    contentType: 'application/json',
    headers: { 'Authorization': 'Bearer ' + CONFIG.OPENAI_API_KEY },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  };

  const response = UrlFetchApp.fetch(url, options);
  const json = JSON.parse(response.getContentText());

  if (json.error) throw new Error(json.error.message);

  return json.choices[0].message.content;
}

// ===================================================
// Notionにページを作成
// ===================================================
function createNotionPage(meeting, structuredMinutes) {
  const url = 'https://api.notion.com/v1/pages';

  // 決定事項とTODOを抽出
  const decisions = extractSection(structuredMinutes, '決定事項');
  const todos = extractSection(structuredMinutes, 'TODO');

  const payload = {
    parent: { database_id: CONFIG.NOTION_DATABASE_ID },
    properties: {
      '会議名': {
        title: [{ text: { content: meeting.name } }]
      },
      '日付': {
        date: { start: formatDate(meeting.date) }
      },
      '参加者': {
        rich_text: [{ text: { content: meeting.participants } }]
      },
      '決定事項': {
        rich_text: [{ text: { content: decisions } }]
      },
      'TODO': {
        rich_text: [{ text: { content: todos } }]
      },
      'ステータス': {
        select: { name: '未処理' }
      }
    },
    children: [
      {
        object: 'block',
        type: 'heading_2',
        heading_2: {
          rich_text: [{ text: { content: '議事録' } }]
        }
      },
      {
        object: 'block',
        type: 'paragraph',
        paragraph: {
          rich_text: [{ text: { content: structuredMinutes } }]
        }
      }
    ]
  };

  const options = {
    method: 'post',
    contentType: 'application/json',
    headers: {
      'Authorization': 'Bearer ' + CONFIG.NOTION_API_KEY,
      'Notion-Version': '2022-06-28'
    },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  };

  const response = UrlFetchApp.fetch(url, options);
  const json = JSON.parse(response.getContentText());

  if (json.object === 'error') {
    throw new Error(json.message);
  }

  return json;
}

// ===================================================
// ユーティリティ関数
// ===================================================
function extractSection(text, sectionName) {
  const regex = new RegExp(`## ${sectionName}\\n([\\s\\S]*?)(?=## |$)`);
  const match = text.match(regex);
  return match ? match[1].trim() : '';
}

function formatDate(date) {
  if (date instanceof Date) {
    return Utilities.formatDate(date, 'Asia/Tokyo', 'yyyy-MM-dd');
  }
  return date;
}

STEP 3: スクリプトプロパティを設定

以下の3つを登録する:

  • OPENAI_API_KEY
  • NOTION_API_KEY
  • NOTION_DATABASE_ID

STEP 4: 実行テスト

  • スプレッドシートにテストデータを入力
  • processMinutesToNotion 関数を実行
  • Notionデータベースにページが作成されていれば成功

[画像: Notionに登録された議事録]


カスタマイズ例

TODO担当者を自動抽出


// TODOから担当者を抽出してプロパティに設定
const todoWithAssignee = todos.match(/(\w+)さん/g);
if (todoWithAssignee) {
  payload.properties['担当者'] = {
    multi_select: todoWithAssignee.map(name => ({ name: name }))
  };
}

会議種別で自動分類


if (meeting.name.includes('定例')) {
  payload.properties['種別'] = { select: { name: '定例会議' } };
} else if (meeting.name.includes('企画')) {
  payload.properties['種別'] = { select: { name: '企画会議' } };
}

トラブルシューティング

エラー 原因 対処法
validation_error プロパティ名の不一致 Notionのプロパティ名を確認
unauthorized API権限エラー インテグレーションの共有設定を確認
object_not_found DB IDが無効 データベースIDを再取得

まとめ

GAS×ChatGPT×Notion連携で、議事録の整理と保存が自動化された。会議メモをスプレッドシートに貼り付けるだけで、構造化された議事録がNotionに蓄積されていく。トリガーを設定すれば、定期的なバッチ処理も回せる。

関連記事:


最終更新: 2026-02-02

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

この記事を書いた人

コメント

コメントする

目次