doodle-on-web

自分で調べたことや、仕事の中で質問されたことなどをまとめています。

Claude Codeでブログ自動化システムを構築した話

スポンサーリンク

この記事でわかること

  • Claude Code を使ってブログ記事の生成〜投稿を半自動化する方法
  • GitHub Actions × はてなブログ AtomPub API の連携手順
  • 実際に構築したシステムの構成と工夫したポイント

きっかけ:「書く」より「仕組みを作る」が先だった

実はこの記事自体が、自動化システムで投稿された最初の記事です。

ブログを定期的に更新したいと思いつつ、ネタ出しと執筆の両方を毎週こなすのは現実的に難しい。そこで発想を変えました。

「自分が書くのではなく、書ける仕組みを作ればいい」

Claude Code は Anthropic が開発した AI コーディングアシスタントで、ターミナルから自然言語で指示するだけでファイル生成・編集・コマンド実行まで行えるツールです。単なるコード補完にとどまらず、「システムを設計して実装する」レベルのタスクを任せられるのが特徴です。

この Claude Code を使って、記事生成から投稿までのパイプラインを構築したのが今回の取り組みです。


システム全体の構成

topics/queue.txt に投稿テーマを追加
        ↓
Claude Code が drafts/ に記事を生成
        ↓
人間がレビュー・修正
        ↓
articles/ に移動して git push
        ↓
GitHub Actions が検知
        ↓
はてなブログに自動投稿
        ↓
posted/history.json に記録

人間の作業は「レビューして push する」だけ。記事生成と投稿はすべて自動化されています。

リポジトリ構成

blog-posts/
├── .github/workflows/post-on-push.yml  # 自動投稿 CI
├── drafts/          # AI 生成の下書き置き場
├── articles/        # レビュー済み・投稿待ち
├── posted/
│   └── history.json # 多重投稿防止の履歴
├── topics/
│   └── queue.txt    # 投稿テーマ一覧
├── prompts/
│   └── system_prompt.txt  # Claude への指示
├── post_to_hatena.py      # 投稿スクリプト
└── test_post.py           # 動作確認スクリプト

Step 1: Claude Code に記事を生成させる

topics/queue.txt にテーマを追加しておき、Claude Code に依頼するだけです。

topics/queue.txt の先頭テーマで drafts/ に記事を生成して。
prompts/system_prompt.txt の指示に従ってください。

Claude Code はシステムプロンプトの内容(読者層・構成ルール・フロントマター形式)を守りながら、Markdown 形式で記事を生成してくれます。

system_prompt.txt のポイント

- 対象読者: 忙しいビジネスパーソン(20〜40代)
- 文字数: 1500〜3000字程度
- 冒頭に「この記事でわかること」を箇条書きで提示
- 見出し・箇条書きを活用してスキャンしやすく

この指示があることで、毎回ブレのない品質の下書きが生成されます。


Step 2: GitHub Actions で自動投稿する

.github/workflows/post-on-push.yml の核心部分はこちらです。

on:
  push:
    paths:
      - 'articles/**.md'   # articles/ への push だけをトリガー

jobs:
  post:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2   # 差分検出に必要

      - name: 変更ファイルを取得
        id: changed-files
        run: |
          # 新規追加ファイルと変更ファイルを区別して取得
          ADDED=$(git diff --name-only --diff-filter=A HEAD~1 HEAD -- 'articles/**.md' | tr '\n' ' ')
          MODIFIED=$(git diff --name-only --diff-filter=M HEAD~1 HEAD -- 'articles/**.md' | tr '\n' ' ')
          echo "added=$ADDED" >> $GITHUB_OUTPUT
          echo "modified=$MODIFIED" >> $GITHUB_OUTPUT

      - name: はてなブログに投稿・更新
        env:
          HATENA_ID: ${{ secrets.HATENA_ID }}
          HATENA_API_KEY: ${{ secrets.HATENA_API_KEY }}
          HATENA_BLOG_DOMAIN: ${{ secrets.HATENA_BLOG_DOMAIN }}
        run: |
          for FILE in ${{ steps.changed-files.outputs.added }}; do
            python post_to_hatena.py "$FILE"          # 新規投稿
          done
          for FILE in ${{ steps.changed-files.outputs.modified }}; do
            python post_to_hatena.py "$FILE" --update # 既存記事を更新
          done

articles/ 以外のファイルを push しても発火しないので、誤投稿の心配がありません。


Step 3: はてなブログ AtomPub API への投稿

post_to_hatena.py では、Markdown のフロントマターを解析してタイトル・タグを取得し、XML 形式で API に送信します。

import frontmatter
import requests

def post_to_hatena(title, content, tags, category):
    xml_body = f"""<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom"
       xmlns:app="http://www.w3.org/2007/app">
  <title>{title}</title>
  <content type="text/x-markdown">{content}</content>
  {''.join(f'<category term="{t}" />' for t in tags)}
  <app:control><app:draft>no</app:draft></app:control>
</entry>"""

    response = requests.post(
        f"https://blog.hatena.ne.jp/{HATENA_ID}/{BLOG_DOMAIN}/atom/entry",
        auth=(HATENA_ID, API_KEY),
        data=xml_body.encode("utf-8"),
        headers={"Content-Type": "application/atom+xml; charset=utf-8"},
    )
    return response  # 201 なら成功

多重投稿防止と記事更新の仕組み

同じファイルを誤って再 push しても二重投稿されないよう、posted/history.json でファイル名と entry_id を管理しています。

{
  "posted": [
    {
      "file": "my-article.md",
      "title": "記事タイトル",
      "url": "https://yourname.hatenablog.com/entry/...",
      "entry_id": "tag:blog.hatena.ne.jp,2013:blog-xxx-12345",
      "posted_at": "2024-01-15T10:30:00+00:00",
      "updated_at": "2024-01-20T08:00:00+00:00"
    }
  ]
}

GitHub Actions は --diff-filter で新規追加(A)と変更(M)を区別します。変更ファイルは --update フラグ付きで実行され、AtomPub API の PUT エンドポイントで既存記事を上書き更新します。ローカルから手動で更新する場合は次のように実行できます。

python post_to_hatena.py articles/my-article.md --update

工夫したポイント 3 選

1. drafts → articles の「人間レビュー」を挟む

完全自動化も技術的には可能ですが、あえて人間のチェックを入れる設計にしました。Claude が生成した記事には独自の視点や体験談が不足しがちです。レビュー時に数行の個人的なコメントを追加するだけで、記事の品質が大きく上がります。

2. topics/queue.txt でテーマを一元管理

書きたいネタが思いついたらすぐ queue.txt に追記。投稿済みのテーマは自動で削除されるので、残っているものが「未投稿」という状態が一目でわかります。

3. テストスクリプトで安全に動作確認

本番投稿の前に test_post.py --dry-run で API 接続を確認、--draft で下書き投稿テストができる仕組みを用意しました。いきなり本番環境で試してエラーになるリスクを減らせます。


API コストの目安

Claude API の料金は使用モデルによって異なりますが、ブログ記事1本(約2,000字)あたりの目安は以下の通りです。

モデル 入力トークン 出力トークン 合計(概算)
claude-sonnet-4-6 約500トークン 約1,500トークン 約2〜3円
claude-haiku-4-5 約500トークン 約1,500トークン 約0.3〜0.5円

月に8本投稿しても 20〜25円程度。コスト面での障壁はほぼありません。なお、system_prompt.txt を短く保つことで入力トークンを抑えられます。


実際に運用してみて

このシステムを動かし始めて感じたメリットは次の通りです。

観点 Before After
記事執筆時間 2〜3 時間/本 レビュー 30 分/本
月間更新頻度 1〜2 本 4〜8 本(週1ペース)
ネタ切れ よく発生 queue.txt にストック
投稿忘れ たまにある push するだけ、自動化

完全に「楽になった」とは言い切れませんが、「書くことへの心理的ハードル」が大幅に下がったのが一番の効果です。


まとめ

Claude Code を活用したブログ自動化システムの構成をまとめると:

  1. テーマ管理: topics/queue.txt にネタを溜める
  2. 記事生成: Claude Code に依頼 → drafts/ に MD を生成
  3. レビュー: 人間がチェック・加筆して articles/ へ移動
  4. 自動投稿: git push → GitHub Actions → はてなブログ API
  5. 履歴管理: posted/history.json で多重投稿を防止

構築にかかった時間は半日程度。それ以降は毎週の更新コストが大幅に下がっています。ブログ更新を習慣化したいビジネスパーソンには、ぜひ試してみてほしい仕組みです。


あなたはどう自動化していますか?

ブログや情報発信の「続かない理由」は人それぞれだと思います。

  • ネタが思いつかない
  • 書く時間が取れない
  • 投稿作業が面倒

このシステムはその3つすべてにアプローチしていますが、あなたが一番困っているのはどれでしょうか?コメントや SNS で教えてもらえると、次の記事のヒントになります。また「こんな自動化もやってみた」という事例があれば、ぜひシェアしてください。一緒に仕組みを育てていきましょう。


次回:GitHub Actions で毎週自動投稿する仕組みを作った


(2026-03-23 更新: ワークフローコードを記事更新対応版に修正しました)