ぬまろぐ

←戻る

WordpressブログをCloudFront + S3に移行する方法

2023/05/05

wordpressブログからAstro + CloudFront + S3に切り替えてランニングコストを下げて表示速度を改善しましたが、既存のwordpressブログを閉じるために移行する方法を試行錯誤したのでまとめます。

試行①:Wordpressから静的ページを生成してS3に配置する

Wordpressのプラグインを使って静的ページを生成してS3に配置します。Wordpressから静的ページを生成するプラグインは「StaticPress」と「WP2Static」が有名どころのようです。

StaticPressを試してみる

まずはStaticPressプラグインで試してみました。

結果、スタイルが崩れてしまい、ブログも表示されずうまくいきませんでした。ブログのパーマリンクが「?p=xxxx」のようにクエリパラメータになっているためブログページが生成できないのかもしれません。

簡単にはいかなそう。

WP2Staticを試してみる

WP2Staticを試しましたが変換を実行しても「500 error」となり変換が失敗しました。こちらも断念。

試行②:Wordpessブログページの記事部分のHTMLを抽出し、Notionでmdファイル化し、Astroに取り込む

回りくどい方法ですが、Wordpressブログページの記事部分をHTMLから抽出し、Notionにインポートすることでmdファイルに変換することができます。変換したmdファイルをAstroに取り込んで静的ページ化しました。

具体的なフローは下図の通りです。

IMG

手動で1つ1つの記事でこれを行うのは現実的でなかったため、GoogleのAppScript自動化しました。

Wordpressページを表示して記事部分を抽出するコードは以下のようになりました。サンプルではスプレッドシートに定義したブログ記事のURL一覧から1つ1つwordpressにアクセスしてHTMLを抽出していっています。

/** @OnlyCurrentDoc */

/**
 * メイン関数
 */
function execute1() {
  // GoogleシートとドライブフォルダのIDを設定
  // Googleのスプレッドシートで対象ページ一覧を作成したため、そこから読み込んでループ処理を実施
  let sheetId = 'xxx';
  let folderId = 'xxx';

  // シートのブログリストを指定
  var response = Sheets.Spreadsheets.Values.batchGet(
    sheetId,
    {
      ranges:
        [
          "content-pages!C100:C181"
        ]
    }
  );

  // URLリクエストして結果をHTMLファイルに保存
  response.valueRanges[0].values.forEach(function (path) {
    if (path[0].includes("wordpress") && path[0].includes("p=")) { // URLが記事ページであれば
			// URL先のHTMLから記事とタイトルを抽出する
      let contents = getBlogContent(path[0]);
      let title = getTitle(contents);

			// 結果を保存
      var folder = DriveApp.getFolderById(folderId);
      var file = folder.createFile(title + ".html", contents, MimeType.HTML);
    }
  });
}

/**
 * ブログURLにアクセスしコンテンツ部分だけを抽出する
 */
function getBlogContent(path) {
  var requestUrl = 'https://mydomain' + path;
  var response = UrlFetchApp.fetch(requestUrl);
  var responseCode = response.getResponseCode();
  var responseText = response.getContentText();

	// 記事部分だけを抽出するための正規表現
  let regex = /<header class="entry-header">.*<!-- .entry-content -->/s;
  let result = responseText.match(regex);
  return result;
}

/**
 * コンテンツからタイトルを抽出する
 */
function getTitle(contents) {
  let regex = /<h1 class="entry-title">(.*)<\/h1>/s;
  let result = String(contents).match(regex);

  return result[1];
}

次にNotionからエクスポートしたマークダウンをAstro用に加工し、記事中の画像ファイルをWordpressからダウンロードするコードになります。かなり独自処理が入ってしまっていますが、参考までに。

function execute2() {
  // GoogleシートとドライブフォルダのIDを設定
  let sheetId = 'xxx';
  let folderId = 'xxx';
  let inFolder = DriveApp.getFolderById(folderId);
  let outFolder = createFolderEx(inFolder, "articles");

  // シートのブログリストを指定
  var response = Sheets.Spreadsheets.Values.batchGet(
    sheetId,
    {
      ranges:
        [
          "C100-C181!A54:C69"
        ]
    }
  );

  // URLリクエストして結果をHTMLファイルに保存
  response.valueRanges[0].values.forEach(function (line) {
    let inFileName = line[0];
    let outFileName = line[1];
    let category = line[2];
    convertMdFile(inFolder, outFolder, inFileName, outFileName, category);
  });
}

/*
  NotinoからエクスポートしたMDファイルを加工する
*/
function convertMdFile(inFolder, outFolder, inFileName, outFileName, category) {
  var inData = inFolder.getFilesByName(inFileName).next().getBlob().getDataAsString("UTF-8");
  var outData = "---\r\n";
  outData += "layout: ../../layouts/PostLayout.astro\r\n";
  outData += "author: Shintaro\r\n";
  outData += "eyecatch: /hp/articles/default.jpg\r\n";
  outData += "categories: " + category + "\r\n";

  let step = 0;
  let imageFolder;
  inData.split(/\r\n|\n/).forEach(function (line) {
    if (line.match(/^$/) && step < 3) {
      return; //空白行はスキップ
    }

    // タイトルの取得
    if (line.match(/^#\s/) && step === 0) {
      step++;
      return;
    } else if (line.match(/^投稿日/) && step === 1) {
      let postedDate = line.match(/^投稿日: \[([^日]+日)/)[1];
      outData += "date: " + Utilities.formatDate(Utilities.parseDate(postedDate, 'JST', 'yyyy年MM月dd日'), 'JST', 'yyyy/MM/dd') + "\r\n";
      return;
    } else if (line.match(/^#\s/) && step === 1) {
      step++;
      outData += "title: " + line.substring(2) + "\r\n";
      return;
    }

    // descriptionの取得
    if (step === 2) {
      outData += "description: " + line + "\r\n";
      outData += "---\r\n"
      step++;
    }

    // 画像があったら画像ファイルを保存する
    if (step >= 3 && (line.match(/^\!\[/))) {
      let url = line.match(/^\!\[.*\]\((.*)\)/)[1];
      console.log(url);

      // mussyu1204.myhome.cxの場合はmussyu1204.comに変更する
      url = url.replace("mussyu1204.myhome.cx", "mussyu1204.com");

      if (imageFolder == null) {
        imageFolder = createFolderEx(outFolder, outFileName);
      }
      getImage(url, imageFolder);
      outData += "<figure>\r\n";
      outData += "  <img src=\"/hp/articles/" + imageFolder + "/" + String(url).match(/([^\/]*)$/)[1] + "\" alt=\"img\" width=\"80%\">\r\n";
      outData += "</figure>\r\n";
    } else if (step >= 3) {
      outData += line + "\r\n";
    }
  });
  var file = outFolder.createFile(outFileName + ".md", outData, MimeType.HTML);
}

/**
 * 画像URLから画像をDLする
 */
function getImage(url, saveDir) {
  let imageFileName = String(url).match(/([^\/]*)$/)[1];
  console.log("image file name is " + imageFileName);
  var response = UrlFetchApp.fetch(url);
  var fileBlob = response.getBlob().setName(imageFileName);
  var file = saveDir.createFile(fileBlob);
}

/**
 * フォルダがなければ作成する、あれば一度削除して作成する
 */
function createFolderEx(baseFolder, target) {
  var folders = baseFolder.getFoldersByName(target);
  if (folders.hasNext()) {
    let folder = folders.next();
    folder.setTrashed(true); // removeFolderは関係を削除するだけのためマイドライブに移ってしまう
    return baseFolder.createFolder(target);
  }
  else {
    return baseFolder.createFolder(target);
  }
}

これでAstro用に加工したMDファイルができるため、Astroのプロジェクトに取り込みビルドしてS3に配置すればブログのお引越しは完了です。