AWS S3开发的比较分析
介绍
在本教程中,我们将通过探索和比较两个强大的库: boto3
和 awswrangler
,深入探讨AWS S3开发的世界。
如果您曾经想过
“与AWS S3存储桶交互的最佳Python工具是什么?”
“如何以最有效的方式执行S3操作?”
那么您来对地方了。
的确,在本文中,我们将涵盖一系列与AWS S3存储桶一起工作所必需的常见操作,其中包括:
- 列出对象 ,
- 检查对象是否存在 ,
- 下载对象 ,
- 上传对象 ,
- 删除对象 ,
- 写入对象 ,
- 读取对象(标准方式或使用SQL)
通过比较这两个库,我们将确定它们的相似之处、不同之处以及每个操作的最佳用例。最后,您将清楚地了解哪个库更适合特定的S3任务。
此外,对于那些一直阅读到最后的人,我们还将探讨如何利用boto3
和awswrangler
使用友好的SQL查询从S3中读取数据。
因此,让我们深入探索与AWS S3交互的最佳工具,并学习如何使用Python使用这两个库高效执行这些操作。
先决条件和数据
本教程中使用的包版本为:
boto3==1.26.80
awswrangler==2.19.0
此外,已经上传了包括随机生成的account_balances
数据的三个初始文件到名为coding-tutorials
的S3存储桶中:
尽管您应该知道有许多方法可以建立与S3存储桶的连接,但在这种情况下,我们将使用boto3
中的setup_default_session()
:
# 连接S3存储桶import osimport ioimport boto3import awswrangler as wrimport pandas as pdboto3.setup_default_session(aws_access_key_id = 'your_access_key', aws_secret_access_key = 'your_secret_access_key')bucket = 'coding-tutorials'
这种方法很方便,因为一旦会话已经设置,它可以被boto3
和awswrangler
共享,这意味着我们不需要在后面传递任何更多的密钥。
比较分析
现在让我们比较boto3
和awswrangler
,同时执行一些常见操作,并找出哪个工具最适合这项工作。
包括以下代码的完整笔记本可以在此GitHub文件夹中找到。
#1 列出对象
列出对象可能是我们在探索新的S3存储桶时应该执行的第一个操作,也是检查会话是否正确设置的简单方式。
使用boto3
可以列出对象:
boto3.client('s3').list_objects()
boto3.resource('s3').Bucket().objects.all()
print('--BOTO3--') # BOTO3 - 首选方法client = boto3.client('s3')for obj in client.list_objects(Bucket=bucket)['Contents']: print('文件名:', obj['Key'], '大小:', round(obj['Size']/ (1024*1024), 2), 'MB') print('----') # BOTO3 - 替代方法resource = boto3.resource('s3')for obj in resource.Bucket(bucket).objects.all(): print('文件名:', obj.key, '大小:', round(obj.size/ (1024*1024), 2), 'MB')
尽管client
和resource
类都能很好地完成工作,但应优先选择client
类,因为它更优雅并提供大量[易于访问的]低级元数据作为嵌套的JSON
(其中包括对象size
)。
另一方面,awswrangler
只提供了一个列出对象的方法:
wr.s3.list_objects()
作为高级方法,它不返回有关对象的任何低级元数据,因此要查找文件size
,我们需要调用:
wr.s3.size_objects
print('--AWS_WRANGLER--') # AWS WRANGLERfor obj in wr.s3.list_objects("s3://coding-tutorials/"): print('文件名:', obj.replace('s3://coding-tutorials/', '')) print('----') for obj, size in wr.s3.size_objects("s3://coding-tutorials/").items(): print('文件名:', obj.replace('s3://coding-tutorials/', '') , '大小:', round(size/ (1024*1024), 2), 'MB')
上面的代码返回:
比较 → Boto3 胜出
尽管awswrangler
更容易使用,但在列出 S3 对象时,boto3
赢得了挑战。实际上,它的低级实现意味着可以使用其类之一检索更多的对象元数据。这些信息在以编程方式访问 S3 存储桶时非常有用。
#2 检查对象是否存在
检查对象的存在性是在我们希望对已经在 S3 中可用的对象触发其他操作时所需的。
使用boto3
可以执行此类检查:
boto3.client('s3').head_object()
object_key = 'account_balances_jan2023.parquet'# BOTO3print('--BOTO3--') client = boto3.client('s3')try: client.head_object(Bucket=bucket, Key = object_key) print(f"对象存在于桶{bucket}中。")except client.exceptions.NoSuchKey: print(f"对象在桶{bucket}中不存在。")
相反,awswrangler
提供了专用方法:
wr.s3.does_object_exist()
# AWS WRANGLERprint('--AWS_WRANGLER--') try: wr.s3.does_object_exist(f's3://{bucket}/{object_key}') print(f"对象存在于桶{bucket}中。")except: print(f"对象在桶{bucket}中不存在。")
上面的代码返回:
比较 → AWSWrangler 胜出
让我们承认吧:boto3
的方法名称[head_object()
]并不是那么直观。
另外,awswrangler
有一个专门的方法,无疑是这场比赛的优势。
# 3 下载对象
使用以下方法,无论是 boto3
还是 awswrangler
,在本地下载对象都非常简单:
boto3.client('s3').download_file()
或wr.s3.download()
唯一的区别在于,download_file()
接受 bucket
、object_key
和 local_file
作为输入变量,而 download()
只需要 S3 的 path
和 local_file
:
object_key = 'account_balances_jan2023.parquet'# BOTO3client = boto3.client('s3')client.download_file(bucket, object_key, 'tmp/account_balances_jan2023_v2.parquet')# AWS WRANGLERwr.s3.download(path=f's3://{bucket}/{object_key}', local_file='tmp/account_balances_jan2023_v3.parquet')
当代码执行时,相同对象的两个版本确实都会被下载到本地的 tmp/
文件夹内:
比较 → 平局
就下载文件而言,我们可以认为这两个库是等价的,因此让我们将其视为平局。
# 4 上传对象
在从本地环境上传文件到 S3 时,同样的原理适用。可以使用以下方法:
boto3.client('s3').upload_file()
或wr.s3.upload()
object_key_1 = 'account_balances_apr2023.parquet'object_key_2 = 'account_balances_may2023.parquet'file_path_1 = os.path.dirname(os.path.realpath(object_key_1)) + '/' + object_key_1file_path_2 = os.path.dirname(os.path.realpath(object_key_2)) + '/' + object_key_2# BOTO3client = boto3.client('s3')client.upload_file(file_path_1, bucket, object_key_1)# AWS WRANGLERwr.s3.upload(local_file=file_path_2, path=f's3://{bucket}/{object_key_2}')
执行代码后,将两个新的 account_balances
对象(2023 年 4 月和 5 月份的)上传到 coding-tutorials
存储桶中:
比较 → 平局
这又是一场平局。到目前为止,这两个库之间存在绝对的平衡!
# 5 删除对象
现在假设我们希望删除以下对象:
#SINGLE OBJECTobject_key = ‘account_balances_jan2023.parquet’#MULTIPLE OBJECTSobject_keys = [‘account_balances_jan2023.parquet’, ‘account_balances_feb2023.parquet’, ‘account_balances_mar2023.parquet’]
boto3
允许逐个或批量删除对象,使用以下方法:
boto3.client('s3').delete_object()
boto3.client('s3').delete_objects()
这两种方法都返回一个 response
,其中包括 ResponseMetadata
,可用于验证对象是否已成功删除。例如:
- 删除单个对象时,
HTTPStatusCode==204
表示操作已成功完成(如果在 S3 存储桶中找到对象); - 删除多个对象时,返回一个
Deleted
列表,其中包含已成功删除的项的名称。
# BOTO3print('--BOTO3--')client = boto3.client('s3')# 删除单个对象response = client.delete_object(Bucket=bucket, Key=object_key)deletion_date = response['ResponseMetadata']['HTTPHeaders']['date']if response['ResponseMetadata']['HTTPStatusCode'] == 204: print(f'对象 {object_key} 在 {deletion_date} 成功删除。')else: print(f'对象无法删除。')# 删除多个对象objects = [{'Key': key} for key in object_keys]response = client.delete_objects(Bucket=bucket, Delete={'Objects': objects})deletion_date = response['ResponseMetadata']['HTTPHeaders']['date']if len(object_keys) == len(response['Deleted']): print(f'所有对象在 {deletion_date} 成功删除。')else: print(f'对象无法删除。')
另一方面,awswrangler
提供了一种可用于单个和批量删除的方法:
wr.s3.delete_objects()
由于可以将object_keys
递归传递给该方法作为list_comprehension
,而不像之前那样先转换为字典,因此使用这种语法非常方便。
# AWS WRANGLERprint('--AWS_WRANGLER--')# 删除单个对象wr.s3.delete_objects(path=f's3://{bucket}/{object_key}')# 删除多个对象try: wr.s3.delete_objects(path=[f's3://{bucket}/{key}' for key in object_keys]) print('所有对象已成功删除。')except: print(f'对象无法删除。')
执行上述代码,将删除S3中的对象,然后返回:
比较 → Boto3 胜出
这个有些棘手:删除多个对象时,awswrangler
具有更简单的语法,因为我们可以直接将完整列表传递给该方法。
然而,boto3
在以编程方式删除对象时返回大量信息的response
,这些信息是非常有用的日志。
因为在生产环境中,低级元数据优于几乎没有元数据,所以boto3
赢得了这场比赛,现在成绩是2-1。
# 6 写入对象
当涉及到将文件写入S3时,boto3
甚至没有提供开箱即用的方法来执行此类操作。
例如,如果我们想使用boto3
创建一个新的parquet
文件,我们首先需要将对象持久化到本地磁盘上(使用pandas
的to_parquet()
方法),然后使用upload_fileobj()
方法将其上传到S3。
与upload_file()
(在第4点中探讨)不同,upload_fileobj()
方法是一个托管传输,如果必要将执行多线程的分块上传:
object_key_1 = 'account_balances_june2023.parquet'# 运行 generator.py 脚本df.to_parquet(object_key_1)# BOTO3client = boto3.client('s3')# 将 Parquet 文件上传到 S3with open(object_key_1, 'rb') as file: client.upload_fileobj(file, bucket, object_key_1)
另一方面,awswrangler
库(在使用pandas
时)的主要优势之一是,它可以直接将对象写入S3桶(而无需将它们保存到本地磁盘),这既优雅又高效。
此外,awswrangler
提供了很大的灵活性,允许用户:
- 应用特定的压缩算法,如
snappy
,gzip
和zstd
; - 通过
dataset = True
时的mode
参数append
或overwrite
现有文件; - 通过
partitions_col
参数指定一个或多个分区列。
object_key_2 ='account_balances_july2023.parquet'# AWS WRANGLER wr.s3.to_parquet(df=df, path=f's3://{bucket}/{object_key_2}', compression ='gzip', partition_cols =['COMPANY_CODE'], dataset=True)
执行后,上述代码将account_balances_june2023
作为单个parquet
文件写入,并将account_balances_july2023
作为已通过COMPANY_CODE
进行分区的四个文件夹写入:
比较 → AWSWrangler胜出
如果使用pandas
是一种选择,awswrangler
在将文件写入S3时提供了一组更高级的操作,尤其是与在这种情况下并不是最好的工具的boto3
相比。
# 7.1 R 读取对象(Python)
当使用boto3
从S3读取对象时,同样的推理适用:由于该库没有内置的读取方法,我们最好的选择是执行API调用(get_object()
),读取response
的Body
,然后将parquet_object
传递给pandas
。
请注意,pd.read_parquet()
方法期望输入为类似文件的对象,这就是为什么我们需要将从parquet_object
中读取的内容作为二进制流传递的原因。
实际上,通过使用io.BytesIO()
,我们在内存中创建了一个临时文件对象,避免了需要在读取之前将Parquet文件保存在本地的需要。这反过来可以提高性能,特别是在处理大文件时:
object_key = 'account_balances_may2023.parquet'#BOTO3client = boto3.client('s3')#读取Parquet文件response = client.get_object(Bucket=bucket,Key=object_key)parquet_object = response['Body'].read()df = pd.read_parquet(io.BytesIO(parquet_object))df.head()
如预期的那样,awswrangler
在从S3读取对象方面表现出色,以pandas
df作为输出返回。
它支持多种输入格式,如csv
,json
,parquet
和最近的delta
表。同时,传递chunked
参数可以以内存友好的方式读取对象:
# AWS WRANGLERdf = wr.s3.read_parquet(path=f's3://{bucket}/{object_key}')df.head()# wr.s3.read_csv()# wr.s3.read_json()# wr.s3.read_parquet_table()# wr.s3.read_deltalake()
执行上述代码将返回一个带有5月数据的pandas
df:
比较 → AWSWrangler胜出
是的,有绕过boto3
缺少适当方法的方法。但是,awswrangler
是一个旨在高效读取S3对象的库,因此它也赢得了这个挑战。
# 7.2 读取对象(SQL)
读到这里的人应该得到一个奖励,这个奖励是使用纯 SQL 从 S3 中读取对象。
假设我们希望使用以下 query
(通过 AS_OF_DATE
过滤数据)从 account_balances_may2023.parquet
对象中获取数据:
object_key = 'account_balances_may2023.parquet'query = """SELECT * FROM s3object s WHERE AS_OF_DATE > CAST('2023-05-13T' AS TIMESTAMP)"""
在 boto3
中,可以通过 select_object_content()
方法实现。请注意,我们还应该指定 inputSerialization
和 OutputSerialization
格式:
# BOTO3client = boto3.client('s3')resp = client.select_object_content( Bucket=bucket, Key=object_key, Expression= query, ExpressionType='SQL', InputSerialization={"Parquet": {}}, OutputSerialization={'JSON': {}},)records = []# 处理响应数据for event in resp['Payload']: if 'Records' in event: records.append(event['Records']['Payload'].decode('utf-8')) # 将 JSON 记录连接成单个字符串json_string = ''.join(records)# 将 JSON 数据加载到 Pandas DataFramedf = pd.read_json(json_string, lines=True)# 打印 DataFramedf.head()
如果可以使用 pandas
df,awswrangler
还提供了非常方便的 select_query()
方法,需要很少的代码:
# AWS WRANGLERdf = wr.s3.select_query( sql=query, path=f's3://{bucket}/{object_key}', input_serialization="Parquet", input_serialization_params={})df.head()
对于这两个库,返回的 df 将如下所示:
结论
在本教程中,我们探讨了可以在 S3 存储桶上执行的 7 个常见操作,并对 boto3
和 awswrangler
库进行了比较分析。
这两种方法都允许我们与 S3 存储桶进行交互,但主要区别在于 boto3
客户端提供了对 AWS 服务的低级访问,而 awswrangler
为各种数据工程任务提供了简化和更高级别的接口。
总的来说,awswrangler
是我们的赢家,拿到了 3 分(检查对象存在性、写入对象、读取对象),而 boto3
仅得到了 2 分(列出对象、删除对象)。上传/下载对象类别均为平局,未分配分数。
尽管如上结果,事实上,当相互使用时,两个库都会在它们为之建立的任务中发挥出最佳水平。
来源
- AWS Wrangler 文档
- Boto3 S3 客户端文档
除非另有说明,所有图片均由作者提供。