2026.02.02

[AIブログライター] 第3回:Claude code+n8n SkillsのバイブコーディングでCore-Satellite記事を自動生成

こんにちは。AI研究開発室のK.S.です。

お久しぶりです。

ブログを書くのは手間がかかりませんか? ネタがあっても、情報収集、要約、図の作成、フォーマット調整などの作業が待っています。特に外国人の私は日本語チェックも必須です(脳内の赤ペンが忙しい)。AI時代だからこそ、精度を保ちつつ効率化できる領域は広がっているはずです。

ということで、今回は「金融特化のAIブログライターを作ってみた」という開発プロセスを、技術進化のステップとして連載でお届けします。最終的には、人間の判断(Human-in-the-Loop)とAIによる自動化がうまく協調できる仕組みを作ることが、個人的な目標です。

この連載では、金融特化のAIブログライターを段階的に作っていきます。

第3回は「Claude code + n8n Skillsでオートメーションワークフローを自動生成し、Core-Satellite記事を自動生成する」回です。

AIブログライター・シリーズのご紹介

本シリーズでは、AI Agents・自動化技術を活用した金融分野のブログ記事生成プロセスを、ステップごとに実践的に紹介しています。

この記事でやること

  • Claude n8n-skillsでn8nワークフローを自動生成
  • できるだけ自分でコードを書かず、Claude codeとのやりとりだけで完結するようにする
  • AIが書いたブログは手を入れずにそのまま共有、画像のURLだけ修正
  • (AI経由で)Core-Satellite戦略を紹介

使う技術

  • claude skills:Claude codeで作成・登録できる「タスク専用スキル」
  • n8n:ノーコード自動化ツール。AIや外部サービスをつなぎ、ワークフロー(自動処理の流れ)を直感的に構築・実行

技術の紹介と実践

Claude code skillsとは

Claude code skillsは、Anthropic社のAIであるClaudeが実行できる「タスク特化型のコード(スキル)」を、ユーザー自身が作成して登録できる仕組みです。これにより、繰り返し作業を自動化したり、独自の計算や外部API連携などの処理を簡単にAIエージェントに担わせることができます。例えば「データから表を作る」「特定の分析指標を計算する」といった作業を、スキルの一つとして登録し、好きなタイミングで呼び出すことができます。

Claude code skillsを利用することで、コーディングの知識がそれほどなくても、高度な自動処理をAIにアウトソースでき、AIによるワークフロー自動化の幅を大きく広げることができます。

n8n-skillsとは

n8n-skillsは、Claude Codeに「n8nワークフローの作り方」を教える専用のスキルセットです。n8n-mcpのMCPサーバーと連携し、AIがn8nのワークフローをプログラム的に構築できるようになります。

n8n-skillsはオープンソースプロジェクトとして公開されており、ドキュメントもきちんと整備されているので、GitHubを参考にしてください。

skillsを設定したら、下記のsettings.jsonのように確認できます。

Claude Skills設定

自動生成されたn8nワークフロー:初期

Claude codeのCLIに下記の指示を入力します。

create n8n workflow for writing a new technology about stock trading         

すると、Claudeがskillsを参照しながら、n8nワークフローを自動生成します。

claude code 0

終わったら、trading_strategy_blog_workflow.jsonが自動生成されます。(上記の設定画像にある通りです)

このjsonファイルをn8nにimportするだけで、自動生成されたn8nワークフローの作成が完了です。

n8nワークフロー例

しかし、Claude codeとのやりとりやn8nワークフローをよく見ると、調査時にSerpAPIを利用するよう推奨されていましたが、このAPIを使いたくなかったので、Claude codeに修正を指示しました。

また、最初の結果はHTML出力はあまり美しくないので、もう少し綺麗な形にしてくださいと指示しました。

その結果、下記のように最終的なn8nワークフローが生成されました。

自動生成されたn8nワークフロー:最終

自動生成されたn8nワークフローの全体像は下記です。

n8nワークフロー詳細

これで、n8nのブロックを一つずつ手作業で作成する必要なく、ワークフローを簡単に作成できました。

 

n8nワークフローのjsonファイルは下記です。興味がある方はファイルをコピーしてn8nにimportすれば動くはずですが、OpenAI APIキーが必要ですので、設定方法は第1回を参考にしてください。

(クリックでワークフローJSON例を開く)
    {
        "name": "Trading Strategy Blog Writer (Japanese) v2",
        "nodes": [
          {
            "parameters": {
              "formTitle": "トレード戦略ブログ生成",
              "formDescription": "ブログにしたいトレード戦略のトピックを入力してください",
              "formFields": {
                "values": [
                  {
                    "fieldLabel": "戦略トピック",
                    "fieldType": "text",
                    "requiredField": true,
                    "placeholder": "例:モメンタム取引、平均回帰、ブレイクアウト戦略"
                  },
                  {
                    "fieldLabel": "対象読者",
                    "fieldType": "dropdown",
                    "requiredField": true,
                    "fieldOptions": {
                      "values": [
                        { "option": "初心者トレーダー" },
                        { "option": "中級トレーダー" },
                        { "option": "上級トレーダー" }
                      ]
                    }
                  },
                  {
                    "fieldLabel": "ブログの長さ",
                    "fieldType": "dropdown",
                    "requiredField": true,
                    "fieldOptions": {
                      "values": [
                        { "option": "短め(500〜800字)" },
                        { "option": "標準(1000〜1500字)" },
                        { "option": "長め(2000字以上)" }
                      ]
                    }
                  }
                ]
              },
              "options": {}
            },
            "id": "form-trigger-1",
            "name": "ブログリクエストフォーム",
            "type": "n8n-nodes-base.formTrigger",
            "typeVersion": 2.1,
            "position": [240, 300],
            "webhookId": "trading-blog-form"
          },
          {
            "parameters": {
              "promptType": "define",
              "text": "あなたはトレード戦略に精通した金融ブログライターです。以下の情報に基づいて、日本語でブログ記事を執筆してください。\n\n**トピック:** {{ $json['戦略トピック'] }}\n**対象読者:** {{ $json['対象読者'] }}\n**希望の長さ:** {{ $json['ブログの長さ'] }}\n\n## ブログの要件:\n1. SEOに強い魅力的なタイトルを作成\n2. 読者の悩みに寄り添う導入文から始める\n3. 戦略をステップバイステップで分かりやすく説明\n4. 具体的なエントリー・イグジットの例を含める\n5. リスク管理のセクションを設ける\n6. よくある失敗パターンを紹介\n7. 実践的なまとめと次のアクションで締める\n8. 見出し、箇条書きを使って読みやすく構成\n\n対象読者のレベルに合わせたトーンで、完全なブログ記事を日本語で書いてください。",
              "options": {
                "systemMessage": "あなたは10年以上の経験を持つプロの金融コンテンツライターです。トレーダーの成長を支援する、明確で魅力的かつ正確なコンテンツを作成します。投資助言ではなく、教育的なコンテンツを提供します。必ず日本語で回答してください。"
              }
            },
            "id": "ai-writer-1",
            "name": "AIブログライター",
            "type": "@n8n/n8n-nodes-langchain.agent",
            "typeVersion": 1.7,
            "position": [480, 300],
            "notesInFlow": true,
            "notes": "トレード戦略のブログ下書きを日本語で作成"
          },
          {
            "parameters": {
              "model": "gpt-5-nano",
              "options": {}
            },
            "id": "openai-model-writer",
            "name": "OpenAI Model (Writer)",
            "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
            "typeVersion": 1,
            "position": [480, 520],
            "credentials": {
              "openAiApi": {
                "id": "YOUR_OPENAI_CREDENTIAL_ID",
                "name": "OpenAI API"
              }
            }
          },
          {
            "parameters": {
              "assignments": {
                "assignments": [
                  {
                    "id": "draft-blog",
                    "name": "draftBlog",
                    "value": "={{ $json.output }}",
                    "type": "string"
                  },
                  {
                    "id": "topic",
                    "name": "topic",
                    "value": "={{ $('ブログリクエストフォーム').item.json['戦略トピック'] }}",
                    "type": "string"
                  },
                  {
                    "id": "audience",
                    "name": "audience",
                    "value": "={{ $('ブログリクエストフォーム').item.json['対象読者'] }}",
                    "type": "string"
                  }
                ]
              },
              "options": {}
            },
            "id": "set-draft-1",
            "name": "下書き保存",
            "type": "n8n-nodes-base.set",
            "typeVersion": 3.4,
            "position": [700, 300]
          },
          {
            "parameters": {
              "promptType": "define",
              "text": "あなたは金融コンテンツ専門の編集者です。以下のトレード戦略ブログ記事を編集・改善してください。\n\n**対象読者:** {{ $json.audience }}\n\n**下書き:**\n{{ $json.draftBlog }}\n\n## 編集チェックリスト:\n1. **正確性**: トレードの概念が正しく説明されているか確認\n2. **明瞭さ**: 複雑な説明を分かりやすく簡潔に\n3. **文法・文体**: 誤字脱字の修正、文章の流れを改善\n4. **構成**: アイデアの論理的な流れを確保\n5. **エンゲージメント**: 導入部と繋ぎを強化\n6. **SEO**: タイトルを最適化、適切な見出しを追加\n7. **免責事項**: 教育目的であり投資助言ではない旨を追加\n8. **CTA**: 強力な結論を確保\n\n編集・校正済みの完成版ブログ記事を日本語で提供してください。\n\n**重要**: 出力は必ず以下のMarkdown形式に従ってください:\n- タイトルは `# タイトル` の形式\n- セクション見出しは `## 見出し` の形式\n- サブ見出しは `### サブ見出し` の形式\n- 箇条書きは `- 項目` の形式(インデントなし)\n- 番号付きリストは `1. 項目` の形式\n- 強調は `**テキスト**` の形式\n- 段落は空行で区切る",
              "options": {
                "systemMessage": "あなたは細部にこだわる金融コンテンツ編集者です。著者の声を維持しながら、明瞭さ、正確さ、エンゲージメントを向上させます。すべてのトレードコンテンツが教育的で適切な免責事項を含むようにします。必ず日本語で回答してください。出力は必ず標準的なMarkdown形式で整形してください。"
              }
            },
            "id": "ai-editor-1",
            "name": "AIブログ編集者",
            "type": "@n8n/n8n-nodes-langchain.agent",
            "typeVersion": 1.7,
            "position": [920, 300],
            "notesInFlow": true,
            "notes": "ブログを編集・校正して完成させる"
          },
          {
            "parameters": {
              "model": "gpt-5-nano",
              "options": {}
            },
            "id": "openai-model-editor",
            "name": "OpenAI Model (Editor)",
            "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
            "typeVersion": 1,
            "position": [920, 520],
            "credentials": {
              "openAiApi": {
                "id": "YOUR_OPENAI_CREDENTIAL_ID",
                "name": "OpenAI API"
              }
            }
          },
          {
            "parameters": {
              "jsCode": "const blog = $input.first().json.output;\nconst topic = $('下書き保存').first().json.topic;\nconst audience = $('下書き保存').first().json.audience;\nconst now = new Date();\nconst timestamp = now.toISOString().slice(0, 10);\n\n// Improved Markdown to HTML conversion\nfunction convertMarkdownToHtml(markdown) {\n  const lines = markdown.split('\\n');\n  const result = [];\n  let inList = false;\n  let listType = null; // 'ul' or 'ol'\n  let inParagraph = false;\n  let paragraphContent = [];\n  \n  function closeParagraph() {\n    if (inParagraph && paragraphContent.length > 0) {\n      result.push('<p>' + paragraphContent.join(' ') + '</p>');\n      paragraphContent = [];\n      inParagraph = false;\n    }\n  }\n  \n  function closeList() {\n    if (inList) {\n      result.push(listType === 'ol' ? '</ol>' : '</ul>');\n      inList = false;\n      listType = null;\n    }\n  }\n  \n  function processInlineMarkdown(text) {\n    return text\n      .replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')\n      .replace(/\\*(.+?)\\*/g, '<em>$1</em>')\n      .replace(/`(.+?)`/g, '<code>$1</code>');\n  }\n  \n  for (let i = 0; i < lines.length; i++) {\n    const line = lines[i];\n    const trimmedLine = line.trim();\n    \n    // Empty line - close current blocks\n    if (trimmedLine === '') {\n      closeParagraph();\n      closeList();\n      continue;\n    }\n    \n    // H1\n    if (trimmedLine.startsWith('# ') && !trimmedLine.startsWith('## ')) {\n      closeParagraph();\n      closeList();\n      const text = processInlineMarkdown(trimmedLine.slice(2));\n      result.push('<h1>' + text + '</h1>');\n      continue;\n    }\n    \n    // H2\n    if (trimmedLine.startsWith('## ') && !trimmedLine.startsWith('### ')) {\n      closeParagraph();\n      closeList();\n      const text = processInlineMarkdown(trimmedLine.slice(3));\n      result.push('<h2>' + text + '</h2>');\n      continue;\n    }\n    \n    // H3\n    if (trimmedLine.startsWith('### ')) {\n      closeParagraph();\n      closeList();\n      const text = processInlineMarkdown(trimmedLine.slice(4));\n      result.push('<h3>' + text + '</h3>');\n      continue;\n    }\n    \n    // Unordered list item\n    if (trimmedLine.startsWith('- ') || trimmedLine.startsWith('* ')) {\n      closeParagraph();\n      if (!inList || listType !== 'ul') {\n        closeList();\n        result.push('<ul>');\n        inList = true;\n        listType = 'ul';\n      }\n      const text = processInlineMarkdown(trimmedLine.slice(2));\n      result.push('<li>' + text + '</li>');\n      continue;\n    }\n    \n    // Ordered list item\n    const olMatch = trimmedLine.match(/^(\\d+)\\.\\s+(.+)$/);\n    if (olMatch) {\n      closeParagraph();\n      if (!inList || listType !== 'ol') {\n        closeList();\n        result.push('<ol>');\n        inList = true;\n        listType = 'ol';\n      }\n      const text = processInlineMarkdown(olMatch[2]);\n      result.push('<li>' + text + '</li>');\n      continue;\n    }\n    \n    // Regular paragraph text\n    closeList();\n    inParagraph = true;\n    paragraphContent.push(processInlineMarkdown(trimmedLine));\n  }\n  \n  // Close any remaining open blocks\n  closeParagraph();\n  closeList();\n  \n  return result.join('\\n');\n}\n\nconst htmlContent = convertMarkdownToHtml(blog);\n\nconst html = `<!DOCTYPE html>\n<html lang=\"ja\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta name=\"description\" content=\"${topic} - トレード戦略ブログ\">\n    <title>${topic} | トレード戦略ブログ</title>\n    <style>\n        :root {\n            --primary-color: #1a365d;\n            --secondary-color: #2c5282;\n            --accent-color: #ed8936;\n            --bg-color: #f7fafc;\n            --text-color: #2d3748;\n        }\n        * { margin: 0; padding: 0; box-sizing: border-box; }\n        body {\n            font-family: 'Hiragino Kaku Gothic ProN', 'Hiragino Sans', Meiryo, sans-serif;\n            line-height: 1.8;\n            color: var(--text-color);\n            background-color: var(--bg-color);\n        }\n        .container { max-width: 800px; margin: 0 auto; padding: 40px 20px; }\n        header {\n            background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));\n            color: white;\n            padding: 60px 20px;\n            text-align: center;\n        }\n        header h1 { font-size: 2.2rem; margin-bottom: 15px; }\n        .meta { font-size: 0.9rem; opacity: 0.9; }\n        .meta span { margin: 0 10px; }\n        article {\n            background: white;\n            padding: 40px;\n            border-radius: 8px;\n            box-shadow: 0 2px 10px rgba(0,0,0,0.1);\n            margin-top: -30px;\n        }\n        article p { margin-bottom: 1.5em; text-align: justify; }\n        article h1 {\n            color: var(--primary-color);\n            font-size: 1.8rem;\n            margin: 0 0 1.5em;\n            text-align: center;\n        }\n        article h2 {\n            color: var(--primary-color);\n            font-size: 1.5rem;\n            margin: 2em 0 1em;\n            padding-bottom: 10px;\n            border-bottom: 3px solid var(--accent-color);\n        }\n        article h3 {\n            color: var(--secondary-color);\n            font-size: 1.25rem;\n            margin: 1.5em 0 0.8em;\n        }\n        article ul, article ol {\n            margin: 1em 0 1.5em 1.5em;\n        }\n        article li {\n            margin-bottom: 0.5em;\n        }\n        article strong {\n            color: var(--primary-color);\n        }\n        article code {\n            background: #edf2f7;\n            padding: 0.2em 0.4em;\n            border-radius: 3px;\n            font-size: 0.9em;\n        }\n        .note-box {\n            background: #edf2f7;\n            border-left: 4px solid var(--accent-color);\n            padding: 1em 1.5em;\n            margin: 1.5em 0;\n            border-radius: 0 8px 8px 0;\n        }\n        .warning-box {\n            background: #fff5f5;\n            border-left: 4px solid #c53030;\n            padding: 1em 1.5em;\n            margin: 1.5em 0;\n            border-radius: 0 8px 8px 0;\n        }\n        footer {\n            text-align: center;\n            padding: 30px;\n            color: #718096;\n            font-size: 0.85rem;\n        }\n    </style>\n</head>\n<body>\n    <header>\n        <div class=\"container\">\n            <h1>${topic}</h1>\n            <div class=\"meta\">\n                <span>${timestamp}</span>\n                <span>${audience}向け</span>\n            </div>\n        </div>\n    </header>\n    <main class=\"container\">\n        <article>\n${htmlContent}\n        </article>\n    </main>\n    <footer>\n        <p>&copy; ${now.getFullYear()} トレード戦略ブログ | AI生成コンテンツ</p>\n    </footer>\n</body>\n</html>`;\n\nconst filename = `blog_${topic.replace(/[^a-zA-Z0-9ぁ-んァ-ン一-龥]/g, '_')}_${timestamp}.html`;\n\n// Convert to binary for file output\nconst binaryData = Buffer.from(html, 'utf-8');\n\nreturn {\n  json: {\n    filename: filename,\n    topic: topic,\n    audience: audience,\n    createdAt: now.toISOString()\n  },\n  binary: {\n    data: {\n      data: binaryData.toString('base64'),\n      mimeType: 'text/html',\n      fileName: filename\n    }\n  }\n};"
            },
            "id": "code-html-1",
            "name": "HTML生成",
            "type": "n8n-nodes-base.code",
            "typeVersion": 2,
            "position": [1140, 300],
            "notesInFlow": true,
            "notes": "ブログをHTMLファイル形式に変換(改良版)"
          },
          {
            "parameters": {
              "fileName": "={{ $json.filename }}",
              "options": {}
            },
            "id": "write-file-1",
            "name": "HTMLファイル保存",
            "type": "n8n-nodes-base.writeBinaryFile",
            "typeVersion": 1,
            "position": [1360, 300],
            "notesInFlow": true,
            "notes": "HTMLファイルをローカルに保存"
          },
          {
            "parameters": {
              "assignments": {
                "assignments": [
                  {
                    "id": "final-file",
                    "name": "savedFile",
                    "value": "={{ $json.fileName }}",
                    "type": "string"
                  },
                  {
                    "id": "status",
                    "name": "status",
                    "value": "完了",
                    "type": "string"
                  }
                ]
              },
              "options": {}
            },
            "id": "set-final-1",
            "name": "最終出力",
            "type": "n8n-nodes-base.set",
            "typeVersion": 3.4,
            "position": [1580, 300]
          }
        ],
        "connections": {
          "ブログリクエストフォーム": {
            "main": [
              [
                {
                  "node": "AIブログライター",
                  "type": "main",
                  "index": 0
                }
              ]
            ]
          },
          "OpenAI Model (Writer)": {
            "ai_languageModel": [
              [
                {
                  "node": "AIブログライター",
                  "type": "ai_languageModel",
                  "index": 0
                }
              ]
            ]
          },
          "AIブログライター": {
            "main": [
              [
                {
                  "node": "下書き保存",
                  "type": "main",
                  "index": 0
                }
              ]
            ]
          },
          "下書き保存": {
            "main": [
              [
                {
                  "node": "AIブログ編集者",
                  "type": "main",
                  "index": 0
                }
              ]
            ]
          },
          "OpenAI Model (Editor)": {
            "ai_languageModel": [
              [
                {
                  "node": "AIブログ編集者",
                  "type": "ai_languageModel",
                  "index": 0
                }
              ]
            ]
          },
          "AIブログ編集者": {
            "main": [
              [
                {
                  "node": "HTML生成",
                  "type": "main",
                  "index": 0
                }
              ]
            ]
          },
          "HTML生成": {
            "main": [
              [
                {
                  "node": "HTMLファイル保存",
                  "type": "main",
                  "index": 0
                }
              ]
            ]
          },
          "HTMLファイル保存": {
            "main": [
              [
                {
                  "node": "最終出力",
                  "type": "main",
                  "index": 0
                }
              ]
            ]
          }
        },
        "pinData": {},
        "settings": {
          "executionOrder": "v1"
        },
        "staticData": null,
        "tags": [
          { "name": "ブログ", "id": "1" },
          { "name": "トレード", "id": "2" },
          { "name": "AI", "id": "3" }
        ],
        "triggerCount": 0,
        "updatedAt": "2026-01-31T00:00:00.000Z",
        "versionId": "6"
      }
      

 

それでは、どういう感じかを実際にテストしてみましょう。

テスト

ワークフローを実行すると、フォーム入力画面が下記のように表示されます。戦略トピックに「core-satellite」を入力し、対象読者やブログの長さを選んで、Submitボタンを押します。

n8n form

Submitボタンを押すと、n8nのワークフローが自動で実行され、HTMLファイルが生成されます。生成結果は下記のようになります。

AIブログライターの記事

今回は生成された結果は下記のようにHTMLフォーマットも生成されましたので、画像を載せます。

================ START ===========

n8n form
n8n form

================ END ===========

感想

いかがでしょうか?

n8n automationをAIに任せる試みは手応えがあると感じました。今回は画像生成を指示していなかったので、文字のみでブログを作成しましたが、次回は画像生成を指示して、より良いブログを作成も期待できそうです。

作成時間もかからず、とても便利です。(余談ですが、昼ご飯を作る時間のほうが長いですね。)

Core-Satelliteの紹介について、一般的に知られている知識もあり、骨組みは自動で揃いやすいかもしれませんが、実際に実践した参考例を入れると、より良いブログになるかもしれません。

ちなみに、免責事項も自動で生成されてくれました。

まとめ

AIブログライター技術面として、Claude Skillsとn8nを組み合わせた自動化の仕組みにより、誰でも記事制作プロセスを構築できることが確認できました。ノーコード・AI活用の利便性を強く実感できる結果となりました。

また、金融知識の紹介としては、AIを活用してCore-Satellite投資戦略を紹介しました。体系的に整理することで、最新NISA制度の活用にも役立つ実践的な視点を得ることができました。分散とコスト意識、ポートフォリオ設計の重要性が再認識されました。

最後に

グループ研究開発本部 AI研究開発室では、データサイエンティスト/機械学習エンジニアを募集しています。 ビッグデータの解析業務などAI研究開発室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。 皆さんのご応募をお待ちしています。 一緒に勉強しながら楽しく働きたい方のご応募をお待ちしています。

  • Twitter
  • Facebook
  • はてなブックマークに追加

グループ研究開発本部の最新情報をTwitterで配信中です。ぜひフォローください。

 
  • AI研究開発室
  • 大阪研究開発グループ

関連記事