วิธีบันทึกวัตถุ S3 ลงในไฟล์โดยใช้ boto3


132

ฉันกำลังพยายามสร้าง "สวัสดีชาวโลก" ด้วยไคลเอนต์boto3ใหม่สำหรับ AWS

กรณีการใช้งานที่ฉันมีค่อนข้างง่าย: รับวัตถุจาก S3 และบันทึกลงในไฟล์

ใน boto 2.X ฉันจะทำเช่นนี้:

import boto
key = boto.connect_s3().get_bucket('foo').get_key('foo')
key.get_contents_to_filename('/tmp/foo')

ใน boto 3. ฉันไม่พบวิธีที่ชัดเจนในการทำสิ่งเดียวกันดังนั้นฉันจึงทำซ้ำบนวัตถุ "สตรีมมิง" ด้วยตนเอง

import boto3
key = boto3.resource('s3').Object('fooo', 'docker/my-image.tar.gz').get()
with open('/tmp/my-image.tar.gz', 'w') as f:
    chunk = key['Body'].read(1024*8)
    while chunk:
        f.write(chunk)
        chunk = key['Body'].read(1024*8)

หรือ

import boto3
key = boto3.resource('s3').Object('fooo', 'docker/my-image.tar.gz').get()
with open('/tmp/my-image.tar.gz', 'w') as f:
    for chunk in iter(lambda: key['Body'].read(4096), b''):
        f.write(chunk)

และใช้งานได้ดี ฉันสงสัยว่ามีฟังก์ชัน boto3 "ดั้งเดิม" ที่จะทำงานเดียวกันหรือไม่

คำตอบ:


216

มีการปรับแต่งที่เข้าสู่ Boto3 เมื่อเร็ว ๆ นี้ซึ่งช่วยในเรื่องนี้ (เหนือสิ่งอื่นใด) ขณะนี้มีการเปิดเผยบนไคลเอนต์ S3 ระดับต่ำและสามารถใช้งานได้ดังนี้:

s3_client = boto3.client('s3')
open('hello.txt').write('Hello, world!')

# Upload the file to S3
s3_client.upload_file('hello.txt', 'MyBucket', 'hello-remote.txt')

# Download the file from S3
s3_client.download_file('MyBucket', 'hello-remote.txt', 'hello2.txt')
print(open('hello2.txt').read())

ฟังก์ชันเหล่านี้จะจัดการไฟล์การอ่าน / เขียนโดยอัตโนมัติตลอดจนการอัปโหลดแบบหลายส่วนพร้อมกันสำหรับไฟล์ขนาดใหญ่

โปรดทราบว่าs3_client.download_fileจะไม่สร้างไดเร็กทอรี สามารถสร้างเป็นpathlib.Path('/path/to/file.txt').parent.mkdir(parents=True, exist_ok=True)ไฟล์.


1
@ แดเนียล: ขอบคุณสำหรับการตอบกลับของคุณ คุณตอบคำตอบได้ไหมว่าฉันต้องการอัปโหลดไฟล์โดยใช้การอัปโหลดแบบหลายส่วนใน boto3
Rahul KP

1
@RahulKumarPatle upload_fileวิธีนี้จะใช้การอัปโหลดหลายส่วนโดยอัตโนมัติสำหรับไฟล์ขนาดใหญ่
Daniel

4
คุณส่งข้อมูลรับรองโดยใช้แนวทางนี้อย่างไร
JHowIX

1
@JHowIX คุณสามารถกำหนดค่าข้อมูลรับรองได้ทั่วโลก (เช่นดูboto3.readthedocs.org/en/latest/guide/… ) หรือคุณสามารถส่งผ่านได้เมื่อสร้างไคลเอนต์ ดูboto3.readthedocs.org/en/latest/reference/core/…สำหรับข้อมูลเพิ่มเติมเกี่ยวกับตัวเลือกที่มี!
Daniel

2
@VladNikiporoff "อัปโหลดจากต้นทางถึงปลายทาง" "ดาวน์โหลดจากต้นทางถึงปลายทาง"
jkdev

59

boto3 มีอินเทอร์เฟซที่ดีกว่าไคลเอนต์:

resource = boto3.resource('s3')
my_bucket = resource.Bucket('MyBucket')
my_bucket.download_file(key, local_filename)

สิ่งนี้ไม่ได้ดีไปกว่าclientคำตอบที่ยอมรับอย่างมาก(แม้ว่าเอกสารจะบอกว่ามันทำงานได้ดีกว่าในการลองอัปโหลดและดาวน์โหลดซ้ำเมื่อเกิดความล้มเหลว) แต่เมื่อพิจารณาว่าทรัพยากรโดยทั่วไปมีการยศาสตร์มากกว่า (ตัวอย่างเช่นถัง s3 และทรัพยากรวัตถุดีกว่าวิธีไคลเอนต์) สิ่งนี้ช่วยให้คุณอยู่ที่เลเยอร์ทรัพยากรโดยไม่ต้องดร็อปดาวน์

Resources โดยทั่วไปสามารถสร้างขึ้นในลักษณะเดียวกับไคลเอนต์และใช้อาร์กิวเมนต์เดียวกันทั้งหมดหรือเกือบทั้งหมดและส่งต่อให้กับลูกค้าภายใน


1
ตัวอย่างที่ดีและหากต้องการเพิ่มเนื่องจากคำถามเดิมถามเกี่ยวกับการบันทึกวัตถุวิธีการที่เกี่ยวข้องในที่นี้คือmy_bucket.upload_file()(หรือmy_bucket.upload_fileobj()ถ้าคุณมีวัตถุ BytesIO)
SMX

เอกสารบอกว่าresourceทำได้ดีกว่าในการลองใหม่ที่ไหน ฉันไม่พบสิ่งบ่งชี้ดังกล่าว
Acumenus

42

สำหรับผู้ที่ต้องการจำลองวิธีการset_contents_from_stringเช่น boto2 คุณสามารถลอง

import boto3
from cStringIO import StringIO

s3c = boto3.client('s3')
contents = 'My string to save to S3 object'
target_bucket = 'hello-world.by.vor'
target_file = 'data/hello.txt'
fake_handle = StringIO(contents)

# notice if you do fake_handle.read() it reads like a file handle
s3c.put_object(Bucket=target_bucket, Key=target_file, Body=fake_handle.read())

สำหรับ Python3:

ใน python3 ทั้งStringIO และ cStringIO จะหายไป ใช้StringIOการนำเข้าเช่น:

from io import StringIO

ในการรองรับทั้งสองเวอร์ชัน:

try:
   from StringIO import StringIO
except ImportError:
   from io import StringIO

15
นั่นคือคำตอบ นี่คือคำถาม: "คุณบันทึกสตริงลงในวัตถุ S3 โดยใช้ boto3 ได้อย่างไร"
jkdev

สำหรับ python3 ฉันต้องใช้ import io; fake_handl e = io.StringIO (เนื้อหา)
เฟลิกซ์

16
# Preface: File is json with contents: {'name': 'Android', 'status': 'ERROR'}

import boto3
import io

s3 = boto3.resource('s3')

obj = s3.Object('my-bucket', 'key-to-file.json')
data = io.BytesIO()
obj.download_fileobj(data)

# object is now a bytes string, Converting it to a dict:
new_dict = json.loads(data.getvalue().decode("utf-8"))

print(new_dict['status']) 
# Should print "Error"

14
อย่าใส่ AWS_ACCESS_KEY_ID หรือ AWS_SECRET_ACCESS_KEY ของคุณในรหัสของคุณ เหล่านี้ควรจะกำหนดด้วย awscli คำสั่งและพวกเขาจะพบได้โดยอัตโนมัติaws configure botocore
Miles Erickson

3

เมื่อคุณต้องการอ่านไฟล์ที่มีการกำหนดค่าต่างจากค่าเริ่มต้นอย่าลังเลที่จะใช้mpu.aws.s3_download(s3path, destination)โดยตรงหรือรหัสที่คัดลอกมา:

def s3_download(source, destination,
                exists_strategy='raise',
                profile_name=None):
    """
    Copy a file from an S3 source to a local destination.

    Parameters
    ----------
    source : str
        Path starting with s3://, e.g. 's3://bucket-name/key/foo.bar'
    destination : str
    exists_strategy : {'raise', 'replace', 'abort'}
        What is done when the destination already exists?
    profile_name : str, optional
        AWS profile

    Raises
    ------
    botocore.exceptions.NoCredentialsError
        Botocore is not able to find your credentials. Either specify
        profile_name or add the environment variables AWS_ACCESS_KEY_ID,
        AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN.
        See https://boto3.readthedocs.io/en/latest/guide/configuration.html
    """
    exists_strategies = ['raise', 'replace', 'abort']
    if exists_strategy not in exists_strategies:
        raise ValueError('exists_strategy \'{}\' is not in {}'
                         .format(exists_strategy, exists_strategies))
    session = boto3.Session(profile_name=profile_name)
    s3 = session.resource('s3')
    bucket_name, key = _s3_path_split(source)
    if os.path.isfile(destination):
        if exists_strategy is 'raise':
            raise RuntimeError('File \'{}\' already exists.'
                               .format(destination))
        elif exists_strategy is 'abort':
            return
    s3.Bucket(bucket_name).download_file(key, destination)

from collections import namedtuple

S3Path = namedtuple("S3Path", ["bucket_name", "key"])


def _s3_path_split(s3_path):
    """
    Split an S3 path into bucket and key.

    Parameters
    ----------
    s3_path : str

    Returns
    -------
    splitted : (str, str)
        (bucket, key)

    Examples
    --------
    >>> _s3_path_split('s3://my-bucket/foo/bar.jpg')
    S3Path(bucket_name='my-bucket', key='foo/bar.jpg')
    """
    if not s3_path.startswith("s3://"):
        raise ValueError(
            "s3_path is expected to start with 's3://', " "but was {}"
            .format(s3_path)
        )
    bucket_key = s3_path[len("s3://"):]
    bucket_name, key = bucket_key.split("/", 1)
    return S3Path(bucket_name, key)

ไม่ทำงาน NameError: name '_s3_path_split' is not defined
Dave Liu

@DaveLiu ขอบคุณสำหรับคำใบ้; ฉันปรับรหัสแล้ว แพ็คเกจควรจะใช้งานได้มาก่อน
Martin Thoma

1

หมายเหตุ: ฉันสมมติว่าคุณได้กำหนดค่าการตรวจสอบสิทธิ์แยกกัน โค้ดด้านล่างนี้คือการดาวน์โหลดอ็อบเจกต์เดียวจากที่เก็บ S3

import boto3

#initiate s3 client 
s3 = boto3.resource('s3')

#Download object to the file    
s3.Bucket('mybucket').download_file('hello.txt', '/tmp/hello.txt')

รหัสนี้จะไม่ดาวน์โหลดจากภายในและโฟลเดอร์ s3 มีวิธีการโดยใช้วิธีนี้หรือไม่?
Marilu
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.