事例 2026年7月1日 読了 約9分

AIに毎朝ニュースを読ませる前段を、GitHub Actionsで作った話

AWSのアップデートやAI界隈のニュース、自分で全部追うのはキツい。AIに任せたい、けれど「自分のマシンを起動していないと動かない」cronだと、結局続きませんでした。収集だけ切り出してGitHub Actionsに移したら、AIに「読んでもらう」前段が安く・確実に回るようになった、という事例です。

QE
Qurated編集部
デザインL

はじめに

AWSのアップデート、AI界隈のニュース、Terraform / UiPath / 開発者ツール … 自分の関心領域だけでも、毎日数十件のフィードが流れてきます。全部追うのは無理、けれど追わないと取り残される、という板挟みが地味にストレスでした。

「AIに毎朝読んでもらえばいいじゃん」とすぐ思いつきます。実際、AIエージェントは長文の要約や関心軸でのフィルタが得意です。

ただ、1回目の試行は失敗しました。AIエージェント(Claude Code)のスケジュールタスクで毎朝の収集を走らせていたのですが、

  • 自分のマシン(Desktop App)が起動していない時間は動かない
  • 平日朝の出勤直前に「あ、今日まだ立ち上げてない」で抜ける
  • 結果として日次のリズムが崩れ、ニュースを読まなくなった

つまり収集をAIに任せた途端、デバイス依存が露呈した。AIの賢さ以前に、毎日確実に動くインフラがなければ運用は続かない。

そこで設計を組み直しました。

「機械的な収集」と「主観的なキュレーション」を分ける — 収集はGitHub Actionsに置き、AI(Claude Code)はその出力を朝読むだけの役割にする

これで毎朝、自分のマシンが寝ていても、PCの蓋を閉じていても、ニュースが集まった状態で1日が始まる設計になりました。本記事ではその構成と、AIに任せたい人が”前段”をどう機械化するかの考え方を共有します。

自己紹介

Qurated編集部のKです。AIを業務と生活の両方に取り込んだ実践事例を、こちらquratedlab/journalで書いています。

全体像

組んだパイプラインはこんな形です。

[GitHub Actions cron] 毎朝 19:30 UTC = 翌朝 04:30 JST

[fetch_daily_info.py] Python標準ライブラリのみ・RSS 8ソースを並列に取得

[整形 + スパムフィルタ] og:description優先で要約を取り直し、Markdownに整形

[auto-commit + push] daily-info/YYYY-MM-DD.md として履歴を残す

[Claude Code /morning] 朝、AIが当日ファイルを読んで自分用にキュレーション

前段の収集はAIを使っていません。これがキモです。RSSの取得・整形・保存は決定的な処理なので、機械の世界(GitHub Actions + Python)に閉じた方が安く・確実に回ります。AIの主観的判断は後ろに寄せます。

部品1: GitHub Actions cron

ワークフローは超シンプルです。

name: Daily Info Fetch

on:
  schedule:
    # 毎日 19:30 UTC = 翌朝 04:30 JST
    - cron: '30 19 * * *'
  workflow_dispatch:    # 手動実行も可

jobs:
  fetch:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - name: Fetch daily info
        run: python scripts/fetch_daily_info.py
      - name: Commit and push
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add daily-info/
          git diff --staged --quiet || git commit -m "chore: daily info $(date +%Y-%m-%d)"
          git push

ポイントは3つ。

  • runs-on: ubuntu-latest: 無料枠で十分。プライベートリポジトリでも個人利用なら月の無料分(2,000分)を使い切ることはまずない
  • schedule + workflow_dispatch の二段構え: 自動で動くだけでなく、Actionsタブから手動でも走らせられる。設計を試行錯誤するときに地味に効く
  • permissions: contents: write: GitHub Actionsから同リポジトリにcommit + pushする最小権限。これを忘れると最後のpushがコケる
INFO

GitHub Actionsの schedule は数分〜十数分のジッタがあります(公式ドキュメントのscheduleにも「保証しない」旨の記載あり)。「ピッタリ4:30」では発火しません。私は JST 4:30着を目安に、逆算で 19:30 UTC に設定して余裕を持たせています。

部品2: Python スクリプト(標準ライブラリのみ)

scripts/fetch_daily_info.py は外部依存ゼロです。requestsfeedparser も使いません。

import urllib.request
import xml.etree.ElementTree as ET
from datetime import datetime, timedelta, timezone

JST = timezone(timedelta(hours=9))

FEEDS = [
    {"name": "AWS Blog",            "url": "https://aws.amazon.com/blogs/aws/feed/", "emoji": "☁️"},
    {"name": "AWS What's New",      "url": "https://aws.amazon.com/about-aws/whats-new/recent/feed/", "emoji": "🆕"},
    {"name": "Terraform Releases",  "url": "https://github.com/hashicorp/terraform/releases.atom", "emoji": "🏗️"},
    {"name": "UiPath Blog(公式)", "url": "https://www.uipath.com/blog/rss.xml", "emoji": "🤖"},
    {"name": "Zenn トレンド",       "url": "https://zenn.dev/feed", "emoji": "📝"},
    {"name": "dev.to (AWS)",        "url": "https://dev.to/feed/tag/aws", "emoji": "👩‍💻"},
    {"name": "Hacker News (AI/Cloud)", "url": "https://hnrss.org/newest?q=aws+OR+terraform+OR+claude+OR+%22generative+AI%22&points=30", "emoji": "🔶"},
]

HOURS_BACK = 48   # 過去48時間以内の記事のみ

なぜ標準ライブラリ縛りか。理由は2つ。

  • GitHub Actionsの起動時間が短い: pip install のステップを省ける。1分以内に終わる
  • メンテが消える: 依存パッケージのバージョンアップで毎月Dependabot PRが来る世界から抜けられる

urllib.request + xml.etree.ElementTree で十分です。

og:descriptionで要約を取り直す

地味に効いたのが、RSSのdescriptionをそのまま使わず、記事ページのog:descriptionを取りに行く設計です。

def fetch_article_text(url: str, fallback: str) -> str:
    """記事URLのog:description / meta descriptionを取得。失敗時はfallback。"""
    req = urllib.request.Request(url, headers=HEADERS)
    with urllib.request.urlopen(req, timeout=5) as resp:
        html = resp.read().decode("utf-8", errors="ignore")
    m = re.search(r'<meta[^>]+property=["\']og:description["\'][^>]+content=["\'](.*?)["\']', html, re.IGNORECASE)
    # ... fallback to name="description" ...
    if m:
        return strip_html(m.group(1))[:200]
    return fallback

RSSのdescriptionは「タイトルの言い換え」レベルで終わる供給元が多く、og:descriptionの方が一段濃い要約になっています。タイトル + og:descriptionの組み合わせで、朝に「これは深掘りすべきか」を1秒で判断できる密度になります。

壊れたRSSへの防御

実在のRSSは想像以上に汚れています。例えばUiPath公式RSSは、XML 1.0で許可されていない制御文字や、エスケープされていない & を含んで配信されることがあります。厳格な ElementTree だとそこで止まります。

_ILLEGAL_XML_RE = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f]")
_BARE_AMP_RE = re.compile(r"&(?!amp;|lt;|gt;|quot;|apos;|#[0-9]+;|#x[0-9a-fA-F]+;)")

def sanitize_xml(raw: bytes) -> bytes:
    text = raw.decode("utf-8", errors="ignore")
    text = _ILLEGAL_XML_RE.sub("", text)
    text = _BARE_AMP_RE.sub("&amp;", text)
    return text.encode("utf-8")

これを通してからparseすれば、正常なフィードはそのまま、壊れたフィードだけ修復されます。

スパムフィルタ

dev.to やZennには時々、「Buy followers」「24/7 service」といった本筋と無関係な投稿が混ざります。タイトル + 本文の語彙ベースで弾きます。

SPAM_TITLE_KEYWORDS = ["buy ", "sell ", "cheap ", " price", "accounts", "followers", "views", "likes"]
SPAM_BODY_PATTERNS = ["telegram:", "discord:", "24/7", "getusasmm", "@get"]

def is_spam_like(title: str, summary: str) -> bool:
    if any(kw in title.lower() for kw in SPAM_TITLE_KEYWORDS):
        return True
    matches = sum(1 for p in SPAM_BODY_PATTERNS if p in summary.lower())
    return matches >= 2

完璧ではないですが、明らかに不要なものは消えるので朝の体験が崩れません。

部品3: auto-commit でファイル化する

整形後のMarkdownは daily-info/YYYY-MM-DD.md として保存され、ワークフローの最後で GitHub Actionsがそのままcommit + pushします

これにより日次のニュースがGitリポジトリ自体に履歴として残るのがポイントです。

  • 「あの日の発表でやりたい記事があった」を後から思い出せる
  • フォーマットや収集ロジックを変えても、過去データは時点保存されている
  • 別のAIエージェント(Claude / Gemini / Codex 等)から同じファイルを読み込める

DBもS3も用意していません。Gitだけで履歴管理が回ります。

WARN

GitHub Actionsから同リポジトリにcommit + pushする時は、permissions: contents: write を明示しないと最後のpushが silentにコケます。ワークフローの最初に書いておくのが安全です。

部品4: AIによる朝のキュレーション

ここで初めてAIが登場します。Claude Codeに /morning というスラッシュコマンドを定義しておき、

  1. 今日の daily-info/YYYY-MM-DD.md を読む
  2. CLAUDE.md(プロジェクトメモリ)に書いてある自分の関心テーマ・価値観を踏まえて
  3. 「今日読むべきもの」を3〜5件に絞って提示

を任せます。

CLAUDE.md には例えば、

## 関心テーマ
- AWS / Terraform / クラウドインフラ / セキュリティ
- AI / 生成AI(Claude Code 含む)
- UiPath / RPA(業務自動化)

## 価値観
- 「人生が自分のリズムで回っている」状態を目指している
- 小さな積み重ねを大切にしたい

といった情報が書いてあります。これがあるおかげで、AIは「ただ集めたもの」から「私のためのキュレーション」へ変換してくれます。

なぜGitHub Actionsを選んだか

最初に作ったClaude CodeのMCPスケジュールタスク方式と比べて、GitHub Actionsを選んだ理由は4つあります。

観点Claude Code MCP cronGitHub Actions cron
起動環境自分のマシン(Desktop App)が起動している必要server-side、マシン状態に依存しない
コスト無料(ただしマシン稼働が前提)無料枠(個人利用ならほぼ尽きない)
履歴管理スケジュールタスクのログのみリポジトリのcommit履歴がそのままアーカイブ
検証性ログを見に行く必要Actionsタブで成功/失敗が常に見える

特に効いたのは1番目です。マシン非依存にすると、その瞬間に運用が楽になります。寝てる間でも、出張で別端末を使ってる時でも、ニュースは集まっている。AIに任せるための前段が、デバイスから切り離される。

効いた設計のコツ

3つあります。

1. 「収集」と「キュレーション」を別レイヤーに分ける

最初の失敗は、両方をAIエージェントに任せていたことでした。収集は決定的・キュレーションは主観的、というレイヤーの違いを意識して分けると、それぞれを別のインフラに最適化できます。

  • 収集: GitHub Actions + Python (cheap, deterministic, no AI)
  • キュレーション: Claude Code + CLAUDE.md (subjective, contextual, AI)

2. 依存ゼロのスクリプトにする

GitHub Actionsで動かすときは、依存を増やせば増やすほど起動が遅くなり、メンテも増えます。Pythonの標準ライブラリ(urllib, xml.etree, re, datetime)で書ける範囲なら、それで完結させた方が幸せです。

feedparser を使えば10行で書けるところを、わざわざ40行で書いています。それでも、pip install の不確実性とDependabotの通知から解放される方が遥かに勝ります。

3. 出力をファイルとして残す

daily-info/YYYY-MM-DD.md のように1日1ファイルで残すと、副次効果が大きいです。

  • AIに「先週の自分」を渡せる(振り返り用途)
  • フォーマット変更があっても過去データは保存されている
  • 他のAIエージェントや、別端末から、同じインターフェース(ファイル)で読める

AI連携の共通インターフェースとして”ファイル”を選ぶのは安い、けれど強い設計だと思います。

何が変わったか

前回の行政MCPの記事家賃の記事で書いた「AIに任せるのは”取りかかるまでのコスト”を消すため」という主題は、今回も同じです。

ただ、今回の学びはもう一段下のレイヤーにあります。

AIに任せたいなら、AIに依存しないインフラを下に置く。

AIエージェントは賢いし、面白いキュレーションをしてくれます。けれどそれは「データが来ていれば」の話。データを毎日確実に届けるレイヤーはむしろAIから外した方が、結局AIをうまく使えました。

まとめ

  • AI(Claude Code)のスケジュールタスクで毎朝の収集を走らせたら、マシン依存で運用が崩れた
  • 収集レイヤーをGitHub Actionsに移すと、マシン非依存・無料・履歴付きで走るようになる
  • Python標準ライブラリのみで書く / og:descriptionで要約を取り直す / RSS sanitization / スパムフィルタ — どれも地味だが効く
  • 出力をMarkdownファイルとしてgitに残すと、AI連携のインターフェースがそのまま安く実現できる
  • 「機械的な収集」と「主観的なキュレーション」をレイヤー分離すると、それぞれを別インフラに最適化できる

結論

AIに任せたいなら、AIに依存しないインフラを下に置く。

毎朝AIに何かを読ませたい、要約させたい、判断させたい — もし試して続かなかった人は、収集の前段にGitHub Actionsを置いてみてください。AIの上に確実な機械の世界があるだけで、運用は驚くほど続きます。


AIを「情報整理の常駐スタッフ」に変える使い方

社内ナレッジの整理、技術キャッチアップ、業務オペレーション -- 情報量が多すぎて回らない領域こそ、収集の前段を機械化するとAIが効きます。導入で迷っている方は、お気軽にご相談ください。

無料で相談する