<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>自動化 - CayTech Lab</title>
	<atom:link href="https://caymezon.com/tag/%e8%87%aa%e5%8b%95%e5%8c%96/feed/" rel="self" type="application/rss+xml" />
	<link>https://caymezon.com</link>
	<description></description>
	<lastBuildDate>Thu, 19 Feb 2026 05:34:39 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0.1</generator>

<image>
	<url>https://caymezon.com/wp-content/uploads/2026/01/cropped-CayTechLab-32x32.jpg</url>
	<title>自動化 - CayTech Lab</title>
	<link>https://caymezon.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<atom:link rel='hub' href='https://caymezon.com/?pushpress=hub'/>
	<item>
		<title>Claude Codeセッションログを一括MD化！拡張版エクスポーターで開発記録を資産化</title>
		<link>https://caymezon.com/claude-code-session-exporter/</link>
					<comments>https://caymezon.com/claude-code-session-exporter/#respond</comments>
		
		<dc:creator><![CDATA[caymezon]]></dc:creator>
		<pubDate>Sun, 15 Feb 2026 04:33:55 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Claude]]></category>
		<category><![CDATA[Claude Code]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[ツール開発]]></category>
		<category><![CDATA[自動化]]></category>
		<guid isPermaLink="false">https://caymezon.com/?p=20143</guid>

					<description><![CDATA[<p>Claude Codeで開発を進めると、セッション毎に貴重なやり取りが蓄積されていきます。しかし、そのログファイル（JSONL形式）は人間が読める形式ではありません。この記事では、以前紹介したClaude Chat Ex [&#8230;]</p>
<p>The post <a href="https://caymezon.com/claude-code-session-exporter/">Claude Codeセッションログを一括MD化！拡張版エクスポーターで開発記録を資産化</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Claude Codeで開発を進めると、セッション毎に貴重なやり取りが蓄積されていきます。しかし、そのログファイル（JSONL形式）は人間が読める形式ではありません。この記事では、以前紹介したClaude Chat Exporterを大幅に拡張し、<strong>Claude Codeのセッションログを一括でMarkdown化できる神機能</strong>を実装したツールを紹介します。</p>
<p><!-- ![Claude Codeセッションログのイメージ](images/claude-code-session-logs.jpg) --></p>

  <div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-2" checked><label class="toc-title" for="toc-checkbox-2">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">なぜClaude Codeのセッションログを管理するのか？</a></li><li><a href="#toc2" tabindex="0">以前のツールとの違い</a><ol><li><a href="#toc3" tabindex="0">新機能一覧</a></li><li><a href="#toc4" tabindex="0">Claude CodeのJSONL形式とは？</a></li></ol></li><li><a href="#toc5" tabindex="0">ツールの全機能</a><ol><li><a href="#toc6" tabindex="0">1. 複数ファイル一括処理</a></li><li><a href="#toc7" tabindex="0">2. JSON/JSONL両対応</a></li><li><a href="#toc8" tabindex="0">3. 賢いフィルタリング</a></li><li><a href="#toc9" tabindex="0">4. セッション識別</a></li><li><a href="#toc10" tabindex="0">5. 安全なファイル名生成</a></li></ol></li><li><a href="#toc11" tabindex="0">使い方</a><ol><li><a href="#toc12" tabindex="0">ステップ1: Claude Codeのセッションログを見つける</a></li><li><a href="#toc13" tabindex="0">ステップ2: エクスポーターを配置</a></li><li><a href="#toc14" tabindex="0">ステップ3: 実行</a></li><li><a href="#toc15" tabindex="0">ステップ4: 出力確認</a></li></ol></li><li><a href="#toc16" tabindex="0">ソースコード全文</a><ol><li><a href="#toc17" tabindex="0">exporter.py</a></li><li><a href="#toc18" tabindex="0">exporter.bat（Windows用）</a></li></ol></li><li><a href="#toc19" tabindex="0">コードの詳細解説</a><ol><li><a href="#toc20" tabindex="0">1. JSONL処理のコア部分</a></li><li><a href="#toc21" tabindex="0">2. 賢いフィルタリング</a></li><li><a href="#toc22" tabindex="0">3. システムタグの除去</a></li><li><a href="#toc23" tabindex="0">4. 配列形式のコンテンツ処理</a></li><li><a href="#toc24" tabindex="0">4. セッション識別</a></li><li><a href="#toc25" tabindex="0">5. 複数ファイル処理</a></li></ol></li><li><a href="#toc26" tabindex="0">出力例</a><ol><li><a href="#toc27" tabindex="0">入力: transcript.jsonl（Claude Codeセッション）</a></li><li><a href="#toc28" tabindex="0">出力: my-project_chat_abc12345.md</a></li></ol></li><li><a href="#toc29" tabindex="0">実際の使用場面</a><ol><li><a href="#toc30" tabindex="0">1. プロジェクト開発の記録</a></li><li><a href="#toc31" tabindex="0">2. トラブルシューティングの記録</a></li><li><a href="#toc32" tabindex="0">3. 学習記録</a></li><li><a href="#toc33" tabindex="0">4. 週次レビュー</a></li><li><a href="#toc34" tabindex="0">5. バックアップと保存</a></li></ol></li><li><a href="#toc35" tabindex="0">応用アイデア</a><ol><li><a href="#toc36" tabindex="0">1. 自動分類スクリプト</a></li><li><a href="#toc37" tabindex="0">2. 統計情報の抽出</a></li><li><a href="#toc38" tabindex="0">3. Obsidian/Notion連携</a></li><li><a href="#toc39" tabindex="0">4. 全文検索インデックス</a></li></ol></li><li><a href="#toc40" tabindex="0">トラブルシューティング</a><ol><li><a href="#toc41" tabindex="0">エラー1: JSONDecodeError</a></li><li><a href="#toc42" tabindex="0">エラー2: セッションIDが見つからない</a></li><li><a href="#toc43" tabindex="0">エラー3: メッセージが含まれていません</a></li><li><a href="#toc44" tabindex="0">エラー4: UnicodeDecodeError</a></li></ol></li><li><a href="#toc45" tabindex="0">まとめ</a></li><li><a href="#toc46" tabindex="0">関連記事</a></li><li><a href="#toc47" tabindex="0">GitHubでの管理</a></li></ol>
    </div>
  </div>

<h2><span id="toc1">なぜClaude Codeのセッションログを管理するのか？</span></h2>
<p>Claude Codeでの開発セッションには、通常のチャット以上の価値があります：</p>
<ul>
<li><strong>実装の全プロセス</strong>: 設計から実装、テスト、デバッグまでの完全な記録</li>
<li><strong>コンテキスト付きの会話</strong>: ファイル編集、コマンド実行の履歴と結果</li>
<li><strong>問題解決の軌跡</strong>: エラーとその解決方法の詳細な記録</li>
<li><strong>アーキテクチャ判断</strong>: 技術選定や設計判断の理由</li>
<li><strong>ベストプラクティス</strong>: Claude Codeとの協働で得られた知見</li>
</ul>
<p>これらを適切に管理することで：</p>
<ul>
<li>✅ プロジェクトの開発履歴として参照できる</li>
<li>✅ 同じ問題に遭遇した時の解決策を即座に見つけられる</li>
<li>✅ チーム内でClaude Code活用のノウハウを共有できる</li>
<li>✅ 技術ブログやドキュメントの素材になる</li>
<li>✅ 開発プロセスの振り返りと改善に活用できる</li>
</ul>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BPython%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D22R8A0FYLVMNU%26sprefix%3D%25E6%259C%25AC%2Bpython%252Caps%252C179%26ref%3Dnb_sb_noss_1" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 Python]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"実践Claude Code入門―現場で活用するためのAIコーディングの思考法","b":"技術評論社","t":"","d":"https:\/\/m.media-amazon.com","c_p":"","p":["\/images\/I\/51kkd9SNiVL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4297153548","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4297153548","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/%E5%AE%9F%E8%B7%B5Claude%20Code%E5%85%A5%E9%96%80%E2%80%95%E7%8F%BE%E5%A0%B4%E3%81%A7%E6%B4%BB%E7%94%A8%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AEAI%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E3%81%AE%E6%80%9D%E8%80%83%E6%B3%95\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=%E5%AE%9F%E8%B7%B5Claude%20Code%E5%85%A5%E9%96%80%E2%80%95%E7%8F%BE%E5%A0%B4%E3%81%A7%E6%B4%BB%E7%94%A8%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AEAI%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E3%81%AE%E6%80%9D%E8%80%83%E6%B3%95","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"lxeQQ","s":"s"});</script></p>
<div id="msmaflink-lxeQQ">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p>
<h2><span id="toc2">以前のツールとの違い</span></h2>
<p>以前の記事で紹介した<a href="https://caymezon.com/claude-chat-exporter-tool/">Claude Chat Exporter</a>は、claude.aiのWebチャット用でした。今回の拡張版では、以下の新機能を追加しています：</p>
<h3><span id="toc3">新機能一覧</span></h3>
<table>
<thead>
<tr>
<th>機能</th>
<th>旧バージョン</th>
<th>新バージョン</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>対応形式</strong></td>
<td>JSON（conversations.json）のみ</td>
<td>JSON + JSONL（Claude Code）</td>
</tr>
<tr>
<td><strong>ファイル処理</strong></td>
<td>単一ファイル</td>
<td>複数ファイル一括処理</td>
</tr>
<tr>
<td><strong>入力方法</strong></td>
<td>ダイアログのみ</td>
<td>ダイアログ + ドラッグ&ドロップ + コマンドライン</td>
</tr>
<tr>
<td><strong>セッション識別</strong></td>
<td>チャットタイトルのみ</td>
<td>セッションID + フォルダ名</td>
</tr>
<tr>
<td><strong>メッセージフィルタ</strong></td>
<td>なし</td>
<td>システムメッセージ、ツール結果を自動除外</td>
</tr>
<tr>
<td><strong>コンテンツ処理</strong></td>
<td>シンプルテキスト</td>
<td>配列形式、複雑な構造に対応</td>
</tr>
</tbody>
</table>
<h3><span id="toc4">Claude CodeのJSONL形式とは？</span></h3>
<p>Claude Codeは各セッションのログを <code>.jsonl</code>（JSON Lines）形式で保存します：</p>
<pre><code class="language-plaintext">C:\Users\&lt;ユーザー名&gt;\.claude\sessions\&lt;セッションID&gt;\transcript.jsonl</code></pre>
<p>各行が独立したJSONオブジェクトで、以下のような情報が含まれます：</p>
<pre><code class="language-json">{"type":"user","sessionId":"abc123...","message":{"role":"user","content":"関数を作成して"}}
{"type":"assistant","sessionId":"abc123...","message":{"role":"assistant","content":"承知しました..."}}</code></pre>
<p>この形式は機械的な処理には適していますが、人間が読むには不向きです。</p>
<h2><span id="toc5">ツールの全機能</span></h2>
<h3><span id="toc6">1. 複数ファイル一括処理</span></h3>
<pre><code class="language-bash">exporter.py にファイルを複数ドロップ → 一括処理

# コマンドライン
python exporter.py file1.jsonl file2.json file3.jsonl

# ダイアログ
python exporter.py → 複数ファイル選択可能</code></pre>
<h3><span id="toc7">2. JSON/JSONL両対応</span></h3>
<ul>
<li><strong>JSON</strong>: claude.aiのWebチャットエクスポート（conversations.json）</li>
<li><strong>JSONL</strong>: Claude Codeのセッションログ（transcript.jsonl）</li>
</ul>
<h3><span id="toc8">3. 賢いフィルタリング</span></h3>
<p>自動で不要なメッセージを除外：</p>
<ul>
<li>システムメッセージ（<code>isMeta: true</code>）</li>
<li>ツール実行結果（<code>tool_result</code>タイプ）</li>
<li>コンテキスト情報のみのメッセージ</li>
</ul>
<h3><span id="toc9">4. セッション識別</span></h3>
<p>Claude Codeのセッションログでは：</p>
<ul>
<li>セッションIDの先頭8文字を抽出</li>
<li>フォルダ名も含めて識別</li>
<li>ファイル名: <code>&amp;lt;フォルダ名&amp;gt;_chat_&amp;lt;セッションID&amp;gt;.md</code></li>
</ul>
<p>例：<code>my-project_chat_abc12345.md</code></p>
<h3><span id="toc10">5. 安全なファイル名生成</span></h3>
<p>Windows/Mac/Linuxで使えない文字を自動変換：</p>
<ul>
<li><code>\ / : * ? &amp;quot; &amp;lt; &amp;gt; |</code> → <code>_</code></li>
</ul>
<h2><span id="toc11">使い方</span></h2>
<h3><span id="toc12">ステップ1: Claude Codeのセッションログを見つける</span></h3>
<pre><code class="language-bash"># Windowsの場合
C:\Users\&lt;ユーザー名&gt;\.claude\sessions\

# macOS/Linuxの場合
~/.claude/sessions/</code></pre>
<p>各セッションフォルダ内に <code>transcript.jsonl</code> があります。</p>
<p><!-- ![セッションフォルダの構造](images/session-folder-structure.jpg) --></p>
<h3><span id="toc13">ステップ2: エクスポーターを配置</span></h3>
<pre><code class="language-bash"># フォルダ作成
mkdir C:\my-local\my-python\claude_chat_exporter
cd C:\my-local\my-python\claude_chat_exporter

# スクリプト配置（後述のコードを保存）</code></pre>
<p><strong>Windows ユーザー向け</strong>: ドラッグ&ドロップで実行するために、<code>exporter.bat</code> も作成してください：</p>
<pre><code class="language-bat">@echo off
python "%~dp0exporter.py" %*
pause</code></pre>
<p>このバッチファイルを <code>exporter.py</code> と同じフォルダに保存することで、ファイルをドラッグ&ドロップして実行できるようになります。</p>
<h3><span id="toc14">ステップ3: 実行</span></h3>
<h4>方法1: ドラッグ&ドロップ（一番簡単！）</h4>
<p><strong>Windows の場合</strong>: <code>.py</code> ファイルへのドラッグ&ドロップは関連付けの問題で動作しないことがあります。その場合は <code>exporter.bat</code>（後述）を使用してください。</p>
<ol>
<li>必要な <code>transcript.jsonl</code> ファイルを複数選択</li>
<li><code>exporter.bat</code>（Windows）または <code>exporter.py</code>（Mac/Linux）にドラッグ&ドロップ</li>
<li>自動処理完了！</li>
</ol>
<h4>方法2: ダイアログ</h4>
<pre><code class="language-bash">python exporter.py</code></pre>
<p>ファイル選択ダイアログで複数のJSON/JSONLファイルを選択</p>
<h4>方法3: コマンドライン</h4>
<pre><code class="language-bash"># 単一ファイル
python exporter.py C:\Users\user\.claude\sessions\abc123\transcript.jsonl

# 複数ファイル
python exporter.py session1\transcript.jsonl session2\transcript.jsonl

# ワイルドカード（PowerShell）
python exporter.py (Get-ChildItem -Path "C:\Users\user\.claude\sessions\*\transcript.jsonl" -Recurse).FullName</code></pre>
<h3><span id="toc15">ステップ4: 出力確認</span></h3>
<pre><code class="language-bash"># 出力先
output\20260215_100000\
├── session1_chat_abc12345.md
├── session2_chat_def67890.md
└── project-x_chat_ghi11111.md</code></pre>
<h2><span id="toc16">ソースコード全文</span></h2>
<h3><span id="toc17">exporter.py</span></h3>
<pre><code class="language-python">import json
import sys
import tkinter.filedialog as fd
from datetime import datetime
from pathlib import Path
import re


def clean_system_tags(text):
    """Claude Codeのシステムタグを除去"""
    # システムタグのパターン（開始タグと終了タグのペア）
    paired_tags = [
        r'&lt;ide_selection&gt;.*?&lt;/ide_selection&gt;',
        r'&lt;ide_opened_file&gt;.*?&lt;/ide_opened_file&gt;',
        r'&lt;system-reminder&gt;.*?&lt;/system-reminder&gt;',
        r'&lt;command-name&gt;.*?&lt;/command-name&gt;',
        r'&lt;command-message&gt;.*?&lt;/command-message&gt;',
        r'&lt;command-args&gt;.*?&lt;/command-args&gt;',
        r'&lt;local-command-caveat&gt;.*?&lt;/local-command-caveat&gt;',
        r'&lt;local-command-stdout&gt;.*?&lt;/local-command-stdout&gt;',
    ]

    # 各パターンを除去（DOTALLフラグで改行を含む）
    for pattern in paired_tags:
        text = re.sub(pattern, '', text, flags=re.DOTALL)

    # 連続する空行を1つにまとめる
    text = re.sub(r'\n{3,}', '\n\n', text)

    return text.strip()


def process_jsonl_file(file_path, output_dir):

    """JSONL形式（Claude Codeのログ）の処理"""
    messages = []
    session_id = None

    # 各行を読み込んでパース
    with open(file_path, 'r', encoding='utf-8') as f:
        for line in f:
            line = line.strip()
            if not line:
                continue

            try:
                entry = json.loads(line)

                # セッションID取得（タイトル用）
                if session_id is None and 'sessionId' in entry:
                    session_id = entry['sessionId']

                # ユーザーまたはアシスタントのメッセージのみ抽出
                if entry.get('type') in ['user', 'assistant'] and 'message' in entry:
                    msg = entry['message']

                    # isMeta=trueのメッセージはスキップ（システムメッセージ）
                    if entry.get('isMeta', False):
                        continue

                    role = msg.get('role', '')
                    content = msg.get('content', '')

                    # contentが配列形式の場合
                    if isinstance(content, list):
                        # tool_resultはスキップ（Task実行結果など）
                        if any(item.get('type') == 'tool_result' for item in content):
                            continue

                        # textタイプのコンテンツのみ抽出（user/assistant共通）
                        text_parts = [item.get('text', '') for item in content if item.get('type') == 'text']
                        content = '\n\n'.join(text_parts)

                    # システムタグを除去
                    content = clean_system_tags(content) if isinstance(content, str) else content

                    if content and role:
                        # roleを表示形式に変換
                        sender = 'human' if role == 'user' else 'assistant'
                        messages.append({'sender': sender, 'text': content})
            except json.JSONDecodeError:
                continue

    # メッセージが1つもない場合はスキップ
    if not messages:
        print(f"スキップ: メッセージが含まれていません ({file_path.name})")
        return

    # フォルダ名とタイトル生成
    folder_name = file_path.parent.name
    if session_id:
        title = f"chat_{session_id[:8]}"
    else:
        title = file_path.stem

    # ファイル名に使えない文字を除去（フォルダ名_タイトル.md）
    safe_folder_name = re.sub(r'[\\/:*?"&lt;&gt;|]', '_', folder_name)
    safe_title = re.sub(r'[\\/:*?"&lt;&gt;|]', '_', title)
    filename = f"{safe_folder_name}_{safe_title}.md"

    # Markdownファイルに出力
    with open(output_dir / filename, 'w', encoding='utf-8') as f:
        f.write(f"# {safe_folder_name}_{safe_title}\n\n")

        for m in messages:
            f.write(f"**[{m['sender']}]**:\n\n{m['text']}\n\n")

    print(f"処理完了: {filename}")


def process_json_file(file_path, output_dir):
    """JSON形式（conversations.json）の処理"""
    try:
        d = json.load(open(file_path, 'r', encoding='utf-8'))
    except:
        d = json.load(open(file_path, 'r', encoding='cp932'))

    # チャット毎にファイル出力
    for i, c in enumerate(d):
        # タイトル取得（なければ chat_N）
        title = c.get('name', f'chat_{i}')

        # フォルダ名を取得
        folder_name = file_path.parent.name
        safe_folder_name = re.sub(r'[\\/:*?"&lt;&gt;|]', '_', folder_name)
        safe_title = re.sub(r'[\\/:*?"&lt;&gt;|]', '_', title)
        filename = f"{safe_folder_name}_{safe_title}.md"

        # Markdownファイルに出力
        with open(output_dir / filename, 'w', encoding='utf-8') as f:
            # タイトルを見出しとして出力
            f.write(f"# {safe_folder_name}_{safe_title}\n\n")

            # メッセージを順番に出力
            for m in c.get('chat_messages', []):
                f.write(f"**[{m['sender']}]**:\n\n{m['text']}\n\n")  # human または assistant

        print(f"処理完了: {filename}")


def main():
    # 出力フォルダ作成（pyファイルと同じ場所/output/yyyymmdd_hh24miss）
    output_dir = Path(__file__).parent / 'output' / datetime.now().strftime('%Y%m%d_%H%M%S')
    output_dir.mkdir(parents=True, exist_ok=True)

    # ファイルパスの取得（コマンドライン引数 or ダイアログ）
    if len(sys.argv) &gt; 1:
        # コマンドライン引数またはドラッグ&amp;ドロップから取得
        file_paths = [Path(arg) for arg in sys.argv[1:] if Path(arg).exists()]
        if not file_paths:
            print("エラー: 有効なファイルパスが指定されていません")
            input("Enterキーを押して終了...")
            return
    else:
        # ダイアログで複数ファイル選択
        paths = fd.askopenfilenames(filetypes=[("JSON/JSONL files", "*.json;*.jsonl")])
        if not paths:
            return
        file_paths = [Path(p) for p in paths]

    # 各ファイルを処理
    for file_path in file_paths:
        print(f"\n処理中: {file_path.name}")

        if file_path.suffix == '.jsonl':
            process_jsonl_file(file_path, output_dir)
        elif file_path.suffix == '.json':
            process_json_file(file_path, output_dir)
        else:
            print(f"スキップ: 未対応のファイル形式 ({file_path.name})")

    print(f"\n全ての処理が完了しました")
    print(f"出力先: {output_dir}")
    input("\nEnterキーを押して終了...")


if __name__ == "__main__":
    main()</code></pre>
<h3><span id="toc18">exporter.bat（Windows用）</span></h3>
<pre><code class="language-bat">@echo off
python "%~dp0exporter.py" %*
pause</code></pre>
<p><strong>説明</strong>:</p>
<ul>
<li><code>%~dp0</code>: バッチファイルのあるフォルダパス（末尾に<code>\</code>付き）</li>
<li><code>%*</code>: ドラッグ&ドロップされたすべてのファイルパス</li>
<li><code>pause</code>: 実行後にウィンドウを閉じずに結果を確認できる</li>
</ul>
<h2><span id="toc19">コードの詳細解説</span></h2>
<h3><span id="toc20">1. JSONL処理のコア部分</span></h3>
<pre><code class="language-python">def process_jsonl_file(file_path, output_dir):
    messages = []
    session_id = None

    with open(file_path, 'r', encoding='utf-8') as f:
        for line in f:
            # 各行をJSONとしてパース
            entry = json.loads(line)</code></pre>
<p><strong>ポイント</strong>:</p>
<ul>
<li>JSONL形式は1行=1JSONオブジェクト</li>
<li><code>json.loads()</code>で各行を個別にパース</li>
<li>セッション全体で必要な情報を蓄積</li>
</ul>
<h3><span id="toc21">2. 賢いフィルタリング</span></h3>
<pre><code class="language-python"># システムメッセージを除外
if entry.get('isMeta', False):
    continue

# ツール実行結果を除外
if isinstance(content, list):
    if any(item.get('type') == 'tool_result' for item in content):
        continue</code></pre>
<p><strong>効果</strong>:</p>
<ul>
<li>人間とアシスタントの会話のみを抽出</li>
<li>ファイル編集やコマンド実行の中間結果をスキップ</li>
<li>読みやすい会話ログを生成</li>
</ul>
<h3><span id="toc22">3. システムタグの除去</span></h3>
<pre><code class="language-python">def clean_system_tags(text):
    """Claude Codeのシステムタグを除去"""
    paired_tags = [
        r'&lt;ide_selection&gt;.*?&lt;/ide_selection&gt;',
        r'&lt;system-reminder&gt;.*?&lt;/system-reminder&gt;',
        # ... その他のタグ
    ]

    for pattern in paired_tags:
        text = re.sub(pattern, '', text, flags=re.DOTALL)

    return text.strip()</code></pre>
<p><strong>必要性</strong>:</p>
<ul>
<li>Claude Codeのログには <code>&amp;lt;ide_selection&amp;gt;</code> や <code>&amp;lt;system-reminder&amp;gt;</code> などのシステムタグが含まれる</li>
<li>これらは内部処理用の情報で、会話ログには不要</li>
<li>正規表現で自動除去することで、純粋な会話内容だけを抽出</li>
</ul>
<h3><span id="toc23">4. 配列形式のコンテンツ処理</span></h3>
<pre><code class="language-python">if isinstance(content, list):
    # textタイプのコンテンツのみ抽出（user/assistant共通）
    text_parts = [item.get('text', '') for item in content if item.get('type') == 'text']
    content = '\n\n'.join(text_parts)

# システムタグを除去
content = clean_system_tags(content) if isinstance(content, str) else content</code></pre>
<p><strong>Claude Codeの特性</strong>:</p>
<ul>
<li>user/assistantともに配列形式でメッセージが格納されることがある</li>
<li>ツール使用の説明と結果が分かれて格納される</li>
<li>textタイプのみを抽出して結合することで、会話部分を取得</li>
<li><code>&amp;lt;ide_selection&amp;gt;</code>や<code>&amp;lt;system-reminder&amp;gt;</code>などのシステムタグを自動除去</li>
</ul>
<h3><span id="toc24">4. セッション識別</span></h3>
<pre><code class="language-python"># セッションID取得
if session_id is None and 'sessionId' in entry:
    session_id = entry['sessionId']

# フォルダ名とセッションIDで識別
folder_name = file_path.parent.name
title = f"chat_{session_id[:8]}"
filename = f"{safe_folder_name}_{safe_title}.md"</code></pre>
<p><strong>実用的な識別</strong>:</p>
<ul>
<li>セッションIDだけでは識別困難</li>
<li>フォルダ名（プロジェクト名など）を含めることで識別性向上</li>
<li>先頭8文字のみ使用して簡潔に</li>
</ul>
<h3><span id="toc25">5. 複数ファイル処理</span></h3>
<pre><code class="language-python">if len(sys.argv) &gt; 1:
    # コマンドライン引数またはドラッグ&amp;ドロップ
    file_paths = [Path(arg) for arg in sys.argv[1:] if Path(arg).exists()]
else:
    # ダイアログで複数選択
    paths = fd.askopenfilenames(filetypes=[("JSON/JSONL files", "*.json;*.jsonl")])</code></pre>
<p><strong>利便性</strong>:</p>
<ul>
<li>複数の入力方法に対応</li>
<li>ドラッグ&ドロップで直感的に処理</li>
<li>スクリプト化も可能</li>
</ul>
<h2><span id="toc26">出力例</span></h2>
<h3><span id="toc27">入力: transcript.jsonl（Claude Codeセッション）</span></h3>
<pre><code class="language-jsonl">{"type":"user","sessionId":"abc123def456","message":{"role":"user","content":"Pythonで素数判定関数を作成して"}}
{"type":"assistant","sessionId":"abc123def456","message":{"role":"assistant","content":[{"type":"text","text":"素数判定関数を作成します..."}]}}</code></pre>
<h3><span id="toc28">出力: my-project_chat_abc12345.md</span></h3>
<pre><code class="language-markdown"># my-project_chat_abc12345

**[human]**:

Pythonで素数判定関数を作成して

**[assistant]**:

素数判定関数を作成します...</code></pre>
<h2><span id="toc29">実際の使用場面</span></h2>
<h3><span id="toc30">1. プロジェクト開発の記録</span></h3>
<pre><code class="language-bash"># プロジェクトフォルダのセッションを一括エクスポート
cd C:\Users\user\.claude\sessions
python exporter.py my-web-app\transcript.jsonl api-server\transcript.jsonl</code></pre>
<p><strong>活用方法</strong>:</p>
<ul>
<li>プロジェクト毎の開発履歴を整理</li>
<li>設計判断の理由を後から確認</li>
<li>新メンバーへのオンボーディング資料</li>
</ul>
<h3><span id="toc31">2. トラブルシューティングの記録</span></h3>
<p>エラー解決のセッションを保存：</p>
<pre><code class="language-bash">python exporter.py bug-fix-session\transcript.jsonl</code></pre>
<p><strong>活用方法</strong>:</p>
<ul>
<li>同じエラーが再発した時の解決策を即座に参照</li>
<li>チーム内でトラブル事例を共有</li>
<li>ナレッジベースに追加</li>
</ul>
<h3><span id="toc32">3. 学習記録</span></h3>
<p>新しい技術を学んだセッションを整理：</p>
<pre><code class="language-bash"># Claude Codeで学習したセッションをまとめてエクスポート
python exporter.py react-learning\transcript.jsonl typescript-basics\transcript.jsonl</code></pre>
<p><strong>活用方法</strong>:</p>
<ul>
<li>学習過程を振り返る</li>
<li>理解した内容を復習</li>
<li>ブログ記事の素材</li>
</ul>
<h3><span id="toc33">4. 週次レビュー</span></h3>
<p>1週間のセッションを一括処理：</p>
<pre><code class="language-bash"># PowerShellで過去7日間のセッションを抽出
$sessions = Get-ChildItem -Path "C:\Users\user\.claude\sessions\*\transcript.jsonl" -Recurse |
            Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-7) }
python exporter.py $sessions.FullName</code></pre>
<p><strong>活用方法</strong>:</p>
<ul>
<li>週の振り返りMTG資料</li>
<li>進捗報告の補助資料</li>
<li>学んだことの整理</li>
</ul>
<h3><span id="toc34">5. バックアップと保存</span></h3>
<p>定期的なバックアップスクリプト：</p>
<pre><code class="language-bash"># バッチファイル例（backup_sessions.bat）
@echo off
set OUTPUT_DIR=D:\claude_backups\%date:~0,4%%date:~5,2%%date:~8,2%
mkdir %OUTPUT_DIR%
python exporter.py C:\Users\user\.claude\sessions\*\transcript.jsonl
move output\* %OUTPUT_DIR%</code></pre>
<h2><span id="toc35">応用アイデア</span></h2>
<h3><span id="toc36">1. 自動分類スクリプト</span></h3>
<pre><code class="language-python"># タグ別にフォルダ分け
import shutil

tag_keywords = {
    'Python': ['python', 'django', 'flask'],
    'JavaScript': ['javascript', 'react', 'node'],
    'Database': ['sql', 'postgres', 'mysql'],
}

for md_file in output_dir.glob('*.md'):
    content = md_file.read_text(encoding='utf-8')

    for tag, keywords in tag_keywords.items():
        if any(kw in content.lower() for kw in keywords):
            tag_dir = output_dir / tag
            tag_dir.mkdir(exist_ok=True)
            shutil.move(md_file, tag_dir / md_file.name)
            break</code></pre>
<h3><span id="toc37">2. 統計情報の抽出</span></h3>
<pre><code class="language-python"># セッションの統計
def analyze_session(file_path):
    with open(file_path, 'r', encoding='utf-8') as f:
        content = f.read()

    human_msgs = content.count('**[human]**')
    assistant_msgs = content.count('**[assistant]**')
    total_chars = len(content)

    print(f"{file_path.name}:")
    print(f"  人間: {human_msgs}回, Claude: {assistant_msgs}回")
    print(f"  総文字数: {total_chars}")</code></pre>
<h3><span id="toc38">3. Obsidian/Notion連携</span></h3>
<p>Markdown出力なので、そのままObsidianやNotionにインポート可能：</p>
<pre><code class="language-bash"># Obsidian vaultにコピー
cp output/20260215_100000/*.md ~/ObsidianVault/Claude-Sessions/</code></pre>
<h3><span id="toc39">4. 全文検索インデックス</span></h3>
<pre><code class="language-python"># 簡易検索機能
import glob

def search_sessions(keyword, output_dir):
    results = []
    for md_file in Path(output_dir).glob('**/*.md'):
        content = md_file.read_text(encoding='utf-8')
        if keyword.lower() in content.lower():
            results.append(md_file)
    return results

# 使用例
found = search_sessions('素数判定', 'output/20260215_100000')
for f in found:
    print(f"見つかりました: {f.name}")</code></pre>
<h2><span id="toc40">トラブルシューティング</span></h2>
<h3><span id="toc41">エラー1: JSONDecodeError</span></h3>
<p><strong>原因</strong>: JSONL形式が壊れている（途中で保存が中断された等）</p>
<p><strong>解決方法</strong>:</p>
<pre><code class="language-python"># スクリプトは自動でスキップするため、通常は問題なし
except json.JSONDecodeError:
    continue  # 壊れた行はスキップ</code></pre>
<h3><span id="toc42">エラー2: セッションIDが見つからない</span></h3>
<p><strong>症状</strong>: ファイル名が <code>&amp;lt;フォルダ名&amp;gt;_transcript.md</code> になる</p>
<p><strong>原因</strong>: セッションIDが記録されていない（古い形式等）</p>
<p><strong>対処</strong>: ファイル名（transcript）で識別されるため問題なし</p>
<h3><span id="toc43">エラー3: メッセージが含まれていません</span></h3>
<p><strong>原因</strong>: ツール実行結果のみでユーザー/アシスタントの会話がない</p>
<p><strong>対処</strong>: これは正常な動作（処理がスキップされる）</p>
<h3><span id="toc44">エラー4: UnicodeDecodeError</span></h3>
<p><strong>原因</strong>: 特殊な文字が含まれている</p>
<p><strong>解決方法</strong>:</p>
<pre><code class="language-python"># open時にerrors='ignore'を追加
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:</code></pre>
<h2><span id="toc45">まとめ</span></h2>
<p>この拡張版Claude Chat Exporterを使えば：</p>
<ul>
<li>✅ Claude Codeの全セッションを一括でMarkdown化</li>
<li>✅ 読みやすい形式で長期保存・検索可能に</li>
<li>✅ プロジェクトの開発履歴として資産化</li>
<li>✅ チーム内でのナレッジ共有が簡単に</li>
<li>✅ 従来のWebチャットにも引き続き対応</li>
</ul>
<p>Claude Codeを使えば使うほど、貴重な知的財産が蓄積されます。このツールで効率的に管理・活用して、開発の生産性を更に高めましょう。</p>
<p><a rel="nofollow" href="//af.moshimo.com/af/c/click?a_id=1384942&p_id=170&pc_id=185&pl_id=4062&url=https%3A%2F%2Fwww.amazon.co.jp%2Fs%3Fk%3D%25E6%259C%25AC%2BPython%26__mk_ja_JP%3D%25E3%2582%25AB%25E3%2582%25BF%25E3%2582%25AB%25E3%2583%258A%26crid%3D22R8A0FYLVMNU%26sprefix%3D%25E6%259C%25AC%2Bpython%252Caps%252C179%26ref%3Dnb_sb_noss_1" referrerpolicy="no-referrer-when-downgrade" attributionsrc>Amazon検索[本 Python]</a><img decoding="async" src="//i.moshimo.com/af/i/impression?a_id=1384942&p_id=170&pc_id=185&pl_id=4062" width="1" height="1" style="border:none;" alt="" loading="lazy"></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"スッキリわかるPython入門 第2版 (スッキリわかる入門シリーズ)","b":"インプレス","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51n9WkczYZL._SL500_.jpg","\/41BGvNM3N3L._SL500_.jpg","\/41kGWd9AmpL._SL500_.jpg","\/41vJJZYliRL._SL500_.jpg","\/41odugJRCOL._SL500_.jpg","\/41wohPLSKPL._SL500_.jpg","\/41j-T3ur0-L._SL500_.jpg","\/41dQJDFQ3FL._SL500_.jpg","\/412w5252osL._SL500_.jpg","\/4134qH97BdL._SL500_.jpg","\/41mxalzneRL._SL500_.jpg","\/41Md05JaK1L._SL500_.jpg","\/41M7LTRfJaL._SL500_.jpg","\/416WUwhIelL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4295016365","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4295016365","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/%E3%82%B9%E3%83%83%E3%82%AD%E3%83%AA%E3%82%8F%E3%81%8B%E3%82%8BPython%E5%85%A5%E9%96%80%20%E7%AC%AC2%E7%89%88%20(%E3%82%B9%E3%83%83%E3%82%AD%E3%83%AA%E3%82%8F%E3%81%8B%E3%82%8B%E5%85%A5%E9%96%80%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA)\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=%E3%82%B9%E3%83%83%E3%82%AD%E3%83%AA%E3%82%8F%E3%81%8B%E3%82%8BPython%E5%85%A5%E9%96%80%20%E7%AC%AC2%E7%89%88%20(%E3%82%B9%E3%83%83%E3%82%AD%E3%83%AA%E3%82%8F%E3%81%8B%E3%82%8B%E5%85%A5%E9%96%80%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA)","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"5C5uQ","s":"s"});</script></p>
<div id="msmaflink-5C5uQ">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p>
<h2><span id="toc46">関連記事</span></h2>
<p>以前のバージョン（Webチャット専用）の解説記事：</p>

<a rel="noopener" href="https://caymezon.com/claude-chat-exporter-tool/" title="Claudeチャットを資産化！エクスポートデータを整形するPythonツール" class="blogcard-wrap internal-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard internal-blogcard ib-left cf"><div class="blogcard-label internal-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail internal-blogcard-thumbnail"><img decoding="async" width="160" height="90" src="https://caymezon.com/wp-content/uploads/2026/01/claude-chat-exporter-tool-featured-966f96-160x90.jpg" class="blogcard-thumb-image internal-blogcard-thumb-image wp-post-image" alt="" srcset="https://caymezon.com/wp-content/uploads/2026/01/claude-chat-exporter-tool-featured-966f96-160x90.jpg 160w, https://caymezon.com/wp-content/uploads/2026/01/claude-chat-exporter-tool-featured-966f96-120x68.jpg 120w, https://caymezon.com/wp-content/uploads/2026/01/claude-chat-exporter-tool-featured-966f96-320x180.jpg 320w, https://caymezon.com/wp-content/uploads/2026/01/claude-chat-exporter-tool-featured-966f96-376x212.jpg 376w" sizes="(max-width: 160px) 100vw, 160px" /></figure><div class="blogcard-content internal-blogcard-content"><div class="blogcard-title internal-blogcard-title">Claudeチャットを資産化！エクスポートデータを整形するPythonツール</div><div class="blogcard-snippet internal-blogcard-snippet">Claudeとの会話履歴は貴重な知的財産です。しかし、エクスポートしたJSONファイルは読みにくく、そのままでは活用しづらいのが現状です。この記事では、ClaudeのエクスポートデータをMarkdown形式に整形し、チャット毎に分割して保存...</div></div><div class="blogcard-footer internal-blogcard-footer cf"><div class="blogcard-site internal-blogcard-site"><div class="blogcard-favicon internal-blogcard-favicon"><img decoding="async" src="https://www.google.com/s2/favicons?domain=https://caymezon.com" alt="" class="blogcard-favicon-image internal-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain internal-blogcard-domain">caymezon.com</div></div><div class="blogcard-date internal-blogcard-date"><div class="blogcard-post-date internal-blogcard-post-date">2026.01.31</div></div></div></div></a>
<h2><span id="toc47">GitHubでの管理</span></h2>
<p>このツールをGitHubで管理する方法：</p>
<pre><code class="language-bash">cd C:\my-local\my-python\claude_chat_exporter

# .gitignoreを作成
cat &gt; .gitignore &lt;&lt; 'EOF'
# 出力ファイル
output/
*.json
*.jsonl

# Python
__pycache__/
*.pyc

# エディタ
.vscode/
.idea/
EOF

# Git初期化
git init
git add exporter.py .gitignore
git commit -m "feat: Claude Code session exporter"

# GitHubにプッシュ（GitHub CLI使用）
gh repo create claude-chat-exporter --private --source=. --push</code></pre>
<p>GitHub連携の詳細は以下の記事を参照：</p>

<a rel="noopener" href="https://caymezon.com/github-cli-complete-guide/" title="GitHub CLI完全ガイド：コマンドラインでGitHub操作を効率化" class="blogcard-wrap internal-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard internal-blogcard ib-left cf"><div class="blogcard-label internal-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail internal-blogcard-thumbnail"><img decoding="async" width="160" height="90" src="https://caymezon.com/wp-content/uploads/2026/01/github-cli-complete-guide-featured-b23a6b-160x90.jpg" class="blogcard-thumb-image internal-blogcard-thumb-image wp-post-image" alt="" srcset="https://caymezon.com/wp-content/uploads/2026/01/github-cli-complete-guide-featured-b23a6b-160x90.jpg 160w, https://caymezon.com/wp-content/uploads/2026/01/github-cli-complete-guide-featured-b23a6b-120x68.jpg 120w, https://caymezon.com/wp-content/uploads/2026/01/github-cli-complete-guide-featured-b23a6b-320x180.jpg 320w, https://caymezon.com/wp-content/uploads/2026/01/github-cli-complete-guide-featured-b23a6b-376x212.jpg 376w" sizes="(max-width: 160px) 100vw, 160px" /></figure><div class="blogcard-content internal-blogcard-content"><div class="blogcard-title internal-blogcard-title">GitHub CLI完全ガイド：コマンドラインでGitHub操作を効率化</div><div class="blogcard-snippet internal-blogcard-snippet">GitHub CLI（ghコマンド）は、GitHubの操作をコマンドラインで完結できる公式ツールです。ブラウザを開かずに、リポジトリ作成からIssue管理、Pull Request作成まで、すべてターミナルで行えます。この記事では、実際の導...</div></div><div class="blogcard-footer internal-blogcard-footer cf"><div class="blogcard-site internal-blogcard-site"><div class="blogcard-favicon internal-blogcard-favicon"><img decoding="async" src="https://www.google.com/s2/favicons?domain=https://caymezon.com" alt="" class="blogcard-favicon-image internal-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain internal-blogcard-domain">caymezon.com</div></div><div class="blogcard-date internal-blogcard-date"><div class="blogcard-post-date internal-blogcard-post-date">2026.01.31</div></div></div></div></a>
<hr>
<p><strong>ご意見・改善提案があれば、ぜひコメントでお知らせください！</strong></p><p>The post <a href="https://caymezon.com/claude-code-session-exporter/">Claude Codeセッションログを一括MD化！拡張版エクスポーターで開発記録を資産化</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://caymezon.com/claude-code-session-exporter/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Claudeチャットを資産化！エクスポートデータを整形するPythonツール</title>
		<link>https://caymezon.com/claude-chat-exporter-tool/</link>
					<comments>https://caymezon.com/claude-chat-exporter-tool/#respond</comments>
		
		<dc:creator><![CDATA[caymezon]]></dc:creator>
		<pubDate>Sat, 31 Jan 2026 06:23:12 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Claude]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[ツール開発]]></category>
		<category><![CDATA[自動化]]></category>
		<guid isPermaLink="false">https://caymezon.com/?p=20035</guid>

					<description><![CDATA[<p>Claudeとの会話履歴は貴重な知的財産です。しかし、エクスポートしたJSONファイルは読みにくく、そのままでは活用しづらいのが現状です。この記事では、ClaudeのエクスポートデータをMarkdown形式に整形し、チャ [&#8230;]</p>
<p>The post <a href="https://caymezon.com/claude-chat-exporter-tool/">Claudeチャットを資産化！エクスポートデータを整形するPythonツール</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Claudeとの会話履歴は貴重な知的財産です。しかし、エクスポートしたJSONファイルは読みにくく、そのままでは活用しづらいのが現状です。この記事では、ClaudeのエクスポートデータをMarkdown形式に整形し、チャット毎に分割して保存するPythonツールを紹介します。</p>

  <div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-4" checked><label class="toc-title" for="toc-checkbox-4">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">なぜClaudeチャットを管理するのか？</a></li><li><a href="#toc2" tabindex="0">Claudeチャットのエクスポート方法</a><ol><li><a href="#toc3" tabindex="0">ステップ1: Claudeチャット画面を開く</a></li><li><a href="#toc4" tabindex="0">ステップ2: エクスポートメニューを表示</a></li><li><a href="#toc5" tabindex="0">ステップ3: JSON形式でダウンロード</a></li><li><a href="#toc6" tabindex="0">エクスポートデータの構造</a></li></ol></li><li><a href="#toc7" tabindex="0">Pythonツールの概要</a><ol><li><a href="#toc8" tabindex="0">機能</a></li><li><a href="#toc9" tabindex="0">プロジェクト構成</a></li></ol></li><li><a href="#toc10" tabindex="0">ソースコード</a><ol><li><a href="#toc11" tabindex="0">exporter.py</a></li><li><a href="#toc12" tabindex="0">.gitignore</a></li><li><a href="#toc13" tabindex="0">README.md</a></li></ol></li><li><a href="#toc14" tabindex="0">出力例</a><ol><li><a href="#toc15" tabindex="0">2. Pythonスクリプトを実行</a></li><li><a href="#toc16" tabindex="0">3. JSONファイルを選択</a></li><li><a href="#toc17" tabindex="0">4. 出力確認</a></li><li><a href="#toc18" tabindex="0">出力例</a></li></ol></li><li><a href="#toc19" tabindex="0">コードの解説</a><ol><li><a href="#toc20" tabindex="0">ファイル選択ダイアログ</a></li><li><a href="#toc21" tabindex="0">エンコーディング自動判定</a></li><li><a href="#toc22" tabindex="0">タイムスタンプ付きフォルダ作成</a></li><li><a href="#toc23" tabindex="0">ファイル名の安全化</a></li><li><a href="#toc24" tabindex="0">Markdown形式で出力</a></li></ol></li><li><a href="#toc25" tabindex="0">VSCodeでのデバッグ実行</a><ol><li><a href="#toc26" tabindex="0">初回実行時の設定</a></li><li><a href="#toc27" tabindex="0">デバッグの活用</a></li></ol></li><li><a href="#toc28" tabindex="0">GitHub連携</a><ol><li><a href="#toc29" tabindex="0">クイックスタート</a></li></ol></li><li><a href="#toc30" tabindex="0">応用例</a><ol><li><a href="#toc31" tabindex="0">1. バッチ処理</a></li><li><a href="#toc32" tabindex="0">2. カテゴリ分類</a></li><li><a href="#toc33" tabindex="0">3. 日付フィルタリング</a></li></ol></li><li><a href="#toc34" tabindex="0">トラブルシューティング</a><ol><li><a href="#toc35" tabindex="0">エラー1: ModuleNotFoundError: No module named 'tkinter'</a></li><li><a href="#toc36" tabindex="0">エラー2: UnicodeDecodeError</a></li><li><a href="#toc37" tabindex="0">エラー3: 出力フォルダが作成されない</a></li></ol></li><li><a href="#toc38" tabindex="0">まとめ</a></li><li><a href="#toc39" tabindex="0">関連記事</a></li></ol>
    </div>
  </div>

<h2><span id="toc1">なぜClaudeチャットを管理するのか？</span></h2>
<p>Claudeとの会話には、以下のような価値があります：</p>
<ul>
<li><strong>問題解決の過程</strong>: エラー解決やトラブルシューティングの記録</li>
<li><strong>学習内容</strong>: 新しい技術や概念の理解プロセス</li>
<li><strong>アイデアの発展</strong>: プロジェクトの企画や設計の議論</li>
<li><strong>コードレビュー</strong>: 実装の改善提案や最適化の議論</li>
</ul>
<p>これらを適切に管理することで：</p>
<ul>
<li>後から見返して復習できる</li>
<li>同じ問題に再度直面したときに参照できる</li>
<li>チーム内で知識を共有できる</li>
<li>ブログ記事や技術文書の素材になる</li>
</ul>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"実践Claude Code入門―現場で活用するためのAIコーディングの思考法","b":"技術評論社","t":"","d":"https:\/\/m.media-amazon.com","c_p":"","p":["\/images\/I\/51kkd9SNiVL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4297153548","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4297153548","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/%E5%AE%9F%E8%B7%B5Claude%20Code%E5%85%A5%E9%96%80%E2%80%95%E7%8F%BE%E5%A0%B4%E3%81%A7%E6%B4%BB%E7%94%A8%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AEAI%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E3%81%AE%E6%80%9D%E8%80%83%E6%B3%95\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=%E5%AE%9F%E8%B7%B5Claude%20Code%E5%85%A5%E9%96%80%E2%80%95%E7%8F%BE%E5%A0%B4%E3%81%A7%E6%B4%BB%E7%94%A8%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AEAI%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E3%81%AE%E6%80%9D%E8%80%83%E6%B3%95","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"lxeQQ","s":"s"});</script></p>
<div id="msmaflink-lxeQQ">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p>
<h2><span id="toc2">Claudeチャットのエクスポート方法</span></h2>
<h3><span id="toc3">ステップ1: Claudeチャット画面を開く</span></h3>
<p><a href="https://claude.ai">https://claude.ai</a> にアクセスし、エクスポートしたい会話を開きます。</p>
<h3><span id="toc4">ステップ2: エクスポートメニューを表示</span></h3>
<p>画面右上の「︙」（3点リーダー）をクリックし、「Export chat」を選択します。</p>
<h3><span id="toc5">ステップ3: JSON形式でダウンロード</span></h3>
<p>「Download as JSON」を選択すると、JSONファイルがダウンロードされます。</p>
<p>ファイル名例: <code>claude_conversations_2026-01-31.json</code></p>
<h3><span id="toc6">エクスポートデータの構造</span></h3>
<p>エクスポートしたJSONファイルは以下のような構造になっています：</p>
<pre><code class="language-json">[
  {
    "uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    "name": "チャットのタイトル",
    "created_at": "2026-01-31T10:00:00.000Z",
    "updated_at": "2026-01-31T12:00:00.000Z",
    "chat_messages": [
      {
        "uuid": "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
        "text": "メッセージ内容",
        "sender": "human",
        "created_at": "2026-01-31T10:00:00.000Z"
      },
      {
        "uuid": "zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz",
        "text": "Claudeの返答",
        "sender": "assistant",
        "created_at": "2026-01-31T10:01:00.000Z"
      }
    ]
  }
]</code></pre>
<p>このままでは読みにくいため、整形ツールが必要です。</p>
<h2><span id="toc7">Pythonツールの概要</span></h2>
<h3><span id="toc8">機能</span></h3>
<ol>
<li><strong>ファイル選択ダイアログ</strong>: GUIでJSONファイルを選択</li>
<li><strong>エンコーディング自動判定</strong>: UTF-8/Shift-JIS両対応</li>
<li><strong>タイムスタンプ付きフォルダ作成</strong>: <code>output/yyyymmdd_hhmmss/</code>形式</li>
<li><strong>チャット毎に分割</strong>: 各チャットを個別のMarkdownファイルに出力</li>
<li><strong>ファイル名の安全化</strong>: 使えない文字を自動置換</li>
</ol>
<h3><span id="toc9">プロジェクト構成</span></h3>
<pre><code class="language-plaintext">C:\my-local\my-python\
├── claude_chat_exporter/
│   ├── exporter.py           # メインスクリプト
│   ├── README.md             # 使い方説明
│   └── output/               # 出力先（自動生成）
│       └── 20260131_120000/  # タイムスタンプフォルダ
│           ├── チャット1.md
│           ├── チャット2.md
│           └── ...
├── .gitignore
├── README.md
└── requirements.txt</code></pre>
<h2><span id="toc10">ソースコード</span></h2>
<h3><span id="toc11">exporter.py</span></h3>
<pre><code class="language-python">import json
import tkinter.filedialog as fd
from datetime import datetime
from pathlib import Path
import re

p = fd.askopenfilename(filetypes=[("JSON files", "*.json")])
if not p: exit()

# JSONファイル読み込み
try:
    d = json.load(open(p, 'r', encoding='utf-8'))
except:
    d = json.load(open(p, 'r', encoding='cp932'))

# 出力フォルダ作成（pyファイルと同じ場所/output/yyyymmdd_hh24miss）
output_dir = Path(__file__).parent / 'output' / datetime.now().strftime('%Y%m%d_%H%M%S')
output_dir.mkdir(parents=True, exist_ok=True)

# チャット毎にファイル出力
for i, c in enumerate(d):
    # タイトル取得（なければ chat_N）
    title = c.get('name', f'chat_{i}')
    
    # ファイル名に使えない文字を除去
    filename = re.sub(r'[\\/:*?"<>|]', '_', title) + '.md'
    
    # Markdownファイルに出力
    with open(output_dir / filename, 'w', encoding='utf-8') as f:
        # タイトルを見出しとして出力
        f.write(f"# {title}\n\n")
        
        # メッセージを順番に出力
        for m in c.get('chat_messages', []):
            f.write(f"**[{m['sender']}]**:\n\n{m['text']}\n\n")  # human または assistant

print(f"出力完了: {output_dir}")</code></pre>
<h3><span id="toc12">.gitignore</span></h3>
<pre><code class="language-plaintext"># Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
venv/
.venv/

# 出力ファイル
*/output/
*.json

# IDE
.vscode/
.idea/

# OS
.DS_Store
Thumbs.db</code></pre>
<h3><span id="toc13">README.md</span></h3>
<pre><code class="language-markdown"># Claude Chat Exporter

ClaudeのエクスポートJSONを整形してMarkdownファイルに出力

## 使い方

```bash
python exporter.py</code></pre>
<ol>
<li>ファイル選択ダイアログでJSONファイルを選択</li>
<li><code>output/yyyymmdd_hhmmss/</code>にチャット毎のmdファイルが出力される</li>
</ol>
<h2><span id="toc14">出力例</span></h2>
<pre><code class="language-plaintext">output/
└── 20260131_120000/
    ├── GitHubリポジトリ作成手順.md
    ├── Python開発環境構築.md
    └── VSCodeデバッグ使い方.md</code></pre>
<pre><code class="language-plaintext">
## 使い方

### 1. プロジェクトフォルダ作成

```bash
# フォルダ作成
mkdir C:\my-local\my-python\claude_chat_exporter
cd C:\my-local\my-python\claude_chat_exporter

# Pythonスクリプト作成
# 上記のexporter.pyの内容をコピー</code></pre>
<p>VSCodeで <code>exporter.py</code> を作成し、上記のコードを貼り付けます。</p>
<h3><span id="toc15">2. Pythonスクリプトを実行</span></h3>
<h4>VSCodeから実行</h4>
<ol>
<li><code>exporter.py</code> を開く</li>
<li>右上の「▶」ボタンをクリック（または <code>F5</code> でデバッグ実行）</li>
<li>ファイル選択ダイアログが表示される</li>
</ol>
<h4>コマンドラインから実行</h4>
<pre><code class="language-bash">cd C:\my-local\my-python\claude_chat_exporter
python exporter.py</code></pre>
<h3><span id="toc16">3. JSONファイルを選択</span></h3>
<p>ファイル選択ダイアログで、ClaudeからエクスポートしたJSONファイルを選択します。</p>
<h3><span id="toc17">4. 出力確認</span></h3>
<pre><code class="language-bash"># 出力フォルダを確認
dir output\20260131_120000</code></pre>
<p>各チャットが個別のMarkdownファイルとして出力されます。</p>
<h3><span id="toc18">出力例</span></h3>
<p><strong>出力ファイル: GitHubリポジトリ作成手順.md</strong></p>
<pre><code class="language-markdown"># GitHubリポジトリ作成手順

**[human]**: 

GitHubにリポジトリを作成する方法を教えて

**[assistant]**: 

GitHubにリポジトリを作成する方法は2つあります...

**[human]**: 

GitHub CLIを使った方法を詳しく教えて

**[assistant]**: 

GitHub CLIを使うと、コマンドラインで簡単にリポジトリを作成できます...</code></pre>
<h2><span id="toc19">コードの解説</span></h2>
<h3><span id="toc20">ファイル選択ダイアログ</span></h3>
<pre><code class="language-python">p = fd.askopenfilename(filetypes=[("JSON files", "*.json")])
if not p:
    exit()</code></pre>
<ul>
<li><code>tkinter.filedialog</code>を使用してGUIでファイル選択</li>
<li><code>.json</code>ファイルのみフィルタ</li>
<li>キャンセル時は終了</li>
</ul>
<h3><span id="toc21">エンコーディング自動判定</span></h3>
<pre><code class="language-python">try:
    d = json.load(open(p, 'r', encoding='utf-8'))
except:
    d = json.load(open(p, 'r', encoding='cp932'))</code></pre>
<ul>
<li>まずUTF-8で読み込みを試行</li>
<li>失敗したらShift-JIS（cp932）で再試行</li>
<li>日本語環境での互換性確保</li>
</ul>
<h3><span id="toc22">タイムスタンプ付きフォルダ作成</span></h3>
<pre><code class="language-python">output_dir = Path(__file__).parent / 'output' / datetime.now().strftime('%Y%m%d_%H%M%S')
output_dir.mkdir(parents=True, exist_ok=True)</code></pre>
<ul>
<li><code>Path(__file__).parent</code>: スクリプトと同じ場所</li>
<li><code>datetime.now().strftime('%Y%m%d_%H%M%S')</code>: <code>20260131_120000</code>形式</li>
<li><code>parents=True</code>: 親フォルダも自動作成</li>
<li><code>exist_ok=True</code>: 既存フォルダでもエラーにしない</li>
</ul>
<h3><span id="toc23">ファイル名の安全化</span></h3>
<pre><code class="language-python">filename = re.sub(r'[\\/:*?"<>|]', '_', title) + '.md'</code></pre>
<ul>
<li>Windowsで使えない文字（<code>\ / : * ? " < > |</code>）を<code>_</code>に置換</li>
<li><code>.md</code>拡張子を追加</li>
</ul>
<h3><span id="toc24">Markdown形式で出力</span></h3>
<pre><code class="language-python">f.write(f"# {title}\n\n")
for m in c.get('chat_messages', []):
    f.write(f"**[{m['sender']}]**:\n\n{m['text']}\n\n")</code></pre>
<ul>
<li>タイトルは <code># タイトル</code> 形式（見出し1）</li>
<li>送信者は <code>**[sender]**:</code> 形式（太字）</li>
<li>メッセージ間は空行で区切る</li>
</ul>
<h2><span id="toc25">VSCodeでのデバッグ実行</span></h2>
<h3><span id="toc26">初回実行時の設定</span></h3>
<ol>
<li><code>F5</code> キーを押す</li>
<li>「Python Debugger」を選択</li>
<li>「Pythonファイル 現在アクティブなPythonファイルをデバッグする」を選択</li>
</ol>
<h3><span id="toc27">デバッグの活用</span></h3>
<p>ブレークポイントを設定して動作を確認：</p>
<pre><code class="language-python"># ①ファイルが読めたか確認
d = json.load(...)  # ←ここに●

# ②チャット数とタイトル確認
for i, c in enumerate(d):  # ←ここに●
    title = c.get('name', f'chat_{i}')
    
    # ③ファイル名が正しいか確認
    filename = re.sub(...)  # ←ここに●</code></pre>
<p>ブレークポイント設定方法：</p>
<ul>
<li>行番号の左側をクリック → 赤丸が表示</li>
<li><code>F10</code>: 次の行へ（ステップオーバー）</li>
<li><code>F11</code>: 関数の中に入る（ステップイン）</li>
<li><code>F5</code>: 続行</li>
</ul>
<h2><span id="toc28">GitHub連携</span></h2>
<p>このツールをGitHubで管理する方法は、以下の記事で詳しく解説しています：</p>
<ul>
<li>GitHubリポジトリ作成の2つの方法：GUI vs CLI徹底比較</li>
</ul>

<a rel="noopener" href="https://caymezon.com/github-repository-creation-gui-vs-cli/" title="GitHubリポジトリ作成の2つの方法：GUI vs CLI徹底比較" class="blogcard-wrap internal-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard internal-blogcard ib-left cf"><div class="blogcard-label internal-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail internal-blogcard-thumbnail"><img decoding="async" width="160" height="90" src="https://caymezon.com/wp-content/uploads/2026/01/github-repository-creation-gui-vs-cli-featured-520c02-160x90.jpg" class="blogcard-thumb-image internal-blogcard-thumb-image wp-post-image" alt="" srcset="https://caymezon.com/wp-content/uploads/2026/01/github-repository-creation-gui-vs-cli-featured-520c02-160x90.jpg 160w, https://caymezon.com/wp-content/uploads/2026/01/github-repository-creation-gui-vs-cli-featured-520c02-120x68.jpg 120w, https://caymezon.com/wp-content/uploads/2026/01/github-repository-creation-gui-vs-cli-featured-520c02-320x180.jpg 320w, https://caymezon.com/wp-content/uploads/2026/01/github-repository-creation-gui-vs-cli-featured-520c02-376x212.jpg 376w" sizes="(max-width: 160px) 100vw, 160px" /></figure><div class="blogcard-content internal-blogcard-content"><div class="blogcard-title internal-blogcard-title">GitHubリポジトリ作成の2つの方法：GUI vs CLI徹底比較</div><div class="blogcard-snippet internal-blogcard-snippet">GitHubでリポジトリを作成する方法は大きく分けて2つあります。ブラウザを使ったGUI操作と、GitHub CLIを使ったコマンドライン操作です。この記事では、実際のPythonプロジェクトを例に、両方の方法を詳しく解説します。前提条件G...</div></div><div class="blogcard-footer internal-blogcard-footer cf"><div class="blogcard-site internal-blogcard-site"><div class="blogcard-favicon internal-blogcard-favicon"><img decoding="async" src="https://www.google.com/s2/favicons?domain=https://caymezon.com" alt="" class="blogcard-favicon-image internal-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain internal-blogcard-domain">caymezon.com</div></div><div class="blogcard-date internal-blogcard-date"><div class="blogcard-post-date internal-blogcard-post-date">2026.01.31</div></div></div></div></a>
<ul>
<li>GitHub CLI完全ガイド：コマンドラインでGitHub操作を効率化</li>
</ul>

<a rel="noopener" href="https://caymezon.com/github-cli-complete-guide/" title="GitHub CLI完全ガイド：コマンドラインでGitHub操作を効率化" class="blogcard-wrap internal-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard internal-blogcard ib-left cf"><div class="blogcard-label internal-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail internal-blogcard-thumbnail"><img decoding="async" width="160" height="90" src="https://caymezon.com/wp-content/uploads/2026/01/github-cli-complete-guide-featured-b23a6b-160x90.jpg" class="blogcard-thumb-image internal-blogcard-thumb-image wp-post-image" alt="" srcset="https://caymezon.com/wp-content/uploads/2026/01/github-cli-complete-guide-featured-b23a6b-160x90.jpg 160w, https://caymezon.com/wp-content/uploads/2026/01/github-cli-complete-guide-featured-b23a6b-120x68.jpg 120w, https://caymezon.com/wp-content/uploads/2026/01/github-cli-complete-guide-featured-b23a6b-320x180.jpg 320w, https://caymezon.com/wp-content/uploads/2026/01/github-cli-complete-guide-featured-b23a6b-376x212.jpg 376w" sizes="(max-width: 160px) 100vw, 160px" /></figure><div class="blogcard-content internal-blogcard-content"><div class="blogcard-title internal-blogcard-title">GitHub CLI完全ガイド：コマンドラインでGitHub操作を効率化</div><div class="blogcard-snippet internal-blogcard-snippet">GitHub CLI（ghコマンド）は、GitHubの操作をコマンドラインで完結できる公式ツールです。ブラウザを開かずに、リポジトリ作成からIssue管理、Pull Request作成まで、すべてターミナルで行えます。この記事では、実際の導...</div></div><div class="blogcard-footer internal-blogcard-footer cf"><div class="blogcard-site internal-blogcard-site"><div class="blogcard-favicon internal-blogcard-favicon"><img decoding="async" src="https://www.google.com/s2/favicons?domain=https://caymezon.com" alt="" class="blogcard-favicon-image internal-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain internal-blogcard-domain">caymezon.com</div></div><div class="blogcard-date internal-blogcard-date"><div class="blogcard-post-date internal-blogcard-post-date">2026.01.31</div></div></div></div></a>
<h3><span id="toc29">クイックスタート</span></h3>
<pre><code class="language-bash">cd C:\my-local\my-python

# Git初期化
git init
git add .
git commit -m "Initial commit: Add claude_chat_exporter"

# GitHubにプッシュ（GitHub CLI使用）
gh repo create my-python --private --source=. --push</code></pre>
<h2><span id="toc30">応用例</span></h2>
<h3><span id="toc31">1. バッチ処理</span></h3>
<p>複数のJSONファイルを一度に処理：</p>
<pre><code class="language-python">import glob

# すべてのJSONファイルを取得
json_files = glob.glob("exports/*.json")

for json_file in json_files:
    # 処理ロジック
    ...</code></pre>
<h3><span id="toc32">2. カテゴリ分類</span></h3>
<p>チャットタイトルに基づいて自動分類：</p>
<pre><code class="language-python"># タイトルにキーワードが含まれていたらフォルダ分け
if "Python" in title:
    category_dir = output_dir / "Python"
elif "GitHub" in title:
    category_dir = output_dir / "GitHub"
else:
    category_dir = output_dir / "Others"

category_dir.mkdir(exist_ok=True)</code></pre>
<h3><span id="toc33">3. 日付フィルタリング</span></h3>
<p>特定期間のチャットのみ抽出：</p>
<pre><code class="language-python">from datetime import datetime, timedelta

# 過去7日間のチャットのみ
cutoff_date = datetime.now() - timedelta(days=7)

for c in d:
    created = datetime.fromisoformat(c['created_at'].replace('Z', '+00:00'))
    if created > cutoff_date:
        # 処理
        ...</code></pre>
<h2><span id="toc34">トラブルシューティング</span></h2>
<h3><span id="toc35">エラー1: ModuleNotFoundError: No module named 'tkinter'</span></h3>
<p><strong>原因</strong>: Pythonインストール時にtkinterが含まれていない</p>
<p><strong>解決方法</strong>:</p>
<ul>
<li>Windows: Python再インストール時に「tcl/tk and IDLE」にチェック</li>
<li>macOS: <code>brew install python-tk</code></li>
<li>Linux: <code>sudo apt install python3-tk</code></li>
</ul>
<h3><span id="toc36">エラー2: UnicodeDecodeError</span></h3>
<p><strong>原因</strong>: JSONファイルのエンコーディングが想定外</p>
<p><strong>解決方法</strong>: コードが自動判定するため通常は発生しません。発生した場合は以下を試してください：</p>
<pre><code class="language-python"># エンコーディングを明示的に指定
d = json.load(open(p, 'r', encoding='utf-8-sig'))  # BOM付きUTF-8</code></pre>
<h3><span id="toc37">エラー3: 出力フォルダが作成されない</span></h3>
<p><strong>原因</strong>: 権限不足</p>
<p><strong>解決方法</strong>:</p>
<ul>
<li>管理者権限でVSCodeを実行</li>
<li>または、書き込み可能なフォルダに移動</li>
</ul>
<h2><span id="toc38">まとめ</span></h2>
<p>このツールを使うことで：</p>
<ul>
<li>✅ Claudeチャットを読みやすいMarkdown形式で保存</li>
<li>✅ チャット毎に分割して管理しやすく</li>
<li>✅ タイムスタンプ付きで履歴管理</li>
<li>✅ GitHubで バージョン管理</li>
</ul>
<p>Claudeとの会話は貴重な知的財産です。このツールを使って、効率的に管理・活用してください。</p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"GitHub Copilot×Python入門","b":"日経BP","t":"","d":"https:\/\/m.media-amazon.com","c_p":"","p":["\/images\/I\/51KyCqK80-L._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4296070975","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4296070975","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/GitHub%20Copilot%C3%97Python%E5%85%A5%E9%96%80\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=GitHub%20Copilot%C3%97Python%E5%85%A5%E9%96%80","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"WRNKr","s":"s"});</script></p>
<div id="msmaflink-WRNKr">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p>
<h2><span id="toc39">関連記事</span></h2>
<ul>
<li>GitHubリポジトリ作成の2つの方法：GUI vs CLI徹底比較</li>
</ul>

<a rel="noopener" href="https://caymezon.com/github-repository-creation-gui-vs-cli/" title="GitHubリポジトリ作成の2つの方法：GUI vs CLI徹底比較" class="blogcard-wrap internal-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard internal-blogcard ib-left cf"><div class="blogcard-label internal-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail internal-blogcard-thumbnail"><img decoding="async" width="160" height="90" src="https://caymezon.com/wp-content/uploads/2026/01/github-repository-creation-gui-vs-cli-featured-520c02-160x90.jpg" class="blogcard-thumb-image internal-blogcard-thumb-image wp-post-image" alt="" srcset="https://caymezon.com/wp-content/uploads/2026/01/github-repository-creation-gui-vs-cli-featured-520c02-160x90.jpg 160w, https://caymezon.com/wp-content/uploads/2026/01/github-repository-creation-gui-vs-cli-featured-520c02-120x68.jpg 120w, https://caymezon.com/wp-content/uploads/2026/01/github-repository-creation-gui-vs-cli-featured-520c02-320x180.jpg 320w, https://caymezon.com/wp-content/uploads/2026/01/github-repository-creation-gui-vs-cli-featured-520c02-376x212.jpg 376w" sizes="(max-width: 160px) 100vw, 160px" /></figure><div class="blogcard-content internal-blogcard-content"><div class="blogcard-title internal-blogcard-title">GitHubリポジトリ作成の2つの方法：GUI vs CLI徹底比較</div><div class="blogcard-snippet internal-blogcard-snippet">GitHubでリポジトリを作成する方法は大きく分けて2つあります。ブラウザを使ったGUI操作と、GitHub CLIを使ったコマンドライン操作です。この記事では、実際のPythonプロジェクトを例に、両方の方法を詳しく解説します。前提条件G...</div></div><div class="blogcard-footer internal-blogcard-footer cf"><div class="blogcard-site internal-blogcard-site"><div class="blogcard-favicon internal-blogcard-favicon"><img decoding="async" src="https://www.google.com/s2/favicons?domain=https://caymezon.com" alt="" class="blogcard-favicon-image internal-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain internal-blogcard-domain">caymezon.com</div></div><div class="blogcard-date internal-blogcard-date"><div class="blogcard-post-date internal-blogcard-post-date">2026.01.31</div></div></div></div></a>
<ul>
<li>GitHub CLI完全ガイド：コマンドラインでGitHub操作を効率化</li>
</ul>

<a rel="noopener" href="https://caymezon.com/github-cli-complete-guide/" title="GitHub CLI完全ガイド：コマンドラインでGitHub操作を効率化" class="blogcard-wrap internal-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard internal-blogcard ib-left cf"><div class="blogcard-label internal-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail internal-blogcard-thumbnail"><img decoding="async" width="160" height="90" src="https://caymezon.com/wp-content/uploads/2026/01/github-cli-complete-guide-featured-b23a6b-160x90.jpg" class="blogcard-thumb-image internal-blogcard-thumb-image wp-post-image" alt="" srcset="https://caymezon.com/wp-content/uploads/2026/01/github-cli-complete-guide-featured-b23a6b-160x90.jpg 160w, https://caymezon.com/wp-content/uploads/2026/01/github-cli-complete-guide-featured-b23a6b-120x68.jpg 120w, https://caymezon.com/wp-content/uploads/2026/01/github-cli-complete-guide-featured-b23a6b-320x180.jpg 320w, https://caymezon.com/wp-content/uploads/2026/01/github-cli-complete-guide-featured-b23a6b-376x212.jpg 376w" sizes="(max-width: 160px) 100vw, 160px" /></figure><div class="blogcard-content internal-blogcard-content"><div class="blogcard-title internal-blogcard-title">GitHub CLI完全ガイド：コマンドラインでGitHub操作を効率化</div><div class="blogcard-snippet internal-blogcard-snippet">GitHub CLI（ghコマンド）は、GitHubの操作をコマンドラインで完結できる公式ツールです。ブラウザを開かずに、リポジトリ作成からIssue管理、Pull Request作成まで、すべてターミナルで行えます。この記事では、実際の導...</div></div><div class="blogcard-footer internal-blogcard-footer cf"><div class="blogcard-site internal-blogcard-site"><div class="blogcard-favicon internal-blogcard-favicon"><img decoding="async" src="https://www.google.com/s2/favicons?domain=https://caymezon.com" alt="" class="blogcard-favicon-image internal-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain internal-blogcard-domain">caymezon.com</div></div><div class="blogcard-date internal-blogcard-date"><div class="blogcard-post-date internal-blogcard-post-date">2026.01.31</div></div></div></div></a>
<p><!-- ## 今後の拡張予定

- 全文検索機能
- Web UI版の開発
- 自動タグ付け機能
- Notion/Obsidianへのエクスポート

ご意見・ご要望があれば、GitHubのIssueでお知らせください！ --></p><p>The post <a href="https://caymezon.com/claude-chat-exporter-tool/">Claudeチャットを資産化！エクスポートデータを整形するPythonツール</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://caymezon.com/claude-chat-exporter-tool/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>【試行錯誤の記録】GitHub ActionsでWordPress自動投稿！403エラーとの戦い</title>
		<link>https://caymezon.com/github-actions-wordpress-403-error-battle/</link>
					<comments>https://caymezon.com/github-actions-wordpress-403-error-battle/#respond</comments>
		
		<dc:creator><![CDATA[caymezon]]></dc:creator>
		<pubDate>Mon, 12 Jan 2026 08:05:15 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[CI/CD]]></category>
		<category><![CDATA[GitHub Actions]]></category>
		<category><![CDATA[REST API]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[トラブルシューティング]]></category>
		<category><![CDATA[自動化]]></category>
		<guid isPermaLink="false">https://caymezon.com/github-actions-wordpress-403-error-battle/</guid>

					<description><![CDATA[<p>目次 はじめに目指したシステム理想の執筆フロー準備：必要な設定ローカル環境では成功していたGitHub Actionsの設定GitHub Secretsの設定最初の実行：403エラーgit pushログを確認問題1：環境 [&#8230;]</p>
<p>The post <a href="https://caymezon.com/github-actions-wordpress-403-error-battle/">【試行錯誤の記録】GitHub ActionsでWordPress自動投稿！403エラーとの戦い</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></description>
										<content:encoded><![CDATA[<div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-6" checked><label class="toc-title" for="toc-checkbox-6">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">はじめに</a></li><li><a href="#toc2" tabindex="0">目指したシステム</a><ol><li><a href="#toc3" tabindex="0">理想の執筆フロー</a></li></ol></li><li><a href="#toc4" tabindex="0">準備：必要な設定</a><ol><li><a href="#toc5" tabindex="0">ローカル環境では成功していた</a></li><li><a href="#toc6" tabindex="0">GitHub Actionsの設定</a></li><li><a href="#toc7" tabindex="0">GitHub Secretsの設定</a></li></ol></li><li><a href="#toc8" tabindex="0">最初の実行：403エラー</a><ol><li><a href="#toc9" tabindex="0">git push</a></li><li><a href="#toc10" tabindex="0">ログを確認</a></li><li><a href="#toc11" tabindex="0">問題1：環境変数が読み込まれていない</a></li><li><a href="#toc12" tabindex="0">再実行：403 Forbidden</a></li></ol></li><li><a href="#toc13" tabindex="0">原因究明の旅</a><ol><li><a href="#toc14" tabindex="0">仮説1：WAF（Web Application Firewall）</a></li><li><a href="#toc15" tabindex="0">仮説2：REST APIが無効化されている</a></li><li><a href="#toc16" tabindex="0">仮説3：Application Passwordの問題</a></li><li><a href="#toc17" tabindex="0">困惑：ローカルからは成功、GitHub Actionsからは失敗</a></li></ol></li><li><a href="#toc18" tabindex="0">ブレイクスルー：ログをもっと詳しく見る</a><ol><li><a href="#toc19" tabindex="0">スクリプトを修正</a></li><li><a href="#toc20" tabindex="0">ログに気づき</a></li></ol></li><li><a href="#toc21" tabindex="0">真の原因を発見</a><ol><li><a href="#toc22" tabindex="0">XSERVERのWordPressセキュリティ設定</a></li><li><a href="#toc23" tabindex="0">原因の理解</a></li><li><a href="#toc24" tabindex="0">なぜローカルからは成功したのか</a></li></ol></li><li><a href="#toc25" tabindex="0">解決：設定変更</a><ol><li><a href="#toc26" tabindex="0">REST APIアクセス制限をOFF</a></li><li><a href="#toc27" tabindex="0">再実行</a></li><li><a href="#toc28" tabindex="0">成功！</a></li><li><a href="#toc29" tabindex="0">WordPress管理画面で確認</a></li></ol></li><li><a href="#toc30" tabindex="0">セキュリティについて</a><ol><li><a href="#toc31" tabindex="0">Q: 国外IPアクセスをOFFにして大丈夫？</a></li></ol></li><li><a href="#toc32" tabindex="0">学んだこと</a><ol><li><a href="#toc33" tabindex="0">1. エラーメッセージをよく読む</a></li><li><a href="#toc34" tabindex="0">2. 同じテストでも環境が違えば結果も違う</a></li><li><a href="#toc35" tabindex="0">3. 段階的にテストする</a></li><li><a href="#toc36" tabindex="0">4. ドキュメント化の重要性</a></li></ol></li><li><a href="#toc37" tabindex="0">トラブルシューティングガイド</a><ol><li><a href="#toc38" tabindex="0">403 Forbiddenエラーが出たら</a></li></ol></li><li><a href="#toc39" tabindex="0">まとめ</a><ol><li><a href="#toc40" tabindex="0">問題解決までの道のり</a></li><li><a href="#toc41" tabindex="0">完成したシステム</a></li><li><a href="#toc42" tabindex="0">この経験から</a></li></ol></li><li><a href="#toc43" tabindex="0">参考情報</a><ol><li><a href="#toc44" tabindex="0">XSERVERの設定</a></li><li><a href="#toc45" tabindex="0">WordPress REST API</a></li><li><a href="#toc46" tabindex="0">GitHub Actions</a></li></ol></li><li><a href="#toc47" tabindex="0">おわりに</a></li></ol>
    </div>
  </div>

<h2><span id="toc1">はじめに</span></h2>
<p>VSCodeでMarkdownを書いて、<code>git push</code> するだけでWordPressに自動投稿される――</p>
<p>そんな夢のようなシステムを作ろうと、GitHub Actions連携に挑戦しました。</p>
<p>しかし、待っていたのは <strong>403 Forbiddenエラーとの長い戦い</strong> でした。</p>
<p>この記事では、その試行錯誤の過程と、最終的に発見した意外な原因について記録します。</p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"GitHub CI\/CD実践ガイド――持続可能なソフトウェア開発を支えるGitHub Actionsの設計と運用 (エンジニア選書)","b":"技術評論社","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/51rzki71LxL._SL500_.jpg","\/41fAMNJywPL._SL500_.jpg","\/41Dy2qzIwgL._SL500_.jpg","\/31MRBqS4faL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4297141736","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4297141736","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/GitHub%20CI%2FCD%E5%AE%9F%E8%B7%B5%E3%82%AC%E3%82%A4%E3%83%89%E2%80%95%E2%80%95%E6%8C%81%E7%B6%9A%E5%8F%AF%E8%83%BD%E3%81%AA%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E9%96%8B%E7%99%BA%E3%82%92%E6%94%AF%E3%81%88%E3%82%8BGitHub%20Actions%E3%81%AE%E8%A8%AD%E8%A8%88%E3%81%A8%E9%81%8B%E7%94%A8%20(%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E9%81%B8%E6%9B%B8)\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=GitHub%20CI%2FCD%E5%AE%9F%E8%B7%B5%E3%82%AC%E3%82%A4%E3%83%89%E2%80%95%E2%80%95%E6%8C%81%E7%B6%9A%E5%8F%AF%E8%83%BD%E3%81%AA%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E9%96%8B%E7%99%BA%E3%82%92%E6%94%AF%E3%81%88%E3%82%8BGitHub%20Actions%E3%81%AE%E8%A8%AD%E8%A8%88%E3%81%A8%E9%81%8B%E7%94%A8%20(%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E9%81%B8%E6%9B%B8)","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"P0azt","s":"s"});</script></p>
<div id="msmaflink-P0azt">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p>
<hr>
<h2><span id="toc2">目指したシステム</span></h2>
<h3><span id="toc3">理想の執筆フロー</span></h3>
<pre><code class="language-plaintext">VSCodeでMarkdown執筆
  ↓
git push origin main
  ↓
GitHub Actions自動実行
  ↓
WordPressに投稿
  ↓
完了！</code></pre>
<p><strong>所要時間：数秒</strong></p>
<p><strong>メリット：</strong></p>
<ul>
<li>慣れ親しんだエディタで執筆</li>
<li>Gitでバージョン管理</li>
<li>完全自動化</li>
</ul>
<p>これを実現するため、GitHub Actions連携に取り組みました。</p>
<hr>
<h2><span id="toc4">準備：必要な設定</span></h2>
<h3><span id="toc5">ローカル環境では成功していた</span></h3>
<p>すでにローカル環境では、スクリプトが完璧に動作していました。</p>
<pre><code class="language-bash">npm run publish</code></pre>
<p>これで、VSCodeで書いた記事がローカルWordPressに投稿される。</p>
<p><strong>次は本番環境へ。</strong></p>
<hr>
<h3><span id="toc6">GitHub Actionsの設定</span></h3>
<p><strong><code>.github/workflows/publish.yml</code>:</strong></p>
<pre><code class="language-yaml">name: Publish to WordPress

on:
  push:
    branches: [main]
    paths: ['posts/**']

jobs:
  publish:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm install
      - name: Publish to WordPress
        env:
          WP_URL: ${{ secrets.WP_URL }}
          WP_USER: ${{ secrets.WP_USER }}
          WP_APP_PASSWORD: ${{ secrets.WP_APP_PASSWORD }}
        run: npm run publish</code></pre>
<p><strong>シンプルな設定。</strong></p>
<hr>
<h3><span id="toc7">GitHub Secretsの設定</span></h3>
<pre><code class="language-plaintext">GitHub → Settings → Secrets and variables → Actions

WP_URL=https://caymezon.com
WP_USER=caymezon
WP_APP_PASSWORD=xxxx xxxx xxxx xxxx
CATEGORY_*=（各カテゴリーID）</code></pre>
<p><strong>準備完了。いざ実行！</strong></p>
<hr>
<h2><span id="toc8">最初の実行：403エラー</span></h2>
<h3><span id="toc9">git push</span></h3>
<pre><code class="language-bash">git checkout main
git merge develop
git push origin main</code></pre>
<p><strong>GitHub Actions起動！</strong></p>
<hr>
<h3><span id="toc10">ログを確認</span></h3>
<pre><code class="language-plaintext">Run npm run publish

📝 処理開始: test-article
──────────────────────────────────────────────────
⚠️  既存記事検索エラー: Invalid URL
❌ タグ処理失敗: Test Invalid URL
❌ 投稿エラー: Invalid URL
❌ test-article の処理に失敗しました</code></pre>
<p><strong>「Invalid URL」？？</strong></p>
<hr>
<h3><span id="toc11">問題1：環境変数が読み込まれていない</span></h3>
<p><strong>原因：</strong></p>
<pre><code class="language-javascript">const WP_URL = process.env.WP_URL;  // undefined</code></pre>
<p><strong>GitHub Secretsの設定ミス。</strong></p>
<p><strong>対処：</strong></p>
<pre><code class="language-plaintext">GitHub Secrets を再確認
→ すべて正しく設定
→ Re-run jobs</code></pre>
<hr>
<h3><span id="toc12">再実行：403 Forbidden</span></h3>
<pre><code class="language-plaintext">📝 処理開始: test-article
⚠️  既存記事検索エラー: Request failed with status code 403
❌ タグ処理失敗: Test <!DOCTYPE html>...403 Forbidden...
❌ 投稿エラー: <!DOCTYPE html>...403 Forbidden...</code></pre>
<p><strong>今度は403エラー。アクセスが拒否されている。</strong></p>
<hr>
<h2><span id="toc13">原因究明の旅</span></h2>
<h3><span id="toc14">仮説1：WAF（Web Application Firewall）</span></h3>
<p><strong>最初に疑ったのはWAF。</strong></p>
<p>XSERVERのWAFがREST APIをブロックしている可能性。</p>
<p><strong>確認：</strong></p>
<pre><code class="language-plaintext">XSERVERサーバーパネル
→ セキュリティ → WAF設定
→ caymezon.com</code></pre>
<p><strong>結果：</strong></p>
<pre><code class="language-plaintext">WAF: すべてOFF</code></pre>
<p><strong>WAFは原因ではなかった。</strong></p>
<hr>
<h3><span id="toc15">仮説2：REST APIが無効化されている</span></h3>
<p><strong>ブラウザで確認：</strong></p>
<pre><code class="language-plaintext">https://caymezon.com/wp-json/wp/v2/posts</code></pre>
<p><strong>結果：</strong></p>
<pre><code class="language-json">[
  {
    "id": 1,
    "title": {"rendered": "記事タイトル"},
    ...
  }
]</code></pre>
<p><strong>REST APIは正常に動作している！</strong></p>
<hr>
<h3><span id="toc16">仮説3：Application Passwordの問題</span></h3>
<p><strong>WordPress管理画面で新しいApplication Passwordを生成：</strong></p>
<pre><code class="language-plaintext">ユーザー → プロフィール
→ アプリケーションパスワード
→ 「GitHub Actions」で新規作成
→ コピー</code></pre>
<p><strong>curlコマンドでテスト：</strong></p>
<pre><code class="language-bash">curl -X POST "https://caymezon.com/wp-json/wp/v2/posts" \
  -u "caymezon:新しいApplication Password" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Curl Test Post",
    "content": "Test",
    "status": "draft"
  }'</code></pre>
<p><strong>結果：</strong></p>
<pre><code class="language-json">{
  "id": 456,
  "title": {"rendered": "Curl Test Post"},
  ...
}</code></pre>
<p><strong>成功！Application Passwordも問題ない！</strong></p>
<hr>
<h3><span id="toc17">困惑：ローカルからは成功、GitHub Actionsからは失敗</span></h3>
<p><strong>状況の整理：</strong></p>
<table>
<thead>
<tr>
<th>テスト方法</th>
<th>結果</th>
</tr>
</thead>
<tbody>
<tr>
<td>ローカル（npm run publish）</td>
<td>✅ 成功</td>
</tr>
<tr>
<td>ブラウザ（GET）</td>
<td>✅ 成功</td>
</tr>
<tr>
<td>curl（POST）</td>
<td>✅ 成功</td>
</tr>
<tr>
<td>GitHub Actions</td>
<td>❌ 403エラー</td>
</tr>
</tbody>
</table>
<p><strong>なぜGitHub Actionsだけ失敗するのか？</strong></p>
<hr>
<h2><span id="toc18">ブレイクスルー：ログをもっと詳しく見る</span></h2>
<h3><span id="toc19">スクリプトを修正</span></h3>
<p>エラーメッセージをもっと詳しく出力するように修正：</p>
<pre><code class="language-javascript">} catch (error) {
  if (error.response?.status === 403) {
    console.error('❌ 投稿エラー: 403 Forbidden');
    console.error('💡 XSERVERのセキュリティ設定を確認してください');
  }
  // ...
}</code></pre>
<p><strong>Re-run jobs で再実行。</strong></p>
<hr>
<h3><span id="toc20">ログに気づき</span></h3>
<pre><code class="language-plaintext">❌ 投稿エラー: 403 Forbidden
   💡 XSERVERのセキュリティ設定を確認してください</code></pre>
<p><strong>「セキュリティ設定」？</strong></p>
<p><strong>WAFはOFFだったけど、他に何かあるのか？</strong></p>
<hr>
<h2><span id="toc21">真の原因を発見</span></h2>
<h3><span id="toc22">XSERVERのWordPressセキュリティ設定</span></h3>
<p><strong>もう一度、サーバーパネルを確認：</strong></p>
<pre><code class="language-plaintext">セキュリティ
→ WordPressセキュリティ設定
→ caymezon.com</code></pre>
<p><strong>見つけた：</strong></p>
<pre><code class="language-plaintext">国外IPアクセス制限設定
  REST API アクセス制限: ON</code></pre>
<p><strong>これだ！！</strong></p>
<hr>
<h3><span id="toc23">原因の理解</span></h3>
<pre><code class="language-plaintext">GitHub Actions（アメリカのサーバー）
  ↓ 国外IPからアクセス
XSERVERの国外IPアクセス制限
  ↓ ブロック
403 Forbidden</code></pre>
<p><strong>GitHub ActionsはクラウドのUbuntu環境（主にアメリカ）で実行される。</strong></p>
<p><strong>そのため、日本国外からのアクセスとして認識され、ブロックされていた！</strong></p>
<hr>
<h3><span id="toc24">なぜローカルからは成功したのか</span></h3>
<pre><code class="language-plaintext">ローカル環境（日本のIP）
  ↓ 日本国内からアクセス
XSERVER
  ↓ 許可
成功 ✅

GitHub Actions（アメリカのIP）
  ↓ 国外からアクセス
XSERVER
  ↓ ブロック
403 Forbidden ❌</code></pre>
<p><strong>これで全てが説明できる！</strong></p>
<hr>
<h2><span id="toc25">解決：設定変更</span></h2>
<h3><span id="toc26">REST APIアクセス制限をOFF</span></h3>
<pre><code class="language-plaintext">XSERVERサーバーパネル
→ セキュリティ
→ WordPressセキュリティ設定
→ caymezon.com
→ 国外IPアクセス制限設定
→ REST API アクセス制限: OFF
→ 設定する</code></pre>
<p><strong>数分待つ（設定反映のため）</strong></p>
<hr>
<h3><span id="toc27">再実行</span></h3>
<pre><code class="language-bash"># 何か変更してpush
git push origin main</code></pre>
<p><strong>GitHub Actions実行...</strong></p>
<hr>
<h3><span id="toc28">成功！</span></h3>
<pre><code class="language-plaintext">📝 処理開始: test-article
──────────────────────────────────────────────────
📄 タイトル: テスト記事
🎯 モード: HTML変換
🏷️  カテゴリー: Development
🔖 タグ: Test

✨ 新規記事を作成します
   📌 Status: draft (新規)

🔧 最初のH1見出しを削除（タイトル重複回避）
✨ Markdown → HTML 変換完了

📤 WordPress投稿中...
✅ 投稿成功！
🔗 URL: https://caymezon.com/test-article/
📊 ステータス: draft

✅ すべての処理が完了しました</code></pre>
<p><strong>完璧！🎉</strong></p>
<hr>
<h3><span id="toc29">WordPress管理画面で確認</span></h3>
<pre><code class="language-plaintext">投稿 → 投稿一覧
→ 「テスト記事」が下書きで追加されている

→ 公開ボタンをクリック

→ 本番サイトで確認

<a rel="noopener" href="https://caymezon.com/test-article/" title="404 NOT FOUND | CayTech Lab" class="blogcard-wrap external-blogcard-wrap a-wrap cf" target="_blank"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img decoding="async" src="https://caymezon.com/wp-content/uploads/cocoon-resources/blog-card-cache/a973a46a52f847d2eac019b8318fab72.jpg" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="160" height="90" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">404 NOT FOUND | CayTech Lab</div><div class="blogcard-snippet external-blogcard-snippet"></div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img decoding="async" src="https://www.google.com/s2/favicons?domain=https://caymezon.com/404/" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">caymezon.com</div></div></div></div></a>
→ 表示されている！</code></pre>
<p><strong>ついに成功！！🎊</strong></p>
<hr>
<h2><span id="toc30">セキュリティについて</span></h2>
<h3><span id="toc31">Q: 国外IPアクセスをOFFにして大丈夫？</span></h3>
<p><strong>A: はい、大丈夫です✅</strong></p>
<p><strong>理由：</strong></p>
<h4>REST APIは二段階で保護されている</h4>
<p><strong>レベル1：読み取り（GET）</strong></p>
<pre><code class="language-plaintext">誰でもアクセス可能
例：記事一覧の取得
→ 公開情報なので問題なし</code></pre>
<p><strong>レベル2：書き込み（POST/PUT/DELETE）</strong></p>
<pre><code class="language-plaintext">Application Password必須
例：記事の作成・更新・削除
→ パスワードなしでは絶対にアクセスできない</code></pre>
<p><strong>つまり：</strong></p>
<ul>
<li>記事を閲覧するだけなら誰でもOK（公開情報だから）</li>
<li>記事を投稿・編集するにはApplication Passwordが必須</li>
<li>パスワードが漏れない限り、安全</li>
</ul>
<hr>
<h2><span id="toc32">学んだこと</span></h2>
<h3><span id="toc33">1. エラーメッセージをよく読む</span></h3>
<p><strong>最初のエラー：</strong></p>
<pre><code class="language-plaintext">Invalid URL</code></pre>
<p><strong>これは環境変数の問題だった。</strong></p>
<p><strong>次のエラー：</strong></p>
<pre><code class="language-plaintext">403 Forbidden</code></pre>
<p><strong>これはアクセス制限の問題だった。</strong></p>
<p><strong>エラーメッセージには原因のヒントが隠れている。</strong></p>
<hr>
<h3><span id="toc34">2. 同じテストでも環境が違えば結果も違う</span></h3>
<p><strong>ローカルからは成功、GitHub Actionsからは失敗。</strong></p>
<p><strong>理由：</strong></p>
<ul>
<li>ローカル：日本国内のIP</li>
<li>GitHub Actions：アメリカのIP</li>
</ul>
<p><strong>環境の違いを意識する重要性。</strong></p>
<hr>
<h3><span id="toc35">3. 段階的にテストする</span></h3>
<p><strong>今回の検証プロセス：</strong></p>
<pre><code class="language-plaintext">1. REST APIは動作している？
   → ブラウザでGET → ✅

2. Application Passwordは正しい？
   → curlでPOST → ✅

3. なぜGitHub Actionsだけ失敗？
   → IPアドレスの違い → 原因発見</code></pre>
<p><strong>一つずつ切り分けていくことで、原因を特定できた。</strong></p>
<hr>
<h3><span id="toc36">4. ドキュメント化の重要性</span></h3>
<p><strong>今回の経験を記録したことで：</strong></p>
<ul>
<li>次に同じ問題が起きても対処できる</li>
<li>他の人も同じ問題を避けられる</li>
<li>知識が蓄積される</li>
</ul>
<p><strong>試行錯誤の過程を記録することは、大きな価値がある。</strong></p>
<hr>
<h2><span id="toc37">トラブルシューティングガイド</span></h2>
<h3><span id="toc38">403 Forbiddenエラーが出たら</span></h3>
<p><strong>チェックリスト：</strong></p>
<h4>1. XSERVERの国外IPアクセス制限</h4>
<pre><code class="language-plaintext">サーバーパネル
→ セキュリティ
→ WordPressセキュリティ設定
→ REST API アクセス制限: OFF</code></pre>
<h4>2. WAF設定</h4>
<pre><code class="language-plaintext">セキュリティ → WAF設定
→ すべてOFF、または /wp-json/ を除外</code></pre>
<h4>3. Application Password</h4>
<pre><code class="language-plaintext">WordPress管理画面
→ ユーザー → プロフィール
→ アプリケーションパスワード
→ 新規作成してGitHub Secretsを更新</code></pre>
<h4>4. REST API動作確認</h4>
<pre><code class="language-plaintext">ブラウザで確認:
https://あなたのサイト.com/wp-json/wp/v2/posts

curlでテスト:
curl -X POST "https://あなたのサイト.com/wp-json/wp/v2/posts" \
  -u "ユーザー名:Application Password" \
  -H "Content-Type: application/json" \
  -d '{"title":"Test","content":"Test","status":"draft"}'</code></pre>
<hr>
<h2><span id="toc39">まとめ</span></h2>
<h3><span id="toc40">問題解決までの道のり</span></h3>
<pre><code class="language-plaintext">GitHub Actions実行
  ↓
403 Forbiddenエラー
  ↓
WAFを疑う → 原因ではなかった
  ↓
REST APIを確認 → 正常に動作
  ↓
Application Passwordを確認 → 問題なし
  ↓
curlテスト → 成功
  ↓
なぜGitHub Actionsだけ失敗？
  ↓
XSERVERの設定を再確認
  ↓
「国外IPアクセス制限」を発見
  ↓
設定をOFF
  ↓
成功！🎉</code></pre>
<p><strong>所要時間：数時間</strong></p>
<p><strong>でも、学びは大きかった。</strong></p>
<hr>
<h3><span id="toc41">完成したシステム</span></h3>
<pre><code class="language-plaintext">VSCodeでMarkdown執筆
  ↓ status: draft
developブランチで自由に編集
  ↓ status: publish
mainブランチにマージ
  ↓ git push
GitHub Actions自動実行
  ↓ REST API（国外IPも許可）
本番WordPress投稿
  ↓ 成功
本番サイトに表示</code></pre>
<p><strong>完全自動化達成！</strong></p>
<hr>
<h3><span id="toc42">この経験から</span></h3>
<p><strong>技術的な問題の多くは、見落としている設定が原因。</strong></p>
<ul>
<li>エラーメッセージをよく読む</li>
<li>段階的にテストする</li>
<li>環境の違いを意識する</li>
<li>諦めずに調べ続ける</li>
</ul>
<p><strong>そして何より、試行錯誤の過程を楽しむこと。</strong></p>
<p>問題を解決したときの達成感は、何物にも代えがたい。</p>
<hr>
<h2><span id="toc43">参考情報</span></h2>
<h3><span id="toc44">XSERVERの設定</span></h3>
<ul>
<li><a href="https://www.xserver.ne.jp/manual/">XSERVERマニュアル - WordPressセキュリティ設定</a></li>
</ul>
<h3><span id="toc45">WordPress REST API</span></h3>
<ul>
<li><a href="https://developer.wordpress.org/rest-api/">WordPress REST API Handbook</a></li>
<li><a href="https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/">Application Passwords</a></li>
</ul>
<h3><span id="toc46">GitHub Actions</span></h3>
<ul>
<li><a href="https://docs.github.com/ja/actions">GitHub Actions Documentation</a></li>
<li><a href="https://docs.github.com/ja/actions/security-guides/encrypted-secrets">Encrypted secrets</a></li>
</ul>
<hr>
<h2><span id="toc47">おわりに</span></h2>
<p>403エラーとの戦いは、予想以上に手強かった。</p>
<p>でも、原因を突き止めて解決できたときの喜びは、忘れられない。</p>
<p>もし同じ問題に遭遇している方がいたら、この記事が少しでも役に立てば幸いです。</p>
<p>そして、諦めずに調べ続ければ、必ず解決策は見つかる――</p>
<p>そんな希望を持ち続けてほしい。</p>
<p><strong>Happy Coding! 🚀</strong></p>
<hr>
<p><strong>最終更新：</strong> 2026-01-12</p><p>The post <a href="https://caymezon.com/github-actions-wordpress-403-error-battle/">【試行錯誤の記録】GitHub ActionsでWordPress自動投稿！403エラーとの戦い</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://caymezon.com/github-actions-wordpress-403-error-battle/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>WordPressブログの執筆環境を劇的改善した話【VSCode × Markdown × 自動化】</title>
		<link>https://caymezon.com/wordpress-writing-environment-improvement/</link>
					<comments>https://caymezon.com/wordpress-writing-environment-improvement/#respond</comments>
		
		<dc:creator><![CDATA[caymezon]]></dc:creator>
		<pubDate>Mon, 12 Jan 2026 07:17:47 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[Markdown]]></category>
		<category><![CDATA[VSCode]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[生産性向上]]></category>
		<category><![CDATA[自動化]]></category>
		<guid isPermaLink="false">https://caymezon.com/wordpress-writing-environment-improvement/</guid>

					<description><![CDATA[<p>目次 はじめに：執筆環境の重要性Before：WordPress標準の執筆フロー従来の方法問題点After：理想の執筆環境新しい方法改善されたこと実際の運用フロー日常的な記事投稿既存記事の更新複数記事の一括投稿GitHu [&#8230;]</p>
<p>The post <a href="https://caymezon.com/wordpress-writing-environment-improvement/">WordPressブログの執筆環境を劇的改善した話【VSCode × Markdown × 自動化】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></description>
										<content:encoded><![CDATA[<div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-8" checked><label class="toc-title" for="toc-checkbox-8">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">はじめに：執筆環境の重要性</a></li><li><a href="#toc2" tabindex="0">Before：WordPress標準の執筆フロー</a><ol><li><a href="#toc3" tabindex="0">従来の方法</a></li><li><a href="#toc4" tabindex="0">問題点</a></li></ol></li><li><a href="#toc5" tabindex="0">After：理想の執筆環境</a><ol><li><a href="#toc6" tabindex="0">新しい方法</a></li><li><a href="#toc7" tabindex="0">改善されたこと</a></li></ol></li><li><a href="#toc8" tabindex="0">実際の運用フロー</a><ol><li><a href="#toc9" tabindex="0">日常的な記事投稿</a></li><li><a href="#toc10" tabindex="0">既存記事の更新</a></li><li><a href="#toc11" tabindex="0">複数記事の一括投稿</a></li></ol></li><li><a href="#toc12" tabindex="0">GitHub Actions連携</a><ol><li><a href="#toc13" tabindex="0">さらなる自動化</a></li></ol></li><li><a href="#toc14" tabindex="0">効果測定</a><ol><li><a href="#toc15" tabindex="0">定量的な改善</a></li><li><a href="#toc16" tabindex="0">定性的な改善</a></li></ol></li><li><a href="#toc17" tabindex="0">Tips & Best Practices</a><ol><li><a href="#toc18" tabindex="0">1. フォルダ構成の工夫</a></li><li><a href="#toc19" tabindex="0">2. テンプレートの活用</a></li><li><a href="#toc20" tabindex="0">3. 画像の命名規則</a></li><li><a href="#toc21" tabindex="0">4. プレビューの活用</a></li><li><a href="#toc22" tabindex="0">5. スニペットの活用</a></li></ol></li><li><a href="#toc23" tabindex="0">トラブルシューティング</a><ol><li><a href="#toc24" tabindex="0">よくある問題と解決策</a></li></ol></li><li><a href="#toc25" tabindex="0">まとめ</a><ol><li><a href="#toc26" tabindex="0">改善のポイント</a></li><li><a href="#toc27" tabindex="0">Before → After</a></li><li><a href="#toc28" tabindex="0">得られたもの</a></li></ol></li><li><a href="#toc29" tabindex="0">おわりに</a></li><li><a href="#toc30" tabindex="0">参考リンク</a></li><li><a href="#toc31" tabindex="0">関連記事</a></li></ol>
    </div>
  </div>

<h2><span id="toc1">はじめに：執筆環境の重要性</span></h2>
<p>ブログを続けるために最も重要なことは何でしょうか？</p>
<ul>
<li>文章力？</li>
<li>ネタ探し？</li>
<li>SEO対策？</li>
</ul>
<p>もちろん、これらも重要です。しかし、私が最も重要だと考えるのは、<strong>執筆環境の快適さ</strong>です。</p>
<p>書きやすい環境があれば、自然と記事を書きたくなります。逆に、環境が悪いと、書くこと自体がストレスになります。</p>
<p>この記事では、私がWordPressブログの執筆環境を劇的に改善した方法をご紹介します。</p>
<p><img decoding="async" src="images/01-environment-comparison.png" alt="執筆環境の変化"></p>
<p> </p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"【改訂第3版】WordPress 仕事の現場でサッと使える！ デザイン教科書 ［WordPress 6.x対応版］ (Webデザイナー養成講座)","b":"技術評論社","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/514CbLHJNVL._SL500_.jpg","\/518zU1JQhaL._SL500_.jpg","\/51JppOL1T3L._SL500_.jpg","\/51ytewMgIGL._SL500_.jpg","\/51ZwjXcqdnL._SL500_.jpg","\/51DY9XYqPTL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4297135779","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4297135779","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/%E3%80%90%E6%94%B9%E8%A8%82%E7%AC%AC3%E7%89%88%E3%80%91WordPress%20%E4%BB%95%E4%BA%8B%E3%81%AE%E7%8F%BE%E5%A0%B4%E3%81%A7%E3%82%B5%E3%83%83%E3%81%A8%E4%BD%BF%E3%81%88%E3%82%8B%EF%BC%81%20%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E6%95%99%E7%A7%91%E6%9B%B8%20%EF%BC%BBWordPress%206.x%E5%AF%BE%E5%BF%9C%E7%89%88%EF%BC%BD%20(Web%E3%83%87%E3%82%B6%E3%82%A4%E3%83%8A%E3%83%BC%E9%A4%8A%E6%88%90%E8%AC%9B%E5%BA%A7)\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=%E3%80%90%E6%94%B9%E8%A8%82%E7%AC%AC3%E7%89%88%E3%80%91WordPress%20%E4%BB%95%E4%BA%8B%E3%81%AE%E7%8F%BE%E5%A0%B4%E3%81%A7%E3%82%B5%E3%83%83%E3%81%A8%E4%BD%BF%E3%81%88%E3%82%8B%EF%BC%81%20%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E6%95%99%E7%A7%91%E6%9B%B8%20%EF%BC%BBWordPress%206.x%E5%AF%BE%E5%BF%9C%E7%89%88%EF%BC%BD%20(Web%E3%83%87%E3%82%B6%E3%82%A4%E3%83%8A%E3%83%BC%E9%A4%8A%E6%88%90%E8%AC%9B%E5%BA%A7)","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"ywMaT","s":"s"});</script></p>
<div id="msmaflink-ywMaT">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p>
<h2><span id="toc2">Before：WordPress標準の執筆フロー</span></h2>
<h3><span id="toc3">従来の方法</span></h3>
<p><strong>私が以前使っていた方法：</strong></p>
<pre><code class="language-plaintext">1. WordPress管理画面を開く
   → ログイン
   
2. Gutenbergエディタで執筆
   → ブラウザ上で直接編集
   
3. 画像をドラッグ&ドロップ
   → Media Libraryにアップロード
   
4. プレビュー確認
   → 別タブで開く
   
5. 公開
   → 「公開」ボタンをクリック</code></pre>
<h3><span id="toc4">問題点</span></h3>
<p><strong>1. ブラウザに依存</strong></p>
<ul>
<li>ブラウザがクラッシュしたら、書いた内容が消える</li>
<li>自動保存はあるが、常に不安</li>
</ul>
<p><strong>2. バージョン管理ができない</strong></p>
<ul>
<li>いつ何を変更したか分からない</li>
<li>過去のバージョンに戻せない</li>
</ul>
<p><strong>3. 画像管理が煩雑</strong></p>
<ul>
<li>同じ画像を何度もアップロード</li>
<li>Media Libraryが肥大化</li>
<li>どれが最新の画像か分からない</li>
</ul>
<p><strong>4. オフライン作業ができない</strong></p>
<ul>
<li>ネット環境が必須</li>
<li>移動中に書けない</li>
</ul>
<p><strong>5. 執筆に集中できない</strong></p>
<ul>
<li>Gutenbergの操作に気を取られる</li>
<li>Markdown記法が使えない</li>
</ul>
<p> </p>
<h2><span id="toc5">After：理想の執筆環境</span></h2>
<h3><span id="toc6">新しい方法</span></h3>
<p><strong>現在の方法：</strong></p>
<pre><code class="language-plaintext">1. VSCodeで記事作成
   → Markdown記法で執筆
   → Ctrl+Shift+V でプレビュー
   
2. 画像を配置
   → ローカルフォルダに保存
   
3. スクリプト実行
   → npm run publish
   
4. WordPress管理画面で確認
   → 公開</code></pre>
<h3><span id="toc7">改善されたこと</span></h3>
<p><strong>1. 慣れ親しんだエディタ</strong></p>
<ul>
<li>VSCodeで執筆</li>
<li>ショートカットキーが使える</li>
<li>拡張機能が使える</li>
</ul>
<p><strong>2. バージョン管理</strong></p>
<pre><code class="language-bash">git commit -m "記事追加：○○について"
git push</code></pre>
<ul>
<li>いつ何を変更したか記録</li>
<li>過去のバージョンに戻せる</li>
<li>チーム開発も可能</li>
</ul>
<p><strong>3. 画像管理の自動化</strong></p>
<ul>
<li>画像を上書き保存すればOK</li>
<li>自動で変更を検知</li>
<li>Media Libraryがクリーン</li>
</ul>
<p><strong>4. オフライン作業</strong></p>
<ul>
<li>ネット環境不要</li>
<li>移動中でも書ける</li>
<li>書き溜めておいて、まとめて投稿</li>
</ul>
<p><strong>5. 執筆に集中</strong></p>
<ul>
<li>Markdownでシンプルに書ける</li>
<li>プレビューも高速</li>
<li>余計な操作が不要</li>
</ul>
<p> </p>
<h2><span id="toc8">実際の運用フロー</span></h2>
<h3><span id="toc9">日常的な記事投稿</span></h3>
<h4>Step 1：記事作成</h4>
<p><strong>VSCodeで記事を書く：</strong></p>
<pre><code class="language-bash">code posts/new-article/post.md</code></pre>
<p><strong>Front Matter（記事のメタ情報）：</strong></p>
<pre><code class="language-yaml">---
title: "記事タイトル"
slug: new-article
category: Development
tags: [WordPress, Markdown, 自動化]
featured_image: featured.jpg
mode: html
status: draft
date: 2026-01-11
---</code></pre>
<p><strong>本文（Markdown記法）：</strong></p>
<pre><code class="language-markdown">## はじめに

この記事では、○○について解説します。

### 対象読者

- ○○に興味がある方
- ○○を始めたい方

## 本文

詳しい説明...

npm install example

![スクリーンショット](images/01-screenshot.png)

## まとめ

この記事では、以下について解説しました...</code></pre>
<p><strong>プレビュー確認：</strong></p>
<pre><code class="language-plaintext">Ctrl+Shift+V</code></pre>
<p>VSCode内でリアルタイムプレビュー！</p>
<p> </p>
<h4>Step 2：画像配置</h4>
<p><strong>フォルダ構成：</strong></p>
<pre><code class="language-plaintext">posts/new-article/
├── post.md
├── featured.jpg  ← アイキャッチ画像
└── images/
    ├── 01-screenshot.png
    └── 02-diagram.png</code></pre>
<p><strong>Markdown内の参照：</strong></p>
<pre><code class="language-markdown">![スクリーンショット](images/01-screenshot.png)
![図解](images/02-diagram.png)</code></pre>
<p><strong>シンプル！</strong></p>
<p> </p>
<h4>Step 3：スクリプト実行</h4>
<pre><code class="language-bash">npm run publish-single new-article</code></pre>
<p><strong>出力：</strong></p>
<pre><code class="language-plaintext">📝 処理開始: new-article
📄 タイトル: 記事タイトル
🎯 モード: HTML変換
🏷️  カテゴリー: Development
🔖 タグ: WordPress, Markdown, 自動化

📸 画像アップロード中...
  ✅ 01-screenshot.png → new-article-01-screenshot-a3f2b1.png
  ✅ 02-diagram.png → new-article-02-diagram-7c4d8e.png

🖼️  アイキャッチ画像アップロード中...
  ✅ featured.jpg → new-article-featured-9e8f7d.jpg

🔧 最初のH1見出しを削除（タイトル重複回避）
✨ Markdown → HTML 変換完了

✨ 新規記事を作成します
   📌 Status: draft (新規)

📤 WordPress投稿中...
✅ 投稿成功！
🔗 URL: http://example.com/new-article/
📊 ステータス: draft</code></pre>
<p><strong>数秒で完了！</strong></p>
<p> </p>
<h4>Step 4：WordPress管理画面で確認</h4>
<pre><code class="language-plaintext">1. WordPress管理画面を開く
2. 投稿 → 投稿一覧 → 下書き
3. 記事を開く
4. 最終確認
5. 「公開」ボタンをクリック</code></pre>
<p><strong>完璧に投稿されている！</strong></p>
<p> </p>
<h3><span id="toc10">既存記事の更新</span></h3>
<p><strong>さらに便利なのが、既存記事の更新です。</strong></p>
<h4>Step 1：VSCodeで編集</h4>
<pre><code class="language-bash">code posts/existing-article/post.md</code></pre>
<p><strong>内容を編集：</strong></p>
<pre><code class="language-markdown">## 追加セクション

新しい情報を追加...</code></pre>
<p> </p>
<h4>Step 2：画像も更新（オプション）</h4>
<p><strong>スクリーンショットを撮り直して、同じファイル名で上書き保存：</strong></p>
<pre><code class="language-plaintext">posts/existing-article/images/01-screenshot.png</code></pre>
<p><strong>ファイル名は変えない！</strong></p>
<p> </p>
<h4>Step 3：スクリプト実行</h4>
<pre><code class="language-bash">npm run test-local</code></pre>
<p><strong>出力：</strong></p>
<pre><code class="language-plaintext">📝 処理開始: existing-article

📸 画像アップロード中...
  🗑️  古い画像を削除: existing-article-01-screenshot-a3f2b1.png
  ✅ 01-screenshot.png → existing-article-01-screenshot-3f8d2a.png
  ♻️  02-diagram.png (変更なし)

📝 既存記事を更新します（ID: 123）
   📌 Status: publish (既存維持)

✅ 更新成功！</code></pre>
<p><strong>自動的に：</strong></p>
<ul>
<li>既存記事を検索</li>
<li>画像の変更を検知</li>
<li>古い画像を削除</li>
<li>新しい画像をアップロード</li>
<li>変更がない画像は再利用</li>
<li>記事を更新</li>
</ul>
<p><strong>完璧！</strong></p>
<p> </p>
<h3><span id="toc11">複数記事の一括投稿</span></h3>
<p><strong>過去記事の移行など：</strong></p>
<pre><code class="language-bash">npm run publish</code></pre>
<p><strong>すべての記事を自動投稿！</strong></p>
<pre><code class="language-plaintext">🚀 3 件の記事を処理します

📝 処理開始: article-1
✅ 投稿成功！

📝 処理開始: article-2
✅ 投稿成功！

📝 処理開始: article-3
✅ 投稿成功！

✅ すべての処理が完了しました</code></pre>
<p> </p>
<h2><span id="toc12">GitHub Actions連携</span></h2>
<h3><span id="toc13">さらなる自動化</span></h3>
<p><strong>次のステップ：git push で自動投稿</strong></p>
<h4>設定ファイル</h4>
<p><strong><code>.github/workflows/publish.yml</code></strong></p>
<pre><code class="language-yaml">name: Publish to WordPress

on:
  push:
    branches: [main]
    paths: ['posts/**']

jobs:
  publish:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
      
      - name: Install dependencies
        run: npm install
      
      - name: Publish to WordPress
        env:
          WP_URL: ${{ secrets.WP_URL }}
          WP_USER: ${{ secrets.WP_USER }}
          WP_APP_PASSWORD: ${{ secrets.WP_APP_PASSWORD }}
        run: npm run publish</code></pre>
<h4>GitHub Secrets設定</h4>
<pre><code class="language-plaintext">GitHub → Settings → Secrets and variables → Actions
→ New repository secret

WP_URL: https://example.com
WP_USER: username
WP_APP_PASSWORD: xxxx xxxx xxxx xxxx</code></pre>
<h4>運用フロー</h4>
<pre><code class="language-plaintext">1. VSCodeで記事作成
   posts/new-article/post.md
   
2. Git commit
   git add .
   git commit -m "記事追加：新しい記事"
   
3. Git push
   git push origin main
   
4. GitHub Actions自動実行
   → WordPressに自動投稿
   
5. メール通知
   → 投稿完了のお知らせ</code></pre>
<p><strong>完全自動化！🎉</strong></p>
<p> </p>
<h2><span id="toc14">効果測定</span></h2>
<h3><span id="toc15">定量的な改善</span></h3>
<p><strong>執筆時間の短縮：</strong></p>
<pre><code class="language-plaintext">従来の方法:
  記事作成: 2時間
  画像管理: 30分
  投稿作業: 10分
  ──────────
  合計: 2時間40分

新しい方法:
  記事作成: 2時間
  画像管理: 0分（自動）
  投稿作業: 30秒
  ──────────
  合計: 2時間30秒

短縮: 約40分</code></pre>
<p><strong>更新作業の効率化：</strong></p>
<pre><code class="language-plaintext">従来の方法:
  WordPress管理画面を開く
  → 記事を探す
  → 編集
  → 画像を差し替え
  → 保存
  合計: 約10分

新しい方法:
  VSCodeで編集
  → npm run publish
  合計: 約30秒

短縮: 約9分30秒</code></pre>
<p><strong>年間の効果：</strong></p>
<pre><code class="language-plaintext">週1回の記事投稿 × 52週 = 52記事/年

短縮時間:
  投稿: 40分 × 52回 = 2,080分 (約35時間)
  更新: 9.5分 × 52回 = 494分 (約8時間)
  ──────────────────
  合計: 約43時間/年

浮いた時間で約21記事追加可能！</code></pre>
<p> </p>
<h3><span id="toc16">定性的な改善</span></h3>
<p><strong>1. ストレスの軽減</strong></p>
<ul>
<li>ブラウザクラッシュの不安がない</li>
<li>画像管理の煩わしさがない</li>
<li>執筆に集中できる</li>
</ul>
<p><strong>2. モチベーションの向上</strong></p>
<ul>
<li>書きやすいから、書きたくなる</li>
<li>ワンコマンドで投稿できる満足感</li>
<li>Git管理による安心感</li>
</ul>
<p><strong>3. 記事の品質向上</strong></p>
<ul>
<li>プレビューで確認しやすい</li>
<li>過去のバージョンと比較できる</li>
<li>落ち着いて推敲できる</li>
</ul>
<p><strong>4. 継続しやすい</strong></p>
<ul>
<li>オフラインでも書ける</li>
<li>スキマ時間を活用</li>
<li>無理なく続けられる</li>
</ul>
<p> </p>
<h2><span id="toc17">Tips & Best Practices</span></h2>
<h3><span id="toc18">1. フォルダ構成の工夫</span></h3>
<p><strong>推奨構成：</strong></p>
<pre><code class="language-plaintext">posts/
├── 2025/
│   ├── 01-january/
│   │   ├── article-1/
│   │   └── article-2/
│   └── 02-february/
│       └── article-3/
└── 2026/
    └── 01-january/
        └── article-4/</code></pre>
<p><strong>メリット：</strong></p>
<ul>
<li>記事が探しやすい</li>
<li>管理しやすい</li>
<li>アーカイブしやすい</li>
</ul>
<p> </p>
<h3><span id="toc19">2. テンプレートの活用</span></h3>
<p><strong>記事テンプレート：</strong></p>
<pre><code class="language-bash">cp templates/post-template.md posts/new-article/post.md</code></pre>
<p><strong>テンプレート内容：</strong></p>
<pre><code class="language-markdown">---
title: "【カテゴリ名】記事タイトル"
slug: article-name
category: Development
tags: [タグ1, タグ2]
featured_image: featured.jpg
mode: html
status: draft
date: 2026-01-11
---

## はじめに

この記事では...

### 対象読者

- ○○な方
- ○○を始めたい方

## 本文

詳しい説明...

## まとめ

この記事では...</code></pre>
<p><strong>効率アップ！</strong></p>
<p> </p>
<h3><span id="toc20">3. 画像の命名規則</span></h3>
<p><strong>推奨：</strong></p>
<pre><code class="language-plaintext">01-descriptive-name.png
02-another-image.png
03-final-screenshot.png</code></pre>
<p><strong>メリット：</strong></p>
<ul>
<li>順番が分かる</li>
<li>内容が分かる</li>
<li>管理しやすい</li>
</ul>
<p> </p>
<h3><span id="toc21">4. プレビューの活用</span></h3>
<p><strong>VSCodeの拡張機能：</strong></p>
<ul>
<li>
<p><strong>Markdown All in One</strong></p>
<ul>
<li>便利なショートカット</li>
<li>Table of Contents自動生成</li>
</ul>
</li>
<li>
<p><strong>Markdown Preview Enhanced</strong></p>
<ul>
<li>リッチなプレビュー</li>
<li>PDF出力</li>
</ul>
</li>
</ul>
<p><strong>Ctrl+Shift+V で即座にプレビュー！</strong></p>
<p> </p>
<h3><span id="toc22">5. スニペットの活用</span></h3>
<p><strong>VSCodeスニペット：</strong></p>
<pre><code class="language-json">{
  "Code Block": {
    "prefix": "code",
    "body": [
      "```${1:bash}",
      "$0",
      "```"
    ]
  },
  "Image": {
    "prefix": "img",
    "body": [
      "![${1:説明}](images/${2:filename}.png)"
    ]
  }
}</code></pre>
<p><strong>使い方：</strong></p>
<pre><code class="language-plaintext">code → Tab → コードブロック完成
img → Tab → 画像リンク完成</code></pre>
<p> </p>
<h2><span id="toc23">トラブルシューティング</span></h2>
<h3><span id="toc24">よくある問題と解決策</span></h3>
<p><strong>問題1：画像が表示されない</strong></p>
<p><strong>原因：</strong></p>
<ul>
<li>画像パスが間違っている</li>
</ul>
<p><strong>確認：</strong></p>
<pre><code class="language-plaintext">post.md: ![説明](images/screenshot.png)
実際のファイル: posts/article/images/screenshot.png</code></pre>
<p><strong>解決：</strong></p>
<ul>
<li>パスを修正</li>
<li>ファイル名を確認</li>
</ul>
<p> </p>
<p><strong>問題2：既存記事が見つからない</strong></p>
<p><strong>原因：</strong></p>
<ul>
<li>スラッグが違っている</li>
</ul>
<p><strong>確認：</strong></p>
<pre><code class="language-plaintext">post.md: slug: article-name
WordPress: 既存記事のスラッグ？</code></pre>
<p><strong>解決：</strong></p>
<ul>
<li>スラッグを一致させる</li>
<li>WordPress管理画面で確認</li>
</ul>
<p> </p>
<p><strong>問題3：認証エラー</strong></p>
<p><strong>原因：</strong></p>
<ul>
<li>Application Passwordが間違っている</li>
</ul>
<p><strong>解決：</strong></p>
<pre><code class="language-plaintext">1. WordPress管理画面
   → ユーザー → プロフィール
   → アプリケーションパスワード
   
2. 新しいパスワードを生成

3. .env.local を更新
   WP_APP_PASSWORD=xxxx xxxx xxxx xxxx</code></pre>
<p> </p>
<h2><span id="toc25">まとめ</span></h2>
<h3><span id="toc26">改善のポイント</span></h3>
<p><strong>1. 執筆環境を整える</strong></p>
<ul>
<li>慣れ親しんだエディタ</li>
<li>Markdown記法</li>
<li>リアルタイムプレビュー</li>
</ul>
<p><strong>2. 自動化する</strong></p>
<ul>
<li>画像アップロード</li>
<li>パス置換</li>
<li>既存記事の更新</li>
</ul>
<p><strong>3. バージョン管理</strong></p>
<ul>
<li>Git管理</li>
<li>履歴が残る</li>
<li>安心感</li>
</ul>
<p><strong>4. 継続的な改善</strong></p>
<ul>
<li>GitHub Actions連携</li>
<li>さらなる自動化</li>
<li>効率化</li>
</ul>
<h3><span id="toc27">Before → After</span></h3>
<p><strong>Before（従来の方法）：</strong></p>
<pre><code class="language-plaintext">WordPress Gutenbergで執筆
  ↓ ブラウザに依存
  ↓ バージョン管理なし
  ↓ 画像管理が煩雑
  ↓ ストレス</code></pre>
<p><strong>After（新しい方法）：</strong></p>
<pre><code class="language-plaintext">VSCodeで執筆
  ↓ Markdown記法
  ↓ Git管理
  ↓ 画像自動管理
  ↓ npm run publish
  ↓ 快適！</code></pre>
<h3><span id="toc28">得られたもの</span></h3>
<p><strong>時間：</strong></p>
<ul>
<li>年間約43時間の短縮</li>
<li>浮いた時間で約21記事追加可能</li>
</ul>
<p><strong>快適さ：</strong></p>
<ul>
<li>ストレスフリーな執筆環境</li>
<li>執筆に集中できる</li>
<li>書くことが楽しくなる</li>
</ul>
<p><strong>継続性：</strong></p>
<ul>
<li>モチベーション維持</li>
<li>無理なく続けられる</li>
<li>ブログが続く</li>
</ul>
<p><strong>品質：</strong></p>
<ul>
<li>落ち着いて推敲できる</li>
<li>バージョン管理で安心</li>
<li>記事の品質が向上</li>
</ul>
<p> </p>
<h2><span id="toc29">おわりに</span></h2>
<p>ブログを続けるために最も重要なのは、<strong>書きやすい環境</strong>です。</p>
<p>環境が整えば、自然と記事を書きたくなります。</p>
<p>私の場合、執筆環境を改善したことで：</p>
<ul>
<li>記事を書くことが楽しくなった</li>
<li>更新頻度が上がった</li>
<li>記事の品質が向上した</li>
</ul>
<p>そして何より、<strong>執筆そのものに集中できる</strong>ようになりました。</p>
<p>もしあなたがWordPressでブログを書いているなら、ぜひこの方法を試してみてください。</p>
<p>きっと、ブログを書くことがもっと楽しくなるはずです。</p>
<p><strong>Happy Blogging! 🚀</strong></p>
<hr>
<h2><span id="toc30">参考リンク</span></h2>
<ul>
<li><a href="https://code.visualstudio.com/">VSCode</a></li>
<li><a href="https://www.markdownguide.org/">Markdown Guide</a></li>
<li><a href="https://github.com/features/actions">GitHub Actions</a></li>
<li><a href="https://developer.wordpress.org/rest-api/">WordPress REST API</a></li>
</ul>
<h2><span id="toc31">関連記事</span></h2>
<ul>
<li>
<p><strong>VSCodeからWordPressへ！Markdown記事を自動投稿するシステムを作った話</strong></p>
<ul>
<li>システムの全体像と開発の経緯</li>
</ul>
</li>
<li>
<p><strong>【試行錯誤の記録】WordPress画像管理を最適化！ハッシュベースの賢い仕組み</strong></p>
<ul>
<li>画像管理の技術的な詳細</li>
</ul>
</li>
</ul>
<p><!-- ## GitHub

実際のコードは、GitHubで公開予定です。 --></p>
<p><strong>最終更新：</strong> 2026-01-11</p><p>The post <a href="https://caymezon.com/wordpress-writing-environment-improvement/">WordPressブログの執筆環境を劇的改善した話【VSCode × Markdown × 自動化】</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://caymezon.com/wordpress-writing-environment-improvement/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>VSCodeからWordPressへ！Markdown記事を自動投稿するシステムを作った話</title>
		<link>https://caymezon.com/vscode-wordpress-markdown-publisher/</link>
					<comments>https://caymezon.com/vscode-wordpress-markdown-publisher/#respond</comments>
		
		<dc:creator><![CDATA[caymezon]]></dc:creator>
		<pubDate>Mon, 12 Jan 2026 05:58:23 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Markdown]]></category>
		<category><![CDATA[Node.js]]></category>
		<category><![CDATA[REST API]]></category>
		<category><![CDATA[VSCode]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[自動化]]></category>
		<guid isPermaLink="false">https://caymezon.com/vscode-wordpress-markdown-publisher/</guid>

					<description><![CDATA[<p>目次 はじめに：なぜこのシステムを作ったのか理想の執筆環境とは求めていた機能開発の流れ：試行錯誤の連続Phase 1：まずは基本機能Phase 2：画像アップロードの実装Phase 3：WP Githuber MDプラグ [&#8230;]</p>
<p>The post <a href="https://caymezon.com/vscode-wordpress-markdown-publisher/">VSCodeからWordPressへ！Markdown記事を自動投稿するシステムを作った話</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></description>
										<content:encoded><![CDATA[<div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-10" checked><label class="toc-title" for="toc-checkbox-10">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">はじめに：なぜこのシステムを作ったのか</a></li><li><a href="#toc2" tabindex="0">理想の執筆環境とは</a><ol><li><a href="#toc3" tabindex="0">求めていた機能</a></li></ol></li><li><a href="#toc4" tabindex="0">開発の流れ：試行錯誤の連続</a><ol><li><a href="#toc5" tabindex="0">Phase 1：まずは基本機能</a></li><li><a href="#toc6" tabindex="0">Phase 2：画像アップロードの実装</a></li><li><a href="#toc7" tabindex="0">Phase 3：WP Githuber MDプラグインとの出会い</a></li></ol></li><li><a href="#toc8" tabindex="0">遭遇した問題たち</a><ol><li><a href="#toc9" tabindex="0">問題1：Markdownがそのまま表示される</a></li><li><a href="#toc10" tabindex="0">問題2：既存記事がHTMLの場合</a></li><li><a href="#toc11" tabindex="0">問題3：画像が増え続ける</a></li></ol></li><li><a href="#toc12" tabindex="0">最大の挑戦：画像管理の最適化</a><ol><li><a href="#toc13" tabindex="0">解決策の検討</a></li><li><a href="#toc14" tabindex="0">ハッシュベースの実装</a></li></ol></li><li><a href="#toc15" tabindex="0">完成したシステム</a><ol><li><a href="#toc16" tabindex="0">最終的なアーキテクチャ</a></li><li><a href="#toc17" tabindex="0">主要機能</a></li></ol></li><li><a href="#toc18" tabindex="0">実際の運用フロー</a><ol><li><a href="#toc19" tabindex="0">日常的な記事投稿</a></li><li><a href="#toc20" tabindex="0">既存記事の更新</a></li></ol></li><li><a href="#toc21" tabindex="0">得られたメリット</a><ol><li><a href="#toc22" tabindex="0">1. 執筆効率の向上</a></li><li><a href="#toc23" tabindex="0">2. 画像管理の最適化</a></li><li><a href="#toc24" tabindex="0">3. 既存記事の更新が楽</a></li><li><a href="#toc25" tabindex="0">4. バージョン管理</a></li></ol></li><li><a href="#toc26" tabindex="0">今後の展望</a><ol><li><a href="#toc27" tabindex="0">GitHub Actions連携</a></li><li><a href="#toc28" tabindex="0">複数サイト対応</a></li><li><a href="#toc29" tabindex="0">バルク投稿</a></li></ol></li><li><a href="#toc30" tabindex="0">まとめ</a><ol><li><a href="#toc31" tabindex="0">開発を通じて学んだこと</a></li><li><a href="#toc32" tabindex="0">このシステムを作って良かったこと</a></li></ol></li><li><a href="#toc33" tabindex="0">技術スタック</a></li><li><a href="#toc34" tabindex="0">おわりに</a></li><li><a href="#toc35" tabindex="0">参考リンク</a></li></ol>
    </div>
  </div>

<h2><span id="toc1">はじめに：なぜこのシステムを作ったのか</span></h2>
<p>ブログを書くとき、皆さんはどんなエディタを使っていますか？</p>
<p>WordPress標準のGutenbergエディタも便利ですが、長文を書いているとこんな不満が出てきませんか？</p>
<ul>
<li><strong>ブラウザがクラッシュしたら書いた内容が消える不安</strong></li>
<li><strong>画像管理が煩雑で、同じ画像が重複してアップロードされる</strong></li>
<li><strong>Markdown記法で書けないもどかしさ</strong></li>
<li><strong>バージョン管理ができない</strong></li>
</ul>
<p>私もまさにこの悩みを抱えていました。そこで、<strong>VSCodeでMarkdownを書いて、ワンコマンドでWordPressに投稿できるシステム</strong>を作ることにしました。</p>
<p>この記事では、その開発の経緯と、遭遇した問題、そして最終的に完成したシステムについてご紹介します。</p>
<p> </p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"Visual Studio Code実践入門! ~ソフトウェア開発の強力手段","b":"","t":"","d":"https:\/\/m.media-amazon.com","c_p":"","p":["\/images\/I\/51+Jrz2cBnL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/486594334X","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/486594334X","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/Visual%20Studio%20Code%E5%AE%9F%E8%B7%B5%E5%85%A5%E9%96%80!%20~%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E9%96%8B%E7%99%BA%E3%81%AE%E5%BC%B7%E5%8A%9B%E6%89%8B%E6%AE%B5\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=Visual%20Studio%20Code%E5%AE%9F%E8%B7%B5%E5%85%A5%E9%96%80!%20~%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2%E9%96%8B%E7%99%BA%E3%81%AE%E5%BC%B7%E5%8A%9B%E6%89%8B%E6%AE%B5","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"sl77U","s":"s"});</script></p>
<div id="msmaflink-sl77U">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p>
<p><!-- START MoshimoAffiliateEasyLink --><script type="text/javascript">(function(b,c,f,g,a,d,e){b.MoshimoAffiliateObject=a;b[a]=b[a]||function(){arguments.currentScript=c.currentScript||c.scripts[c.scripts.length-2];(b[a].q=b[a].q||[]).push(arguments)};c.getElementById(a)||(d=c.createElement(f),d.src=g,d.id=a,e=c.getElementsByTagName("body")[0],e.appendChild(d))})(window,document,"script","//dn.msmstatic.com/site/cardlink/bundle.js?20220329","msmaflink");msmaflink({"n":"【改訂第3版】WordPress 仕事の現場でサッと使える！ デザイン教科書 ［WordPress 6.x対応版］ (Webデザイナー養成講座)","b":"技術評論社","t":"","d":"https:\/\/m.media-amazon.com","c_p":"\/images\/I","p":["\/514CbLHJNVL._SL500_.jpg","\/518zU1JQhaL._SL500_.jpg","\/51JppOL1T3L._SL500_.jpg","\/51ytewMgIGL._SL500_.jpg","\/51ZwjXcqdnL._SL500_.jpg","\/51DY9XYqPTL._SL500_.jpg"],"u":{"u":"https:\/\/www.amazon.co.jp\/dp\/4297135779","t":"amazon","r_v":""},"v":"2.1","b_l":[{"id":1,"u_tx":"Amazonで見る","u_bc":"#f79256","u_url":"https:\/\/www.amazon.co.jp\/dp\/4297135779","a_id":1384942,"p_id":170,"pl_id":27060,"pc_id":185,"s_n":"amazon","u_so":1},{"id":2,"u_tx":"楽天市場で見る","u_bc":"#f76956","u_url":"https:\/\/search.rakuten.co.jp\/search\/mall\/%E3%80%90%E6%94%B9%E8%A8%82%E7%AC%AC3%E7%89%88%E3%80%91WordPress%20%E4%BB%95%E4%BA%8B%E3%81%AE%E7%8F%BE%E5%A0%B4%E3%81%A7%E3%82%B5%E3%83%83%E3%81%A8%E4%BD%BF%E3%81%88%E3%82%8B%EF%BC%81%20%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E6%95%99%E7%A7%91%E6%9B%B8%20%EF%BC%BBWordPress%206.x%E5%AF%BE%E5%BF%9C%E7%89%88%EF%BC%BD%20(Web%E3%83%87%E3%82%B6%E3%82%A4%E3%83%8A%E3%83%BC%E9%A4%8A%E6%88%90%E8%AC%9B%E5%BA%A7)\/","a_id":1384917,"p_id":54,"pl_id":27059,"pc_id":54,"s_n":"rakuten","u_so":2},{"id":3,"u_tx":"Yahoo!ショッピングで見る","u_bc":"#66a7ff","u_url":"https:\/\/shopping.yahoo.co.jp\/search?first=1\u0026p=%E3%80%90%E6%94%B9%E8%A8%82%E7%AC%AC3%E7%89%88%E3%80%91WordPress%20%E4%BB%95%E4%BA%8B%E3%81%AE%E7%8F%BE%E5%A0%B4%E3%81%A7%E3%82%B5%E3%83%83%E3%81%A8%E4%BD%BF%E3%81%88%E3%82%8B%EF%BC%81%20%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E6%95%99%E7%A7%91%E6%9B%B8%20%EF%BC%BBWordPress%206.x%E5%AF%BE%E5%BF%9C%E7%89%88%EF%BC%BD%20(Web%E3%83%87%E3%82%B6%E3%82%A4%E3%83%8A%E3%83%BC%E9%A4%8A%E6%88%90%E8%AC%9B%E5%BA%A7)","a_id":1466950,"p_id":1225,"pl_id":27061,"pc_id":1925,"s_n":"yahoo","u_so":3}],"eid":"ywMaT","s":"s"});</script></p>
<div id="msmaflink-ywMaT">リンク</div>
<p><!-- MoshimoAffiliateEasyLink END --></p>
<h2><span id="toc2">理想の執筆環境とは</span></h2>
<p>まず、私が目指した理想の執筆環境はこんな感じです：</p>
<h3><span id="toc3">求めていた機能</span></h3>
<p><strong>1. VSCodeでMarkdownを書きたい</strong></p>
<ul>
<li>慣れ親しんだエディタで執筆</li>
<li>リアルタイムプレビュー（Ctrl+Shift+V）</li>
<li>Gitでバージョン管理</li>
</ul>
<p><strong>2. 画像管理を自動化したい</strong></p>
<ul>
<li>ローカルの画像を自動アップロード</li>
<li>画像パスを自動で置換</li>
<li>同じ画像の重複を避ける</li>
</ul>
<p><strong>3. ワンコマンドで投稿したい</strong></p>
<pre><code class="language-bash">npm run publish</code></pre>
<p>これだけで完了！</p>
<p><strong>4. 既存記事の更新も自動化したい</strong></p>
<ul>
<li>スラッグで既存記事を検索</li>
<li>見つかったら自動更新</li>
<li>見つからなければ新規投稿</li>
</ul>
<p> </p>
<h2><span id="toc4">開発の流れ：試行錯誤の連続</span></h2>
<h3><span id="toc5">Phase 1：まずは基本機能</span></h3>
<p>最初は、シンプルに「MarkdownをWordPressに投稿する」ことを目指しました。</p>
<p><strong>使用した技術：</strong></p>
<ul>
<li>Node.js</li>
<li>WordPress REST API</li>
<li><code>marked</code>（Markdown → HTML変換）</li>
<li><code>gray-matter</code>（Front Matter解析）</li>
</ul>
<p><strong>基本的な処理フロー：</strong></p>
<pre><code class="language-plaintext">1. post.md を読み込み
2. Front Matter解析（タイトル、スラッグなど）
3. Markdown → HTML変換
4. WordPress REST APIで投稿</code></pre>
<p><strong>最初のコード（簡略版）：</strong></p>
<pre><code class="language-javascript">const marked = require('marked');
const matter = require('gray-matter');
const axios = require('axios');

// Markdownファイル読み込み
const fileContent = fs.readFileSync('post.md', 'utf-8');
const { data: frontMatter, content: markdown } = matter(fileContent);

// HTML変換
const htmlContent = marked(markdown);

// WordPress投稿
await axios.post(
  `${WP_URL}/wp-json/wp/v2/posts`,
  {
    title: frontMatter.title,
    content: htmlContent,
    slug: frontMatter.slug,
    status: 'draft'
  },
  { auth: { username: WP_USER, password: WP_APP_PASSWORD } }
);</code></pre>
<p>この時点では、<strong>「動く！」という感動</strong>がありました。VSCodeで書いた記事が本当にWordPressに投稿されたのです。</p>
<p> </p>
<h3><span id="toc6">Phase 2：画像アップロードの実装</span></h3>
<p>次に、画像の自動アップロードに取り組みました。</p>
<p><strong>課題：</strong></p>
<ul>
<li>Markdown内の相対パス（<code>images/screenshot.png</code>）を、WordPressのURLに置換する必要がある</li>
</ul>
<p><strong>解決策：</strong></p>
<ol>
<li><code>images/</code> フォルダ内の画像を全てアップロード</li>
<li>アップロード後のURLを取得</li>
<li>Markdown内のパスを置換</li>
</ol>
<p><strong>実装：</strong></p>
<pre><code class="language-javascript">// 画像アップロード
const imageMap = {};

for (const imageFile of imageFiles) {
  const imagePath = path.join(imagesDir, imageFile);
  
  const form = new FormData();
  form.append('file', fs.createReadStream(imagePath));
  
  const response = await axios.post(
    `${WP_URL}/wp-json/wp/v2/media`,
    form,
    { auth: { username: WP_USER, password: WP_APP_PASSWORD } }
  );
  
  // ローカルパス → WordPressのURL
  imageMap[`images/${imageFile}`] = response.data.source_url;
}

// Markdown内のパスを置換
let updatedMarkdown = markdown;
for (const [localPath, wpUrl] of Object.entries(imageMap)) {
  updatedMarkdown = updatedMarkdown.replace(
    new RegExp(`\\(${localPath}\\)`, 'g'),
    `(${wpUrl})`
  );
}</code></pre>
<p>これで、<strong>画像が自動でアップロードされ、記事内のリンクも正しく置換される</strong>ようになりました！</p>
<p> </p>
<h3><span id="toc7">Phase 3：WP Githuber MDプラグインとの出会い</span></h3>
<p>ここで、<strong>WP Githuber MD</strong>という素晴らしいプラグインを発見しました。</p>
<p>このプラグインを使うと、WordPress側でもMarkdown記法で編集できます。つまり：</p>
<pre><code class="language-plaintext">VSCodeでMarkdown編集
  ↓
WordPressに投稿（Markdownのまま）
  ↓
WordPress側でもMarkdown編集可能！</code></pre>
<p><strong>試してみた結果：</strong></p>
<p>最初は「Markdown → HTML変換」していましたが、WP Githuber MDを使えば<strong>Markdownをそのまま投稿</strong>できるはず...</p>
<p><strong>しかし、ここで大きな問題が発生しました。</strong></p>
<p> </p>
<h2><span id="toc8">遭遇した問題たち</span></h2>
<h3><span id="toc9">問題1：Markdownがそのまま表示される</span></h3>
<p><strong>症状：</strong></p>
<pre><code class="language-plaintext">
## 見出し

本文...</code></pre>
<p>このMarkdownをそのまま送信すると、WordPress側で：</p>
<pre><code class="language-html"><p># タイトル</p>
<p>## 見出し</p>
<p>本文...</p></code></pre>
<p><strong>Markdownの記号がそのまま表示されてしまいました。</strong></p>
<p><strong>原因：</strong></p>
<ul>
<li>WordPress REST APIは、デフォルトでMarkdownを認識しない</li>
<li>WP Githuber MDのメタフィールドを設定する必要がある</li>
</ul>
<p><strong>解決策：</strong></p>
<pre><code class="language-javascript">// メタフィールドを追加
const postData = {
  title: frontMatter.title,
  content: markdown,  // HTMLではなくMarkdown
  meta: {
    _is_githuber_markdown: '1',
    _is_githuber_markdown_enabled: 'yes'
  }
};</code></pre>
<p>これで、<strong>Markdownがそのまま送信され、WordPress側で正しく表示される</strong>ようになりました！</p>
<p> </p>
<h3><span id="toc10">問題2：既存記事がHTMLの場合</span></h3>
<p><strong>次の問題：</strong></p>
<p>既存記事（すでにHTMLで保存されている）を、Markdownで更新しようとすると...</p>
<p><strong>症状：</strong></p>
<pre><code class="language-plaintext">既存記事（HTML形式）
  ↓
Markdownを送信
  ↓
WordPress: 「これはテキストだ」
  ↓
<p>## 見出し</p> で保存される</code></pre>
<p><strong>つまり、一度HTMLとして保存された記事は、Markdownとして認識されない！</strong></p>
<p><strong>試行錯誤：</strong></p>
<ol>
<li>WordPress管理画面で手動変換 → 面倒</li>
<li>記事を削除して再投稿 → 一時的に記事が消える</li>
<li>モード切替可能にする → <strong>これだ！</strong></li>
</ol>
<p> </p>
<h3><span id="toc11">問題3：画像が増え続ける</span></h3>
<p><strong>最大の問題：</strong></p>
<p>記事を更新するたびに、画像が新規アップロードされる：</p>
<pre><code class="language-plaintext">1回目: featured.jpg
2回目: featured-2.jpg  ← 新規
3回目: featured-3.jpg  ← どんどん増える</code></pre>
<p><strong>WordPress Media Libraryが肥大化！</strong></p>
<p>この問題の解決が、今回の開発で最も時間をかけた部分でした。</p>
<p> </p>
<h2><span id="toc12">最大の挑戦：画像管理の最適化</span></h2>
<h3><span id="toc13">解決策の検討</span></h3>
<p><strong>方法1：画像を再利用</strong></p>
<ul>
<li>ファイル名で検索 → 見つかったら再利用</li>
<li><strong>問題：</strong> 内容が変わっても検出できない</li>
</ul>
<p><strong>方法2：削除して再アップロード</strong></p>
<ul>
<li>毎回削除 → 再アップロード</li>
<li><strong>問題：</strong> 遅い、無駄</li>
</ul>
<p><strong>方法3：ハッシュベース（採用！）</strong></p>
<ul>
<li>画像の内容からハッシュを計算</li>
<li>ハッシュが一致 → 再利用</li>
<li>ハッシュが異なる → 削除して再アップロード</li>
</ul>
<h3><span id="toc14">ハッシュベースの実装</span></h3>
<p><strong>1. ハッシュ計算：</strong></p>
<pre><code class="language-javascript">const crypto = require('crypto');

function calculateFileHash(filePath) {
  const fileBuffer = fs.readFileSync(filePath);
  const hashSum = crypto.createHash('md5');
  hashSum.update(fileBuffer);
  const hex = hashSum.digest('hex');
  return hex.substring(0, 6);  // 最初の6桁
}</code></pre>
<p><strong>2. ファイル名にハッシュを含める：</strong></p>
<pre><code class="language-plaintext">元のファイル名: featured.jpg
  ↓
ハッシュ計算: a3f2b1
  ↓
新しいファイル名: article-slug-featured-a3f2b1.jpg</code></pre>
<p><strong>3. 既存画像を検索：</strong></p>
<pre><code class="language-javascript">async function findExistingImageByPattern(slug, originalFileName) {
  const searchPattern = `${slug}-${originalFileName}`;
  
  const response = await axios.get(
    `${WP_URL}/wp-json/wp/v2/media`,
    {
      auth: { username: WP_USER, password: WP_APP_PASSWORD },
      params: { search: searchPattern }
    }
  );
  
  // パターンに一致する画像を探す
  for (const media of response.data) {
    const fileName = path.basename(media.source_url);
    if (fileName.startsWith(searchPattern)) {
      return media;
    }
  }
  
  return null;
}</code></pre>
<p><strong>4. 統合処理：</strong></p>
<pre><code class="language-javascript">async function uploadOrReuseImage(imagePath, fileName, slug) {
  // ハッシュ計算
  const hash = calculateFileHash(imagePath);
  const newFileName = `${slug}-${fileName}-${hash}.jpg`;
  
  // 既存画像を検索
  const existingImage = await findExistingImageByPattern(slug, fileName);
  
  if (existingImage) {
    const existingFileName = path.basename(existingImage.source_url);
    
    if (existingFileName.includes(`-${hash}`)) {
      // ハッシュ一致 → 再利用
      console.log(`♻️  ${fileName} (変更なし)`);
      return { id: existingImage.id, url: existingImage.source_url };
    } else {
      // ハッシュ不一致 → 削除
      console.log(`🗑️  古い画像を削除`);
      await deleteImage(existingImage.id);
    }
  }
  
  // 新規アップロード
  console.log(`✅ ${fileName} → ${newFileName}`);
  return await uploadImage(imagePath, newFileName);
}</code></pre>
<p><strong>結果：</strong></p>
<pre><code class="language-plaintext">1回目実行:
  ✅ featured.jpg → article-featured-a3f2b1.jpg

2回目実行（画像変更なし）:
  ♻️  featured.jpg (変更なし)  ← 再利用！

3回目実行（画像を更新）:
  🗑️  古い画像を削除: article-featured-a3f2b1.jpg
  ✅ featured.jpg → article-featured-9e8f7d.jpg  ← 新しいハッシュ</code></pre>
<p><strong>完璧に動作しました！🎉</strong></p>
<p> </p>
<h2><span id="toc15">完成したシステム</span></h2>
<h3><span id="toc16">最終的なアーキテクチャ</span></h3>
<pre><code class="language-plaintext">VSCode
  ↓ Markdown記法で執筆
posts/
├── article-name/
│   ├── post.md
│   ├── featured.jpg
│   └── images/
│       └── screenshot.png
  ↓ npm run publish
Node.jsスクリプト
  ├─ Front Matter解析
  ├─ H1削除
  ├─ 画像アップロード（ハッシュ管理）
  ├─ パス置換
  ├─ HTML変換（HTMLモード）
  └─ 既存記事検索・更新
  ↓ WordPress REST API
WordPress
  ↓ 表示
ブログ記事</code></pre>
<h3><span id="toc17">主要機能</span></h3>
<p><strong>1. 2つのモード</strong></p>
<pre><code class="language-yaml"># HTMLモード（推奨）
mode: html
→ スクリプト側でHTML変換
→ Gutenbergエディタ

# Markdownモード
mode: markdown
→ Markdownをそのまま送信
→ WP Githuber MDエディタ</code></pre>
<p><strong>2. 既存記事の自動更新</strong></p>
<pre><code class="language-javascript">// 既存記事を検索
const existingPost = await findExistingPost(frontMatter.slug);

if (existingPost) {
  // 更新
  await axios.post(`/wp-json/wp/v2/posts/${existingPost.id}`, data);
} else {
  // 新規投稿
  await axios.post(`/wp-json/wp/v2/posts`, data);
}</code></pre>
<p><strong>3. ハッシュベースの画像管理</strong></p>
<pre><code class="language-plaintext">画像変更なし → 再利用 ♻️
画像変更あり → 削除 🗑️ → 再アップロード ✅</code></pre>
<p><strong>4. タイトル重複の自動回避</strong></p>
<pre><code class="language-javascript">// 最初のH1を自動削除
updatedMarkdown = removeFirstH1(updatedMarkdown);</code></pre>
<p> </p>
<h2><span id="toc18">実際の運用フロー</span></h2>
<h3><span id="toc19">日常的な記事投稿</span></h3>
<pre><code class="language-bash"># 1. 記事作成
code posts/new-article/post.md

# 2. 画像配置
posts/new-article/
├── post.md
├── featured.jpg
└── images/
    └── screenshot.png

# 3. プレビュー確認
Ctrl+Shift+V

# 4. 投稿
npm run publish-single new-article

# 5. WordPress管理画面で確認
→ 公開</code></pre>
<p><strong>所要時間：わずか数秒！</strong></p>
<h3><span id="toc20">既存記事の更新</span></h3>
<pre><code class="language-bash"># 1. VSCodeで編集
code posts/existing-article/post.md

# 2. 画像も更新（上書き保存）
posts/existing-article/images/screenshot.png

# 3. 投稿
npm run test-local

# 4. 出力確認
📸 画像アップロード中...
  🗑️  古い画像を削除: article-screenshot-a3f2b1.png
  ✅ screenshot.png → article-screenshot-9e8f7d.png
📝 既存記事を更新します（ID: 123）
✅ 更新成功！</code></pre>
<p><strong>画像の更新も自動検出！</strong></p>
<p> </p>
<h2><span id="toc21">得られたメリット</span></h2>
<h3><span id="toc22">1. 執筆効率の向上</span></h3>
<p><strong>Before：</strong></p>
<pre><code class="language-plaintext">WordPress Gutenbergで執筆
  ↓ 画像をドラッグ&ドロップ
  ↓ ブラウザがクラッシュしないか不安
  ↓ バージョン管理なし</code></pre>
<p><strong>After：</strong></p>
<pre><code class="language-plaintext">VSCodeで執筆
  ↓ Ctrl+Shift+V でプレビュー
  ↓ Gitでバージョン管理
  ↓ npm run publish
  ↓ 完了！</code></pre>
<h3><span id="toc23">2. 画像管理の最適化</span></h3>
<p><strong>Before：</strong></p>
<pre><code class="language-plaintext">同じ画像が何度もアップロードされる
→ Media Libraryが肥大化
→ 管理が大変</code></pre>
<p><strong>After：</strong></p>
<pre><code class="language-plaintext">画像の内容が変わっていなければ再利用
→ Media Libraryがクリーン
→ 無駄なアップロードなし</code></pre>
<h3><span id="toc24">3. 既存記事の更新が楽</span></h3>
<p><strong>Before：</strong></p>
<pre><code class="language-plaintext">WordPress管理画面を開く
→ 記事を探す
→ 編集
→ 保存</code></pre>
<p><strong>After：</strong></p>
<pre><code class="language-plaintext">VSCodeで編集
→ npm run publish
→ 完了！</code></pre>
<h3><span id="toc25">4. バージョン管理</span></h3>
<p><strong>Gitで管理：</strong></p>
<pre><code class="language-bash">git commit -m "記事追加：○○について"
git push</code></pre>
<p><strong>履歴が残る：</strong></p>
<ul>
<li>いつ何を変更したか</li>
<li>過去のバージョンに戻せる</li>
<li>チーム開発も可能</li>
</ul>
<p> </p>
<h2><span id="toc26">今後の展望</span></h2>
<h3><span id="toc27">GitHub Actions連携</span></h3>
<p>次のステップは、<strong>GitHub Actionsでの自動投稿</strong>です：</p>
<pre><code class="language-yaml">name: Publish to WordPress

on:
  push:
    branches: [main]
    paths: ['posts/**']

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
      - run: npm install
      - env:
          WP_URL: ${{ secrets.WP_URL }}
          WP_USER: ${{ secrets.WP_USER }}
          WP_APP_PASSWORD: ${{ secrets.WP_APP_PASSWORD }}
        run: npm run publish</code></pre>
<p><strong>運用フロー：</strong></p>
<pre><code class="language-plaintext">VSCodeで記事作成
  ↓
git push
  ↓
GitHub Actions自動実行
  ↓
WordPressに自動投稿</code></pre>
<p><strong>完全自動化！</strong></p>
<h3><span id="toc28">複数サイト対応</span></h3>
<p>複数のWordPressサイトへの投稿も検討中：</p>
<pre><code class="language-bash">npm run publish-site1
npm run publish-site2</code></pre>
<h3><span id="toc29">バルク投稿</span></h3>
<p>過去記事の一括移行：</p>
<pre><code class="language-bash">npm run publish  # 全記事を投稿</code></pre>
<p> </p>
<h2><span id="toc30">まとめ</span></h2>
<h3><span id="toc31">開発を通じて学んだこと</span></h3>
<p><strong>1. 試行錯誤の重要性</strong></p>
<p>最初から完璧なシステムは作れません。問題に遭遇するたびに、解決策を考え、実装し、改善する。この繰り返しが重要でした。</p>
<p><strong>2. ユーザー体験の大切さ</strong></p>
<p>技術的に可能でも、運用が面倒では意味がありません。「ファイル名を変える」という一手間を省くために、ハッシュベースの仕組みを実装しました。</p>
<p><strong>3. ドキュメントの価値</strong></p>
<p>試行錯誤の過程を記録しておくことで、後から見返したときに「なぜこうしたのか」が分かります。</p>
<h3><span id="toc32">このシステムを作って良かったこと</span></h3>
<ul>
<li><strong>執筆が楽しくなった</strong></li>
<li><strong>バージョン管理ができるようになった</strong></li>
<li><strong>画像管理のストレスがなくなった</strong></li>
<li><strong>記事の更新が簡単になった</strong></li>
</ul>
<p>そして何より、<strong>このシステム自体がブログ記事になりました！</strong></p>
<p> </p>
<h2><span id="toc33">技術スタック</span></h2>
<p>最後に、使用した技術をまとめておきます：</p>
<p><strong>言語・ランタイム：</strong></p>
<ul>
<li>Node.js</li>
</ul>
<p><strong>ライブラリ：</strong></p>
<ul>
<li><code>axios</code> - HTTP通信</li>
<li><code>marked</code> - Markdown → HTML変換</li>
<li><code>gray-matter</code> - Front Matter解析</li>
<li><code>form-data</code> - ファイルアップロード</li>
<li><code>dotenv</code> - 環境変数管理</li>
<li><code>crypto</code> - ハッシュ計算</li>
</ul>
<p><strong>WordPress側：</strong></p>
<ul>
<li>WordPress REST API</li>
<li>WP Githuber MD（オプション）</li>
</ul>
<p><strong>エディタ：</strong></p>
<ul>
<li>VSCode</li>
</ul>
<p><strong>バージョン管理：</strong></p>
<ul>
<li>Git / GitHub</li>
</ul>
<p> </p>
<h2><span id="toc34">おわりに</span></h2>
<p>VSCodeでブログを書きたい、画像管理を自動化したい、という単純な動機から始まったこのプロジェクト。</p>
<p>試行錯誤を繰り返しながら、最終的には想像以上に完成度の高いシステムになりました。</p>
<p>もし同じような悩みを抱えている方がいたら、この記事が参考になれば嬉しいです。</p>
<p>そして、「自分でツールを作る」という選択肢を持つことで、日々の作業がもっと楽しくなるかもしれません。</p>
<p><strong>Happy Blogging! 🚀</strong></p>
<hr>
<h2><span id="toc35">参考リンク</span></h2>
<ul>
<li><a href="https://developer.wordpress.org/rest-api/">WordPress REST API ハンドブック</a></li>
<li><a href="https://wordpress.org/plugins/wp-githuber-md/">WP Githuber MD</a></li>
<li><a href="https://marked.js.org/">Marked.js</a></li>
<li><a href="https://nodejs.org/api/crypto.html">Node.js Crypto</a></li>
</ul>
<p><!-- ## GitHub

実際のコードは、GitHubで公開する予定です。（準備中） --></p>
<p><strong>最終更新：</strong> 2026-01-11</p><p>The post <a href="https://caymezon.com/vscode-wordpress-markdown-publisher/">VSCodeからWordPressへ！Markdown記事を自動投稿するシステムを作った話</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://caymezon.com/vscode-wordpress-markdown-publisher/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
