您可以使用流而不是本地文件上传到S3吗?

时间:2015-06-24 16:02:29

标签: python csv amazon-s3 boto buffering

我需要创建一个CSV并将其上传到S3存储桶。由于我正在动态创建文件,如果我可以在创建它时直接将其写入S3存储桶而不是在本地写入整个文件,然后在最后上传文件,那会更好。

有办法做到这一点吗?我的项目是用Python编写的,而且我对这门语言还是比较陌生的。这是我到目前为止所尝试的:

import csv
import csv
import io
import boto
from boto.s3.key import Key


conn = boto.connect_s3()
bucket = conn.get_bucket('dev-vs')
k = Key(bucket)
k.key = 'foo/foobar'

fieldnames = ['first_name', 'last_name']
writer = csv.DictWriter(io.StringIO(), fieldnames=fieldnames)
k.set_contents_from_stream(writer.writeheader())

我收到此错误:BotoClientError:s3不支持分块传输

更新:我找到了一种直接写入S3的方法,但我找不到清除缓冲区的方法,却没有实际删除我已编写的行。所以,例如:

conn = boto.connect_s3()
bucket = conn.get_bucket('dev-vs')
k = Key(bucket)
k.key = 'foo/foobar'

testDict = [{
    "fieldA": "8",
    "fieldB": None,
    "fieldC": "888888888888"},
    {
    "fieldA": "9",
    "fieldB": None,
    "fieldC": "99999999999"}]

f = io.StringIO()
fieldnames = ['fieldA', 'fieldB', 'fieldC']
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
k.set_contents_from_string(f.getvalue())

for row in testDict:
    writer.writerow(row)
    k.set_contents_from_string(f.getvalue())

f.close()

将3行写入文件,但是我无法释放内存来写大文件。如果我添加:

f.seek(0)
f.truncate(0)

到循环,然后只写入文件的最后一行。有没有办法在不删除文件中的行的情况下释放资源?

6 个答案:

答案 0 :(得分:24)

我确实找到了我的问题的解决方案,我将在此处发布以防其他人感兴趣。我决定在分段上传中将其作为部分。您无法流式传输到S3。还有一个软件包可以将您的流媒体文件更改为我使用的分段上传:Smart Open

<span>

答案 1 :(得分:4)

根据docs,可能

s3.Object('mybucket', 'hello.txt').put(Body=open('/tmp/hello.txt', 'rb'))

所以我们可以通常的方式使用StringIO

来自@inquiring minds的

更新smart_open lib是更好的解决方案

答案 2 :(得分:1)

我们试图将文件内容作为Django请求中的InMemoryUploadedFile对象上传到s3。我们结束了以下操作,因为我们不想在本地保存文件。希望对您有所帮助:

@action(detail=False, methods=['post'])
def upload_document(self, request):
     document = request.data.get('image').file
     s3.upload_fileobj(document, BUCKET_NAME, 
                                 DESIRED_NAME_OF_FILE_IN_S3, 
                                 ExtraArgs={"ServerSideEncryption": "aws:kms"})

答案 3 :(得分:1)

这是一个使用 boto3

的完整示例
import boto3
import io

session = boto3.Session(
    aws_access_key_id="...",
    aws_secret_access_key="..."
)

s3 = session.resource("s3")

buff = io.BytesIO()

buff.write("test1\n".encode())
buff.write("test2\n".encode())

s3.Object(bucket, keypath).put(Body=buff.getvalue())

答案 4 :(得分:0)

要将字符串写到S3对象,请使用:

s3.Object('my_bucket', 'my_file.txt').put('Hello there')

因此将流转换为字符串,您就在那里。

答案 5 :(得分:0)

在GitHub smart_open问题(#82)中提到了一个有趣的代码解决方案,我一直想尝试一下。为了后代而在此处复制粘贴...看来boto3是必需的:

csv_data = io.BytesIO()
writer = csv.writer(csv_data)
writer.writerows(my_data)

gz_stream = io.BytesIO()
with gzip.GzipFile(fileobj=gz_stream, mode="w") as gz:
    gz.write(csv_data.getvalue())
gz_stream.seek(0)

s3 = boto3.client('s3')
s3.upload_fileobj(gz_stream, bucket_name, key)

此特定示例正在流式传输到压缩的S3密钥/文件,但似乎是通用方法-将boto3 S3客户端的upload_fileobj()方法与目标流而非文件结合使用-应该可以。