web-dev-qa-db-ja.com

Node.jsとAmazon S3:バケット内のすべてのファイルを反復処理する方法は?

S3バケット内のすべてのファイルのリストを許可するNode.js用のAmazon S3クライアントライブラリはありますか?

最も知られている aws2js および knox は、この機能を備えていないようです。

42
nab

実際、 aws2js は、s3.get()メソッド呼び出しを介して、低レベルのバケット内のオブジェクトのリストをサポートします。そのためには、 Amazon S3 REST APIページ :に記載されているprefixパラメーターを渡す必要があります。

var s3 = require('aws2js').load('s3', awsAccessKeyId, awsSecretAccessKey);    
s3.setBucket(bucketName);

var folder = encodeURI('some/path/to/S3/folder');
var url = '?prefix=' + folder;

s3.get(url, 'xml', function (error, data) {
    console.log(error);
    console.log(data);
});

上記のスニペットのdata変数には、bucketNameバケット内のすべてのオブジェクトのリストが含まれています。

8
nab

公式の aws-sdk を使用:

_var allKeys = [];
function listAllKeys(marker, cb)
{
  s3.listObjects({Bucket: s3bucket, Marker: marker}, function(err, data){
    allKeys.Push(data.Contents);

    if(data.IsTruncated)
      listAllKeys(data.NextMarker, cb);
    else
      cb();
  });
}
_

s3.listObjects を参照してください

Edit 2017:基本的な考え方は同じですが、listObjectsV2( ... )が推奨され、ContinuationTokenを使用します( s3.listObjectsV2 ):

_var allKeys = [];
function listAllKeys(token, cb)
{
  var opts = { Bucket: s3bucket };
  if(token) opts.ContinuationToken = token;

  s3.listObjectsV2(opts, function(err, data){
    allKeys = allKeys.concat(data.Contents);

    if(data.IsTruncated)
      listAllKeys(data.NextContinuationToken, cb);
    else
      cb();
  });
}
_
60
Meekohi

Node切り捨てられたリストからS3オブジェクトを組み立てるために書いたコードです。

var params = {
    Bucket: <yourbucket>,
    Prefix: <yourprefix>,
};

var s3DataContents = [];    // Single array of all combined S3 data.Contents

function s3Print() {
    if (program.al) {
        // --al: Print all objects
        console.log(JSON.stringify(s3DataContents, null, "    "));
    } else {
        // --b: Print key only, otherwise also print index 
        var i;
        for (i = 0; i < s3DataContents.length; i++) {
            var head = !program.b ? (i+1) + ': ' : '';
            console.log(head + s3DataContents[i].Key);
        }
    }
}

function s3ListObjects(params, cb) {
    s3.listObjects(params, function(err, data) {
        if (err) {
            console.log("listS3Objects Error:", err);
        } else {
            var contents = data.Contents;
            s3DataContents = s3DataContents.concat(contents);
            if (data.IsTruncated) {
                // Set Marker to last returned key
                params.Marker = contents[contents.length-1].Key;
                s3ListObjects(params, cb);
            } else {
                cb();
            }
        }
    });
}

s3ListObjects(params, s3Print);

listObject's NextMarkerのドキュメントに注意してください。これは[〜#〜] not [〜#〜]に常に存在します返されたデータオブジェクトなので、上記のコードではまったく使用しません...

NextMarker —(文字列)応答が切り捨てられる場合(IsTruncated応答の要素値がtrue) 、このフィールドのキー名を後続のリクエストのマーカーとして使用して、次のオブジェクトのセットを取得できます。 Amazon S3はオブジェクトをアルファベット順にリストします注:この要素は、デリミタリクエストパラメータが指定されている場合にのみ返されます。応答にNextMarkerが含まれておらず、切り捨てられている場合、応答の最後のキーの値を後続の要求のマーカーとして使用して、オブジェクトキーの次のセット

プログラム全体が https://github.com/kenklin/s3list にプッシュされました。

15
Ken Lin

公開されたknox-copy良い既存のソリューションが見つからなかったとき。 Rest APIのすべてのページネーションの詳細を使い慣れたノードストリームにラップします。

var knoxCopy = require('knox-copy');

var client = knoxCopy.createClient({
  key: '<api-key-here>',
  secret: '<secret-here>',
  bucket: 'mrbucket'
});

client.streamKeys({
  // omit the prefix to list the whole bucket
  prefix: 'buckets/of/fun' 
}).on('data', function(key) {
  console.log(key);
});

1000個未満のファイルをリストしている場合、単一のページが機能します。

client.listPageOfKeys({
  prefix: 'smaller/bucket/o/fun'
}, function(err, page) {
  console.log(page.Contents); // <- Here's your list of files
});
5
hurrymaplelad

Meekohiは非常に良い答えを提供しましたが、(新しい)ドキュメントにはNextMarkerを未定義にすることができると記載されています。この場合、最後のキーをマーカーとして使用する必要があります。

したがって、彼のコードサンプルは次のように変更できます。

var allKeys = [];
function listAllKeys(marker, cb) {
  s3.listObjects({Bucket: s3bucket, Marker: marker}, function(err, data){
    allKeys.Push(data.Contents);
    if(data.IsTruncated)
      listAllKeys(data.NextMarker || data.Contents[data.Contents.length-1].Key, cb);
    else
      cb();
  });
}

必要な評判がないため、元の回答についてコメントできませんでした。悪いマークアップをおApびします。

3
Thijs Lowette

これは古い質問であり、AWS JS SDKは尋ねられてから大きく変わったと思います。最近の別の方法は次のとおりです。

s3.listObjects({Bucket:'mybucket', Prefix:'some-pfx'}).
on('success', function handlePage(r) {
    //... handle page of contents r.data.Contents

    if(r.hasNextPage()) {
        // There's another page; handle it
        r.nextPage().on('success', handlePage).send();
    } else {
        // Finished!
    }
}).
on('error', function(r) {
    // Error!
}).
send();
2
logidelic

最終的にListObjectsV2のラッパー関数を作成し、同じ方法で同じパラメーターを使用しますが、IsTruncated = falseになるまで再帰的に動作し、コールバック関数の2番目のパラメーターで配列として見つかったすべてのキーを返します

const AWS = require('aws-sdk')
const s3 = new AWS.S3()

function listAllKeys(params, cb)
{
   var keys = []
   if(params.data){
      keys = keys.concat(params.data)
   }
   delete params['data']

   s3.listObjectsV2(params, function(err, data){
     if(err){
       cb(err)
     } else if (data.IsTruncated) {
       params['ContinuationToken'] = data.NextContinuationToken
       params['data'] = data.Contents
       listAllKeys(params, cb)
     } else {
       keys = keys.concat(data.Contents)
       cb(null,keys)
     }
   })
}
1

S3バケット内の特定のフォルダー内でのみキーのリストを取得する場合、これは便利です。

基本的に、listObjects関数は設定したMarkerから検索を開始し、制限としてmaxKeys: 1000まで検索します。そのため、フォルダを1つずつ検索し、バケット内の異なるフォルダから最初に1000個のキーを取得します。

prod/some date/, Ex: prod/2017/05/12/ ,prod/2017/05/13/,etcのプレフィックスが付いたバケット内に多くのフォルダーがあると考えてください。

prod/2017/05/12/フォルダー内でのみオブジェクト(ファイル名)のリストを取得したい場合、prod/2017/05/12/を開始として、prod/2017/05/13/ [次のフォルダー名]を終了として、コードiで指定します私は終わりに遭遇したときにループを壊しています。

Keyin data.Contentsは次のようになります。

{      Key: 'prod/2017/05/13/4bf2c675-a417-4c1f-a0b4-22fc45f99207.jpg',
       LastModified: 2017-05-13T00:59:02.000Z,
       ETag: '"630b2sdfsdfs49ef392bcc16c833004f94ae850"',
       Size: 134236366,
       StorageClass: 'STANDARD',
       Owner: { } 
 }

コード:

var list = [];

function listAllKeys(s3bucket, start, end) {
  s3.listObjects({
    Bucket: s3bucket,
    Marker: start,
    MaxKeys: 1000,
  }, function(err, data) {
      if (data.Contents) {
        for (var i = 0; i < data.Contents.length; i++) {
         var key = data.Contents[i].Key;    //See above code for the structure of data.Contents
          if (key.substring(0, 19) != end) {
             list.Push(key);
          } else {
             break;   // break the loop if end arrived
          }
       }
        console.log(list);
        console.log('Total - ', list.length);      
     }
   });
 }

listAllKeys('BucketName', 'prod/2017/05/12/', 'prod/2017/05/13/');

出力:

[ 'prod/2017/05/12/05/4bf2c675-a417-4c1f-a0b4-22fc45f99207.jpg',
  'prod/2017/05/12/05/a36528b9-e071-4b83-a7e6-9b32d6bce6d8.jpg',
  'prod/2017/05/12/05/bc4d6d4b-4455-48b3-a548-7a714c489060.jpg',
  'prod/2017/05/12/05/f4b8d599-80d0-46fa-a996-e73b8fd0cd6d.jpg',
  ... 689 more items ]
Total - 692
1
Prasanth Jaya

非同期ジェネレーターの使用

const { S3 } = require('aws-sdk');
const s3 = new S3();

async function* listAllKeys(opts) {
    do {
        const data = await s3.listObjectsV2(opts).promise();
        opts.ContinuationToken = data.NextContinuationToken;
        yield data;
    } while (opts.ContinuationToken)
}

const opts = {
    Bucket: 'bucket-xyz',
    /* required */
    // ContinuationToken: 'STRING_VALUE',
    // Delimiter: 'STRING_VALUE',
    // EncodingType: url,
    // FetchOwner: true || false,
    // MaxKeys: 'NUMBER_VALUE',
    // Prefix: 'STRING_VALUE',
    // RequestPayer: requester,
    // StartAfter: 'STRING_VALUE'
};

async function main() {
    // using for of await loop
    for await (const data of listAllKeys(opts)) {
        console.log(data.Contents)
    }

    // or lazy-load
    const keys = listAllKeys(opts);
    console.log(await keys.next());
    // {value: {…}, done: false}
    console.log(await keys.next());
    // {value: {…}, done: false}
    console.log(await keys.next());
    // {value: undefined, done: true}
}
main();

// Making Observable

const lister = opts => o => {
    let needMore = true;
    (async () => {
        const keys = listAllKeys(opts);
        for await (const data of keys) {
            if (data.done) break;
            o.next(data);
            if (!needMore) break;
        }
        o.complete();
    })();
    return () => (needMore = false);
}

// Using Rxjs

const { Observable } = require('rxjs');
const { flatMap } = require('rxjs/operators')

function listAll() {
    return Observable.create(lister(opts))
        .pipe(flatMap(v => v.Contents))
        .subscribe(console.log);
}

listAll();


// Using Nodejs EventEmitter

const EventEmitter = require('events');

const _eve = new EventEmitter();
_eve.on('next', console.log);

const stop = lister(opts)({
    next: v => _eve.emit('next', v),
    error: e => _eve.emit('error', e),
    complete: v => _eve.emit('complete', v)
});
0
nkitku