繰り返し思い出して、知識を知恵に近づける
0. この記事について
本で学んだ内容や気づきを
「忘れないように通知するだけ」 の小さな仕組みを、
Googleスプレッドシート+Pythonで作りました。
1. はじめに / 背景
業務や自己研鑽で本を読んだあと、
- いい言葉だったのに思い出せない
- あの時のフレーズ、また活かせず忘れている
という状態が続いていました。そこで、
- 1回に1つ
- 思い出すきっかけだけ作る
- 身についたら除外できる
という割り切った仕組みで、繰り返し思い出せる仕組みを作りました。
2. 全体像・仕組み
構成はかなり単純です。
- 各内容は、数秒で読める単位に分割
- 各内容を Google スプレッドシートで管理
- Python でランダムに1件取得
- Webex に内容(フレーズ)を送信
- 身についたものは、通知除外できるしくみ
処理の流れ
3. 構築手順・基本構成
最小構成
- Googleスプレッドシートを1枚用意
- PythonでSheets APIを読む
- Webex通知用モジュールを外だし
※ Windows + Python を想定
4. 各論
4-1. スプレッドシート設計
シート名:学びの言葉
| 列 | 内容 |
|---|---|
| A | 検索用ID |
| B | 管理ID |
| C | キーワード |
| D | 書籍名 |
| E | 通知除外フラグ |
- 管理IDはレコードのキーID
- 検索用IDは通知対象のみの連番(Pythonで利用)
- 除外したいものはフラグを立てるだけ
👉 Python側の処理を単純にするためです。
4-2. Python側の考え方
Pythonでは以下だけをやっています。
- A列(検索用ID)を読んで最大値取得
- 1〜max の乱数を生成
- 一致する行だけを再取得
- 通知文を組み立てる
(通知対象外は検索用IDが空白)
通知メッセージ表示例(ダミー)
ロボット名 14:00
繰り返し思い出せば、知識は知恵に近づく
~(出展:~~ダミー出版~~)~
※参考コードは後述
4-3. Webex通知は外だし
通知処理は 別Pythonファイル にしています。
from send_webex_message_utf8 import send_webex_message
本体側は、
send_webex_message(message)
と呼ぶだけ。
別ファイルなので、この通知ファイルは他にも流用が可能です。
※コードは後述
5. 注意点・運用上の工夫
このシステムは、振り返り用の通知を出すことに特化しています。
特に「身についたかどうか」を管理するシートは作っていません。
通知除外フラグが増えたら身に付いていると代替判定します。
6. まとめ・応用
- 学びを「忘れない」だけで価値がある
- 最小構成+外だしで保守しやすい
- スプレッドシート×Pythonは業務に転用しやすい
この構成は、
- 定期リマインド
- チェックリスト通知
- 業務ルールの再確認
などにもそのまま使えます。
付録:参考コード(最小構成)
記事中で説明した「スプレッドシートからランダムに1件選んで Webex に通知する」部分の参考コードです。
※ トークン等の秘匿情報はconfig.iniに外出ししています。
フォルダ構成(最小)
project/
├─ 書籍からの学びの自動通知システム.py
├─ send_webex_message_utf8.py
├─ config.ini
└─ <サービスアカウント鍵.json>
config.ini(例)
[google]
service_account_json = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.json
spreadsheet_id = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
sheet_name = 学びの言葉
id_start = 1
[sendwebexmessage]
access_token = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
room_id =
to_person_email = your_mail@example.com
[logging]
log_path =
本体:書籍からの学びの自動通知システム.py
import configparser
import logging
import os
import random
import sys
from typing import Optional, Tuple
from google.oauth2.service_account import Credentials
from googleapiclient.discovery import build
from send_webex_message_utf8 import send_webex_message
def setup_logger(log_path: Optional[str]) -> logging.Logger:
logger = logging.getLogger("remind_notifier")
logger.setLevel(logging.INFO)
fmt = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
sh = logging.StreamHandler(sys.stdout)
sh.setFormatter(fmt)
logger.addHandler(sh)
if log_path:
fh = logging.FileHandler(log_path)
fh.setFormatter(fmt)
logger.addHandler(fh)
return logger
def build_sheets_service(sa_json_path: str):
scopes = ["https://www.googleapis.com/auth/spreadsheets.readonly"]
creds = Credentials.from_service_account_file(sa_json_path, scopes=scopes)
return build("sheets", "v4", credentials=creds, cache_discovery=False)
def format_message(keyword: str, book: str) -> str:
text = keyword.strip()
b = book.strip() if book.strip() else "不明"
return f"{text}\n ~(出展:{b})~"
def main():
base_dir = os.path.dirname(os.path.abspath(__file__))
cfg = configparser.ConfigParser()
cfg.read(os.path.join(base_dir, "config.ini"), encoding="utf-8")
sa_json = os.path.join(base_dir, cfg.get("google", "service_account_json"))
spreadsheet_id = cfg.get("google", "spreadsheet_id")
sheet_name = cfg.get("google", "sheet_name")
service = build_sheets_service(sa_json)
resp = service.spreadsheets().values().get(
spreadsheetId=spreadsheet_id,
range=f"{sheet_name}!A2:D"
).execute()
rows = resp.get("values", [])
if not rows:
return
search_id = random.choice(rows)[0]
for row in rows:
if row[0] == search_id:
keyword = row[2]
book = row[3] if len(row) > 3 else ""
message = format_message(keyword, book)
send_webex_message(message)
break
if __name__ == "__main__":
main()
通知モジュール:send_webex_message_utf8.py
import configparser
import requests
from pathlib import Path
SECTION_NAME = "sendwebexmessage"
def load_config(section: str = SECTION_NAME) -> dict:
script_dir = Path(__file__).resolve().parent
config_file = script_dir / "config.ini"
if not config_file.exists():
config_file = script_dir.parent / "config.ini"
config = configparser.ConfigParser()
config.read(config_file, encoding="utf-8")
s = config[section]
return {
"access_token": s.get("access_token", "").strip(),
"room_id": s.get("room_id", "").strip(),
"to_person_email": s.get("to_person_email", "").strip(),
}
def send_webex_message(message: str, section: str = SECTION_NAME) -> None:
config = load_config(section)
url = "https://webexapis.com/v1/messages"
headers = {
"Authorization": f"Bearer {config['access_token']}",
"Content-Type": "application/json",
}
payload = {"text": message}
if config["room_id"]:
payload["roomId"] = config["room_id"]
else:
payload["toPersonEmail"] = config["to_person_email"]
response = requests.post(url, headers=headers, json=payload, timeout=10)
response.raise_for_status()