概要
microCMSには標準でコンテンツを一括エクスポートする機能が提供されていないため、データのバックアップにはAPIを利用した仕組みを個別に構築する必要があります。
本稿では、Google Apps Script (GAS) を用い、microCMSのコンテンツとメディア(画像など)を定期的にGoogle Driveへ自動バックアップするスクリプトについて説明します。
このスクリプトにより、以下の処理が自動化されます。
- コンテンツをスプレッドシート形式で保存
- コンテンツ内のメディアファイルをGoogle Driveのフォルダに保存
- 上記の処理をトリガーによって定期的(日次など)に実行
背景
マイクロCMSには管理画面からコンテンツをエクスポートする機能がありません。
恐らくWordPressだと使える機能かと思うので、不便に感じるかもしれません。
その代わりバックアップ取得用のAPIが提供されていますが、使うのに少々工夫が必要です。
下記工夫が必要なポイント、懸念点です。
-
APIレスポンスの整形
APIから取得したJSONデータには、インポート時には不要なタイムスタンプ(createdAtなど)が含まれます。また、画像データもオブジェクト形式({ "url": "..." })のため、URLを抽出する処理が必要です。 -
コンテンツとメディアの分離
記事などのコンテンツバックアップ用APIとメディアバックアップ用のAPIが別物なので、完全なバックアップを取得するには2種類のAPIを使った処理を一つにまとめる必要があります。本稿ではメディアバックアップ用のAPIは使わずに、コンテンツバックアップ用APIから返ってきたメディアのURLにGASから直接アクセスしてメディアを保存する方法を採用しています。 -
手動操作によるデータ損失リスク
データが入っている状態でAPIスキーマを更新すると、そのプロパティのデータが全て空になります……(筆者はこれを経験したため、バックアップをGASで定期的に取得しようと思いました)。
成果物の構成
スクリプトを実行すると、指定したGoogle Driveフォルダ内に実行日時を名称とするフォルダが作成されます。その中には、コンテンツのExcelファイルと、APIエンドポイントごとに分類された画像フォルダが格納されます。
📂 Google Drive
└─ 📂 microCMS_Backups
└─ 📂 2025-07-29_09-30-00
├─ 📂 image
│ ├─ 📂 news
│ │ ├─ 📄 image_a.png
│ │ └─ 📄 image_b.jpg
│ └─ 📂 blog
│ └─ 📄 image_c.png
└─ 📄 コンテンツバックアップ.xlsx
設定手順
1. Google Apps Script プロジェクトの作成
Google Driveから「新規」 → 「その他」 → 「Google Apps Script」を選択し、プロジェクトを新規作成します。
2. スクリプトプロパティの設定
APIキーなどの機密情報をコードに直接記述するのを避けるため、スクリプトプロパティ機能を使用します。
GASエディタの左メニューから「プロジェクトの設定」(歯車アイコン ⚙️)を開き、「スクリプト プロパティ」セクションに以下の3つのキーと値を設定してください。
| プロパティ名 | 値 | 説明 |
|---|---|---|
MICROCMS_SERVICE_DOMAIN |
your-service-name |
microCMSのサービスドメイン |
MICROCMS_API_KEY |
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx |
microCMSのAPIキー(X-MICROCMS-API-KEY) |
GOOGLE_DRIVE_FOLDER_ID |
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |
バックアップ保存先のGoogle DriveフォルダID |
Google DriveのフォルダIDは、対象フォルダを開いた際のURL
https://drive.google.com/drive/folders/ここに表示されるIDの末尾部分です。
3. スクリプトの貼り付け
エディタの既存コード(function myFunction() { ... })をすべて削除し、以下のスクリプトを貼り付けます。
/**
* スクリプトプロパティから設定情報を読み込みます。
*/
const SCRIPT_PROPS = PropertiesService.getScriptProperties();
const MICROCMS_SERVICE_DOMAIN = SCRIPT_PROPS.getProperty('MICROCMS_SERVICE_DOMAIN');
const MICROCMS_API_KEY = SCRIPT_PROPS.getProperty('MICROCMS_API_KEY');
const GOOGLE_DRIVE_FOLDER_ID = SCRIPT_PROPS.getProperty('GOOGLE_DRIVE_FOLDER_ID');
/**
* バックアップしたいAPIのエンドポイントを配列で指定します。
* 例: ['news', 'blogs', 'categories']
*/
const API_ENDPOINTS = ['news', 'blogs', 'categories'];
/**
* 実行中にダウンロードしたメディアのURLを記録し、重複ダウンロードを防ぐためのセット
*/
const downloadedUrls = new Set();
/**
* メインの処理を実行する関数
*/
function createBackup() {
if (!MICROCMS_SERVICE_DOMAIN || !MICROCMS_API_KEY || !GOOGLE_DRIVE_FOLDER_ID) {
console.error('スクリプトプロパティが設定されていません。プロジェクト設定を確認してください。');
return;
}
// 1. フォルダ構造の準備
const parentFolder = DriveApp.getFolderById(GOOGLE_DRIVE_FOLDER_ID);
const timestamp = Utilities.formatDate(new Date(), 'JST', 'yyyy-MM-dd_HH-mm-ss');
const mainFolder = parentFolder.createFolder(timestamp);
const imageFolder = mainFolder.createFolder('image');
console.log(`メインフォルダを作成しました: ${timestamp}`);
// 2. スプレッドシートの準備
const spreadsheet = SpreadsheetApp.create('コンテンツバックアップ');
const tempFile = DriveApp.getFileById(spreadsheet.getId());
try {
// 3. APIごとのループ処理
API_ENDPOINTS.forEach((endpoint, index) => {
console.log(`[${endpoint}] の処理を開始します...`);
const endpointImageFolder = imageFolder.createFolder(endpoint);
const allContents = fetchAllMicroCMSContents_(endpoint);
if (allContents && allContents.length > 0) {
allContents.forEach(content => {
findAndDownloadMedia_(content, endpointImageFolder);
});
const sheet = (index === 0)
? spreadsheet.getSheets()[0].setName(endpoint)
: spreadsheet.insertSheet(endpoint);
populateSheet_(sheet, allContents);
console.log(` [${endpoint}] のシート書き込みが完了しました。(${allContents.length}件)`);
} else {
const sheet = (index === 0)
? spreadsheet.getSheets()[0].setName(endpoint)
: spreadsheet.insertSheet(endpoint);
sheet.getRange(1, 1).setValue('コンテンツはありませんでした。');
console.log(` [${endpoint}] にはコンテンツがありませんでした。`);
}
});
SpreadsheetApp.flush();
// スプレッドシートをExcel形式で保存
tempFile.moveTo(mainFolder);
saveAsExcel_(spreadsheet, mainFolder);
console.log(`Excelファイルの保存が完了しました。`);
} catch (e) {
console.error(`エラーが発生しました: ${e.stack}`);
} finally {
// 一時スプレッドシートを削除
tempFile.setTrashed(true);
console.log(`一時スプレッドシートを削除しました。`);
}
}
/**
* コンテンツデータの中からメディアオブジェクトを再帰的に探し、ファイルをダウンロードする関数
* @param {Object|Array} data - 検索対象のコンテンツデータ(オブジェクトまたは配列)
* @param {GoogleAppsScript.Drive.Folder} targetFolder - メディアの保存先フォルダ
*/
function findAndDownloadMedia_(data, targetFolder) {
if (Array.isArray(data)) {
data.forEach(item => findAndDownloadMedia_(item, targetFolder));
return;
}
if (typeof data === 'object' && data !== null) {
if (data.url && data.height !== undefined && data.width !== undefined) {
if (!downloadedUrls.has(data.url)) {
try {
const response = UrlFetchApp.fetch(data.url);
const blob = response.getBlob();
const fileName = data.url.substring(data.url.lastIndexOf('/') + 1).split('?')[0];
targetFolder.createFile(blob.setName(fileName));
downloadedUrls.add(data.url);
console.log(` 画像 ${fileName} を [${targetFolder.getName()}] に保存しました。`);
} catch (err) {
console.error(` 画像のダウンロードに失敗しました: ${data.url}, エラー: ${err.message}`);
}
}
} else {
Object.values(data).forEach(value => findAndDownloadMedia_(value, targetFolder));
}
}
}
/**
* スプレッドシートにデータを書き込む関数
* @param {GoogleAppsScript.Spreadsheet.Sheet} sheet - 書き込み対象のシート
* @param {Array<Object>} contents - 書き込むコンテンツデータ
*/
function populateSheet_(sheet, contents) {
const excludedColumns = ['createdAt', 'updatedAt', 'publishedAt', 'revisedAt'];
const allHeaders = Object.keys(contents[0]);
const headers = allHeaders.filter(header => !excludedColumns.includes(header));
const rows = contents.map(content => {
return headers.map(header => {
const value = content[header];
if (typeof value === 'object' && value !== null) {
if (value.url && value.height !== undefined && value.width !== undefined) {
return value.url;
}
return JSON.stringify(value);
}
return value;
});
});
sheet.getRange(1, 1, 1, headers.length).setValues([headers]);
sheet.getRange(2, 1, rows.length, headers.length).setValues(rows);
}
/**
* スプレッドシートをExcelファイル(.xlsx)として指定のフォルダに保存する関数
* @param {GoogleAppsScript.Spreadsheet.Spreadsheet} spreadsheet - 保存対象のスプレッドシート
* @param {GoogleAppsScript.Drive.Folder} targetFolder - 保存先のフォルダ
*/
function saveAsExcel_(spreadsheet, targetFolder) {
const url = `https://www.googleapis.com/drive/v3/files/${spreadsheet.getId()}/export?mimeType=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`;
const options = {
method: 'get',
headers: { Authorization: 'Bearer ' + ScriptApp.getOAuthToken() },
muteHttpExceptions: true
};
const blob = UrlFetchApp.fetch(url, options).getBlob();
blob.setName(`${spreadsheet.getName()}.xlsx`);
targetFolder.createFile(blob);
}
/**
* microCMSから全件データを取得する関数(ページネーション対応)
* @param {string} endpoint - APIのエンドポイント名
* @returns {Array<Object>} 全コンテンツデータ
*/
function fetchAllMicroCMSContents_(endpoint) {
const allContents = [];
let offset = 0;
const limit = 50;
while (true) {
const url = `https://${MICROCMS_SERVICE_DOMAIN}.microcms.io/api/v1/${endpoint}?limit=${limit}&offset=${offset}`;
const options = {
method: 'get',
headers: { 'X-MICROCMS-API-KEY': MICROCMS_API_KEY },
muteHttpExceptions: true
};
const response = UrlFetchApp.fetch(url, options);
const responseCode = response.getResponseCode();
if (responseCode !== 200) {
console.error(`APIリクエストに失敗 (${endpoint}): Status ${responseCode}, Body: ${response.getContentText()}`);
throw new Error(`APIリクエストに失敗しました(${endpoint})。`);
}
const json = JSON.parse(response.getContentText());
if (json.contents.length === 0) {
break;
}
allContents.push(...json.contents);
offset += json.contents.length;
if (offset >= json.totalCount) {
break;
}
}
return allContents;
}
4. バックアップ対象APIの指定
スクリプト上部の API_ENDPOINTS 配列を、自身のmicroCMS環境に合わせて編集します。
/**
* バックアップしたいAPIのエンドポイントを配列で指定します。
* 例: ['news', 'blogs', 'categories']
*/
const API_ENDPOINTS = ['news', 'blog'];
実行と自動化設定
定期実行(トリガー設定)
処理を自動化するためにトリガーを設定します。
- GASエディタの左メニューから「トリガー」(時計アイコン ⏰)を開きます。
- 「トリガーを追加」をクリックし、以下のように設定して保存します。
-
実行する関数を選択:
createBackup -
イベントのソースを選択:
時間主導型 -
時間ベースのトリガーのタイプを選択:
日タイマーなど任意の頻度 - 時刻を選択: 実行したい時間帯
-
実行する関数を選択:
これで設定したスケジュールに従い、バックアップが自動的に実行されます。
最後に
microCMSとの契約内容にバックアップの取得&提供も含まれている?ようですが、開発中にパパッとバックアップが必要になることもあると思うので、設定していて損はないと思います。
gasには6分の実行時間制限があるので、データが大量にある場合はエンドポイント毎に実行するなどの対応が必要かもしれません。