Claude Code CLIで生成AIブログ記事を自動生成する手順

Claude Code CLIで生成AIブログ記事を自動生成する手順

「生成AIでブログ記事を自動生成したいが、APIキーの発行や課金管理まではやりたくない」という人向けの記事です。使うのは Claude Code CLI(claude -p)と Python の subprocess だけ。私はこの構成で記事生成をバッチ化して自動実行していて、APIキーは使っていません(ログイン済みのCLIセッションをそのまま呼ぶ形です)。アイキャッチ画像は Stable Diffusion WebUI の HTTP API から生成する構成にできます。

全体像:3つの工程に分ける

工程 使うもの 出力
1. 記事の生成 Claude Code CLI claude -p JSON(title / slug / body_md など)
2. ファイル書き出し Python 標準ライブラリ content/posts/<slug>.md
3. アイキャッチ生成 Stable Diffusion WebUI(--api PNG ファイル

設計のポイントは、生成AIに Markdown ファイルを直接書かせないことです。構造化した JSON で受け取り、ファイル名・保存先・front matter は Python 側で決めます。こうするとスラッグの形式違反や上書き事故をコードで弾けます。

1. まず claude -p を手で1回叩く

スクリプトを書く前に、CLI の出力がどんな形で返るかを目で確認します。

claude --version
claude -p "1行で自己紹介して"
claude -p "JSONだけを返して。キーはokのみ" --output-format json

-p--print)は対話セッションを開かずに1回実行して終了するモードです。--output-format json を付けると、標準出力に返るのはメタ情報を含むオブジェクトで、モデルの返答本文は result フィールドに文字列として入る仕様です(執筆時点)。つまり本文の JSON を取り出すには2段パースになります。フィールド名や階層はバージョンによって変わる可能性があります。

オプション 用途
-p / --print 非対話で1回だけ実行
--output-format json メタ情報つきの JSON で受け取る
--model 使うモデルを指定する
--append-system-prompt 執筆ルールなど共通指示を追加する

オプション名や返却フィールドはバージョンで変わりうるので、組み込む前に claude -p --help と生の標準出力を必ず自分で確認してください。

2. Python の subprocess からバッチで呼ぶ

import json, re, subprocess

CLAUDE = ["claude", "-p", "--output-format", "json"]

def run_claude(prompt: str, timeout: int = 900) -> str:
    proc = subprocess.run(
        CLAUDE, input=prompt, capture_output=True, text=True, timeout=timeout
    )
    if proc.returncode != 0:
        raise RuntimeError(f"claude failed ({proc.returncode}): {proc.stderr[:300]}")
    return json.loads(proc.stdout)["result"]  # ← 返答本文(文字列)

FENCE = re.compile(r"^\s*```(?:json)?\s*|\s*```\s*$")

def parse_article(text: str) -> dict:
    return json.loads(FENCE.sub("", text.strip()))

注意点は3つです。

  • timeout を必ず付ける。長い記事だと数分かかることがあります(所要時間は環境によって異なります)。
  • shell=True は使わず、リストで引数を渡す。プロンプトに含まれる記号がシェルに解釈されません。
  • 長いプロンプトは引数ではなく input= で標準入力から渡す。コマンドライン長の上限を避けられます。

3. JSON を Markdown ファイルに書き出す

import datetime, pathlib

def write_post(data: dict, out_dir="content/posts") -> pathlib.Path:
    slug = data["slug"]
    if not re.fullmatch(r"[a-z0-9-]{3,60}", slug):
        raise ValueError(f"invalid slug: {slug}")
    path = pathlib.Path(out_dir) / f"{slug}.md"
    if path.exists():
        raise FileExistsError(path)  # 上書き事故を防ぐ
    fm = {
        "title": data["title"],
        "description": data["description"],
        "date": datetime.date.today().isoformat(),
        "tags": data["tags"],
    }
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(
        "---\n" + json.dumps(fm, ensure_ascii=False, indent=2) + "\n---\n\n" + data["body_md"],
        encoding="utf-8",
    )
    return path

front matter を JSON で書いているのは、日本語タイトルに含まれるコロンや引用符を自分でエスケープしなくて済むからです。ただし解釈できるかは使っている Markdown パーサ次第なので、通らない場合は YAML で書き出してください。バッチは並列にせず、トピックのリストを逐次で回すほうが失敗箇所を切り分けやすいと考えられます。

4. アイキャッチを Stable Diffusion WebUI の API で作る

WebUI を API 付きで起動します。

./webui.sh --api --opt-split-attention
import base64, requests

def make_thumbnail(prompt: str, out: pathlib.Path) -> None:
    payload = {
        "prompt": prompt,
        "negative_prompt": "text, letters, watermark, logo",
        "sampler_name": "LCM",
        "steps": 6,
        "cfg_scale": 1.5,
        "width": 512,
        "height": 768,
        "seed": -1,
    }
    r = requests.post("http://127.0.0.1:7860/sdapi/v1/txt2img", json=payload, timeout=300)
    r.raise_for_status()
    out.write_bytes(base64.b64decode(r.json()["images"][0]))

サンプラー LCM / Steps 6 / CFG 1.5 は LCM-LoRA を読み込んでいる前提の設定で、この組み合わせだと少ないステップ数で生成できます。LoRA はプロンプト側に記法で指定します。

Mac(Apple Silicon)で大きめの画像を出す場合は注意が必要です。1024×1536 を一発で生成しようとすると --opt-sdp-attention ではバッファ確保のエラーになり、--opt-split-attention に変更して回避しました。Hires fix も同様に通らなかったため、txt2img 512×768 → ESRGAN で2倍 → img2img でディテール強化、の3ステップに分けています。同じ現象が出るかどうかは、搭載メモリ量や WebUI・PyTorch のバージョンによって異なります。アイキャッチ用途なら 512×768 のままでも実用になると考えられます。

つまずきやすい点

  • JSON が取り出せない--output-format json の戻り値はエンベロープなので、result を取り出してからもう一度パースします。モデルがコードフェンスで囲むことがあるため、剥がす処理を入れておきます。失敗時は生の標準出力をログに残すと原因を特定しやすくなります。
  • 途中で止まるreturncodestderr を必ず見ること。例外を握りつぶすと、空の記事ファイルだけが増えます。
  • 事実が混ざる:生成AIは数値や固有名詞をもっともらしく埋めてきます。プロンプトに「一次情報にない数値・実績値は書かない」と明記したうえで、公開前に数値・URL・製品名は人間が確認してください。ここは自動化しないほうが安全だと考えています。
  • 同じスラッグが再生成される:上書きを禁止し、既存ファイルがあれば例外で止める設計にしておきます。

まとめ

記事生成の自動化でいちばん重要なのは、生成AIに「構造化データを返させる」ことと、ファイル操作を自分のコードで持つことです。まずは claude -p "テスト" --output-format json を手元で1回実行し、標準出力の生の形を自分の目で確認するところから始めてください。そこが分かれば、あとは今回のスクリプトの run_claude に差し込むだけです。

よくある質問

Claude Code CLIの自動生成にAPIキーは必要ですか?

私はAPIキーを使わず、ログイン済みのCLIをPythonのsubprocessから呼ぶ構成で運用しています。認証方式や利用条件は執筆時点の仕様なので、実際に組む前に公式ドキュメントで最新の扱いを確認してください。

生成AIが返すJSONがパースできないときはどうすればいいですか?

原因はたいてい2つです。`--output-format json`の戻り値はメタ情報を含むエンベロープなので`result`から本文を取り出す必要があること、返答がコードフェンスで囲まれていることです。剥がしてから再パースし、失敗時は生出力をログに残してください。

記事本文と一緒にアイキャッチ画像も自動生成できますか?

できます。Stable Diffusion WebUIを`--api`付きで起動し、`/sdapi/v1/txt2img`にPOSTすればbase64で画像が返るので、記事生成と同じスクリプトから呼べます。生成用の英語プロンプトもJSONの1フィールドとして一緒に返させると楽です。

自動生成した記事はそのまま公開して大丈夫ですか?

数値・固有名詞・URLは生成AIがもっともらしく埋めてくることがあるため、公開前に人間の確認をおすすめします。文章の骨組みや構成は自動化し、事実確認だけ手作業に残す切り分けが現実的だと考えています。