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

问题描述 投票:0回答:8

我需要创建一个 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)

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

python csv amazon-s3 boto buffering
8个回答
57
投票

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

import smart_open
import io
import csv

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

fieldnames = ['fieldA', 'fieldB', 'fieldC']
f = io.StringIO()
with smart_open.smart_open('s3://dev-test/bar/foo.csv', 'wb') as fout:
    writer = csv.DictWriter(f, fieldnames=fieldnames)
    writer.writeheader()
    fout.write(f.getvalue())

    for row in testDict:
        f.seek(0)
        f.truncate(0)
        writer.writerow(row)
        fout.write(f.getvalue())

f.close()

2
投票

根据docs这是可能的

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

所以我们可以用普通方式使用

StringIO

更新:来自@inquiring minds答案的smart_open lib是更好的解决方案


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"})

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()
方法与目标流(而不是文件)结合使用)应该可行。


0
投票

这是一个使用

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())

0
投票

有一个支持良好的库可以做到这一点:

pip install s3fs

s3fs 使用起来真的很简单:

import s3fs

s3fs.S3FileSystem(anon=False)

with s3.open('mybucket/new-file', 'wb') as f:
    f.write(2*2**20 * b'a')
    f.write(2*2**20 * b'a')

顺便说一句,boto3 中还内置了一些东西(由 AWS API 支持),称为 MultiPartUpload

这不被视为 python 流,这对某些人来说可能是一个优势。相反,您可以开始上传并一次发送一个部分。


0
投票

要将流上传到 s3 您可以使用 Boto3 资源

r = requests.get(download_url, stream=True)
session = boto3.Session(aws_access_key_id=S3_ACCESS_KEY, aws_secret_access_key=S3_SECRET_KEY)
s3 = session.resource("s3")
bucket = s3.Bucket(UPLOAD_BUCKET_NAME)
bucket.upload_fileobj(r.raw, key)

此代码可用于调用 URL,强制下载文件以将其作为流获取,然后将其上传到 s3。


-4
投票

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

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

因此,将流转换为字符串即可。

© www.soinside.com 2019 - 2024. All rights reserved.