<?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>サイト運営 | 個人的な備忘録</title>
	<atom:link href="https://mkb.stars.ne.jp/blog/category/site/feed/" rel="self" type="application/rss+xml" />
	<link>https://mkb.stars.ne.jp/blog</link>
	<description></description>
	<lastBuildDate>Wed, 29 Oct 2025 08:03:04 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://mkb.stars.ne.jp/blog/wp-content/uploads/2025/10/cropped-favicon-32x32.png</url>
	<title>サイト運営 | 個人的な備忘録</title>
	<link>https://mkb.stars.ne.jp/blog</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>【Node.js】画像をWebP変換＆サムネイル生成する方法</title>
		<link>https://mkb.stars.ne.jp/blog/bulk-generate-webp-thumbnails/</link>
		
		<dc:creator><![CDATA[みかみ]]></dc:creator>
		<pubDate>Mon, 30 Dec 2024 12:27:12 +0000</pubDate>
				<category><![CDATA[サイト運営]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[Node.js]]></category>
		<guid isPermaLink="false">https://mkb.stars.ne.jp/blog/?p=21</guid>

					<description><![CDATA[個人サイトの運営といえば、やっぱり画像フォーマットとの戦いですよね。いかにサイズを小さくしつつ、画質の劣化を抑えるか…。 最初は TinyPNG という、ブラウザで PNG 画像を変換してくれるサービスを利用していました [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>個人サイトの運営といえば、やっぱり画像フォーマットとの戦いですよね。<br>いかにサイズを小さくしつつ、画質の劣化を抑えるか…。</p>



<p>最初は <a rel="noopener" target="_blank" href="https://tinypng.com/">TinyPNG</a> という、ブラウザで PNG 画像を変換してくれるサービスを利用していました。</p>



<p>ところが調べてみると、どうやら <strong>WebP</strong> というフォーマットがサイト運営にとても向いているらしい、という情報が Google 検索でたくさん出てきます。</p>



<p>この記事を書いた当時は TinyPNG も CLIP STUDIO PAINT も WebP に対応していなかったため、ローカルで一括変換する方法を探しました。</p>



<h2 class="wp-block-heading"><span id="toc1">WebP 変換スクリプト</span></h2>



<div class="wp-block-cocoon-blocks-blogcard blogcard-type bct-reference-link">

<a rel="noopener" target="_blank" href="https://qiita.com/taqumo/items/60de0af9699415150035" title="【Node.js】sharp でサクッと「AVIF」「WebP」生成 - Qiita" class="blogcard-wrap external-blogcard-wrap a-wrap cf"><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://qiita-user-contents.imgix.net/https%3A%2F%2Fqiita-user-contents.imgix.net%2Fhttps%253A%252F%252Fcdn.qiita.com%252Fassets%252Fpublic%252Farticle-ogp-background-afbab5eb44e0b055cce1258705637a91.png%3Fixlib%3Drb-4.0.0%26w%3D1200%26blend64%3DaHR0cHM6Ly9xaWl0YS11c2VyLXByb2ZpbGUtaW1hZ2VzLmltZ2l4Lm5ldC9odHRwcyUzQSUyRiUyRnMzLWFwLW5vcnRoZWFzdC0xLmFtYXpvbmF3cy5jb20lMkZxaWl0YS1pbWFnZS1zdG9yZSUyRjAlMkY0OTI2NzUlMkZmNTM0NDBhZTI4YzIzYWNkOWFjNThmMWVmNDc3ZTI1MzRiZTljNjU1JTJGbGFyZ2UucG5nJTNGMTU2NzY0OTIyMT9peGxpYj1yYi00LjAuMCZhcj0xJTNBMSZmaXQ9Y3JvcCZtYXNrPWVsbGlwc2UmYmc9RkZGRkZGJmZtPXBuZzMyJnM9ZTZlZjQ2N2Y3NjBhOGE4YjA0YjU4NDE4MzhmZTFiMmQ%26blend-x%3D120%26blend-y%3D467%26blend-w%3D82%26blend-h%3D82%26blend-mode%3Dnormal%26s%3Dc4c8d37dc2257e165488de996e417e38?ixlib=rb-4.0.0&#038;w=1200&#038;fm=jpg&#038;mark64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTk2MCZoPTMyNCZ0eHQ9JUUzJTgwJTkwTm9kZS5qcyVFMyU4MCU5MXNoYXJwJTIwJUUzJTgxJUE3JUUzJTgyJUI1JUUzJTgyJUFGJUUzJTgzJTgzJUUzJTgxJUE4JUUzJTgwJThDQVZJRiVFMyU4MCU4RCVFMyU4MCU4Q1dlYlAlRTMlODAlOEQlRTclOTQlOUYlRTYlODglOTAmdHh0LWFsaWduPWxlZnQlMkN0b3AmdHh0LWNvbG9yPSUyMzFFMjEyMSZ0eHQtZm9udD1IaXJhZ2lubyUyMFNhbnMlMjBXNiZ0eHQtc2l6ZT01NiZ0eHQtcGFkPTAmcz1lNmY4MzI5OTc5ZjI3OWZlYTMwYjdmYjU3N2E4MzQ3YQ&#038;mark-x=120&#038;mark-y=112&#038;blend64=aHR0cHM6Ly9xaWl0YS11c2VyLWNvbnRlbnRzLmltZ2l4Lm5ldC9-dGV4dD9peGxpYj1yYi00LjAuMCZ3PTgzOCZoPTU4JnR4dD0lNDB0YXF1bW8mdHh0LWNvbG9yPSUyMzFFMjEyMSZ0eHQtZm9udD1IaXJhZ2lubyUyMFNhbnMlMjBXNiZ0eHQtc2l6ZT0zNiZ0eHQtcGFkPTAmcz02NWZiZjVlOTNjMjAyZTVhODgyYWU4YWYzNzUwMzJmZA&#038;blend-x=242&#038;blend-y=480&#038;blend-w=838&#038;blend-h=46&#038;blend-fit=crop&#038;blend-crop=left%2Cbottom&#038;blend-mode=normal&#038;s=151cd0c62f6f8e71b0a3cf1eeb1c88c6" 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">【Node.js】sharp でサクッと「AVIF」「WebP」生成 - Qiita</div><div class="blogcard-snippet external-blogcard-snippet">次世代画像形式といわれる「AVIF」と「WebP」。 WEBサイトに組み込むことで、表示速度を大幅に向上させることができます。 とっても魅力的な「AVIF」と「WebP」ですが、画像を生成するのが課題となります。 対応しているグラフィックソ...</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://qiita.com/taqumo/items/60de0af9699415150035" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">qiita.com</div></div></div></div></a>
</div>



<p>この記事を参考に環境を構築し、AI に相談しながら自分好みにカスタマイズしたスクリプトを作りました。<br><code>src</code> フォルダに入れた PNG 画像が、<code>dest</code> フォルダに WebP 形式で出力されます。</p>



<pre class="wp-block-code javascript"><code>import c from 'ansi-colors';
import log from 'fancy-log';
import fs from 'fs';
import globule from 'globule';
import sharp from 'sharp';

class ImageFormatConverter {
  constructor(options = {}) {
    this.srcBase = options.srcBase || 'src';
    this.destBase = options.destBase || 'dest';
    this.includeExtensionName = options.includeExtensionName || false;
    this.formats = options.formats || &#91;
      {
        type: 'webp',
        quality: 80,
      },
    ];
    this.srcImages = `${this.srcBase}/**/*.{jpg,jpeg,png}`;
    this.init();
  }

  init = async () =&gt; {
    const imagePathList = this.findImagePaths();
    await this.convertImages(imagePathList);
  };

  /**
   * globパターンで指定した画像パスを配列化して返す
   * @return { array } 画像パスの配列
   */
  findImagePaths = () =&gt; {
    return globule.find({
      src: &#91;this.srcImages],
    });
  };

  /**
   * 画像を変換する
   * @param { string } imagePath 画像パス
   * @param { object } format 画像形式と圧縮品質
   */
  convertImageFormat = async (imagePath, format) =&gt; {
    const reg = /\\\\/(.*)\\\\.(jpe?g|png)$/i;
    const &#91;, imageName, imageExtension] = imagePath.match(reg);
    const imageFileName = this.includeExtensionName ? `${imageName}.${imageExtension}` : imageName;
    const destPath = `${this.destBase}/${imageFileName}.${format.type}`;
    await sharp(imagePath)
      .toFormat(format.type, { quality: format.quality })
      .toFile(destPath)
      .then((info) =&gt; {
        log(`Converted ${c.blue(imagePath)} to ${c.yellow(format.type.toUpperCase())} ${c.green(destPath)}`);
      })
      .catch((err) =&gt; {
        log(c.red(`Error converting image to ${c.yellow(format.type.toUpperCase())}\\\\n${err}`));
      });
  };

  /**
   * 配列内の画像パスのファイルを変換する
   * @param { array } imagePathList 画像パスの配列
   */
  convertImages = async (imagePathList) =&gt; {
    if (imagePathList.length === 0) {
      log(c.red('No images found to convert'));
      return;
    }
    for (const imagePath of imagePathList) {
      const reg = new RegExp(`^${this.srcBase}/(.*/)?`);
      const path = imagePath.match(reg)&#91;1] || '';
      const destDir = `${this.destBase}/${path}`;
      if (!fs.existsSync(destDir)) {
        try {
          fs.mkdirSync(destDir, { recursive: true });
          log(`Created directory ${c.green(destDir)}`);
        } catch (err) {
          log(`Failed to create directory ${c.green(destDir)}\\\\n${err}`);
        }
      }
      const conversionPromises = this.formats.map((format) =&gt; this.convertImageFormat(imagePath, format));
      await Promise.all(conversionPromises);
    }
  };
}
const imageFormatConverter = new ImageFormatConverter();</code></pre>



<h2 class="wp-block-heading"><span id="toc2">リサイズとサムネイル生成を同時に行うスクリプトの作成</span></h2>



<p>私は B5 サイズで絵や漫画を描き、PNG で原寸保存しています。<br>そのため、サイト掲載用には横幅 800px、ギャラリー表示用には正方形 200px の WebP 画像を自動で出力できるようにカスタマイズしました。</p>



<p>AI に聞いた結果できあがったこのスクリプトを <code>imageProcessor.js</code> という名前で保存しています。</p>



<pre class="wp-block-code javascript"><code>import c from "ansi-colors";
import log from "fancy-log";
import fs from "fs";
import path from "path";
import globule from "globule";
import sharp from "sharp";

class ImageProcessor {
  constructor(options = {}) {
    this.srcBase = options.srcBase || "src";
    this.destBase = "dest";
    this.includeExtensionName = options.includeExtensionName || false;
    this.formats = options.formats || &#91;
      {
        type: "webp",
        quality: 80,
      },
    ];
    this.miniFolder = "mini";
    this.srcImages = `${this.srcBase}/**/*.{jpg,jpeg,png,webp,gif}`;
    this.mainWidth = 800;
    this.miniWidth = 200;
    this.init();
  }

  init = async () =&gt; {
    const imagePathList = this.findImagePaths();
    await this.processImages(imagePathList);
  };

  findImagePaths = () =&gt; {
    return globule.find({
      src: &#91;this.srcImages],
    });
  };

  convertImageFormat = async (imagePath, format) =&gt; {
    const reg = /\/(.*)\.(jpe?g|png|webp|gif)$/i;
    const &#91;, imageName, imageExtension] = imagePath.match(reg);
    const imageFileName = this.includeExtensionName ? `${imageName}.${imageExtension}` : imageName;
    const destPath = `${this.destBase}/${imageFileName}.${format.type}`;

    try {
      let sharpOptions = {};
      if (imageExtension.toLowerCase() === "gif") {
        sharpOptions.pages = 1;
      }

      // メインの画像を800pxで出力
      await sharp(imagePath, sharpOptions)
        .resize({
          width: this.mainWidth,
          withoutEnlargement: true,
        })
        .toFormat(format.type, { quality: format.quality })
        .toFile(destPath);

      log(
        `Converted ${c.blue(imagePath)} to ${c.yellow(format.type.toUpperCase())} ${c.green(
          destPath
        )}`
      );

      // WebPの場合、ミニバージョン(200px)も作成
      if (format.type === "webp") {
        await this.createMiniVersion(imagePath, imageFileName);
      }
    } catch (err) {
      log(c.red(`Error converting image to ${c.yellow(format.type.toUpperCase())}\n${err}`));
    }
  };

  createMiniVersion = async (imagePath, imageFileName) =&gt; {
    const miniDestPath = path.join(this.destBase, this.miniFolder, `${imageFileName}_th.webp`);

    try {
      fs.mkdirSync(path.dirname(miniDestPath), { recursive: true });
      await sharp(imagePath)
        .resize({
          width: this.miniWidth,
          height: this.miniWidth,
          position: "center",
        })
        .toFormat("webp", { quality: 80 })
        .toFile(miniDestPath);

      log(`Created mini version: ${c.green(miniDestPath)}`);
    } catch (err) {
      log(c.red(`Error creating mini version\n${err}`));
    }
  };

  processImages = async (imagePathList) =&gt; {
    if (imagePathList.length === 0) {
      log(c.red("No images found to process"));
      return;
    }

    for (const imagePath of imagePathList) {
      const reg = new RegExp(`^${this.srcBase}/(.*/)?`);
      const path = imagePath.match(reg)&#91;1] || "";
      const destDir = `${this.destBase}/${path}`;

      if (!fs.existsSync(destDir)) {
        try {
          fs.mkdirSync(destDir, { recursive: true });
          log(`Created directory ${c.green(destDir)}`);
        } catch (err) {
          log(`Failed to create directory ${c.green(destDir)}\n${err}`);
        }
      }

      const conversionPromises = this.formats.map((format) =&gt;
        this.convertImageFormat(imagePath, format)
      );
      await Promise.all(conversionPromises);
    }
  };
}

const imageProcessor = new ImageProcessor();</code></pre>



<h3 class="wp-block-heading"><span id="toc3">package.json の編集</span></h3>



<p>バッチファイルから簡単に変換できるよう、<code>package.json</code> の <code>"scripts"</code> に次のように追記しました。</p>



<pre class="wp-block-code"><code>"imageProcessor": "node imageProcessor.js"</code></pre>



<p>全体はこんな感じです。</p>



<pre class="wp-block-code"><code>{
  "name": "image-format-converter",
  "version": "1.0.0",
  "license": "UNLICENSED",
  "private": true,
  "scripts": {
    "image-format-converter": "node ./image-format-converter.js",
    "imageProcessor": "node imageProcessor.js"
  },
  "type": "module",
  "devDependencies": {
    "ansi-colors": "^4.1.3",
    "fancy-log": "^2.0.0",
    "globule": "^1.3.4",
    "sharp": "^0.33.5"
  }
}</code></pre>



<h2 class="wp-block-heading"><span id="toc4">バッチファイルの作成</span></h2>



<p>変換をワンクリックでできるように、フォルダの上のほうに置いておくため <code>!imageProcessor.bat</code> という名前で保存しました。</p>



<pre class="wp-block-code"><code>@echo off
npm run imageProcessor
pause</code></pre>



<h2 class="wp-block-heading"><span id="toc5">スクリプトを実行</span></h2>



<p><code>!imageProcessor.bat</code> をダブルクリックすると、コマンドプロンプトが開き、画像がまとめて変換されます。</p>



<p>また、<code>imageProcessor.js</code> 内の</p>



<pre class="wp-block-code javascript"><code>this.mainWidth = 800;
this.miniWidth = 200;</code></pre>



<p>の数値を変えることで、メイン画像やサムネイルのサイズを自由に変更できます。</p>



<p>私は、簡単なラクガキを 500px や 600px で掲載することが多いので、用途ごとに変換スクリプトやバッチファイルを作り分けています。<br>現在は「同時変換用」「サムネイル専用」「500px」「600px」「1000px」用のスクリプトをそれぞれ運用しています。</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>手打ちサイトでRSS対応の更新履歴を作成しました</title>
		<link>https://mkb.stars.ne.jp/blog/handmade-website-rss-update-history/</link>
		
		<dc:creator><![CDATA[みかみ]]></dc:creator>
		<pubDate>Mon, 30 Dec 2024 12:03:06 +0000</pubDate>
				<category><![CDATA[サイト運営]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[Node.js]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[CSS]]></category>
		<guid isPermaLink="false">https://mkb.stars.ne.jp/blog/?p=11</guid>

					<description><![CDATA[他の個人サイト運営者の更新を「てがろぐ」のRSSで把握しているため、自分のサイトでも更新履歴をRSS対応にできないかと考えました。作業にあたっては、ChatGPTやClaudeに大変お世話になりました。 RSSの土台を準 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-cocoon-blocks-icon-box common-icon-box block-box alert-box">
<p>現在はPHPでサイトを管理しているためこの方法は利用していません。<br>HTMLでサイトを作っている人用の記事です。</p>
</div>



<p>他の個人サイト運営者の更新を「てがろぐ」のRSSで把握しているため、自分のサイトでも更新履歴をRSS対応にできないかと考えました。<br>作業にあたっては、<a rel="noopener" target="_blank" href="https://chatgpt.com/">ChatGPT</a>や<a rel="noopener" target="_blank" href="https://claude.ai/">Claude</a>に大変お世話になりました。</p>



<h2 class="wp-block-heading"><span id="toc1">RSSの土台を準備</span></h2>



<p>RSSの基本コードは以下の通りです。<code>rss.xml</code>という名前で<code>index.html</code>があるフォルダと同じ階層にに保存します。</p>



<pre class="wp-block-code xml"><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;rss version="2.0"&gt;
  &lt;channel&gt;
    &lt;title&gt;サイト名&lt;/title&gt;
    &lt;link&gt;サイトアドレス&lt;/link&gt;
    &lt;description&gt;サイト名：更新情報&lt;/description&gt;
    &lt;language&gt;ja&lt;/language&gt;
    &lt;item&gt;
      &lt;title&gt;更新内容&lt;/title&gt;
      &lt;pubDate&gt;Wed, 20 Nov 2024 21:25:04 +0900&lt;/pubDate&gt;
      &lt;enclosure url="自サイトのOGP画像" type="image/png"/&gt;
      &lt;link&gt;サイトアドレス&lt;/link&gt;
    &lt;/item&gt;
    &lt;item&gt;
      &lt;title&gt;更新内容&lt;/title&gt;
      &lt;pubDate&gt;Wed, 19 Nov 2024 21:25:04 +0900&lt;/pubDate&gt;
      &lt;enclosure url="自サイトのOGP画像" type="image/png"/&gt;
      &lt;link&gt;サイトアドレス&lt;/link&gt;
    &lt;/item&gt;
    &lt;item&gt;
      &lt;title&gt;更新内容&lt;/title&gt;
      &lt;pubDate&gt;Wed, 18 Nov 2024 21:25:04 +0900&lt;/pubDate&gt;
      &lt;enclosure url="自サイトのOGP画像" type="image/png"/&gt;
      &lt;link&gt;サイトアドレス&lt;/link&gt;
    &lt;/item&gt;
  &lt;/channel&gt;
&lt;/rss&gt;</code></pre>



<h2 class="wp-block-heading"><span id="toc2">AIにRSSを埋め込む方法を質問する</span></h2>



<p><code>index.html</code>にRSSを埋め込む方法をAIに質問しました。<br>PHPとjQueryの方法があるようでしたが、既にjQueryを使用していたため、jQueryでの実装を採用しました。</p>



<p>AI用プロンプトは以下の通りです。</p>



<pre class="wp-block-code plaintext"><code>同じ階層にある `rss.xml` の最新 3 件を `index.html` に jQuery を使って表示したいです。

現在、更新履歴の HTML は以下の形式です：
&lt;section class="white-1"&gt;
 &lt;h2 class="news"&gt;Update&lt;/h2&gt;
  &lt;dl class="news"&gt;
    &lt;dt&gt;2024.11.18&lt;/dt&gt;
    &lt;dd&gt;更新内容&lt;/dd&gt;
  &lt;/dl&gt;
    &lt;dl class="news"&gt;
    &lt;dt&gt;2024.09.11&lt;/dt&gt;
    &lt;dd&gt;更新内容&lt;/dd&gt;
  &lt;/dl&gt;
    &lt;dl class="news"&gt;
    &lt;dt&gt;2024.08.13&lt;/dt&gt;
    &lt;dd&gt;更新内容&lt;/dd&gt;
  &lt;/dl&gt;
&lt;/section&gt;


`rss.xml` の内容は以下です：
&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;rss version="2.0"&gt;
  &lt;channel&gt;
    &lt;title&gt;サイト名&lt;/title&gt;
    &lt;link&gt;サイトアドレス&lt;/link&gt;
    &lt;description&gt;サイト名：更新情報&lt;/description&gt;
    &lt;language&gt;ja&lt;/language&gt;
    &lt;item&gt;
      &lt;title&gt;更新内容&lt;/title&gt;
      &lt;pubDate&gt;Wed, 20 Nov 2024 21:25:04 +0900&lt;/pubDate&gt;
      &lt;enclosure url="自サイトのOGP画像" type="image/png"/&gt;
      &lt;link&gt;サイトアドレス&lt;/link&gt;
    &lt;/item&gt;
    &lt;item&gt;
      &lt;title&gt;更新内容&lt;/title&gt;
      &lt;pubDate&gt;Wed, 19 Nov 2024 21:25:04 +0900&lt;/pubDate&gt;
      &lt;enclosure url="自サイトのOGP画像" type="image/png"/&gt;
      &lt;link&gt;サイトアドレス&lt;/link&gt;
    &lt;/item&gt;
    &lt;item&gt;
      &lt;title&gt;更新内容&lt;/title&gt;
      &lt;pubDate&gt;Wed, 18 Nov 2024 21:25:04 +0900&lt;/pubDate&gt;
      &lt;enclosure url="自サイトのOGP画像" type="image/png"/&gt;
      &lt;link&gt;サイトアドレス&lt;/link&gt;
    &lt;/item&gt;
  &lt;/channel&gt;
&lt;/rss&gt;


jQuery を使った実装例を教えていただけますか？</code></pre>



<div class="wp-block-cocoon-blocks-icon-box common-icon-box block-box alert-box">
<p><strong>&#8220;現在、更新履歴の HTML は以下の形式です&#8221;</strong>の下に記述してあるHTMLは自サイトで使用しているものであり、サイト環境によって異なるため注意が必要です。</p>
</div>



<p>AIが出力したコードを、<code>rss.js</code>として保存しました。</p>



<h2 class="wp-block-heading"><span id="toc3">index.htmlにjQueryとrss.jsを設置</span></h2>



<p>index.htmlの&lt;/body&gt;前に作成した<code>rss.js</code>を設置します。</p>



<pre class="wp-block-code html"><code>&lt;script src="&lt;https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js&gt;"&gt;&lt;/script&gt;
&lt;script src="assets/js/rss.js"&gt;&lt;/script&gt;
&lt;/body&gt;</code></pre>



<p>当サイトでは<a rel="noopener" target="_blank" href="https://do.gt-gt.org/">do</a>様作成プログラムの「いいねボタン改」と「コイブミ」を既に設置していたため、設置マニュアル通りにjQueryのCDNを読み込みました。<br><code>rss.js</code>は<code>assets/js</code>フォルダに配置しています。</p>



<div class="wp-block-cocoon-blocks-icon-box common-icon-box block-box alert-box">
<p>rss.jsをどのフォルダに置くかもサイト環境によって異なるので注意が必要です。</p>
</div>



<h2 class="wp-block-heading"><span id="toc4">CSSの高さをAIに計算してもらう</span></h2>



<p>RSS読み込み中は内容が表示されず、レイアウトが崩れることがあったため、更新履歴セクションの高さをAIに計算してもらい、CSSに反映しました。</p>



<p>AI質問用プロンプト</p>



<pre class="wp-block-code plaintext"><code>## 質問内容

安価なサーバーを使用しており、RSS フィードの読み込みに時間がかかります。
そのため、HTML の読み込み前後で`&lt;section&gt;`の高さが不安定になります。
以下のコードを元に、CSS で適切な高さを設定したいです。
読み込み後の`&lt;section&gt;`の高さを計算して具体的な rem 単位を教えてください。

読み込み前の HTML：
エディタに書いてる更新履歴のソースコードを載せる

読み込み後の HTML：
サイトをブラウザで表示した際の更新履歴の部分のソースコードを開発者ツールなどで見てそれをコピペ

css：
大元の body と、更新履歴で使ってる CSS をコピペ</code></pre>



<p>AIが計算した数値をCSSに追加することで、サイトデザインが崩れなくなりました。</p>



<h2 class="wp-block-heading"><span id="toc5">動作確認</span></h2>



<p>動作テスト用をするために、サーバーに<code>test</code>フォルダを作成し、画像以外のサイト構成ファイルをアップロードしました。<br>アドレス末尾に <code>/test/index.html</code> を入力して確認し、表示やエラーがあればAIに相談して修正しました。</p>



<p>動作確認が完了すれば、更新のたびに <code>rss.xml</code> に <code>&lt;item&gt;</code> を追加することで自動的に表示が変わります。更新履歴の作成とRSS発信を同時に行えるため、一石二鳥です♪</p>



<div style="height:10em" aria-hidden="true" class="wp-block-spacer"></div>



<h2 class="wp-block-heading"><span id="toc6">発展編：更新コード作成の自動化</span></h2>



<p>毎回<code>rss.xml</code>内の日時要素である<code>&lt;pubDate&gt;Wed, 20 Nov 2024 21:25:04 +0900&lt;/pubDate&gt;</code> の確認と記述をするのが手間だったため、AIに作成してもらったプログラムで自動化しました。</p>



<h3 class="wp-block-heading"><span id="toc7">仕組み</span></h3>



<p><code>rss.xml</code>と同じフォルダに<code>update.txt</code>を作成し、一行ごとに更新内容を書きます。<br>プログラムを実行すると、内容と日時を <code>&lt;item&gt;</code> として <code>rss.xml</code> に追加します。<br>このへんのアイデアはじぇねろぐに影響を受けています。</p>



<div class="wp-block-cocoon-blocks-blogcard blogcard-type bct-none">

<a rel="noopener" target="_blank" href="https://do.gt-gt.org/product/genelog/" title="なんでも一覧自動生成プログラム じぇねログ | 創作・同人サイト制作支援メディア do" class="blogcard-wrap external-blogcard-wrap a-wrap cf"><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://do.gt-gt.org/wp-content/uploads/2024/02/genelog.png" 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">なんでも一覧自動生成プログラム じぇねログ | 創作・同人サイト制作支援メディア do</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://do.gt-gt.org/product/genelog/" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">do.gt-gt.org</div></div></div></div></a>
</div>



<div class="wp-block-cocoon-blocks-icon-box common-icon-box block-box alert-box">
<p>Pythonが必要です。コマンドプロンプトやインストールに不安がある場合は注意してください。<br>私は何も理解していないのにAIに言われるがままやっています。AI様の奴隷です。</p>
</div>



<div class="wp-block-cocoon-blocks-blogcard blogcard-type bct-dl">

<a rel="noopener" target="_blank" href="https://www.python.org/" title="Welcome to Python.org" class="blogcard-wrap external-blogcard-wrap a-wrap cf"><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://www.python.org/static/opengraph-icon-200x200.png" 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">Welcome to Python.org</div><div class="blogcard-snippet external-blogcard-snippet">The official home of the Python Programming Language</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://www.python.org/" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">www.python.org</div></div></div></div></a>
</div>



<h3 class="wp-block-heading"><span id="toc8">AIに自動化するためのコードを作ってもらう</span></h3>



<p>AI に色々質問して出来たコードはこちらです。<code>rss.py</code>で保存します。</p>



<pre class="wp-block-code python"><code>import xml.etree.ElementTree as ET
from datetime import datetime
import xml.dom.minidom
import re

def prettify_xml(xml_string):
    """XMLを整形して返す"""
    # minidomを使用して整形
    dom = xml.dom.minidom.parseString(xml_string)
    pretty_xml = dom.toprettyxml(indent='  ', encoding='utf-8').decode('utf-8')
    
    # 空行を削除
    lines = pretty_xml.split('\n')
    # 空白行や空白文字のみの行を除去
    cleaned_lines = &#91;line for line in lines if line.strip()]
    # 最初の行（XML宣言）を除去（後で追加するため）
    cleaned_lines = cleaned_lines&#91;1:]
    
    # 行を結合して返す
    return '\n'.join(cleaned_lines)

def insert_updates_to_rss(rss_file_path, update_file_path):
    try:
        # 既存のXMLファイルを文字列として読み込む
        with open(rss_file_path, 'r', encoding='utf-8') as f:
            xml_content = f.read()
        
        # XMLパーサーを作成
        parser = ET.XMLParser(encoding='utf-8')
        tree = ET.fromstring(xml_content, parser=parser)
        
        # channelタグを見つける
        channel = tree.find('channel')
        if channel is None:
            raise ValueError("Channel element not found in RSS")
        
        # update.txtから更新情報を読み込む
        with open(update_file_path, 'r', encoding='utf-8') as f:
            updates = f.readlines()
        
        # 現在時刻をRFC822フォーマットで取得
        current_time = datetime.now().strftime('%a, %d %b %Y %H:%M:%S +0900')
        
        # channelの既存の要素を取得
        existing_items = channel.findall('item')
        
        # 既存のitem要素を一時的に削除
        for item in existing_items:
            channel.remove(item)
        
        # 新しいitem要素を追加
        for update in updates:
            update = update.strip()
            if update:
                item = ET.SubElement(channel, 'item')
                
                title = ET.SubElement(item, 'title')
                title.text = update
                
                pubdate = ET.SubElement(item, 'pubDate')
                pubdate.text = current_time
                
                enclosure = ET.SubElement(item, 'enclosure')
                enclosure.set('url', 'https://mikamibox.com/img/OGP.png')
                enclosure.set('length', '0')
                enclosure.set('type', 'image/png')
                
                link = ET.SubElement(item, 'link')
                link.text = 'https://mikamibox.com/'
        
        # 既存のitem要素を元の位置に戻す
        for item in existing_items:
            channel.append(item)
        
        # XMLツリーを文字列に変換して整形
        rough_string = ET.tostring(tree, encoding='utf-8')
        pretty_xml = prettify_xml(rough_string)
        
        # 整形済みXMLを保存
        with open(rss_file_path, 'w', encoding='utf-8') as f:
            f.write('&lt;?xml version="1.0" encoding="utf-8"?&gt;\n')
            f.write(pretty_xml)
            
        print("RSS file has been successfully updated!")
        
    except Exception as e:
        print(f"Error occurred: {e}")

# プログラムの実行
if __name__ == '__main__':
    rss_file_path = 'rss.xml'
    update_file_path = 'update.txt'
    insert_updates_to_rss(rss_file_path, update_file_path)</code></pre>



<p>色々試行錯誤したため、ちゃんと動作したこのコードがどうやって生まれたのか説明が難しいです。<br>なのでこのコードができるようなプロンプトを逆算してもらいました。<br>この内容を自分の要望に合わせてカスタマイズして質問してみるといいのかもしれないです。（試していない）</p>



<pre class="wp-block-code"><code>RSSフィードを更新するPythonスクリプトを作成してください。以下の要件を満たしてください：
1. 既存のRSS XMLファイルを読み込み、新しい更新情報をチャンネルの最上部に追加
2. 更新情報は別のテキストファイル（update.txt）から読み込む
3. 各更新項目に以下を含む：
   - タイトル（更新テキスト）
   - 公開日時（現在時刻、RFC822フォーマット）
   - エンクロージャー（OGP画像URL）
   - リンク（ウェブサイトURL）
4. XMLの整形機能（インデント付き）
5. 既存項目は保持
6. エラーハンドリングを実装</code></pre>



<h3 class="wp-block-heading"><span id="toc9">バッチファイルで実行（Windowsのみ）</span></h3>



<p>新しいテキストファイルを作成し、以下をコピーして <code>rss.bat</code> として保存します。</p>



<pre class="wp-block-code"><code>@echo off
python rss.py
pause</code></pre>



<p>以降は <code>update.txt</code> に更新内容を入力し、<code>rss.bat</code> をダブルクリックするだけで <code>rss.xml</code> が更新され、<code>index.html</code> を直接編集する必要がなくなります。やったー！</p>



<p>…とても面倒くさいな。面倒くさいとか言ってたら静的サイトベタ打ちしません。そりゃそうじゃ。</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
