<?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/%e3%83%8f%e3%83%83%e3%82%b7%e3%83%a5/feed/" rel="self" type="application/rss+xml" />
	<link>https://caymezon.com</link>
	<description></description>
	<lastBuildDate>Sat, 07 Feb 2026 09:58: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>【試行錯誤の記録】WordPress画像管理を最適化！ハッシュベースの賢い仕組み</title>
		<link>https://caymezon.com/wordpress-image-management-hash-based/</link>
					<comments>https://caymezon.com/wordpress-image-management-hash-based/#respond</comments>
		
		<dc:creator><![CDATA[caymezon]]></dc:creator>
		<pubDate>Mon, 12 Jan 2026 06:49:44 +0000</pubDate>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Node.js]]></category>
		<category><![CDATA[REST API]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[ハッシュ]]></category>
		<category><![CDATA[最適化]]></category>
		<category><![CDATA[画像管理]]></category>
		<guid isPermaLink="false">https://caymezon.com/wordpress-image-management-hash-based/</guid>

					<description><![CDATA[<p>目次 はじめに：画像管理の悩み問題の発生：画像が増え続ける最初のシステムしかし、問題が...なぜこうなるのか？解決策の模索方法1：ファイル名にタイムスタンプを付ける方法2：既存画像を削除して再アップロード方法3：既存画像 [&#8230;]</p>
<p>The post <a href="https://caymezon.com/wordpress-image-management-hash-based/">【試行錯誤の記録】WordPress画像管理を最適化！ハッシュベースの賢い仕組み</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-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">はじめに：画像管理の悩み</a></li><li><a href="#toc2" tabindex="0">問題の発生：画像が増え続ける</a><ol><li><a href="#toc3" tabindex="0">最初のシステム</a></li><li><a href="#toc4" tabindex="0">しかし、問題が...</a></li><li><a href="#toc5" tabindex="0">なぜこうなるのか？</a></li></ol></li><li><a href="#toc6" tabindex="0">解決策の模索</a><ol><li><a href="#toc7" tabindex="0">方法1：ファイル名にタイムスタンプを付ける</a></li><li><a href="#toc8" tabindex="0">方法2：既存画像を削除して再アップロード</a></li><li><a href="#toc9" tabindex="0">方法3：既存画像を再利用（改善）</a></li><li><a href="#toc10" tabindex="0">方法3の問題点</a></li></ol></li><li><a href="#toc11" tabindex="0">ハッシュベースの画像管理</a><ol><li><a href="#toc12" tabindex="0">ハッシュとは？</a></li><li><a href="#toc13" tabindex="0">画像のハッシュを計算</a></li><li><a href="#toc14" tabindex="0">ハッシュをファイル名に含める</a></li><li><a href="#toc15" tabindex="0">既存画像の検索</a></li><li><a href="#toc16" tabindex="0">統合：再利用 or 削除＆再アップロード</a></li></ol></li><li><a href="#toc17" tabindex="0">実際の動作</a><ol><li><a href="#toc18" tabindex="0">ケース1：初回アップロード</a></li><li><a href="#toc19" tabindex="0">ケース2：2回目実行（画像変更なし）</a></li><li><a href="#toc20" tabindex="0">ケース3：画像を更新</a></li><li><a href="#toc21" tabindex="0">ケース4：複数画像の同時更新</a></li></ol></li><li><a href="#toc22" tabindex="0">パフォーマンスの比較</a><ol><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">衝突とは？</a></li><li><a href="#toc27" tabindex="0">どれくらいの確率？</a></li><li><a href="#toc28" tabindex="0">もし衝突したら？</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></ol></li><li><a href="#toc34" tabindex="0">応用：画像の圧縮</a><ol><li><a href="#toc35" tabindex="0">シナリオ</a></li></ol></li><li><a href="#toc36" tabindex="0">まとめ</a><ol><li><a href="#toc37" tabindex="0">ハッシュベースの画像管理のメリット</a></li><li><a href="#toc38" tabindex="0">実装のポイント</a></li><li><a href="#toc39" tabindex="0">学んだこと</a></li></ol></li><li><a href="#toc40" tabindex="0">おわりに</a></li><li><a href="#toc41" tabindex="0">参考リンク</a></li></ol>
    </div>
  </div>

<h2><span id="toc1">はじめに：画像管理の悩み</span></h2>
<p>WordPressで記事を更新するとき、こんな経験はありませんか？</p>
<pre><code class="language-plaintext">記事を編集
  ↓
画像を差し替え
  ↓
新しい画像をアップロード
  ↓
古い画像は放置...</code></pre>
<p>気づけば、<strong>WordPress Media Libraryに同じような画像が大量に溜まっている</strong>。</p>
<p><img decoding="async" src="images/01-media-library-before.png" alt="WordPressのメディアライブラリ"></p>
<pre><code class="language-plaintext">screenshot.png
screenshot-2.png
screenshot-3.png
screenshot-4.png
...</code></pre>
<p><strong>どれが最新の画像なのか分からない！</strong></p>
<p>この記事では、私がWordPress自動投稿システムを作る中で遭遇した<strong>画像管理の問題</strong>と、それを解決した<strong>ハッシュベースの仕組み</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":"【改訂第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>
<h3><span id="toc3">最初のシステム</span></h3>
<p>私は最初、シンプルなシステムを作りました：</p>
<pre><code class="language-javascript">// 画像をアップロード
async function uploadImage(imagePath, fileName) {
  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 } }
  );
  
  return response.data.source_url;
}

// 使用
for (const imageFile of imageFiles) {
  const url = await uploadImage(imagePath, imageFile);
  console.log(`✅ ${imageFile} → ${url}`);
}</code></pre>
<p><strong>動作確認：</strong></p>
<pre><code class="language-bash">npm run publish</code></pre>
<pre><code class="language-plaintext">✅ screenshot.png → http://example.com/wp-content/uploads/2026/01/screenshot.png</code></pre>
<p><strong>素晴らしい！動いた！</strong></p>
<p> </p>
<h3><span id="toc4">しかし、問題が...</span></h3>
<p><strong>記事を少し修正して、もう一度実行：</strong></p>
<pre><code class="language-bash">npm run publish</code></pre>
<pre><code class="language-plaintext">✅ screenshot.png → http://example.com/wp-content/uploads/2026/01/screenshot-2.png</code></pre>
<p><strong>あれ？<code>screenshot-2.png</code> になってる...</strong></p>
<p><strong>WordPress Media Library：</strong></p>
<pre><code class="language-plaintext">screenshot.png (古い)
screenshot-2.png (新しい)</code></pre>
<p><strong>さらに3回目：</strong></p>
<pre><code class="language-plaintext">✅ screenshot.png → .../screenshot-3.png</code></pre>
<p><strong>WordPress Media Library：</strong></p>
<pre><code class="language-plaintext">screenshot.png (古い)
screenshot-2.png (古い)
screenshot-3.png (新しい)</code></pre>
<p><strong>画像が増え続ける！😱</strong></p>
<p> </p>
<h3><span id="toc5">なぜこうなるのか？</span></h3>
<p><strong>WordPress REST APIの仕様：</strong></p>
<pre><code class="language-plaintext">同じファイル名でアップロード
  ↓
WordPress: 「既にある！」
  ↓
自動的に連番を付ける
  ↓
screenshot-2.png</code></pre>
<p><strong>つまり、WordPressには「上書き」という概念がない。</strong></p>
<p>これは、誤って既存の画像を上書きしないための安全機構ですが、私のユースケースでは問題になりました。</p>
<p> </p>
<h2><span id="toc6">解決策の模索</span></h2>
<h3><span id="toc7">方法1：ファイル名にタイムスタンプを付ける</span></h3>
<p><strong>アイデア：</strong></p>
<pre><code class="language-javascript">const timestamp = Date.now();
const newFileName = `screenshot-${timestamp}.png`;</code></pre>
<p><strong>結果：</strong></p>
<pre><code class="language-plaintext">screenshot-1736584800000.png
screenshot-1736584801234.png
screenshot-1736584802567.png</code></pre>
<p><strong>問題：</strong></p>
<ul>
<li>❌ 画像は増え続ける（解決していない）</li>
<li>❌ ファイル名が分かりにくい</li>
</ul>
<p><strong>却下。</strong></p>
<p> </p>
<h3><span id="toc8">方法2：既存画像を削除して再アップロード</span></h3>
<p><strong>アイデア：</strong></p>
<pre><code class="language-javascript">// 1. 既存画像を検索
const existingImages = await searchImages(slug);

// 2. 削除
for (const image of existingImages) {
  await deleteImage(image.id);
}

// 3. 再アップロード
await uploadImage(imagePath, fileName);</code></pre>
<p><strong>メリット：</strong></p>
<ul>
<li>✅ 画像は増えない</li>
</ul>
<p><strong>デメリット：</strong></p>
<ul>
<li>❌ 毎回削除＆再アップロード（遅い）</li>
<li>❌ 画像が変わっていなくても処理される（無駄）</li>
<li>❌ 他の記事で使っている画像を削除してしまうリスク</li>
</ul>
<p><strong>もっと良い方法はないか？</strong></p>
<p> </p>
<h3><span id="toc9">方法3：既存画像を再利用（改善）</span></h3>
<p><strong>アイデア：</strong></p>
<pre><code class="language-javascript">// 1. 既存画像を検索
const existingImage = await searchImage(fileName);

if (existingImage) {
  // 2. 見つかったら再利用
  console.log(`♻️  ${fileName} (再利用)`);
  return existingImage.url;
} else {
  // 3. 見つからなければアップロード
  console.log(`✅ ${fileName} (新規)`);
  return await uploadImage(imagePath, fileName);
}</code></pre>
<p><strong>メリット：</strong></p>
<ul>
<li>✅ 画像は増えない</li>
<li>✅ 再アップロード不要（高速）</li>
</ul>
<p><strong>しかし、新たな問題が...</strong></p>
<p> </p>
<h3><span id="toc10">方法3の問題点</span></h3>
<p><strong>シナリオ：画像を更新したい</strong></p>
<pre><code class="language-plaintext">1. スクリーンショットを撮り直す
2. 同じファイル名（screenshot.png）で保存
3. スクリプト実行</code></pre>
<p><strong>期待：</strong></p>
<pre><code class="language-plaintext">新しい画像がアップロードされる</code></pre>
<p><strong>実際：</strong></p>
<pre><code class="language-plaintext">♻️  screenshot.png (再利用)  ← 古い画像を再利用してしまう</code></pre>
<p><strong>問題：</strong></p>
<ul>
<li>ファイル名が同じ</li>
<li>でも内容は違う</li>
<li>これを検出できない</li>
</ul>
<p><strong>もっと賢い方法が必要だ。</strong></p>
<p> </p>
<h2><span id="toc11">ハッシュベースの画像管理</span></h2>
<h3><span id="toc12">ハッシュとは？</span></h3>
<p>**ハッシュ（Hash）**とは、データから計算される固定長の文字列です。</p>
<pre><code class="language-plaintext">データ → ハッシュ関数 → ハッシュ値</code></pre>
<p><strong>重要な性質：</strong></p>
<ol>
<li><strong>同じデータ → 同じハッシュ</strong></li>
<li><strong>異なるデータ → 異なるハッシュ</strong></li>
<li><strong>元のデータを復元できない（一方向）</strong></li>
</ol>
<p><strong>例：MD5ハッシュ</strong></p>
<pre><code class="language-plaintext">"Hello, World!" → 65a8e27d8879283831b664bd8b7f0ad4
"Hello, World?" → 1e825dfe1c22c5d6b46c87b61f7a3f2a</code></pre>
<p>たった1文字違うだけで、まったく異なるハッシュになります。</p>
<p> </p>
<h3><span id="toc13">画像のハッシュを計算</span></h3>
<p><strong>Node.jsでの実装：</strong></p>
<pre><code class="language-javascript">const crypto = require('crypto');
const fs = require('fs');

function calculateFileHash(filePath) {
  // 1. ファイルを読み込み
  const fileBuffer = fs.readFileSync(filePath);
  
  // 2. MD5ハッシュを計算
  const hashSum = crypto.createHash('md5');
  hashSum.update(fileBuffer);
  
  // 3. 16進数に変換
  const hex = hashSum.digest('hex');
  
  // 4. 最初の6桁を返す
  return hex.substring(0, 6);
}</code></pre>
<p><strong>使用例：</strong></p>
<pre><code class="language-javascript">const hash1 = calculateFileHash('screenshot.png');
console.log(hash1);  // a3f2b1

// 画像を編集して保存

const hash2 = calculateFileHash('screenshot.png');
console.log(hash2);  // 9e8f7d  ← 変わった！</code></pre>
<p><strong>完璧！これで画像の内容が変わったかどうかを検出できる。</strong></p>
<p> </p>
<h3><span id="toc14">ハッシュをファイル名に含める</span></h3>
<p><strong>アイデア：</strong></p>
<pre><code class="language-plaintext">元のファイル名: screenshot.png
  ↓ ハッシュ計算
ハッシュ: a3f2b1
  ↓ ファイル名生成
新しいファイル名: article-slug-screenshot-a3f2b1.png</code></pre>
<p><strong>実装：</strong></p>
<pre><code class="language-javascript">async function uploadImageWithHash(imagePath, fileName, slug) {
  // 1. ハッシュ計算
  const hash = calculateFileHash(imagePath);
  
  // 2. ファイル名生成
  const ext = path.extname(fileName);
  const nameWithoutExt = path.basename(fileName, ext);
  const newFileName = `${slug}-${nameWithoutExt}-${hash}${ext}`;
  
  // 3. アップロード
  const form = new FormData();
  form.append('file', fs.createReadStream(imagePath), newFileName);
  
  const response = await axios.post(
    `${WP_URL}/wp-json/wp/v2/media`,
    form,
    { auth: { username: WP_USER, password: WP_APP_PASSWORD } }
  );
  
  console.log(`✅ ${fileName} → ${newFileName}`);
  return response.data;
}</code></pre>
<p><strong>結果：</strong></p>
<pre><code class="language-plaintext">✅ screenshot.png → article-name-screenshot-a3f2b1.png</code></pre>
<p><strong>メリット：</strong></p>
<ul>
<li>✅ ファイル名にハッシュが含まれる</li>
<li>✅ 内容が変われば、ハッシュも変わる</li>
<li>✅ ファイル名から内容の変更が分かる</li>
</ul>
<p> </p>
<h3><span id="toc15">既存画像の検索</span></h3>
<p><strong>次のステップ：既存画像を探す</strong></p>
<pre><code class="language-javascript">async function findExistingImageByPattern(slug, originalFileName) {
  // 1. 検索パターン作成
  const nameWithoutExt = path.basename(originalFileName, path.extname(originalFileName));
  const searchPattern = `${slug}-${nameWithoutExt}`;
  
  // 2. WordPress Media Libraryを検索
  const response = await axios.get(
    `${WP_URL}/wp-json/wp/v2/media`,
    {
      auth: { username: WP_USER, password: WP_APP_PASSWORD },
      params: {
        search: searchPattern,  // 例：article-name-screenshot
        per_page: 10
      }
    }
  );
  
  // 3. パターンに一致する画像を探す
  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>動作例：</strong></p>
<pre><code class="language-javascript">const existingImage = await findExistingImageByPattern('article-name', 'screenshot.png');

if (existingImage) {
  console.log('既存画像:', existingImage.source_url);
  // 例：article-name-screenshot-a3f2b1.png
}</code></pre>
<p> </p>
<h3><span id="toc16">統合：再利用 or 削除＆再アップロード</span></h3>
<p><strong>最終的な実装：</strong></p>
<pre><code class="language-javascript">async function uploadOrReuseImage(imagePath, fileName, slug) {
  // 1. 現在の画像のハッシュを計算
  const currentHash = calculateFileHash(imagePath);
  
  // 2. 新しいファイル名を生成
  const ext = path.extname(fileName);
  const nameWithoutExt = path.basename(fileName, ext);
  const newFileName = `${slug}-${nameWithoutExt}-${currentHash}${ext}`;
  
  // 3. 既存画像を検索
  const existingImage = await findExistingImageByPattern(slug, fileName);
  
  if (existingImage) {
    const existingFileName = path.basename(existingImage.source_url);
    
    // 4. ハッシュを比較
    if (existingFileName.includes(`-${currentHash}${ext}`)) {
      // ハッシュ一致 → 再利用
      console.log(`♻️  ${fileName} (変更なし)`);
      return {
        id: existingImage.id,
        url: existingImage.source_url
      };
    } else {
      // ハッシュ不一致 → 古い画像を削除
      console.log(`🗑️  古い画像を削除: ${existingFileName}`);
      await deleteImage(existingImage.id);
    }
  }
  
  // 5. 新規アップロード
  console.log(`✅ ${fileName} → ${newFileName}`);
  return await uploadImage(imagePath, newFileName);
}</code></pre>
<p><strong>画像削除の実装：</strong></p>
<pre><code class="language-javascript">async function deleteImage(imageId) {
  try {
    await axios.delete(
      `${WP_URL}/wp-json/wp/v2/media/${imageId}`,
      {
        auth: { username: WP_USER, password: WP_APP_PASSWORD },
        params: {
          force: true  // 完全削除（ゴミ箱に入れない）
        }
      }
    );
  } catch (error) {
    console.error(`画像削除エラー (ID: ${imageId}):`, error.message);
  }
}</code></pre>
<p> </p>
<h2><span id="toc17">実際の動作</span></h2>
<h3><span id="toc18">ケース1：初回アップロード</span></h3>
<pre><code class="language-bash">npm run publish</code></pre>
<p><strong>処理：</strong></p>
<pre><code class="language-plaintext">screenshot.png を読み込み
  ↓
ハッシュ計算: a3f2b1
  ↓
既存画像を検索: 見つからない
  ↓
新規アップロード</code></pre>
<p><strong>出力：</strong></p>
<pre><code class="language-plaintext">✅ screenshot.png → article-name-screenshot-a3f2b1.png</code></pre>
<p><strong>WordPress Media Library：</strong></p>
<pre><code class="language-plaintext">article-name-screenshot-a3f2b1.png (新規)</code></pre>
<p> </p>
<h3><span id="toc19">ケース2：2回目実行（画像変更なし）</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">screenshot.png を読み込み
  ↓
ハッシュ計算: a3f2b1 (同じ)
  ↓
既存画像を検索: 見つかった
  ↓
ハッシュ比較: a3f2b1 == a3f2b1
  ↓
再利用！</code></pre>
<p><strong>出力：</strong></p>
<pre><code class="language-plaintext">♻️  screenshot.png (変更なし)</code></pre>
<p><strong>WordPress Media Library：</strong></p>
<pre><code class="language-plaintext">article-name-screenshot-a3f2b1.png (既存のまま)</code></pre>
<p><strong>再アップロードしない！高速！</strong></p>
<p> </p>
<h3><span id="toc20">ケース3：画像を更新</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">screenshot.png を読み込み（内容が変わっている）
  ↓
ハッシュ計算: 9e8f7d (変わった！)
  ↓
既存画像を検索: 見つかった
  ↓
ハッシュ比較: 9e8f7d != a3f2b1
  ↓
古い画像を削除
  ↓
新規アップロード</code></pre>
<p><strong>出力：</strong></p>
<pre><code class="language-plaintext">🗑️  古い画像を削除: article-name-screenshot-a3f2b1.png
✅ screenshot.png → article-name-screenshot-9e8f7d.png</code></pre>
<p><strong>WordPress Media Library：</strong></p>
<pre><code class="language-plaintext">article-name-screenshot-9e8f7d.png (新しい)</code></pre>
<p><strong>古い画像は削除されている！</strong></p>
<p> </p>
<h3><span id="toc21">ケース4：複数画像の同時更新</span></h3>
<p><strong>3つの画像があって、2つを更新：</strong></p>
<pre><code class="language-plaintext">01-install.png (更新)
02-config.png (更新)
03-run.png (変更なし)</code></pre>
<pre><code class="language-bash">npm run publish</code></pre>
<p><strong>出力：</strong></p>
<pre><code class="language-plaintext">🗑️  古い画像を削除: article-name-01-install-a3f2b1.png
✅ 01-install.png → article-name-01-install-7d9e4c.png

🗑️  古い画像を削除: article-name-02-config-b5c8f2.png
✅ 02-config.png → article-name-02-config-3a6d91.png

♻️  03-run.png (変更なし)</code></pre>
<p><strong>WordPress Media Library：</strong></p>
<pre><code class="language-plaintext">article-name-01-install-7d9e4c.png (新しい)
article-name-02-config-3a6d91.png (新しい)
article-name-03-run-c9a1b3.png (既存)</code></pre>
<p><strong>変更がない画像は再利用！効率的！</strong></p>
<p> </p>
<h2><span id="toc22">パフォーマンスの比較</span></h2>
<h3><span id="toc23">従来の方式（毎回アップロード）</span></h3>
<pre><code class="language-plaintext">記事更新（画像3枚、変更なし）
  ↓
3枚すべて再アップロード
  ↓
所要時間: 約5秒
  ↓
WordPress Media Library: 画像が増える</code></pre>
<h3><span id="toc24">ハッシュベース（賢く判断）</span></h3>
<pre><code class="language-plaintext">記事更新（画像3枚、変更なし）
  ↓
3枚すべて再利用
  ↓
所要時間: 約0.5秒
  ↓
WordPress Media Library: 変化なし</code></pre>
<p><strong>約10倍高速！</strong></p>
<p> </p>
<h2><span id="toc25">ハッシュの衝突について</span></h2>
<h3><span id="toc26">衝突とは？</span></h3>
<p><strong>異なる画像が同じハッシュになること：</strong></p>
<pre><code class="language-plaintext">画像A → ハッシュ: a3f2b1
画像B → ハッシュ: a3f2b1  ← 衝突</code></pre>
<h3><span id="toc27">どれくらいの確率？</span></h3>
<p><strong>6桁の16進数（3バイト）：</strong></p>
<pre><code class="language-plaintext">16^6 = 16,777,216 通り</code></pre>
<p><strong>現実的には：</strong></p>
<ul>
<li>1つの記事で10枚の画像を使用</li>
<li>1,677,721記事まで衝突しない計算</li>
</ul>
<p><strong>個人ブログでは、まず起こりえない。</strong></p>
<h3><span id="toc28">もし衝突したら？</span></h3>
<p><strong>桁数を増やせばOK：</strong></p>
<pre><code class="language-javascript">return hex.substring(0, 8);  // 6 → 8桁</code></pre>
<p><strong>8桁の場合：</strong></p>
<pre><code class="language-plaintext">16^8 = 4,294,967,296 通り</code></pre>
<p><strong>42億通り！完璧。</strong></p>
<p> </p>
<h2><span id="toc29">実装時の注意点</span></h2>
<h3><span id="toc30">1. ファイル名の形式</span></h3>
<p><strong>推奨：</strong></p>
<pre><code class="language-plaintext">{スラッグ}-{元のファイル名}-{ハッシュ}.{拡張子}</code></pre>
<p><strong>例：</strong></p>
<pre><code class="language-plaintext">article-name-screenshot-a3f2b1.png
article-name-featured-7c4d8e.jpg</code></pre>
<p><strong>メリット：</strong></p>
<ul>
<li>どの記事の画像か一目瞭然</li>
<li>元のファイル名が分かる</li>
<li>ハッシュで内容が分かる</li>
</ul>
<p> </p>
<h3><span id="toc31">2. 削除のタイミング</span></h3>
<p><strong>選択肢A：即座に削除（採用）</strong></p>
<pre><code class="language-javascript">await deleteImage(existingImage.id);
await uploadImage(imagePath, newFileName);</code></pre>
<p><strong>メリット：</strong></p>
<ul>
<li>シンプル</li>
<li>Media Libraryがクリーン</li>
</ul>
<p><strong>デメリット：</strong></p>
<ul>
<li>アップロードが失敗したら、古い画像も削除済み</li>
</ul>
<p><strong>選択肢B：アップロード後に削除</strong></p>
<pre><code class="language-javascript">const newImage = await uploadImage(imagePath, newFileName);
await deleteImage(existingImage.id);</code></pre>
<p><strong>メリット：</strong></p>
<ul>
<li>より安全</li>
</ul>
<p><strong>デメリット：</strong></p>
<ul>
<li>一時的に両方の画像が存在</li>
</ul>
<p><strong>私は選択肢Aを採用しました（シンプルさ優先）。</strong></p>
<p> </p>
<h3><span id="toc32">3. エラーハンドリング</span></h3>
<p><strong>画像削除が失敗しても続行：</strong></p>
<pre><code class="language-javascript">async function deleteImage(imageId) {
  try {
    await axios.delete(...);
  } catch (error) {
    console.error(`⚠️  画像削除エラー (ID: ${imageId}):`, error.message);
    // エラーを投げない → 続行
  }
}</code></pre>
<p><strong>理由：</strong></p>
<ul>
<li>削除に失敗しても、新規アップロードは続けたい</li>
<li>古い画像が残るだけなので、大きな問題ではない</li>
</ul>
<p> </p>
<h3><span id="toc33">4. 複数記事で同じ画像を使う場合</span></h3>
<p><strong>例：ロゴ画像</strong></p>
<pre><code class="language-plaintext">記事A: article-a-logo-a3f2b1.png
記事B: article-b-logo-a3f2b1.png</code></pre>
<p><strong>ハッシュは同じだが、スラッグが違うので別ファイル。</strong></p>
<p><strong>メリット：</strong></p>
<ul>
<li>記事ごとに独立して管理</li>
<li>一方を削除しても他方に影響なし</li>
</ul>
<p><strong>もし完全に共有したい場合：</strong></p>
<ul>
<li>スラッグなしで <code>logo-a3f2b1.png</code></li>
<li>ただし、記事ごとの管理が複雑になる</li>
</ul>
<p><strong>私は記事ごとの管理を優先しました。</strong></p>
<p> </p>
<h2><span id="toc34">応用：画像の圧縮</span></h2>
<p><strong>ハッシュベースの仕組みは、画像の圧縮にも使えます。</strong></p>
<h3><span id="toc35">シナリオ</span></h3>
<pre><code class="language-plaintext">1. TinyPNGで画像を圧縮
2. 同じファイル名で保存
3. スクリプト実行</code></pre>
<p><strong>処理：</strong></p>
<pre><code class="language-plaintext">screenshot.png (圧縮後)
  ↓
ハッシュ計算: 3f8d2a (変わった)
  ↓
既存画像を検索: 見つかった
  ↓
ハッシュ比較: 3f8d2a != a3f2b1
  ↓
古い画像を削除
  ↓
新しい画像をアップロード</code></pre>
<p><strong>結果：</strong></p>
<pre><code class="language-plaintext">🗑️  古い画像を削除: article-name-screenshot-a3f2b1.png (1.2MB)
✅ screenshot.png → article-name-screenshot-3f8d2a.png (300KB)</code></pre>
<p><strong>自動的に軽量化された画像に差し替わる！</strong></p>
<p> </p>
<h2><span id="toc36">まとめ</span></h2>
<h3><span id="toc37">ハッシュベースの画像管理のメリット</span></h3>
<p><strong>1. 画像が増えない</strong></p>
<ul>
<li>古い画像は自動削除</li>
<li>Media Libraryがクリーン</li>
</ul>
<p><strong>2. 無駄なアップロードを回避</strong></p>
<ul>
<li>変更がない画像は再利用</li>
<li>処理が高速</li>
</ul>
<p><strong>3. 完全自動</strong></p>
<ul>
<li>ファイル名を変える必要なし</li>
<li>画像を上書き保存すればOK</li>
</ul>
<p><strong>4. 賢い判断</strong></p>
<ul>
<li>内容が変わったかを自動検出</li>
<li>ユーザーは何も考えなくていい</li>
</ul>
<h3><span id="toc38">実装のポイント</span></h3>
<p><strong>1. ハッシュ計算</strong></p>
<pre><code class="language-javascript">const hash = crypto.createHash('md5').update(fileBuffer).digest('hex');</code></pre>
<p><strong>2. ファイル名にハッシュを含める</strong></p>
<pre><code class="language-javascript">const newFileName = `${slug}-${nameWithoutExt}-${hash}${ext}`;</code></pre>
<p><strong>3. 既存画像を検索</strong></p>
<pre><code class="language-javascript">const existingImage = await findExistingImageByPattern(slug, fileName);</code></pre>
<p><strong>4. ハッシュを比較</strong></p>
<pre><code class="language-javascript">if (existingFileName.includes(`-${hash}`)) {
  // 再利用
} else {
  // 削除 → 再アップロード
}</code></pre>
<h3><span id="toc39">学んだこと</span></h3>
<p><strong>1. 問題を分解する</strong></p>
<ul>
<li>「画像が増える」という問題</li>
<li>→ 「内容の変更を検出できない」という本質</li>
</ul>
<p><strong>2. 適切なツールを使う</strong></p>
<ul>
<li>ハッシュは内容の変更検出に最適</li>
</ul>
<p><strong>3. ユーザー体験を優先</strong></p>
<ul>
<li>「ファイル名を変える」という手間を省く</li>
</ul>
<p><strong>4. 試行錯誤の価値</strong></p>
<ul>
<li>最初からベストな方法は見つからない</li>
<li>改善を繰り返すことが重要</li>
</ul>
<p> </p>
<h2><span id="toc40">おわりに</span></h2>
<p>最初は「画像をアップロードする」だけのシンプルな機能でした。</p>
<p>しかし、実際に使ってみると、<strong>画像が増え続ける</strong>という問題に直面しました。</p>
<p>この問題を解決する過程で、ハッシュベースの画像管理という、想像以上に完成度の高い仕組みを作ることができました。</p>
<p>もし同じような問題に悩んでいる方がいたら、この記事が参考になれば嬉しいです。</p>
<p>そして、「問題を分解して、本質を見つける」というアプローチは、他の場面でも役立つはずです。</p>
<p><strong>Happy Coding! 🚀</strong></p>
<hr>
<h2><span id="toc41">参考リンク</span></h2>
<ul>
<li><a href="https://nodejs.org/api/crypto.html">Node.js Crypto</a></li>
<li><a href="https://developer.wordpress.org/rest-api/reference/media/">WordPress REST API - Media</a></li>
<li><a href="https://ja.wikipedia.org/wiki/MD5">MD5 - Wikipedia</a></li>
</ul>
<p><!-- ## コードサンプル

この記事で紹介したコードの完全版は、GitHubで公開予定です。 --></p>
<p><strong>最終更新：</strong> 2026-01-11</p><p>The post <a href="https://caymezon.com/wordpress-image-management-hash-based/">【試行錯誤の記録】WordPress画像管理を最適化！ハッシュベースの賢い仕組み</a> first appeared on <a href="https://caymezon.com">CayTech Lab</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://caymezon.com/wordpress-image-management-hash-based/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
