n8n + Ollama で朝のニュースダイジェストを自動配信する

8 min
n8nollamaself-hostedline

何を作ったか

n8n-ollama-news-digest は、毎朝 7 時に技術系ニュースを自動収集し、ローカル LLM で要約して LINE に配信するシステムです。

// 毎朝 7 時に自動実行
RSS 5 サイト(Zenn, はてな, ITmedia, Publickey, GIGAZINE)
↓ 24 時間以内の記事を抽出
↓ 上位 20 件に絞る
↓ Ollama (gemma3:12b) で重要 5 本を選定・要約
↓ LINE にプッシュ通知

なぜセルフホストか

OpenAI や Claude の API でも要約はできますが、毎朝の自動実行だと API コストが積み重なります。n8nOllama を Docker Compose でセルフホストすれば外部 API 費用ゼロで済みます。 ローカル LLM はレスポンスが遅いですが、朝の自動実行なので問題ありません。

アーキテクチャ

┌─────────────────────────────────────────────────┐
│  Docker Compose                                 │
│                                                 │
│  ┌───────────────┐       ┌───────────────────┐  │
│  │  n8n          │       │  Ollama           │  │
│  │  :5678        │──────→│  :11434           │  │
│  │               │       │  (gemma3:12b)     │  │
│  │  ワークフロー.  │       │  ローカル LLM      │  │
│  └───────┬───────┘       └───────────────────┘  │
│          │                                      │
└──────────┼──────────────────────────────────────┘
           │
           ▼
    LINE Messaging API
技術用途
n8nワークフローエンジン。スケジュール実行、RSS 取得、データ加工
Ollamagemma3:12b モデルでニュース要約を生成
LINE Messaging API要約結果をプッシュ通知で配信
Docker Composen8n と Ollama のコンテナ管理

ワークフローの全体像

n8n 上のワークフローはこのようになっています。

n8n ワークフロー

処理は大きく 3 段階に分かれます。

  1. RSS 取得 — 5 つのフィードを並列取得して Merge ノードで統合
  2. AI 要約 — 記事を整形して Ollama に要約を依頼
  3. LINE 配信 — メッセージを整形して LINE に送信

Docker Compose の構成

n8n と Ollama を同一ネットワークに置いているだけのシンプルな構成で済ませています。

services:
  n8n:
    image: docker.n8n.io/n8nio/n8n
    ports:
      - "5678:5678"
    environment:
      - GENERIC_TIMEZONE=Asia/Tokyo
      - N8N_SECURE_COOKIE=false
    volumes:
      - ./n8n-data:/home/node/.n8n
    depends_on:
      - ollama
    restart: unless-stopped

  ollama:
    image: ollama/ollama
    ports:
      - "11434:11434"
    volumes:
      - ./ollama-data:/root/.ollama
    restart: unless-stopped

Docker Compose のサービス名がそのままホスト名になるので、n8n のコードノードから http://ollama:11434/api/generate で Ollama を呼べます。特別な設定は不要です。

各ノードの詳細

RSS 取得(rssFeedRead ノード)

n8n のビルトインノード rssFeedRead を使っています。 URL を指定するだけで RSS/Atom フィードの取得・パースをしてくれます。内部では rss-parser が使われており、各記事が title, link, pubDate などのフィールドを持つアイテムとして出力されます。

5 つのフィードをトリガーから並列に実行し、Merge ノードで 1 つに統合します。

毎朝 7 時 ─┬→ RSS Zenn      ─→ Merge.in(0)
           ├→ RSS Hatena    ─→ Merge.in(1)
           ├→ RSS ITmedia   ─→ Merge.in(2)
           ├→ RSS Publickey ─→ Merge.in(3)
           └→ RSS GIGAZINE  ─→ Merge.in(4)

記事整形(Code ノード)

Merge で統合された全記事から、24 時間以内のものだけフィルタリングしてソース名を付与します。

const articles = items
  .map((item) => ({
    title: item.json.title,
    link: item.json.link,
    pubDate: new Date(
      item.json.pubDate || item.json.published || item.json.date || 0,
    ),
    source: (() => {
      const link = item.json.link || "";
      if (link.includes("zenn.dev")) return "Zenn";
      if (link.includes("hatena")) return "はてな";
      if (link.includes("itmedia")) return "ITmedia";
      if (link.includes("publickey")) return "Publickey";
      if (link.includes("gigazine")) return "GIGAZINE";
      return "その他";
    })(),
  }))
  .filter((a) => a.pubDate > oneDayAgo)
  .sort((a, b) => b.pubDate - a.pubDate)
  .slice(0, 20);

上位 20 件に絞っているのは、Ollama に渡すプロンプトが長すぎるとレスポンスが遅くなるためです。20 件あれば選択肢としては十分です。

Ollama 要約(Code ノード)

整形した記事リストを Ollama の Generate API に渡して、重要 5 本の要約を生成させます。

const body = {
  model: "gemma3:12b",
  prompt: `指示:以下のニュース一覧から重要度の高い5本を選び、
番号付きリストで出力せよ。各項目は50文字以内の日本語で要約すること。
選定基準は技術トレンド、個人開発者への影響、話題性。
前置きや挨拶は不要。リストのみ出力せよ。

${articleText}`,
  stream: false,
};

const response = await this.helpers.httpRequest({
  method: "POST",
  url: "http://ollama:11434/api/generate",
  body: body,
  json: true,
  timeout: 300000,
});

プロンプトで重要なのは「前置きや挨拶は不要。リストのみ出力せよ。」の部分です。これがないと LLM が「はい、以下にまとめました!」のような余計な前置きを付けてしまい、LINE のメッセージが冗長になることがあります。 タイムアウトは 5 分に設定しています。gemma3:12b をローカルで動かすと数十秒〜数分かかることがあるので、余裕を持たせました。

なぜ gemma3:12b か

Gemma 3 は Gemini 2.0 と同じ 262K vocab のトークナイザーを使っていて、日本語のトークン効率が従来モデルから改善されています。 12B パラメータのモデルを試したところ、ニュース要約程度のタスクであれば問題なさそうな品質でした。

LINE 配信

日付ヘッダーとフッターを付けて、2000 文字以内に収めています。LINE のテキストメッセージには 5000 文字の制限がありますが、可読性を考えて 2000 文字で切り詰めています。

実際に届くメッセージはこのような形です。

LINE 配信例

n8n-as-code でワークフローを管理する

n8n のワークフローは通常 JSON で管理しますが、n8n-as-code を使えば TypeScript で管理できます。

なぜ JSON ではなく TypeScript か

n8n のワークフロー JSON は数百行になることがあり、差分レビューが困難です。TypeScript に変換すればノードの定義と接続がクラスベースで表現されるので、コードレビューがしやすくなります。

@workflow({
  name: "Morning News Digest",
  settings: {
    executionOrder: "v1",
    timezone: "Asia/Tokyo",
  },
})
export class MorningNewsDigestWorkflow {
  @node({
    name: "毎朝7時",
    type: "n8n-nodes-base.scheduleTrigger",
    version: 1.2,
  })
  _7 = {
    rule: {
      interval: [{ triggerAtHour: 7 }],
    },
  } as any as NodeProxy;

  // ...

  @links()
  defineRouting() {
    this._7.out(0).to(this.RssZenn.in(0));
    this._7.out(0).to(this.RssHatena.in(0));
    // ...
  }
}
  • @workflow — ワークフロー全体のメタデータです
  • @node — 各ノードの定義です。n8n JSON の nodes[] に対応します
  • @links — ノード間の接続です。n8n JSON の connections に対応します

JSON と TypeScript は n8nac CLI で双方向に変換できます。

# JSON → TypeScript
n8nac convert workflow.json

# n8n にデプロイ
n8nac sync --push

まとめ

  • n8n + Ollama を Docker Compose でセルフホストすることで、外部 API 費用ゼロでニュース要約を自動化できました
  • gemma3:12b は日本語の要約タスクには十分。プロンプト設計で出力フォーマットを制御することが重要です
  • n8n-as-code を使えばワークフローを TypeScript で管理できます