我需要创建一个 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)
进入循环,则仅写入文件的最后一行。有没有办法在不删除文件行的情况下释放资源?
我确实找到了我的问题的解决方案,如果其他人感兴趣,我将在此处发布该解决方案。我决定将其作为分段上传的一部分来执行。您无法流式传输到 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()
根据docs这是可能的
s3.Object('mybucket', 'hello.txt').put(Body=open('/tmp/hello.txt', 'rb'))
所以我们可以用普通方式使用
StringIO
更新:来自@inquiring minds答案的smart_open lib是更好的解决方案
当文件内容作为 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"})
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()
方法与目标流(而不是文件)结合使用)应该可行。
这是一个使用
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())
有一个支持良好的库可以做到这一点:
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 流,这对某些人来说可能是一个优势。相反,您可以开始上传并一次发送一个部分。
要将流上传到 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。
要将字符串写入 S3 对象,请使用:
s3.Object('my_bucket', 'my_file.txt').put('Hello there')
因此,将流转换为字符串即可。