การดึงชื่อโฟลเดอร์ย่อยในบัคเก็ต S3 จาก boto3


94

เมื่อใช้ boto3 ฉันสามารถเข้าถึงที่เก็บข้อมูล AWS S3 ของฉัน:

s3 = boto3.resource('s3')
bucket = s3.Bucket('my-bucket-name')

ตอนนี้ถังประกอบด้วยโฟลเดอร์ที่ตัวเองมีหลายโฟลเดอร์ย่อยชื่อด้วยการประทับเวลาตัวอย่างเช่นfirst-level 1456753904534ฉันต้องการทราบชื่อของโฟลเดอร์ย่อยเหล่านี้สำหรับงานอื่นที่ฉันกำลังทำอยู่และฉันสงสัยว่าจะให้ boto3 ดึงข้อมูลเหล่านั้นมาให้ฉันได้หรือไม่

ฉันจึงลอง:

objs = bucket.meta.client.list_objects(Bucket='my-bucket-name')

ซึ่งให้พจนานุกรมซึ่งคีย์ 'เนื้อหา' ให้ไฟล์ระดับที่สามทั้งหมดแทนไดเร็กทอรีการประทับเวลาระดับที่สองในความเป็นจริงฉันได้รับรายการที่มีสิ่งต่างๆเป็น

{u'ETag ':' "etag" ', u'Key': ระดับแรก / 1456753904534 / part-00014 ', u'LastModified': datetime.datetime (2016, 2, 29, 13, 52, 24, tzinfo = tzutc ()),
u'Owner ': {u'DisplayName': 'owner', u'ID ':' id '},
u'Size': size, u'StorageClass ':' storageclass '}

คุณจะเห็นว่าไฟล์เฉพาะในกรณีนี้part-00014ถูกดึงออกมาในขณะที่ฉันต้องการรับชื่อไดเร็กทอรีเพียงอย่างเดียว โดยหลักการแล้วฉันสามารถตัดชื่อไดเร็กทอรีออกจากทุกเส้นทางได้ แต่มันน่าเกลียดและมีราคาแพงที่จะดึงทุกอย่างในระดับที่สามเพื่อให้ได้ระดับที่สอง!

ฉันได้ลองรายงานที่นี่ด้วย :

for o in bucket.objects.filter(Delimiter='/'):
    print(o.key)

แต่ฉันไม่ได้รับโฟลเดอร์ในระดับที่ต้องการ

มีวิธีแก้ปัญหานี้หรือไม่?


คุณกำลังบอกว่ามันใช้ไม่ได้เหรอ? คุณช่วยโพสต์สิ่งที่เกิดขึ้นเมื่อคุณเรียกใช้งานได้ไหม
Jordon Phillips

1
@JordonPhillips ฉันได้ลองบรรทัดแรกของลิงก์ที่คุณส่งซึ่งฉันวางที่นี่และฉันได้รับไฟล์ข้อความในระดับแรกสุดของที่เก็บข้อมูลและไม่มีโฟลเดอร์
ดีบุก Mar

@mar tin คุณเคยแก้ไขปัญหานี้หรือไม่ ฉันกำลังเผชิญกับภาวะที่กลืนไม่เข้าคายไม่ออกคล้าย ๆ กันซึ่งฉันต้องการองค์ประกอบแรกในทุกโฟลเดอร์ย่อยที่เก็บข้อมูล
Ted Taylor of Life

1
@TedTaylorofLife ใช่ไม่มีวิธีอื่นนอกจากการรับวัตถุทั้งหมดและแยกโดย/เพื่อให้ได้โฟลเดอร์ย่อย
Mar Tin

1
@ mar tin วิธีเดียวที่ฉันทำคือเอาเอาต์พุตโยนลงในรูปแบบข้อความและคั่นด้วยเครื่องหมายจุลภาคด้วย "/" จากนั้นคัดลอกและวางองค์ประกอบแรก เจ็บตูดอะไรอย่างนี้
Ted Taylor of Life

คำตอบ:


65

S3 เป็นพื้นที่จัดเก็บอ็อบเจ็กต์ แต่ไม่มีโครงสร้างไดเร็กทอรีจริง "/" ค่อนข้างเป็นเครื่องสำอาง เหตุผลหนึ่งที่ผู้คนต้องการมีโครงสร้างไดเร็กทอรีเนื่องจากสามารถดูแล / ตัด / เพิ่มต้นไม้ให้กับแอปพลิเคชันได้ สำหรับ S3 คุณจะปฏิบัติตามโครงสร้างดังกล่าวเช่นการจัดเรียงดัชนีหรือแท็กการค้นหา

ในการจัดการวัตถุใน S3 คุณต้องมี boto3.client หรือ boto3.resource เช่นเพื่อแสดงรายการวัตถุทั้งหมด

import boto3 
s3 = boto3.client("s3")
all_objects = s3.list_objects(Bucket = 'bucket-name') 

http://boto3.readthedocs.org/en/latest/reference/services/s3.html#S3.Client.list_objects

ในความเป็นจริงถ้าชื่ออ็อบเจ็กต์ s3 ถูกเก็บโดยใช้ตัวคั่น '/' list_objects เวอร์ชันล่าสุด (list_objects_v2) ช่วยให้คุณสามารถ จำกัด การตอบสนองต่อคีย์ที่ขึ้นต้นด้วยคำนำหน้าที่ระบุ

ในการ จำกัด รายการให้เป็นรายการภายใต้โฟลเดอร์ย่อยบางโฟลเดอร์:

    import boto3 
    s3 = boto3.client("s3")
    response = s3.list_objects_v2(
            Bucket=BUCKET,
            Prefix ='DIR1/DIR2',
            MaxKeys=100 )

เอกสารประกอบ

อีกทางเลือกหนึ่งคือการใช้ฟังก์ชัน python os.path เพื่อแยกคำนำหน้าโฟลเดอร์ ปัญหาคือสิ่งนี้จะต้องมีการแสดงรายการวัตถุจากไดเรกทอรีที่ไม่ต้องการ

import os
s3_key = 'first-level/1456753904534/part-00014'
filename = os.path.basename(s3_key) 
foldername = os.path.dirname(s3_key)

# if you are not using conventional delimiter like '#' 
s3_key = 'first-level#1456753904534#part-00014
filename = s3_key.split("#")[-1]

คำเตือนเกี่ยวกับ boto3: boto3.resource เป็น API ระดับสูงที่ดี มีข้อดีข้อเสียในการใช้ boto3.client vs boto3.resource หากคุณพัฒนาไลบรารีที่ใช้ร่วมกันภายในการใช้ boto3.resource จะทำให้คุณมีเลเยอร์ blackbox เหนือทรัพยากรที่ใช้


2
สิ่งนี้ให้ผลลัพธ์แบบเดียวกับที่ฉันได้รับจากความพยายามในคำถามนี้ ฉันเดาว่าฉันจะต้องแก้วิธีที่ยากโดยการคว้าคีย์ทั้งหมดจากวัตถุที่ส่งคืนและแยกสตริงเพื่อรับชื่อโฟลเดอร์
ดีบุก Mar

1
@martina: งูหลามขี้เกียจแยกและรับข้อมูลสุดท้ายในรายการเช่น filename = keyname.split ("/") [- 1]
mootmoot

1
@martin directory_name = os.path.dirname(directory/path/and/filename.txt)และfile_name = os.path.basename(directory/path/and/filename.txt)
jkdev

109

ด้านล่างของโค้ดจะแสดงผลเฉพาะ 'โฟลเดอร์ย่อย' ใน 'โฟลเดอร์' จากที่เก็บข้อมูล s3

import boto3
bucket = 'my-bucket'
#Make sure you provide / in the end
prefix = 'prefix-name-with-slash/'  

client = boto3.client('s3')
result = client.list_objects(Bucket=bucket, Prefix=prefix, Delimiter='/')
for o in result.get('CommonPrefixes'):
    print 'sub folder : ', o.get('Prefix')

ดูรายละเอียดเพิ่มเติมได้ที่https://github.com/boto/boto3/issues/134


12
จะเกิดอะไรขึ้นหากฉันต้องการแสดงรายการเนื้อหาของโฟลเดอร์ย่อยเฉพาะ
azhar22k

1
@ azhar22k ฉันคิดว่าคุณสามารถเรียกใช้ฟังก์ชันซ้ำสำหรับแต่ละ 'โฟลเดอร์ย่อย'
Serban Cezar

จะเป็นอย่างไรถ้ามีคำนำหน้ามากกว่า 1,000 คำ
Kostrahb

42

คำตอบสั้น ๆ :

  • ใช้Delimiter='/'. วิธีนี้จะหลีกเลี่ยงการทำรายการที่เก็บข้อมูลซ้ำ คำตอบบางข้อผิดพลาดในที่นี้แนะนำให้ทำรายการแบบเต็มและใช้การจัดการสตริงเพื่อดึงชื่อไดเร็กทอรี สิ่งนี้อาจไร้ประสิทธิภาพอย่างมาก โปรดจำไว้ว่า S3 แทบไม่ จำกัด จำนวนออบเจ็กต์ที่ที่เก็บข้อมูลสามารถมีได้ ลองนึกดูว่าระหว่างbar/และfoo/คุณมีวัตถุเป็นล้านล้านชิ้นคุณคงรอนานมากกว่าจะได้['bar/', 'foo/']มา

  • ใช้Paginators. ด้วยเหตุผลเดียวกัน (S3 เป็นการประมาณอินฟินิตี้ของวิศวกร) คุณต้องแสดงรายการผ่านหน้าต่างๆและหลีกเลี่ยงการจัดเก็บรายชื่อทั้งหมดไว้ในหน่วยความจำ ให้พิจารณา "ลิสเตอร์" ของคุณเป็นตัววนซ้ำและจัดการสตรีมที่สร้างขึ้น

  • ใช้ไม่ได้boto3.client รุ่นดูเหมือนจะไม่จัดการที่ดีตัวเลือก ถ้าคุณมีทรัพยากรบอกว่าคุณจะได้รับลูกค้าที่สอดคล้องกันด้วย:boto3.resourceresourceDelimiterbucket = boto3.resource('s3').Bucket(name)bucket.meta.client

คำตอบยาว :

ต่อไปนี้เป็นตัววนซ้ำที่ฉันใช้สำหรับที่เก็บข้อมูลแบบธรรมดา (ไม่มีการจัดการเวอร์ชัน)

import boto3
from collections import namedtuple
from operator import attrgetter


S3Obj = namedtuple('S3Obj', ['key', 'mtime', 'size', 'ETag'])


def s3list(bucket, path, start=None, end=None, recursive=True, list_dirs=True,
           list_objs=True, limit=None):
    """
    Iterator that lists a bucket's objects under path, (optionally) starting with
    start and ending before end.

    If recursive is False, then list only the "depth=0" items (dirs and objects).

    If recursive is True, then list recursively all objects (no dirs).

    Args:
        bucket:
            a boto3.resource('s3').Bucket().
        path:
            a directory in the bucket.
        start:
            optional: start key, inclusive (may be a relative path under path, or
            absolute in the bucket)
        end:
            optional: stop key, exclusive (may be a relative path under path, or
            absolute in the bucket)
        recursive:
            optional, default True. If True, lists only objects. If False, lists
            only depth 0 "directories" and objects.
        list_dirs:
            optional, default True. Has no effect in recursive listing. On
            non-recursive listing, if False, then directories are omitted.
        list_objs:
            optional, default True. If False, then directories are omitted.
        limit:
            optional. If specified, then lists at most this many items.

    Returns:
        an iterator of S3Obj.

    Examples:
        # set up
        >>> s3 = boto3.resource('s3')
        ... bucket = s3.Bucket(name)

        # iterate through all S3 objects under some dir
        >>> for p in s3ls(bucket, 'some/dir'):
        ...     print(p)

        # iterate through up to 20 S3 objects under some dir, starting with foo_0010
        >>> for p in s3ls(bucket, 'some/dir', limit=20, start='foo_0010'):
        ...     print(p)

        # non-recursive listing under some dir:
        >>> for p in s3ls(bucket, 'some/dir', recursive=False):
        ...     print(p)

        # non-recursive listing under some dir, listing only dirs:
        >>> for p in s3ls(bucket, 'some/dir', recursive=False, list_objs=False):
        ...     print(p)
"""
    kwargs = dict()
    if start is not None:
        if not start.startswith(path):
            start = os.path.join(path, start)
        # note: need to use a string just smaller than start, because
        # the list_object API specifies that start is excluded (the first
        # result is *after* start).
        kwargs.update(Marker=__prev_str(start))
    if end is not None:
        if not end.startswith(path):
            end = os.path.join(path, end)
    if not recursive:
        kwargs.update(Delimiter='/')
        if not path.endswith('/'):
            path += '/'
    kwargs.update(Prefix=path)
    if limit is not None:
        kwargs.update(PaginationConfig={'MaxItems': limit})

    paginator = bucket.meta.client.get_paginator('list_objects')
    for resp in paginator.paginate(Bucket=bucket.name, **kwargs):
        q = []
        if 'CommonPrefixes' in resp and list_dirs:
            q = [S3Obj(f['Prefix'], None, None, None) for f in resp['CommonPrefixes']]
        if 'Contents' in resp and list_objs:
            q += [S3Obj(f['Key'], f['LastModified'], f['Size'], f['ETag']) for f in resp['Contents']]
        # note: even with sorted lists, it is faster to sort(a+b)
        # than heapq.merge(a, b) at least up to 10K elements in each list
        q = sorted(q, key=attrgetter('key'))
        if limit is not None:
            q = q[:limit]
            limit -= len(q)
        for p in q:
            if end is not None and p.key >= end:
                return
            yield p


def __prev_str(s):
    if len(s) == 0:
        return s
    s, c = s[:-1], ord(s[-1])
    if c > 0:
        s += chr(c - 1)
    s += ''.join(['\u7FFF' for _ in range(10)])
    return s

ทดสอบ :

ต่อไปนี้จะเป็นประโยชน์ในการทดสอบพฤติกรรมของและpaginator list_objectsมันสร้าง dirs และไฟล์จำนวนมาก เนื่องจากเพจมีมากถึง 1,000 รายการเราจึงใช้หลายหน้าสำหรับ dirs และไฟล์ dirsมีไดเร็กทอรีเท่านั้น (แต่ละอันมีอ็อบเจ็กต์เดียว) mixedมีส่วนผสมของ dirs และอ็อบเจ็กต์โดยมีอัตราส่วน 2 อ็อบเจกต์สำหรับแต่ละ dir (แน่นอนว่าบวกอ็อบเจ็กต์หนึ่งอันภายใต้ dir S3 เก็บอ็อบเจ็กต์เท่านั้น)

import concurrent
def genkeys(top='tmp/test', n=2000):
    for k in range(n):
        if k % 100 == 0:
            print(k)
        for name in [
            os.path.join(top, 'dirs', f'{k:04d}_dir', 'foo'),
            os.path.join(top, 'mixed', f'{k:04d}_dir', 'foo'),
            os.path.join(top, 'mixed', f'{k:04d}_foo_a'),
            os.path.join(top, 'mixed', f'{k:04d}_foo_b'),
        ]:
            yield name


with concurrent.futures.ThreadPoolExecutor(max_workers=32) as executor:
    executor.map(lambda name: bucket.put_object(Key=name, Body='hi\n'.encode()), genkeys())

โครงสร้างผลลัพธ์คือ:

./dirs/0000_dir/foo
./dirs/0001_dir/foo
./dirs/0002_dir/foo
...
./dirs/1999_dir/foo
./mixed/0000_dir/foo
./mixed/0000_foo_a
./mixed/0000_foo_b
./mixed/0001_dir/foo
./mixed/0001_foo_a
./mixed/0001_foo_b
./mixed/0002_dir/foo
./mixed/0002_foo_a
./mixed/0002_foo_b
...
./mixed/1999_dir/foo
./mixed/1999_foo_a
./mixed/1999_foo_b

ด้วยการศึกษารหัสที่ให้ไว้ข้างต้นเล็กน้อยs3listเพื่อตรวจสอบการตอบสนองจากสิ่งpaginatorนี้คุณสามารถสังเกตข้อเท็จจริงที่น่าสนใจ:

  • Markerเป็นพิเศษจริงๆ ป.ร. ให้ไว้Marker=topdir + 'mixed/0500_foo_a'จะทำให้การเริ่มต้นรายการหลังจากที่สำคัญที่ (ตามAmazonS3 API ) .../mixed/0500_foo_bคือมี __prev_str()นั่นเป็นเหตุผลที่

  • การใช้Delimiterเมื่อแสดงรายการmixed/แต่ละคำตอบจากpaginatorคีย์ประกอบด้วย 666 คีย์และคำนำหน้าทั่วไป 334 รายการ ค่อนข้างดีที่จะไม่สร้างคำตอบมากมาย

  • ในทางตรงกันข้ามเมื่อแสดงรายการdirs/แต่ละคำตอบจากpaginatorคำนำหน้าทั่วไป 1,000 คำ (และไม่มีคีย์)

  • ผ่านขีด จำกัด ในรูปแบบของการPaginationConfig={'MaxItems': limit}จำกัด จำนวนคีย์เท่านั้นไม่ใช่คำนำหน้าทั่วไป เราจัดการกับสิ่งนั้นโดยตัดทอนสตรีมของตัววนซ้ำของเราเพิ่มเติม


@ Mehdi: มันไม่ซับซ้อนมากสำหรับระบบที่มีขนาดและความน่าเชื่อถือที่ไม่น่าเชื่อ หากคุณเคยจัดการกับวัณโรคมากกว่าสองสามร้อยตัวคุณจะได้รับความชื่นชมจากสิ่งที่พวกเขานำเสนอ โปรดจำไว้ว่าไดรฟ์มีค่า MTBF> 0 เสมอ ... ลองนึกถึงความหมายของการจัดเก็บข้อมูลขนาดใหญ่ ข้อจำกัดความรับผิดชอบ: ฉันเป็นผู้ใช้ AWS ที่กระตือรือร้นและมีความสุขไม่มีการเชื่อมต่ออื่น ๆ ยกเว้นฉันทำงานกับข้อมูลขนาดเพตะไบต์มาตั้งแต่ปี 2550 และมันเคยยากกว่านี้มาก
Pierre D

40

ฉันใช้เวลาพอสมควรในการคิดออก แต่สุดท้ายนี่เป็นวิธีง่ายๆในการแสดงรายการเนื้อหาของโฟลเดอร์ย่อยในถัง S3 โดยใช้ boto3 หวังว่าจะช่วยได้

prefix = "folderone/foldertwo/"
s3 = boto3.resource('s3')
bucket = s3.Bucket(name="bucket_name_here")
FilesNotFound = True
for obj in bucket.objects.filter(Prefix=prefix):
     print('{0}:{1}'.format(bucket.name, obj.key))
     FilesNotFound = False
if FilesNotFound:
     print("ALERT", "No file in {0}/{1}".format(bucket, prefix))

3
จะเกิดอะไรขึ้นถ้าโฟลเดอร์ของคุณมีวัตถุจำนวนมหาศาล?
Pierre D

3
ประเด็นของฉันคือนี่เป็นวิธีแก้ปัญหาที่ไม่มีประสิทธิภาพอย่างมาก S3 สร้างขึ้นเพื่อจัดการกับตัวคั่นตามอำเภอใจในคีย์ ตัวอย่างเช่น'/'. ให้คุณข้าม "โฟลเดอร์" ที่เต็มไปด้วยวัตถุโดยไม่ต้องใส่เลขหน้า จากนั้นแม้ว่าคุณจะยืนยันในรายชื่อแบบเต็ม (เช่นค่าเทียบเท่า 'เรียกซ้ำ' ใน aws cli) คุณต้องใช้ตัวแบ่งหน้าไม่เช่นนั้นคุณจะแสดงรายการเพียง 1,000 รายการแรก
Pierre D

นี่คือคำตอบที่ยอดเยี่ยม สำหรับผู้ที่ต้องการมันผมได้นำมาใช้limitกับมันในคำตอบที่ได้มาของฉัน
Acumenus

16

ความเข้าใจที่ดีกับ S3 คือไม่มีโฟลเดอร์ / ไดเรกทอรีเพียงแค่คีย์ โครงสร้างโฟลเดอร์ที่ชัดเจนจะใช้ได้เพียงเพื่อชื่อไฟล์ที่จะกลายเป็น 'คีย์' เพื่อที่จะแสดงรายการเนื้อหาของmyBucket's some/path/to/the/file/คุณสามารถลอง:

s3 = boto3.client('s3')
for obj in s3.list_objects_v2(Bucket="myBucket", Prefix="some/path/to/the/file/")['Contents']:
    print(obj['Key'])

ซึ่งจะให้สิ่งที่ชอบ:

some/path/to/the/file/yo.jpg
some/path/to/the/file/meAndYou.gif
...

นี่เป็นคำตอบที่ดี แต่จะดึงข้อมูลได้มากถึง 1,000 วัตถุและไม่มีอีกต่อไป ฉันได้สร้างคำตอบที่ได้มาซึ่งสามารถดึงวัตถุจำนวนมากขึ้นได้
Acumenus

ใช่ @Acumenus ฉันเดาว่าคำตอบของคุณซับซ้อนมากขึ้น
CpILL

16

ฉันมีปัญหาเดียวกัน แต่สามารถแก้ไขได้โดยใช้boto3.clientand list_objects_v2with Bucketand StartAfterparameters

s3client = boto3.client('s3')
bucket = 'my-bucket-name'
startAfter = 'firstlevelFolder/secondLevelFolder'

theobjects = s3client.list_objects_v2(Bucket=bucket, StartAfter=startAfter )
for object in theobjects['Contents']:
    print object['Key']

ผลลัพธ์ผลลัพธ์สำหรับโค้ดด้านบนจะแสดงสิ่งต่อไปนี้:

firstlevelFolder/secondLevelFolder/item1
firstlevelFolder/secondLevelFolder/item2

Boto3 list_objects_v2 เอกสารประกอบ

ในการตัดเฉพาะชื่อไดเร็กทอรีสำหรับsecondLevelFolderฉันเพิ่งใช้วิธี python split():

s3client = boto3.client('s3')
bucket = 'my-bucket-name'
startAfter = 'firstlevelFolder/secondLevelFolder'

theobjects = s3client.list_objects_v2(Bucket=bucket, StartAfter=startAfter )
for object in theobjects['Contents']:
    direcoryName = object['Key'].encode("string_escape").split('/')
    print direcoryName[1]

ผลลัพธ์ผลลัพธ์สำหรับโค้ดด้านบนจะแสดงสิ่งต่อไปนี้:

secondLevelFolder
secondLevelFolder

Python split () เอกสารประกอบ

หากคุณต้องการรับชื่อไดเร็กทอรีและชื่อรายการเนื้อหาให้แทนที่บรรทัดการพิมพ์ด้วยสิ่งต่อไปนี้:

print "{}/{}".format(fileName[1], fileName[2])

และสิ่งต่อไปนี้จะถูกส่งออก:

secondLevelFolder/item2
secondLevelFolder/item2

หวังว่านี่จะช่วยได้


9

สิ่งต่อไปนี้ใช้ได้กับฉัน ... วัตถุ S3:

s3://bucket/
    form1/
       section11/
          file111
          file112
       section12/
          file121
    form2/
       section21/
          file211
          file112
       section22/
          file221
          file222
          ...
      ...
   ...

ใช้:

from boto3.session import Session
s3client = session.client('s3')
resp = s3client.list_objects(Bucket=bucket, Prefix='', Delimiter="/")
forms = [x['Prefix'] for x in resp['CommonPrefixes']] 

เราได้รับ:

form1/
form2/
...

ด้วย:

resp = s3client.list_objects(Bucket=bucket, Prefix='form1/', Delimiter="/")
sections = [x['Prefix'] for x in resp['CommonPrefixes']] 

เราได้รับ:

form1/section11/
form1/section12/

นี่เป็นวิธีเดียวที่ใช้ได้ผลสำหรับฉันเพราะฉันต้องการ "โฟลเดอร์" ในรูทของที่เก็บข้อมูลคำนำหน้าจะต้องเป็น "" "ในขณะที่มิฉะนั้นจะต้องลงท้ายด้วย" / "
Oliver

7

AWS cli ทำสิ่งนี้ (โดยไม่ต้องดึงข้อมูลและวนซ้ำผ่านคีย์ทั้งหมดในที่เก็บข้อมูล) เมื่อคุณเรียกใช้aws s3 ls s3://my-bucket/ดังนั้นฉันคิดว่าต้องมีวิธีใช้ boto3

https://github.com/aws/aws-cli/blob/0fedc4c1b6a7aee13e2ed10c3ada778c702c22c3/awscli/customizations/s3/subcommands.py#L499

ดูเหมือนว่าพวกเขาใช้คำนำหน้าและตัวคั่น - ฉันสามารถเขียนฟังก์ชันที่จะทำให้ฉันได้รับไดเร็กทอรีทั้งหมดในระดับรูทของที่เก็บข้อมูลโดยแก้ไขโค้ดนั้นเล็กน้อย:

def list_folders_in_bucket(bucket):
    paginator = boto3.client('s3').get_paginator('list_objects')
    folders = []
    iterator = paginator.paginate(Bucket=bucket, Prefix='', Delimiter='/', PaginationConfig={'PageSize': None})
    for response_data in iterator:
        prefixes = response_data.get('CommonPrefixes', [])
        for prefix in prefixes:
            prefix_name = prefix['Prefix']
            if prefix_name.endswith('/'):
                folders.append(prefix_name.rstrip('/'))
    return folders

2

นี่คือทางออกที่เป็นไปได้:

def download_list_s3_folder(my_bucket,my_folder):
    import boto3
    s3 = boto3.client('s3')
    response = s3.list_objects_v2(
        Bucket=my_bucket,
        Prefix=my_folder,
        MaxKeys=1000)
    return [item["Key"] for item in response['Contents']]

2

ทำไมไม่ใช้s3pathแพคเกจที่สะดวกพอ ๆ กับการทำงานด้วยpathlibล่ะ? อย่างไรก็ตามหากคุณต้องใช้boto3:

การใช้ boto3.resource

นี้สร้างเมื่อคำตอบโดย itz-Azharlimitเพื่อนำไปใช้เป็นตัวเลือก เห็นได้ชัดว่าใช้งานง่ายกว่าboto3.clientเวอร์ชันนี้มาก

import logging
from typing import List, Optional

import boto3
from boto3_type_annotations.s3 import ObjectSummary  # pip install boto3_type_annotations

log = logging.getLogger(__name__)
_S3_RESOURCE = boto3.resource("s3")

def s3_list(bucket_name: str, prefix: str, *, limit: Optional[int] = None) -> List[ObjectSummary]:
    """Return a list of S3 object summaries."""
    # Ref: https://stackoverflow.com/a/57718002/
    return list(_S3_RESOURCE.Bucket(bucket_name).objects.limit(count=limit).filter(Prefix=prefix))


if __name__ == "__main__":
    s3_list("noaa-gefs-pds", "gefs.20190828/12/pgrb2a", limit=10_000)

การใช้ boto3.client

สิ่งนี้ใช้list_objects_v2และสร้างตามคำตอบโดย CpILLเพื่อให้สามารถดึงข้อมูลมากกว่า 1,000 วัตถุ

import logging
from typing import cast, List

import boto3

log = logging.getLogger(__name__)
_S3_CLIENT = boto3.client("s3")

def s3_list(bucket_name: str, prefix: str, *, limit: int = cast(int, float("inf"))) -> List[dict]:
    """Return a list of S3 object summaries."""
    # Ref: https://stackoverflow.com/a/57718002/
    contents: List[dict] = []
    continuation_token = None
    if limit <= 0:
        return contents
    while True:
        max_keys = min(1000, limit - len(contents))
        request_kwargs = {"Bucket": bucket_name, "Prefix": prefix, "MaxKeys": max_keys}
        if continuation_token:
            log.info(  # type: ignore
                "Listing %s objects in s3://%s/%s using continuation token ending with %s with %s objects listed thus far.",
                max_keys, bucket_name, prefix, continuation_token[-6:], len(contents))  # pylint: disable=unsubscriptable-object
            response = _S3_CLIENT.list_objects_v2(**request_kwargs, ContinuationToken=continuation_token)
        else:
            log.info("Listing %s objects in s3://%s/%s with %s objects listed thus far.", max_keys, bucket_name, prefix, len(contents))
            response = _S3_CLIENT.list_objects_v2(**request_kwargs)
        assert response["ResponseMetadata"]["HTTPStatusCode"] == 200
        contents.extend(response["Contents"])
        is_truncated = response["IsTruncated"]
        if (not is_truncated) or (len(contents) >= limit):
            break
        continuation_token = response["NextContinuationToken"]
    assert len(contents) <= limit
    log.info("Returning %s objects from s3://%s/%s.", len(contents), bucket_name, prefix)
    return contents


if __name__ == "__main__":
    s3_list("noaa-gefs-pds", "gefs.20190828/12/pgrb2a", limit=10_000)

0

ก่อนอื่นไม่มีแนวคิดเกี่ยวกับโฟลเดอร์จริงใน S3 คุณสามารถมีไฟล์ @ ได้แน่นอน'/folder/subfolder/myfile.txt'และไม่มีโฟลเดอร์หรือโฟลเดอร์ย่อย

ในการ "จำลอง" โฟลเดอร์ใน S3 คุณต้องสร้างไฟล์เปล่าโดยมีเครื่องหมาย "/" ต่อท้ายชื่อ (ดูAmazon S3 boto - จะสร้างโฟลเดอร์ได้อย่างไร )

สำหรับปัญหาของคุณคุณควรใช้วิธีนี้get_all_keysกับพารามิเตอร์ 2 ตัว: prefixและdelimiter

https://github.com/boto/boto/blob/develop/boto/s3/bucket.py#L427

for key in bucket.get_all_keys(prefix='first-level/', delimiter='/'):
    print(key.name)

1
ฉันกลัวว่าฉันไม่มีเมธอด get_all_keys บนวัตถุที่เก็บข้อมูล ฉันใช้ boto3 เวอร์ชัน 1.2.3
ดีบุก Mar

ตรวจสอบเพียง 1.2A Boto: มีถังมีวิธีการlistที่มีและprefix delimiterฉันคิดว่ามันควรจะทำงาน
Pirheas

1
อ็อบเจ็กต์ Bucket ที่ดึงมาตามที่ฉันโพสต์ในคำถามไม่มีเมธอดเหล่านั้น ฉันใช้ boto3 1.2.6 ลิงก์ของคุณอ้างถึงเวอร์ชันใด
ดีบุก Mar


0

ฉันรู้ว่า boto3 เป็นหัวข้อที่กำลังพูดถึงที่นี่ แต่ฉันพบว่าโดยปกติแล้วมันจะเร็วกว่าและใช้งานง่ายกว่าเพียงแค่ใช้awscliสำหรับสิ่งนี้ - awscli ยังคงมีความสามารถมากกว่า boto3 สำหรับสิ่งที่คุ้มค่ากว่า

ตัวอย่างเช่นถ้าฉันมีออบเจ็กต์ที่บันทึกไว้ใน "โฟลเดอร์ย่อย" ที่เชื่อมโยงกับที่เก็บข้อมูลที่กำหนดฉันสามารถแสดงรายการทั้งหมดได้ด้วยสิ่งต่อไปนี้

1) 'mydata' = ชื่อที่เก็บข้อมูล

2) 'f1 / f2 / f3' = "เส้นทาง" ที่นำไปสู่ ​​"ไฟล์" หรือวัตถุ

3) 'foo2.csv, barfar.segy, gar.tar' = วัตถุทั้งหมด "ภายใน" f3

ดังนั้นเราจึงสามารถนึกถึง "เส้นทางสัมบูรณ์" ที่นำไปสู่วัตถุเหล่านี้คือ 'mydata / f1 / f2 / f3 / foo2.csv' ...

การใช้คำสั่ง awscli เราสามารถแสดงรายการวัตถุทั้งหมดภายใน "โฟลเดอร์ย่อย" ที่กำหนดได้อย่างง่ายดายผ่านทาง:

aws s3 ls s3: // mydata / f1 / f2 / f3 / --recursive


0

ต่อไปนี้เป็นส่วนของโค้ดที่สามารถจัดการการแบ่งหน้าได้หากคุณพยายามดึงอ็อบเจ็กต์บัคเก็ต S3 จำนวนมาก:

def get_matching_s3_objects(bucket, prefix="", suffix=""):

    s3 = boto3.client("s3")
    paginator = s3.get_paginator("list_objects_v2")

    kwargs = {'Bucket': bucket}

    # We can pass the prefix directly to the S3 API.  If the user has passed
    # a tuple or list of prefixes, we go through them one by one.
    if isinstance(prefix, str):
        prefixes = (prefix, )
    else:
        prefixes = prefix

    for key_prefix in prefixes:
        kwargs["Prefix"] = key_prefix

        for page in paginator.paginate(**kwargs):
            try:
                contents = page["Contents"]
            except KeyError:
                return

            for obj in contents:
                key = obj["Key"]
                if key.endswith(suffix):
                    yield obj

จะเกิดอะไรขึ้นถ้าหน้าแรกเต็มไปด้วย "CommonPrefixes" และไม่มีคีย์ "Contents" ใด ๆ ฉันคิดว่าการใช้งานที่เหมาะสมจะข้ามคีย์เนื้อหาที่ขาดหายไปและดำเนินการต่อในหน้าถัดไป
ม.ค. Vlcinsky

0

สำหรับ Boto 1.13.3 จะกลายเป็นเรื่องง่าย (หากคุณข้ามการพิจารณาการแบ่งหน้าทั้งหมดซึ่งครอบคลุมในคำตอบอื่น ๆ ):

def get_sub_paths(bucket, prefix):
s3 = boto3.client('s3')
response = s3.list_objects_v2(
    Bucket=bucket,
    Prefix=prefix,
    MaxKeys=1000)
return [item["Prefix"] for item in response['CommonPrefixes']]
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.