見出し画像

【つくってみた】Notionを使ったニュースチェックシステム


はじめに

2023年の7月末から、だいたい毎日IT・AIニュースチェックを続けています。

せっかくチェックしているのだから、ブログにまとめはじめましたが、結構なめんどくささなので、これまでいろいろ効率化に取り組んできました。

Notionを使ったシステムがとてもいい感じにできて、大活躍しているので、今回この記事にまとめます。

ニュースチェックシステムの変遷

RSSリーダーからコピペ

RSSリーダーに気になるニュースサイトを登録して、毎日チェックし、タイトルとURLを手でコピペ(!)していました。

1ヶ月ほど頑張ったところで、あまりにもめんどくさいのでなんとかせねば…と思い始めました。

Pythonのライブラリ「feedparser」の活用

PythonにはRSS feedをとってきてくれる「feedparser」というライブラリがあります。

これを活用してpythonプログラムを書き、毎日プログラムを定期実行して、ピックアップしたツールをテキストの形で保存するようにしました。

自動でピックアップして1日分にまとめてくれるようになったのでめちゃくちゃ楽になりました。

ただ、これの問題は、1日分にまとめるのは便利になったのですが、毎週・毎月のまとめをするときには手でコピペする必要があるところでした。

また、記事の要約はサイトにアクセスしてコピーし、ChatGPTに投げる、というのを手動で行っており、それも時間がかかっていました。

Claude 3 APIの活用とExcelへの保存

1日分のテキストファイルではなく、月単位でエクセルファイルにまとめて保存し、day, week, monthのようなflagをつけておき、ピックアップ用のプログラムを実行してニュースを抜き出すことにしました。

contentカラムのところに、要約したい文章を入れておくと、ピックアップするときにClaude 3 Haikuが要約してcontent_summaryカラムに格納してくれる仕組みです。

要約とピックアップが効率化できたことで、かなり作業は時短になったのですが、Excelでは要約文を編集しにくいのと、RSS以外で読んだ記事を追加するのが面倒という問題がありました。

結局、要約文をそのまま使うことってそんなに多くなくて、編集して自分なりにまとめる作業が必要なのでした。

毎日のニュースチェックの更新の際には、ピックアップしたファイルの中身を編集することになって、原本であるExcelは更新されていない状態でした。

そうなると、月ごとのまとめ用ピックアップを実行しても、また要約文の編集が必要になっていました。

Notion API × Notion AI × Chrome拡張機能を使ったシステム

そこで登場したのがNotionです!

もともと、Chromeの拡張機能を使うと、ウェブページをまるっと(タイトル、写真やテキスト、URLなど)Notionのデータベースに保存できることは知っていて、よく使っていました。

SNSとかで見たニュースを保存しておきたいときにとっても便利なんですよね。

これを活かしたいと思ったときに、もともと作っていたRSSからニュースを取ってくるシステムと組み合わせたらいけるんじゃないか?と思ってNotion APIを叩いてみました。

Notion APIを使う手順

1. Notionワークスペースのインテグレーションの設定をする
2. Notionデータベースとインテグレーションをコネクトする
3. インテグレーションキーとデータベースIDを使ってAPIで通信する

インテグレーションの設定が結構わかりにくかったです。

Notionのアイコンから「設定」→「コネクト」を選択。
一番下の灰色の文字のところに「インテグレーションの作成または管理する」とあるので、それを押下します。

webページが開くので、そこで「インテグレーションを追加」すると、インテグレーションキーが取得できます。

インテグレーションキーとデータベースIDは、外部に漏れないよう厳重に管理する必要があります。プログラム上にハードコーディングするのは避け、環境変数に設定しておきます。

Notionを使ったニュースチェックシステム概要

手順の概要は次の図のとおりです。

①気になるニュースのURLにアクセスして拡張機能でNotionの【ピックアップ】DBに保存

②【ピックアップ】DBに保存されたニュースをチェックして、Notion AIで要約 & pickup typeの選択 & flagの選択(手動操作)

③ニュースまとめ作成プログラムを実行して、毎月or毎日のニュースまとめを作成

①気になるニュースのURLにアクセスして拡張機能でNotionに保存

この作業は手動です。

定期実行データベースにたまっているニュースやSNSなどで気になったニュースにアクセスし、気になったものについてはChrome拡張機能を使ってピックアップ用データベースにニュースを保存します。

②Notion AIで要約 & pickup typeの選択 & flagの選択

★Notion AIとは?ChatGPTと何が違うの?

Notion AIはNotionの中でAIと対話・生成してもらえるツールです。

これはNotionの課金プランとは「別に」課金されるサービスです(月額10ドル)。

ページの中でAIに質問・指示できるのは悪くはないですが、最近はClaudeばかり使っています。(賢い気がするので)

Notion AIの目玉は、データベース機能における自動要約機能です。

Notionは「データベース」と呼ばれる表形式のページが使えて、これがとても便利なのですが、Notion AIでは要約プロパティ(プロパティは列のことです)が使え、ボタン一つでページの中身を丸ごと要約してくれます。

これが意外と便利!中に何が書いてあったかページを開いて見るのが面倒なときも、表形式の状態で要約が読めるので検索しやすくなりました。

また、「AI keyword」プロパティもあります。これはページの中身から「自動で」キーワードを抜き出してタグとして追加してくれます。これも結構便利!

ニュースチェックシステムにおけるNotion AIの活用

実際にピックアップしたニュースのページは下の図のような感じになっています。


データベースのテーブルビューだとこんな感じ👇になっています。

URL」はニュースのURLです。ChromeのNotion拡張機能でニュースを保存したときに自動で埋まります。

Summay AI」はニュースの要約です。プロパティのところを押下すると、AIが自動で要約してくれます。注意としては、全文英語の記事は英語で要約されてしまうことが多いですが、その場合は本文のページに行って「日本語で要約して」もらった後に自動要約処理をしてもらっています。

flag」はこのニュースを日ごと、週ごと、月ごとのページにいれるかどうかのフラグです。これが「pick」になっていた場合、ニュースをまとめるプログラム実行時に選ばれます。

pickup_type」が「day」の場合は日ごとのページのみに、「week」の場合は日ごとと週ごとのページに、「month」の場合は日ごと、週ごと、月ごとのページに選ばれます。

③ニュースまとめ作成プログラム

【ピックアップ】ニュースリスト から、pickup_typeに応じたニュースを取得します(データベースから情報の抽出)。

どのflagのニュースを取得するかは、argparseライブラリを使って、プログラムの実行引数で与えます。

python3 news_picker --pickup_type day


のような感じです。

argparseライブラリとても便利で良く使います。

import requests
import os
from datetime import datetime, timedelta, date
import argparse

def pick_daily_news_from_database(url, headers):

# データベースからデータを取得するためのフィルター
   data = {
       "filter": {
           "and": [
               {
                   "property": "flag",
                   "status": {
                       "equals": "pick"
                   }
               },
               {
                   "or": [
                       {
                           "property": "pickup_type",
                           "select": {
                               "equals": "day"
                           }
                       },
                       {
                           "property": "pickup_type",
                           "select": {
                               "equals": "week"
                           }
                       },
                       {
                           "property": "pickup_type",
                           "select": {
                               "equals": "month"
                           }
                       }
                   ]
               }
           ]
       }
   }

   # APIリクエストを送信
   response = requests.post(url, headers=headers, json=data)
   results = response.json()
   return results

def pick_weekly_news_from_database(url, headers):

# データベースからデータを取得するためのフィルター
   data = {
       "filter": {
           "and": [
               {
                   "property": "flag",
                   "status": {
                       "equals": "pick"
                   }
               },
               {
                   "or": [
                       {
                           "property": "pickup_type",
                           "select": {
                               "equals": "week"
                           }
                       },
                       {
                           "property": "pickup_type",
                           "select": {
                               "equals": "month"
                           }
                       }
                   ]
               }
           ]
       }
   }

   # APIリクエストを送信
   response = requests.post(url, headers=headers, json=data)
   results = response.json()
   return results

def pick_monthly_news_from_database(url, headers):

   # データベースからデータを取得するためのフィルター
   data = {
       "filter": {
           "and": [
               {
                   "property": "flag",
                   "status": {
                       "equals": "pick"
                   }
               },
               {
                   "property": "pickup_type",
                   "select": {
                       "equals": "month"
                   }
               }
           ]
       }

   }

   # APIリクエストを送信
   response = requests.post(url, headers=headers, json=data)
   results = response.json()
   print(results)
   return results


先ほど、pickup_typeに応じて、ニュースを取得しました。たとえば、

python3 news_picker.pypickup_type month

の場合には、「pickup_type」のプロパティが「month」のニュースだけが抽出されています。これらを、月ごとのデータベースにページを作って、一括したテキストとして貼り付けます。

データベースへの「書き込み」をするということですね。

ここで注意としては、書き込みは一度に2000文字しかできないので、テキストを分割して繰り返し書き込みをしています。これにより、「毎月のニュースチェック」のページのテキストはところどころへんな余白が入ってしまいます。気にはなるけど、まあ、許容範囲かな…

def add_page_to_database(page_title, database_key, news_contents):

   NOTION_API_KEY = os.getenv('NOTION_API_KEY')
   DATABASE_ID = os.getenv(database_key)

   url = 'https://api.notion.com/v1/pages'

   headers =  {
       'Notion-Version': '2022-06-28',
       'Authorization': 'Bearer ' + NOTION_API_KEY,
       'Content-Type': 'application/json',
   }

   json_data = {
       'parent': { 'database_id': DATABASE_ID },
       'properties': {
           'name': {
               'title': [
                   {
                       'text': {
                           'content': page_title
                       }
                   }
               ]
           },
       },
   }

   page_response = requests.post('https://api.notion.com/v1/pages', headers=headers, json=json_data)
   new_page_id = page_response.json()['id']  # 新しく作成されたページのIDを取得
   return new_page_id

def add_content_to_page(new_page_id, text):
   

   # ページにブロック(テキスト)を追加
   block_data = {
       "children": [
           {
               "object": "block",
               "type": "paragraph",
               "paragraph": {
                   "rich_text": [
                       {
                           "type": "text",
                           "text": {
                               "content": text
                           }
                       }
                   ]
               }
           }
       ]
   }

   block_response = requests.patch(f'https://api.notion.com/v1/blocks/{new_page_id}/children', headers=headers, json=block_data)

def split_text(long_text, length=2000):
   return [long_text[i:i+length] for i in range(0, len(long_text), length)]

メイン処理は次のような感じです。

実行時引数では必須の引数として、「pickup_type」を与える必要があり、任意の引数として「title_date(デフォルトは実行時の日付)」、「title_name(デフォルトは””)」も指定できます。title_nameはたとえば「〇〇に関するニュース」などを与えておくと、ほかのページと区別できるようになります。

if __name__ == "__main__":
   NOTION_API_KEY = os.getenv('NOTION_API_KEY')
   DATABASE_ID = os.getenv('PICKUP_DATABASE_KEY')

   url = f'https://api.notion.com/v1/databases/{DATABASE_ID}/query'

   headers =  {
       'Notion-Version': '2022-06-28',
       'Authorization': 'Bearer ' + NOTION_API_KEY,
       'Content-Type': 'application/json',
   }

   today = date.today()
   formatted_date = today.strftime("%Y-%m-%d")

   parser = argparse.ArgumentParser(description='pick news as pick type.')
   parser.add_argument('--pickup_type', default="day", required=True, type=str)
   parser.add_argument('--title_date', default=formatted_date, type=str)
   parser.add_argument('--title_name', default="", type=str)
   args = parser.parse_args()

   pickup_type = args.pickup_type
   title_date = args.title_date
   title_name = args.title_name

   if pickup_type == "day":
       update_database_key = "DAILY_DATABASE_KEY"
       results = pick_daily_news_from_database(url, headers)
       page_title_header = "daily_news"
   elif pickup_type == "week":
       update_database_key = "WEEKLY_DATABASE_KEY"
       results = pick_weekly_news_from_database(url, headers)
       page_title_header = "weekly_news"
   elif pickup_type == "month":
       update_database_key = "MONTHLY_DATABASE_KEY"
       results = pick_monthly_news_from_database(url, headers)
       page_title_header = "monthly_news"

   contents_list = []
   for result in results["results"]:
       properties = result["properties"]
       news_url = properties["URL"]["url"]
       tag = properties["tag"]["select"]["name"]
       if not properties["abstract"]["rich_text"]:
           print("abstractが空です")
           abstract = ""
       else:
           abstract = properties["abstract"]["rich_text"][0]["text"]["content"]
       title = properties["name"]["title"][0]["text"]["content"]
       content = [tag, title, news_url, abstract]
       contents_list.append(content)

   sorted_contents_list = sorted(contents_list, key=lambda x: x[0], reverse=True)

   news_contents = ""
   for content in sorted_contents_list:
       tag = content[0]
       title = content[1]
       news_url = content[2]
       abstract = content[3]

       news_contents += f"\n【{tag}{title} {news_url} \n {abstract} \n --------------------------------"

   page_title = f"{page_title_header}{title_date}{title_name}"
   

   new_page_id = add_page_to_database(page_title, update_database_key, news_contents)

   contents = split_text(news_contents)

   for content in contents:
       add_content_to_page(new_page_id, content)


「毎月のニュースチェック」に生成されたページはこのような感じです。


flag = pickup、pickup_type=monthのニュースの、タグ、タイトル、URL、Summary AIの中身が一覧になって貼り付けられています。これをこのままblogに使っています。

べんり〜!

このシステムの良いところ

  • Notionをベースにしているので、スマホやタブレットからも簡単にニュースを確認・要約の作成ができる

  • Chromeで開けばNotionデータベースに登録できる。

  • Notionデータベースの情報を更新しやすい

  • flagを簡単に付け替えて、気軽にニュースまとめを作成できる

ちょっと良くないポイント

  • ニュースまとめにいれるかどうかを決めるときに、毎回「flag」を選択しないといけない。

    •   ニュースまとめにいれたあとに、自動でflagを「not pickup」にしてもいいかもしれない

  • ニュースをデータベースに登録した日付はプロパティに格納できるが、ニュース記事自体の公開日付をプロパティに登録することができない。

    •  あとから「いつのニュースだったか?」を確認したいときに不都合になりそう

おわりに - ニュースチェックはいいぞ!

いくつか困っているポイントがありますが、それでもかなり快適になりました。

本当は、一つ一つのニュースをしっかり読んで吟味できたらいいのでしょうけれど、普段仕事をしているとそこまで時間があるわけではないので、効率よく情報収集するためにこういうやり方をしています。

このやりかたでも、最新の技術動向は把握できていて、エンジニア同士の雑談の時に効果を感じています。

エンジニアと話していると「最近〇〇って技術が流行っていますよね」とか「◻︎◻︎すごいらしいですね」とかよく話題になって、以前はそのたびに自分が無知であることを突きつけられてつらかったです…
(みなさんも経験ありますよね?)

ニュースまとめをするようになってから、一応一通りの話題はさらうことができているので、会話についていけるようになりましたし、なんなら「こんな技術もありますよ」と紹介できるくらいになってきました。

さすがに毎日ブログを書くのは普通の人はめんどくさいと思いますけれど、Notion Web Clipper(Chromeの拡張機能)はとても便利なので、ぜひ自分の気になったニュースを溜めていくことをオススメします!

わたし自身は、今後「最近話題になっている技術をさっそく試してみましたよ!」と言えるようになりたいと思っています!

週末をそういう時間に充てられたらいいな〜

いいなと思ったら応援しよう!

この記事が参加している募集