0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【非エンジニアでも作れる】noteを毎日自動投稿する仕組み——AIに頼むだけの全プロンプト公開

0
Posted at

TL;DR

Discord の絵文字リアクションを承認トリガーに、毎日の note 投稿を自動化するシステムを Python × Claude API で実装できます。所要時間は 1~2 時間。エンジニアでなくても指示を出せば Claude Code が基本構造を生成してくれます。


背景:なぜ「全自動」ではなく「人間承認フロー」か

毎日の note 投稿を完全自動化したいというニーズはよくあります。しかし、実装の観点から見ると「AI が完全に任せるのは品質リスク」という問題があります。

生成 AI は優秀ですが、毎日同じプロンプトで出力すると:

  • トーンの微妙なぶれ
  • ネタの重複・使い尽くし
  • 業界トレンドの反映漏れ

こうしたズレが月に数度発生し、投稿後の修正・削除コストが発生します。

最適な運用は「AI が 80%やって、人間が 20%の確認」。毎朝 Discord で生成記事を目視し、絵文字(✅ or ❌)をポチするだけで、ブランド品質を保ちながら投稿を自動化できます。


システム全体設計

このシステムは 4 つのコンポーネントで構成されます。

[定時実行] 毎朝8時
    ↓
[1] トピック提案AI(過去記事を分析、テーマ3案を提示)
    ↓
[2] 本文執筆AI(選定テーマで記事本体を生成)
    ↓ Discord に投稿
[3] 人間の目視と絵文字承認(✅ で確定、❌ で下書き保存)
    ↓
[4] note 自動投稿 + エラー通知

各ステップは Python スクリプトと Discord API、note API で連携します。


実装手順

ステップ 1:環境セットアップ

必要な環境変数を .env に設定します。

# .env
DISCORD_BOT_TOKEN=your_bot_token_here
DISCORD_CHANNEL_ID=your_channel_id
NOTE_API_KEY=your_note_api_key
CLAUDE_API_KEY=your_claude_api_key

bot token は Discord Developer Portal から取得します。サーバー内で bot に「メッセージ送信」「リアクション読み取り」権限を付与してください。

ステップ 2:トピック提案スクリプト

毎朝、過去の note タイトルを分析してテーマ案を生成します。

import os
import csv
from datetime import datetime
from anthropic import Anthropic

def load_past_titles(csv_path: str) -> list[str]:
    """CSVから過去記事タイトルを読み込む"""
    titles = []
    with open(csv_path, 'r', encoding='utf-8') as f:
        reader = csv.reader(f)
        next(reader)  # ヘッダースキップ
        titles = [row[0] for row in reader]
    return titles

def generate_topic_proposals(titles: list[str]) -> str:
    """Claude APIで今日のテーマ案を3つ生成"""
    client = Anthropic()
    past_titles_text = '\n'.join(f"- {t}" for t in titles[-20:])
    
    message = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=500,
        messages=[
            {
                "role": "user",
                "content": f"""以下は過去20件のnote記事タイトルです。

{past_titles_text}

このテーマ群を踏まえて、今日のnote記事として提案できるテーマを3つ、順番に提示してください。
各テーマは1行で、端的に書いてください。重複のない、読者に有益なテーマを心がけてください。"""
            }
        ]
    )
    return message.content[0].text

def post_to_discord(content: str):
    """Discord に投稿"""
    import discord
    from discord.ext import commands
    
    bot = commands.Bot(command_prefix='!')
    
    @bot.event
    async def on_ready():
        channel = bot.get_channel(int(os.getenv('DISCORD_CHANNEL_ID')))
        await channel.send(f"📝 【今日のテーマ案】\n\n{content}")
        await bot.close()
    
    bot.run(os.getenv('DISCORD_BOT_TOKEN'))

if __name__ == '__main__':
    titles = load_past_titles('past_notes.csv')
    proposals = generate_topic_proposals(titles)
    print(proposals)
    post_to_discord(proposals)

ステップ 3:本文執筆スクリプト

Discord でトピック案に対してリアクション(例:📝)が付いたら、本体を生成します。

from anthropic import Anthropic
import json

def generate_article(topic: str, past_articles: list[dict]) -> str:
    """選定テーマで記事本文を生成"""
    client = Anthropic()
    
    # 過去記事の文体を学習
    past_text = '\n---\n'.join(
        f"タイトル: {a['title']}\n本文:\n{a['body'][:500]}..."
        for a in past_articles[-5:]
    )
    
    message = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=2000,
        messages=[
            {
                "role": "user",
                "content": f"""以下は私の過去のnote記事のサンプルです。これらから文体・トーン・構成パターンを学んでください。

{past_text}

---

今日のテーマ: {topic}

このテーマで、1200~1500文字程度のnote記事本文を書いてください。
上記の過去記事と同じ文体・トーン・深さで、読者にとって有益で実践的な内容をお願いします。"""
            }
        ]
    )
    return message.content[0].text

def save_article_for_approval(topic: str, body: str, message_id: str):
    """Discord 承認待ちの記事を JSON で保存"""
    article = {
        "message_id": message_id,
        "topic": topic,
        "body": body,
        "status": "pending_approval",
        "created_at": datetime.now().isoformat()
    }
    with open(f'pending_{message_id}.json', 'w', encoding='utf-8') as f:
        json.dump(article, f, ensure_ascii=False, indent=2)

ステップ 4:承認検知と自動投稿

Discord の絵文字リアクション(✅)を監視し、note に自動投稿します。

import discord
from discord.ext import commands
import aiohttp

class ApprovalBot(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
    
    @commands.Cog.listener()
    async def on_reaction_add(self, reaction, user):
        """✅ リアクションを検知して自動投稿"""
        if user.bot or str(reaction.emoji) != '':
            return
        
        # pending ファイルを検索
        message_id = reaction.message.id
        article_file = f'pending_{message_id}.json'
        
        try:
            with open(article_file, 'r', encoding='utf-8') as f:
                article = json.load(f)
            
            # note API で投稿
            await post_to_note(article['topic'], article['body'])
            
            # 投稿完了を Discord に報告
            await reaction.message.channel.send(
                f"✅ 投稿完了: {article['topic']}"
            )
            
            # pending ファイルを削除
            os.remove(article_file)
            
        except FileNotFoundError:
            await reaction.message.channel.send(
                "❌ 承認対象の記事が見つかりません"
            )

async def post_to_note(title: str, body: str):
    """note API で投稿"""
    headers = {
        "Authorization": f"Bearer {os.getenv('NOTE_API_KEY')}",
        "Content-Type": "application/json"
    }
    payload = {
        "title": title,
        "body": body,
        "published": True
    }
    async with aiohttp.ClientSession() as session:
        async with session.post(
            "https://note.com/api/v2/notes",
            json=payload,
            headers=headers
        ) as resp:
            if resp.status != 201:
                raise Exception(f"note API error: {resp.status}")

ステップ 5:エラー通知

各ステップで例外が発生したら、自動で通知します。

async def send_error_notification(error_type: str, detail: str):
    """#エラー通知 チャンネルにエラーをレポート"""
    error_channel = bot.get_channel(int(os.getenv('ERROR_CHANNEL_ID')))
    await error_channel.send(
        f"⚠️ エラー発生\n"
        f"**種類**: {error_type}\n"
        f"**詳細**: {detail}\n"
        f"**時刻**: {datetime.now().strftime('%H:%M:%S')}"
    )

# 各スクリプトで try-except でラップ
try:
    proposals = generate_topic_proposals(titles)
    post_to_discord(proposals)
except Exception as e:
    send_error_notification("トピック提案エラー", str(e))

つまづきやすいポイントと対処法

絵文字リアクション検知が失敗する

原因: emoji の Unicode や Discord のキャッシュ問題。

対処:

# リアクション検知をデバッグ
@commands.Cog.listener()
async def on_reaction_add(self, reaction, user):
    print(f"リアクション: {reaction.emoji} (タイプ: {type(reaction.emoji)})")
    print(f"比較: {str(reaction.emoji)} == ✅ ? {str(reaction.emoji) == ''}")

print() で実際に来ているリアクションを確認し、条件式に合わせてください。

note API の認証エラー

原因: トークンの有効期限切れ、スコープ不足。

対処: note API ドキュメントで「投稿作成」のスコープが含まれているか確認。トークン再発行も検討してください。

スケジュール実行されない

原因: ローカル環境でのスクリプト実行は、PC シャットダウンで停止。

対処: GitHub Actions、AWS Lambda、Google Cloud Functions などのサーバーレス環境で「毎朝 8 時に実行」を設定します。

# .github/workflows/schedule.yml
name: Daily Topic Proposal
on:
  schedule:
    - cron: '0 8 * * *'  # 毎日 8:00 UTC
jobs:
  run:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Run topic proposal
        env:
          DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN }}
          CLAUDE_API_KEY: ${{ secrets.CLAUDE_API_KEY }}
        run: python scripts/daily_topic.py

まとめ

Discord と Claude API を組み合わせることで、非エンジニアでも段階的に自動化システムを構築できます

  • トピック提案:過去記事の分析で毎日のネタ出しを自動化
  • 本文生成:文体を学習した AI が記事を作成
  • 人間承認:絵文字ポチで品質を保証
  • 自動投稿:note API で確定内容を投稿

最初は「Discord に記事を投稿する」だけで始めて、安定してから「自動投稿」に進める段階的なアプローチが失敗を減らします。


さらに詳しい実装手順は note で公開中

この記事では実装の要点のみ紹介しました。完全なスクリプト・プロンプト全文・運用ノウハウは以下の note で公開しています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?