Cloudinary のアセット一覧をPythonでCSV出力してみた

Cloudinary API を使ってアセット一覧を取得し、JSONの結果をCSVファイルに出力する方法を紹介します。
2020.09.04

Guten Tag, ベルリンより伊藤です。

Cloudinaryの画像一覧やURL一覧をCSVで出力できないか?という問いについて、要望はチラホラあるようですが、現時点でコンソールやAPIでそのまま取得する機能は提供されていません。

Admin API の Get Resources で JSON 形式で一覧を取得することができるので、簡単なスクリプトを書いてCSV出力をやってみました。

◆ 参考:Cloudinary: How do I browse through all the resources in my account using the API?

Python の場合

Pythonで実装しました。Cloudinary API のクレデンシャル設定など基本的なところは過去のブログをご参考ください。

画像一覧を取得する

Get Resources の概要は次の通り:

  • 何もオプションを指定しないと、画像アセットのみを最大10件出力
  • resource_type オプションは image/video/raw から指定(デフォルトは image)
  • max_results オプションで、1リクエストで最大500件まで結果を取得するよう指定できる(デフォルトは10件)
  • 結果が最大件数を超える場合、JSONのレスポンスに next_cursor という次のページのIDのような値が含まれる

注記 本稿の例では、配信タイプが upload/private/authenticated のいずれかのみの環境で検証しています。fetch、facebook、text 等ではレスポンスに本稿の記載とは異なるキーを持つ可能性があり、それらが混在すると正しく結果取得できない場合があります。(2020年9月10日 追記)

ここでは、結果にこの next_cursor のキーが含まれる限り処理を繰り返すとしています。

export_image_list.py

# -*- coding:utf-8 -*-
import cloudinary
import pandas as pd

cloudinary.config(
  cloud_name = 'クラウド名', 
  api_key = 'APIキー',
  api_secret = 'シークレット'
)

res = cloudinary.api.resources(max_results = 500)
df = pd.json_normalize(res['resources'])
df.to_csv('./export.csv', index=False)

while 'next_cursor' in res:
  res = cloudinary.api.resources(max_results = 500, next_cursor = res['next_cursor'])
  df = pd.json_normalize(res['resources'])
  df.to_csv('./export.csv', mode='a', header=False, index=False)

こんな感じで出力することができました。

asset_id,public_id,format,version,resource_type,type,created_at,bytes,width,height,access_mode,url,secure_url
b409166171ae281e6b9476fb703dc8e9,test/stairs,jpg,1598530919,image,upload,2020-08-27T12:21:59Z,493498,1529,2040,authenticated,http://res.cloudinary.com/CLOUD_NAME/image/upload/s--wvi-tlEu--/v1598530919/test/stairs.jpg,https://res.cloudinary.com/CLOUD_NAME/image/upload/s--wvi-tlEu--/v1598530919/test/stairs.jpg
24ffd9776a737b04222d7301cfc29972,items/upa6it2sfs7sanb4sx0n,jpg,1598283014,image,upload,2020-08-24T15:30:14Z,54205,1100,1100,public,http://res.cloudinary.com/CLOUD_NAME/image/upload/v1598283014/items/upa6it2sfs7sanb4sx0n.jpg,https://res.cloudinary.com/CLOUD_NAME/image/upload/v1598283014/items/upa6it2sfs7sanb4sx0n.jpg

すべてのアセット一覧を取得する

[2020年9月10日 更新] upload/private/authenticated で、画像と動画とそれ以外のアセットをまとめて出力する方法を追記しました。

画像と動画のレスポンスは同様の値を持ちますが、raw(画像・動画以外)のレスポンスには 'format', 'width', 'height' の列がありません。以下、rawのみの出力例です。

asset_id,public_id,version,resource_type,type,created_at,bytes,access_mode,url,secure_url
5e8156b10301d93ce4556218d27dccf8,caption.vtt,1575568128,raw,upload,2019-12-05T17:48:48Z,92,public,http://res.cloudinary.com/CLOUD_NAME/raw/upload/v1575568128/caption.vtt,https://res.cloudinary.com/CLOUD_NAME/raw/upload/v1575568128/caption.vtt

raw の場合、不足するこれらの列には値に "-" が入るようにし、すべて(画像、動画、それ以外)のアセット一覧を一つのファイルに出力する処理を以下で行ないます。

また、ここでは配信タイプに明示的に upload/private/authenticated を指定しており、fetch 等の他の配信タイプのアセットは取得されません。

export_all_list.py

# ここでimportとクレデンシャル
re_types = ['image', 'video', 'raw']
dlv_types = ['upload', 'private', 'authenticated']
output_path = './export.csv'
num = 0

def get_resources(num, re_type, dlv_type):
    res = cloudinary.api.resources(
        resource_type = re_type, type = dlv_type, max_results = 500)
    if num == 0: # 初回だけヘッダありで新規出力/上書き
        write_new_csv(res)
    else:        # 2回目以降はヘッダなしで追記
        write_add_csv(res, re_type)

    while 'next_cursor' in res:
        res = cloudinary.api.resources(
            resource_type = re_type, type = dlv_type, max_results = 500, next_cursor = res['next_cursor'])
        write_add_csv(res, re_type)

def write_new_csv(res):
    df = pd.json_normalize(res['resources'])
    df.to_csv(output_path, index=False)

def write_add_csv(res, re_type):
    df = pd.json_normalize(res['resources'])
    if re_type == re_types[2] and not df.empty:  # rawで、かつ値がある場合
        df.insert(2, 'format', '-')
        df.insert(8, 'width', '-')
        df.insert(9, 'height', '-')
    df.to_csv(output_path, mode='a', header=False, index=False)

for re_type in re_types:
    for dlv_type in dlv_types:
        get_resources(num, re_type, dlv_type)
        num += 1

参考

Node.js の場合

REPL で画像一覧を取得する

冒頭の Cloudinary の記事内コメントを参考に Node.js も簡単に試してみました。こちらはより触り慣れていないので対話型での実装で、非同期処理までは動作確認できていません。。

冒頭のクレデンシャル等は過去のブログを参照してください。

var result = [];
var options = { max_results: 500, resource_type: 'image' };

function listResources(next_cursor) {
  if(next_cursor) {
    options["next_cursor"] = next_cursor; // next_cursorがあればオプションに加える
  }
  cloudinary.api.resources(options, function(error, res){
    resources = res.resources;
    result = result.concat(resources);
    var more = res.next_cursor;
    if (more) {  // next_cursorがあれば指定して繰り返す、なければundefinedで終了
      listResources(more);
    }
  });
}

listResources();
const createCsvWriter = require('csv-writer').createObjectCsvWriter;
const csvWriter = createCsvWriter({
  path: './output.csv',
  header: [
    // 必要な列を記述していく
    {id: 'public_id', title: 'public_id'},
    {id: 'format', title: 'format'},
    {id: 'type', title: 'type'},
    {id: 'created_at', title: 'created_at'},
    {id: 'bytes', title: 'bytes'},
    {id: 'width', title: 'width'},
    {id: 'height', title: 'height'},
    {id: 'secure_url', title: 'secure_url'}]
});

csvWriter.writeRecords(result).then(() => {
    console.log('done');
  });

出力例は以下の通りです。

public_id,format,type,created_at,bytes,width,height,secure_url
test/stairs,jpg,upload,2020-08-27T12:21:59Z,493498,1529,2040,https://res.cloudinary.com/CLOUD_NAME/image/upload/s--wvi-tlEu--/v1598530919/test/stairs.jpg
items/upa6it2sfs7sanb4sx0n,jpg,upload,2020-08-24T15:30:14Z,54205,1100,1100,https://res.cloudinary.com/CLOUD_NAME/image/upload/v1598283014/items/upa6it2sfs7sanb4sx0n.jpg

スクリプトファイルで画像一覧を取得する

[2020年9月8日 更新] 非同期処理で動作確認できたので、追記しました。

非同期処理というものを勉強して実装させたのがこちらです。

export_csv_list.js

const cloudinary = require('cloudinary').v2;
const createCsvWriter = require('csv-writer').createObjectCsvWriter;

cloudinary.config({ 
  cloud_name: 'クラウド名', 
  api_key: 'APIキー', 
  api_secret: 'シークレット'
});

var result = [];
var options = { max_results: 500, resource_type: 'image' };

const listResources = (next_cursor) => {
  return new Promise((resolve) => {
    if(next_cursor) {
      options["next_cursor"] = next_cursor;
    }
    cloudinary.api.resources(options, function(error, res){
      resources = res.resources;
      result = result.concat(resources);
      var more = res.next_cursor;
      if (more) {
        listResources(more).then(() => {
          resolve(result);
        });
      } else {
        resolve();
      }
    })
  })
}

const csvWriter = createCsvWriter({
  path: './output.csv',
  header: [
    // 必要な列を記述(※前項と同様のため抜粋)
    {id: 'public_id', title: 'public_id'}
    ]
});

listResources().then((result) => {
  csvWriter.writeRecords(result);
}).then(() => {
  console.log('done');
}).catch((e) => {
  console.log('err: '+e);
})

参考

後半のCSV出力部分は1つ目の参考サイト様のコピペでございます。非同期処理での再帰呼び出しは2つ目のサイト様をかなり参考にさせていただきました。ありがとうございます。

注意点

Admin API はプランごとに定められたリクエストレート上限があります。無料試用アカウントなら1時間あたり500リクエストです。アカウントの各種上限値は、設定画面の右パネルから確認できます。


クラスメソッドはCloudinaryのパートナーとして導入のお手伝いをさせていただいています。お気軽にこちらからお問い合わせください。こちらから無料でもサインアップいただけます。