【问题标题】:s3.upload_fileobj gives error a bytes-like object is requireds3.upload_fileobj 给出错误需要类似字节的对象
【发布时间】:2020-03-14 05:55:11
【问题描述】:

我的问题受到previous SO 的启发,主题是:在 Amazon Web Services (AWS) S3 中将 DataFrames 上传并保存为 csv 文件。使用 Python3,我想使用s3.upload_fileobj - 多部分上传 - 使数据传输到 S3 更快。当我在接受的答案中运行代码时,我收到一条错误消息:"TypeError: a bytes-like object is required, not 'str'"..

这个答案最近被多次投票。所以我认为必须有一种方法可以在 Python3 中运行此代码而不会出错。

请在下面找到代码。为了方便起见,让我们使用一个简单的 DataFrame。实际上,这个 DataFrame 要大得多(大约 500 MB)。

import pandas as pd
import io

df = pd.DataFrame({'A':[1,2,3], 'B':[6,7,8]})

代码如下。为了方便起见,我将其转为函数:

def upload_file(dataframe, bucket, key):
    """dat=DataFrame, bucket=bucket name in AWS S3, key=key name in AWS S3"""
    s3 = boto3.client('s3')
    csv_buffer = io.BytesIO()
    dataframe.to_csv(csv_buffer, compression='gzip')
    s3.upload_fileobj(csv_buffer, bucket, key)

upload_file(df, your-bucket, your-key)

非常感谢您的建议!

【问题讨论】:

标签: python pandas amazon-s3


【解决方案1】:

离开this reference,您似乎需要将gzip.GzipFile 对象包裹在您的BytesIO 周围,然后它将为您执行压缩。

import io
import gzip

buffer = io.BytesIO()     
with gzip.GzipFile(fileobj=buffer, mode="wb") as f:
    f.write(df.to_csv().encode())
buffer.seek(0)

s3.upload_fileobj(buffer, bucket, key)

最小可验证示例

import io
import gzip
import zlib

# Encode
df = pd.DataFrame({'A':[1,2,3], 'B':[6,7,8]})

buffer = io.BytesIO()     
with gzip.GzipFile(fileobj=buffer, mode="wb") as f:
    f.write(df.to_csv().encode())

buffer.getvalue()
# b'\x1f\x8b\x08\x00\xf0\x0b\x11]\x02\xff\xd3q\xd4q\xe22\xd01\xd41\xe32\xd41\xd21\xe72\xd21\xd6\xb1\xe0\x02\x00Td\xc2\xf5\x17\x00\x00\x00'

# Decode
print(zlib.decompress(out.getvalue(), 16+zlib.MAX_WBITS).decode())

# ,A,B
# 0,1,6
# 1,2,7
# 2,3,8

【讨论】:

  • 谢谢!我收到一个错误“未定义”。你能对你的代码做一些额外的解释吗?
  • @RuthgerRighart "out -> buffer" 抱歉,复制粘贴错字。为了更清晰,我正在重命名变量。我的方法是获取 IO 缓冲区并将其包装在 GzipFile 对象周围。流通过 GzipFile 并在放入缓冲区之前进行压缩。然后,您可以像往常一样对待缓冲区。它与 Tarun Lalwani 所做的类似,只是他们使用了 TextIOWrapper 并让 pandas 负责压缩(无论哪种方式都很好,IMO,如果我没记错的话,它们都依赖于相同的实现/库)。
  • 由于某种原因,现在再次运行您的代码会导致上传的文件为 0 B(空文件)。我不记得我以前有过这个。如果有机会,你愿意检查一下你的系统吗?我想知道它是否与包的版本更新有关。
  • @RuthgerRighart 确保您首先找到缓冲区的开头。
  • @pc_pyr 我在我的回答中没有看到compression,如果你使用GzipFile,我认为你不需要它。
【解决方案2】:

您唯一需要的是 TextIOWrapper,因为 to_csv 需要 stringupload_fileobj 需要 bytes

def upload_file(dataframe, bucket, key):
    """dat=DataFrame, bucket=bucket name in AWS S3, key=key name in AWS S3"""
    s3 = boto3.client('s3')
    csv_buffer = io.BytesIO()
    w = io.TextIOWrapper(csv_buffer)
    dataframe.to_csv(w, compression='gzip')
    w.seek(0)
    s3.upload_fileobj(csv_buffer, bucket, key)

而且代码上传正常

$ cat test.csv
,A,B
0,1,6
1,2,7
2,3,8

【讨论】:

  • 感谢您的回答!该脚本有效,但存在一个问题。将 s3.upload_fileobj(使用 io.BytesIO 和您的 TextIOWrapper 解决方案)与 s3_resource.Object(使用 io.StringIO)进行比较,我发现了一些非凡的事情。当 DF 比较小时,s3.upload_fileobj 方法更快。但是,当 DF 有更多行(例如 > 200K)时,s3_resource.Object 变得最快。重要的是,两者都非常缓慢。对于更快地将大文件上传到 S3 有什么建议吗?
  • 如果您决定将其全部保存在内存中以获得更大的块,它可能不会给您带来性能,因此最好转储到临时文件然后上传文件
  • 你的意思是上传临时文件到S3,所有临时文件都应该在S3合并?
  • 我的意思是,如果文件较大,将所有内容保存在内存中将无济于事。所以最好的办法是将文件保存到磁盘然后上传。如果需要合并,您可以在磁盘本身上合并。我会使用 OS cat 命令而不是使用 python
【解决方案3】:

你可以试试这样的。

import pandas as pd
import io

df = pd.DataFrame({'A':[1,2,3], 'B':[6,7,8]})

def upload_file(dataframe, bucket, key):
    """dat=DataFrame, bucket=bucket name in AWS S3, key=key name in AWS S3"""
    s3 = boto3.client('s3')
    csv_buffer = io.StringIO()
    dataframe.to_csv(csv_buffer, compression='gzip')
    csv_buffer.seek(0)
    s3.upload_fileobj(csv_buffer, bucket, key)

upload_file(df, your-bucket, your-key)

【讨论】:

  • 现在它给出错误“TypeError: Unicode-objects must be encrypted before hashing”
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-11-04
  • 2018-08-14
  • 1970-01-01
  • 2020-07-16
  • 1970-01-01
  • 1970-01-01
  • 2022-12-04
相关资源
最近更新 更多