はじめに

AWSは毎日のように新サービスや機能アップデートを発表していますが、その膨大な情報をすべてタイムリーに追い続けるのはなかなか大変なことです。公式ページで英語の一次情報を確認するのが一番ですが、毎日サイトを開いて読み解くプロセスをもっと効率化できないかと考えていました。

少しでもそのハードルを下げたいと思い、NotebookLM と AWS Lambda、そして Slack を組み合わせて、AWSの新着情報を日本語のラジオ風音声にして毎朝Slackに届けるシステムを作りました。再生ボタンを押すだけで聴けるので、自分のようなめんどくさがりでも続けられます。

何ができるのか

平日の朝9時にAWSの新着情報があると、Slackチャンネルに以下が届きます。
音声ファイル:再生ボタンを押すだけで、2人のパーソナリティが掛け合い形式でAWSの新着情報を日本語で解説
テキスト要約:Canvasに日本語要約が蓄積され、リンクから原文にも飛べる
新着がない日は何も届きません。作業の合間に「ながら聴き」できるのがポイントです。

システム構成

構成図

                        毎朝 9:00 JST(平日のみ)
                              |
                    +---------v---------+
                    | Amazon EventBridge |
                    | Scheduler          |
                    +---------+---------+
                              |
                    +---------v---------+
                    |                   |
                    |   AWS Lambda      |
                    |   (Python 3.12)   |
                    |                   |
                    +---+-----+---+----+
                        |     |   |
            +-----------+     |   +-----------+
            |                 |               |
   +--------v--------+  +----v----+  +--------v---------+
   | AWS What's New   |  | Amazon  |  | Google NotebookLM |
   | RSS Feed (EN)    |  | S3      |  | - ラジオ音声生成   |
   +-----------------+  | (記事ID)|  | - テキスト要約     |
                         +---------+  +--------+---------+
                                               |
                                      +--------v--------+
                                      | Slack            |
                                      | - 音声ファイル    |
                                      | - Canvas(テキスト)|
                                      | - チャンネル通知   |
                                      +-----------------+

処理フロー

1. EventBridge が Lambda を起動(毎朝 JST 9:00、平日のみ)
       |
2. AWS公式サイトから英語版RSSフィードを取得
       |
3. S3 の処理済み記事IDと比較 → 新着のみ抽出
       |          新着なし → ログ出力して終了
       |
4. NotebookLM に英語元記事を投入
       |   → 日本語ラジオ風音声を自動生成 → MP4ダウンロード
       |   → 日本語テキスト要約も生成(Canvas用)
       |
5. Slack に音声ファイル + 通知を1メッセージで投稿
       |
6. Slack Canvas にテキスト要約を先頭に追記(蓄積型)
       |
7. S3 に処理済み記事IDを保存

使用サービス

サービス 役割
Amazon EventBridge 毎朝9時の定時実行トリガー(平日のみ)
AWS Lambda 全体の処理を1関数で実行(Python 3.12)
NotebookLM(API) 英語記事 → 日本語ラジオ風音声 + テキスト要約
Amazon S3 処理済み記事IDの管理
Slack API 音声配信 + Canvas更新 + 通知

なぜNotebookLMを使ったのか

当初はAmazon Pollyで音声を生成していましたが、「機械的な読み上げ」で聴き続けるのが辛いという課題がありました。
NotebookLMのAudio Overview機能は、2人のパーソナリティが自然な会話形式でコンテンツを解説してくれます。「へー、なるほど」「これは面白いですね」といった相槌が入り、ラジオ番組を聴いているような体験が得られます。
英語の元記事を渡すだけで日本語の音声もテキスト要約も生成してくれるため、音声生成とテキスト要約を1つのサービスに集約できました。

新着が多い日はどうなるのか

notebooklm-pyでは音声生成時に AudioLength パラメータで長さを指定できます。今回は AudioLength.SHORT を指定しており、5〜7分程度の音声が生成されます。他にも DEFAULT(10〜15分)や LONG(それ以上)が選べるので、より多くの記事を網羅したい場合は変更可能です。新着が少ない日はすべての記事を紹介してくれますが、新着が多い日はNotebookLMが関連するトピックをまとめ、重要度の高いものに絞って解説します。
例えば10件以上の新着があった日は、開発者向けの主要アップデート5〜6件がテーマ別にまとめられ、リージョン拡大系の記事などは省略されていました。音声でカバーされなかった記事も含め、すべての記事のテキスト要約はCanvasに記載されるので、そちらで確認できます。

実装のポイント

NotebookLMでの音声生成・テキスト要約

notebooklm-pyはNotebookLMをプログラムから操作するための非公式Pythonライブラリです。
NotebookLMには公式のEnterprise APIが存在し、ノートブック作成・ソース追加・音声生成の開始まではできます。しかし、生成された音声ファイルをダウンロードするAPIが現時点では提供されていません公式ドキュメントにもcreateとdeleteのみ記載)。
一方、NotebookLMのWebUI上では音声のダウンロードが可能です。notebooklm-pyはこのWebUIが内部で使用している非公開APIを再現することで、プログラムから音声ファイルを取得できるようにしています。公式APIではなくWebUIの内部通信に依存しているため、Google側の変更で動作しなくなるリスクはあります。

今回のシステムでは、Lambda(app.py)の中でこのライブラリをインポートして使っています。

事前準備

  1. NotebookLMのWebUIでノートブックを1つ作成しておく
  2. notebooklm login でブラウザ認証
  3. ノートブックIDをLambdaの環境変数に設定

Lambdaは毎回このノートブックの中身(ソース・音声)を入れ替えて使い回す仕組みです。

from notebooklm import NotebookLMClient
from notebooklm.rpc.types import AudioLength

# NotebookLMに接続
client = await NotebookLMClient.from_storage()
async with client:
    # 1. 前回のソースと音声を削除(毎回クリーンな状態にする)
    for src in await client.sources.list(notebook_id):
        await client.sources.delete(notebook_id, src.id)
    for art in await client.artifacts.list(notebook_id):
        await client.artifacts.delete(notebook_id, art.id)

    # 2. 新着記事をソースとして追加
    await client.sources.add_text(notebook_id, title="AWS News", content=articles_text)

    # 3. 音声生成(日本語・短めに指定)
    status = await client.artifacts.generate_audio(
        notebook_id,
        language="ja",
        audio_length=AudioLength.SHORT,
        instructions="AWSエンジニア向けの朝のラジオ番組風に解説してください。",
    )

    # 4. 完了まで待機 → ダウンロード
    await client.artifacts.wait_for_completion(notebook_id, status.task_id)
    await client.artifacts.download_audio(notebook_id, "news.mp4")

    # 5. テキスト要約もchat.askで取得(Canvas用)
    result = await client.chat.ask(notebook_id, "全記事を日本語で要約してください")
    summary = result.answer

generate_audioinstructions を渡すことで、音声のトーンや進行スタイルをカスタマイズできます。

Slackへの音声ファイルアップロード

Slackでファイルをアップロードするには、Slack Appの作成と以下の準備が必要です。
1. Slack APIでAppを作成し、Bot Token Scopesに files:write(ファイルアップロード)と chat:write(メッセージ投稿)を追加
2. Appをワークスペースにインストールし、Bot User OAuth Tokenを取得
3. 投稿先チャンネルにBotを招待(/invite @Bot名

ファイルアップロードは3ステップのAPIで行います。
1. files.getUploadURLExternal — ファイルのアップロード先URLを取得
2. 取得したURLにファイルを送信
3. files.completeUploadExternal — アップロードしたファイルをチャンネルに投稿

3番目のステップでは initial_comment パラメータが指定できます。これはファイル共有時に一緒に表示するメッセージを指定するSlack APIの機能で、これを使うことでファイルとテキストが1つの投稿にまとまります。

# 1. アップロードURLを取得
params = urllib.parse.urlencode({"filename": "aws_news.mp4", "length": file_size})
req = urllib.request.Request(
    f"https://slack.com/api/files.getUploadURLExternal?{params}",
    headers={"Authorization": f"Bearer {SLACK_BOT_TOKEN}"},
)
with urllib.request.urlopen(req) as response:
    result = json.loads(response.read())

upload_url = result["upload_url"]
file_id = result["file_id"]

# 2. ファイルをアップロード
with open("news.mp4", "rb") as f:
    req = urllib.request.Request(upload_url, data=f.read(),
        headers={"Content-Type": "application/octet-stream"}, method="POST")
    urllib.request.urlopen(req)

# 3. アップロード完了 → チャンネルに投稿
payload = json.dumps({
    "files": [{"id": file_id, "title": "aws_news.mp4"}],
    "channel_id": SLACK_CHANNEL_ID,
    "initial_comment": "今日のAWS新着情報を更新しました\nテキスト要約はCanvasで確認",
}).encode("utf-8")
req = urllib.request.Request(
    "https://slack.com/api/files.completeUploadExternal",
    data=payload,
    headers={"Authorization": f"Bearer {SLACK_BOT_TOKEN}",
             "Content-Type": "application/json"}, method="POST")
urllib.request.urlopen(req)

工夫した点

NotebookLMへの一本化

当初はAmazon Bedrockでテキスト要約、NotebookLMで音声生成と役割を分けていましたが、NotebookLMのChat機能で日本語テキスト要約も取得できることが分かり、NotebookLMに一本化しました。
Bedrockのコストが不要になり、構成もシンプルになりました。

Canvasの蓄積型更新

Canvasは毎回上書きではなく、先頭に追記する方式にしました。過去の情報も残るため、「先週のあの発表なんだっけ?」と振り返ることもできます。

RSSフィードの言語選択

日本語版RSSフィード(/jp/new/feed/)は翻訳の関係で英語版よりかなり遅れます。実際に確認したところ、英語版の最新が3/20の記事に対して日本語版は2/24で止まっており、約4週間の遅延がありました。英語版RSS(/new/feed/)を取得しNotebookLMが日本語に変換する構成にすることで、最新情報をタイムリーに届けられるようにしました。

ハマったポイント

①NotebookLMのAPI事情

NotebookLM Enterprise APIではノートブック作成・ソース追加・音声生成の開始はできますが、生成した音声ファイルのダウンロードAPIが提供されていません。そのため非公式ライブラリを利用しています。

②RSSのHTMLタグ問題

RSSフィードのdescriptionにHTMLタグが含まれており、NotebookLMがテキストとして正しく読めない問題がありました。ソースに追加する前にHTMLタグを除去することで解決しました。

desc = re.sub(r"<[^>]+>", "", article["description"]).strip()

③初回実行時の全件処理

RSSフィードには過去約2週間分(100件程度)の記事が含まれています。初回実行時に全件が新着扱いになり、膨大な量の音声とCanvasが生成されてしまいました。
対策として初回は直近3日分のみ音声生成し、RSS内の全記事IDを処理済みとして保存することで、次回以降は本当の新着のみが処理されるようにしました。

まとめ

AWSの最新情報を「読む」から「聴く」に変えることで、情報キャッチアップのハードルを下げることができました。Slackだけで「聴く・読む・原文を確認する」が完結します。
今回利用しているSlackやNotebookLM、LambdaとS3はすべて有償サービスですが、今回の用途であればそれぞれの無料枠内に収まるため、追加コストなしで構築できます。
NotebookLMの非公式ライブラリに依存している点は課題ですが、まずは自分が毎朝聴く習慣をつけつつ、チームにも広げていければと思います!