Google Apps ScriptでWordPressのカテゴリーを付け替える【GAS】

WordPressに対して長年記事を投稿し続けると、ある種の変更や修正を全記事に対して行うといった作業が発生すると、記事数の多さや手間の前に気が遠くなります。これらの作業を手作業でするなんてどれだけの時間と労力が掛かるか・・・・

そんな作業の1つに「カテゴリーの再割り当て」作業があります。時とともにカテゴリーが時代に合わなくなり、再整理したいと思っても、人力でやる気は起きません。そこで、このカテゴリーの再割り当て作業をGoogle Apps Scriptでやらせてみました。半自動化なので500記事でおよそ1時間未満くらいで終了させることが出来ました。

今回利用する素材

今回の記事は事実上前回の投稿記事取得の続編となるため、設定関係や事前準備等については以下の記事を参照して行ってください。その上での追加の準備やコードとなるため、本記事のみ対応すると失敗します。

また、記事の一覧取得したものに対して、手動でカテゴリーを付与し、WordPressに対してカテゴリー割当するスクリプトであり、以下のような仕様にしています。

  • 既存の割り当て済みカテゴリーは削除しません
  • しかし、WordPress上で手動で断捨離結果、未割当というカテゴリーが付くので、これは削除します。
  • カテゴリーIDをもってリクエストをして複数同時に付与します。
  • 既存のカテゴリーリストを別途GASで引き抜いて使っています。

Google Apps ScriptでWordPressの投稿記事を取得する【GAS】

事前準備

WordPressのカテゴリの断捨離

まずは実行前に、WordPress側で以下のような作業をしてカテゴリーの断捨離と整備をしておきます。

  • 不要になったカテゴリーを削除します。
  • 追加で必要となったカテゴリーを追加します
  • またサブカテゴリーまで今回は対応してるのでサブカテゴリーが必要であれば追加します。
  • 残しておいたカテゴリーはそのままでOK

各カテゴリーにはカテゴリーIDというのが後ろで自動付与されます。これをGASで引き抜いて利用します。また、カテゴリーが1個のみ割り当てられていて、それが不要なカテゴリーで削除した場合には「未割当」というカテゴリーが自動で付与されます。

この未割当カテゴリーはGASからカテゴリー割当時に自動削除するようにしているので、そのままでOKです。

図:カテゴリーを整理しておきましょう

カテゴリー割当作業

次項のカテゴリーリスト取得のスクリプトを実行すると、カテゴリシートに整理しておいたカテゴリのリストが取得されます。このカテゴリーリストを元に、記事一覧の記事リストの「カテゴリー列」では、複数選択のプルダウンとしてセットしてあります。

記事一覧を取得しておいて、対象のレコードで手動でカテゴリーを割り当てていきます。ここではカテゴリー名で割り当ててますが、GASでカテゴリー名からカテゴリIDをカテゴリシートから拾ってくる仕様にしていますので、遠慮なく選択を追加します。

最後に、処理を行うレコードに対して、記事一覧の処理対象のチェックボックスにチェックをして準備は完了です。チェックの入っていないレコードは処理がスキップされます。

※但し記事数が多い場合、処理が6分で完結できない場合があります。故に100レコードずつくらいチェックを入れて実行をすると良いでしょう。

図:取得されたカテゴリーリスト

図:記事リストとカテゴリ割当

ソースコード

カテゴリーリストを取得

WordPress APIを利用してカテゴリのリストを取得します。カテゴリリストは認証不要で取得が可能です。

また、カテゴリがサブカテゴリーの場合、親カテゴリーも取得するようにしています。ここで重要なのはカテゴリのIDと名前のみ。ここでカテゴリーを取得しておかないと手動割当作業がまず出来ません。

カテゴリーが取得できたら前回の記事の、記事一覧取得を実行して手動割当作業をします。

カテゴリシートが無い場合には、自動的に作成するようになっています。

//カテゴリの一覧を取得する
function getWordPressCategoriesWithParent() {
  // カテゴリリストを取得するAPIエンドポイント (per_page=100で最大100件取得)
  let categoriesApiUrl = WP_URL + '/wp-json/wp/v2/categories?per_page=100'; 
  
  // スプレッドシートのIDとシート名を指定
  let ssid = SpreadsheetApp.getActiveSpreadsheet().getId();
  let sheetName = 'カテゴリ';
  
  try {
    // すべてのカテゴリデータを取得
    let response = UrlFetchApp.fetch(categoriesApiUrl);
    let jsonText = response.getContentText();
    let categories = JSON.parse(jsonText);
    
    // 親カテゴリ名を効率的に検索するためのマップを作成
    let categoryIdToNameMap = {};
    for (let i = 0; i < categories.length; i++) {
      categoryIdToNameMap[categories[i].id] = categories[i].name;
    }
    
    // スプレッドシートを取得
    let spreadsheet = SpreadsheetApp.openById(ssid);
    let sheet = spreadsheet.getSheetByName(sheetName);
    
    //シートの有無チェック
    if (!sheet) {
      //シートが存在しない場合
      sheet = spreadsheet.insertSheet(sheetName);

      // ヘッダー行を書き込む(「親カテゴリID」「親カテゴリ名」を追加)
      sheet.appendRow(['ID', '名前', 'スラッグ', '投稿数', '親カテゴリID', '親カテゴリ名']);
    }else{
      //シートをクリアする
      sheet.getRange("A2:F").clearContent();
    }

    // カテゴリデータをループしてスプレッドシートに書き込み
    for (let i = 0; i < categories.length; i++) {
      let category = categories[i];
      let parentId = category.parent;
      let parentName = '';
      
      // 親カテゴリIDが0でない場合(=サブカテゴリの場合)、マップから親カテゴリ名を検索
      if (parentId !== 0) {
        parentName = categoryIdToNameMap[parentId] || '';
      }
      
      //1行ずつカテゴリをシートに書き出し
      sheet.appendRow([category.id, category.name, category.slug, category.count, parentId, parentName]);
    }
    
    console.log('WordPressのカテゴリリストをスプレッドシートに書き出しました。');
    
  } catch(e) {
    console.log('エラーが発生しました: ' + e.message);
  }
}

カテゴリ付与処理

いよいよ事前準備をすべて終えて、WordPressに対して認証情報を元にリクエストを送り、カテゴリの付与を行います。ここでは以下の作業も行っています。

  • カテゴリ名からカテゴリIDを探索し、複数まとめてリクエストで付与します。
  • 処理対象でありカテゴリが空っぽではないレコードのみを処理するようにしています。
  • 未割当カテゴリがある場合には同時に削除処理も行っていますが、それ以外の既存の割当カテゴリはそのまま残ります。
  • リクエストメソッドはPOSTではなくPUTで行う必要があります。

あまり処理速度が早くはないので、せいぜい1度の処理で100件程度が目安です。

※未割当カテゴリや重複除外をするために一旦カテゴリを取得して整理して、2回目のリクエストでカテゴリ付与を行っています。事前に記事一覧でカテゴリもデータ化しておけば、1回のリクエストで行けるのではないかと思います。

//対象記事に対してカテゴリ追加する
function updatePostCategoriesFromSheet() {
  // 1. 設定と認証情報の取得
  const prop = PropertiesService.getScriptProperties();
  const apiKey = prop.getProperty("apppw");
  const userid = prop.getProperty("userid");

  if (!apiKey || !userid) {
    SpreadsheetApp.getUi().alert('スクリプトプロパティに「userid」と「apppw」が設定されていません。');
    return;
  }

  // スプレッドシートの取得
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const postSheet = ss.getSheetByName('記事一覧');
  const categorySheet = ss.getSheetByName('カテゴリ');

  if (!postSheet || !categorySheet) {
    SpreadsheetApp.getUi().alert('シート「記事一覧」または「カテゴリ」が見つかりません。');
    return;
  }

  // カテゴリ名とIDの対応表(Map)を作成
  const categoryData = categorySheet.getDataRange().getValues();
  const categoryNameToIdMap = new Map();
  for (let i = 1; i < categoryData.length; i++) {
    const categoryId = categoryData[i][0];
    const categoryName = categoryData[i][1];
    if (categoryName && categoryId) {
      categoryNameToIdMap.set(categoryName.toString().trim(), categoryId);
    }
  }

  // 記事一覧シートをループ処理
  const postData = postSheet.getDataRange().getValues();
  const encodedAuth = Utilities.base64Encode(`${userid}:${apiKey}`);
  const headers = { 'Authorization': `Basic ${encodedAuth}` };
  ss.toast('カテゴリ更新処理を開始します...', '処理中', -1);

  for (let i = 1; i < postData.length; i++) {
    const row = postData[i];
    const postId = row[0]; // A列: 記事ID
    const categoryCellData = row[5]; // F列: カテゴリ名
    const execflg = row[6];  // G列:処理対象フラグ

    // 「チェックがない」または「シート上のカテゴリ指定が空」の行は処理しない
    if (execflg !== true || !categoryCellData || categoryCellData.toString().trim() === '') {
      continue;
    }

    // 記事IDがなければスキップ
    if (!postId) {
      continue;
    }
    
    //カテゴリリストからIDを拾ってくる
    const categoryNames = categoryCellData.toString().split(/[,、]/).filter(Boolean);
    const categoryIdsToAdd = [];
    for (const name of categoryNames) {
      const trimmedName = name.trim();
      if (categoryNameToIdMap.has(trimmedName)) {
        categoryIdsToAdd.push(categoryNameToIdMap.get(trimmedName));
      } else {
        console.log(`WARN: 記事ID ${postId} - カテゴリ名 "${trimmedName}" のIDが見つかりませんでした。`);
      }
    }

    if (categoryIdsToAdd.length === 0) {
      // 指定されたカテゴリ名がすべて無効だった場合
      continue;
    }

    try {
      // 既存カテゴリの読み込み
      const getResponse = UrlFetchApp.fetch(`${WP_URL}/wp-json/wp/v2/posts/${postId}?_fields=categories`, {
        headers: headers,
        muteHttpExceptions: true
      });

      //カテゴリリスト取得失敗時
      if (getResponse.getResponseCode() !== 200) {
        console.log(`ERROR: 記事ID ${postId} の読み込みに失敗: ${getResponse.getContentText()}`);
        continue;
      }
      
      //既存カテゴリをリスト化
      const existingPostData = JSON.parse(getResponse.getContentText());
      const existingCategoryIds = existingPostData.categories || [];
      
      //カテゴリのユニークな値をリスト化
      let updatedCategoryIds = [...new Set([...existingCategoryIds, ...categoryIdsToAdd])];

      // カテゴリー未分類(ID:1)を削除
      if (updatedCategoryIds.length > 1) {
        updatedCategoryIds = updatedCategoryIds.filter(id => id !== 1);
      }

      // カテゴリー更新処理
      const updateOptions = {
        method: 'put',
        contentType: 'application/json',
        headers: headers,
        payload: JSON.stringify({
          categories: updatedCategoryIds
        }),
        muteHttpExceptions: true
      };

      //HTTPリクエスト
      const updateResponse = UrlFetchApp.fetch(`${WP_URL}/wp-json/wp/v2/posts/${postId}`, updateOptions);

      if (updateResponse.getResponseCode() === 200) {
        console.log(`SUCCESS: 記事ID ${postId} のカテゴリを更新しました。`);
      } else {
        console.log(`ERROR: 記事ID ${postId} の更新に失敗 (Code: ${updateResponse.getResponseCode()}): ${updateResponse.getContentText()}`);
      }
    } catch (e) {
      console.log(`CRITICAL: 記事ID ${postId} の処理中に予期せぬエラー: ${e.message}`);
    }
  }

  //終了メッセージ
  ss.toast('処理が完了しました。詳細は実行ログを確認してください。', '完了', 10);
}

関連リンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)