使用python boto3 SDK访问S3对象存储 boto3 SDK简介 Boto3是亚马逊AWS提供的python SDK,最为常用的功能是S3对象存储的访问。交大云对象存储兼容S3 API, 可以使用该标准的S3 SDK进行访问。本文介绍如何使用 Boto3 SDK访问对象存储服务, 包括软件安装、管理存储空间、 管理文件。更多boto3访问对象存储的介绍,请参看:https://boto3.amazonaws.com/v1/documentation/api/latest/index.html 
示例中本地操作系统为CentOS8,其他系统请按需修改准备工作中软件安装命令。
本文部分结果的验证用到了s3 browser,了解如何使用s3 browser访问对象存储:https://jcloud.sjtu.edu.cn/document/detail.html?mod=cos&id=1232  。
软件安装 
安装python,建议使用Python 3.6及以后的版本。
 
要验证安装,请输入以下命令检查Python版本。
 
 
安装python s3 boto3 SDK软件包
 
 
 
初始化 Python SDK几乎所有的操作都是通过session.client()进行的。下面将详细说明如何初始化这个类。
获取AK/SK 使用boto3之前,需要先通过云平台控制台获取项目的的access key和secret_key,用于访问验证。
登录交大云平台:https://home.jcloud.sjtu.edu.cn 。
 
选择并确认需要创建对象存储的项目。
 
点击界面左侧的:存储、对象存储条目,进入对象存储管理界面。
 
 
点击管理界面左上方的:获取AK/SK 
 
确定EndPoint EndPoint是对象存储服务提供的服务地址及端口,交大云对象存储统一为:”https://s3.jcloud.sjtu.edu.cn:443" 
初始化session.client类 1 2 3 4 5 6 7 8 9 from boto3.session import Session import boto3   # 加载boto3 skd包 # session.client初始化, access_key和secret_key填入上文获取的内容 access_key = "您的AccessKey" secret_key = "您的SecretKey" url = "https://s3.jcloud.sjtu.edu.cn:443"		# 交大云对象存储URL session = Session(access_key, secret_key) s3_client = session.client('s3', endpoint_url=url) 
 
代码解释:
管理存储空间 存储空间(Bucket)是对象存储服务器上的命名空间,也是计费,权限控制,日志记录等高级功能的管理实体。
1. 查看Bucket列表 创建python文件ls_bucket.py 输入以下内容,并保存。
1 2 3 4 5 ... #初始化session.client() if __name__ == '__main__':     for bucket in s3_client.list_buckets()['Buckets']:         print([bucket['Name']]) 
 
执行命令:
 
执行结果:
 
2. 创建Bucket 桶的名字必须是唯一的 ,如果Bucket已经被其他用户使用,则会创建失败。
创建python文件create_bucket.py ,输入以下内容,并保存。
1 2 3 4 5 6 7 8 ... #初始化session.client() if __name__ == '__main__':     # 默认创建私有桶     s3_client.create_bucket(Bucket = 'private_own_bucket')     # 创建公开可读桶     s3_client.create_bucket(Bucket = '02-bucket', ACL = 'public-read') 
 
ACL可选:private, public-read, public-read-write authenticated-read。 
 
执行命令:
1 $python3 create_bucket.py 
 
查看结果:
1 2 3 4 $python3 ls_bucket.py ['01-bucket'] ['02-bucket'] ['private_own_bucket'] 
 
3. 查看Bucket内对象个数和使用空间 1 2 3 4 5 6 ... #初始化session.client() if __name__ == '__main__':     resp = s3_client.head_bucket(Bucket = "first-bucket")     print('使用空间(字节):' + str(resp['ResponseMetadata']['HTTPHeaders']['x-rgw-bytes-used']))     print('桶内对象个数: '+ str(resp['ResponseMetadata']['HTTPHeaders']['x-rgw-object-count'])) 
 
执行命令:
1 2 3 $python3 resource_bucket.py  使用空间(字节):532 桶内对象个数: 4 
 
4. 查看Bucket访问权限 创建python文件query_acl.py 输入以下内容,并保存。
1 2 3 4 5 ... #初始化session.client() if __name__ == '__main__':     res = s3_client.get_bucket_acl(Bucket = '02-bucket')     print(res['Grants']) 
 
执行命令:
 
执行结果:
1 2 3 [{'Grantee': {'Type': 'Group', 'URI': 'http://acs.amazonaws.com/groups/global/AllUsers'}, 'Permission': 'READ'}, {'Grantee': {'DisplayName': '****_project', 'ID': '*************', 'Type': 'CanonicalUser'}, 'Permission': 'FULL_CONTROL'}] 
 
5. 设置Bucket访问权限 创建python文件set_acl.py 输入以下内容,并保存。
1 2 3 4 ... #初始化session.client() if __name__ == '__main__':     res = s3_client.put_bucket_acl(Bucket = '02-bucket', ACL = 'public-read-write') 
 
执行命令:
 
查看结果:
1 2 3 4 5 6 $python3 query_acl.py  [{'Grantee': {'Type': 'Group', 'URI': 'http://acs.amazonaws.com/groups/global/AllUsers'}, 'Permission': 'READ'}, {'Grantee': {'Type': 'Group', 'URI':  'http://acs.amazonaws.com/groups/global/AllUsers'}, 'Permission': 'WRITE'}, {'Grantee':  {'DisplayName': '****_project', 'ID': '*************', 'Type': 'CanonicalUser'},  'Permission': 'FULL_CONTROL'}] 
 
6. 删除bucket 删除指定的桶。只有该桶中所有的对象被删除了,该桶才能被删除。另外,只有该桶的拥有者才能删除它,与桶的访问控制权限无关。
创建python文件del_bucket.py 输入以下内容,并保存。
1 2 3 4 5 6 7 8 ... #初始化session.client() from botocore.exceptions import ClientError if __name__ == '__main__':    try:         res = s3_client.delete_bucket(Bucket = '01-bucket')     except ClientError as e:         print (e.response['Error']['Code']) 
 
执行命令:
 
查看结果:
1 2 3 $python3 ls_bucket.py  ['02-bucket'] ['private_own_bucket'] 
 
管理文件 通过Python SDK 用户可以上传、下载、罗列、删除、拷贝文件。也可以查看文件信息,更改文件元信息等。
1. 上传文件 对象存储有多种上传方式,不同的上传方式能够上传的数据大小也不一样。普通上传(PutObject)最多只能上传小于或者等于5GB的文件;而分片上传(MultipartUpload)每个分片可以达到5GB,合并后的文件能够达到5TB.
首先介绍普通上传,我们会详细展示提供数据的各种方式,即方法中的data参数。其他上传接口类似的data参数。
上传字符串 上传内存中的字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 ... #初始化session.client() if __name__ == '__main__':     # 上传内存中的字符串     res = s3_client.put_object(Bucket = '02-bucket', Key = 'local01.txt', Body =      'local01') 	# 可以执行上传的是bytes     res = s3_client.put_object(Bucket = '02-bucket', Key = 'local02.txt', Body = b'0101') 	# 可以执行为unicode     res = s3_client.put_object(Bucket = '02-bucket', Key = 'local03.txt', Body = u'嗨,你     好') 
 
执行命令:
 
执行结果:
1 2 3 4 5 # 利用s3cmd查看文件列表 $ s3cmd ls s3://02-bucket 2021-12-31 05:30          133  s3://02-bucket/local01.txt 2021-12-31 05:27            4  s3://02-bucket/local02.txt 2021-12-31 05:27           12  s3://02-bucket/local03.txt 
 
上传本地文件 把当前目录下的local01.txt文件上传到对象存储服务器
1 2 3 4 5 ... #初始化session.client() if __name__ == '__main__':     res = s3_client.put_object(Bucket = '02-bucket', Key = 'local01.txt', Body =      open("local01.txt") 
 
执行命令:
 
执行结果:
1 2 3 4 5 6 # 利用s3cmd查看文件列表 $ s3cmd ls s3://02-bucket 2021-12-31 05:36          133  s3://02-bucket/local.txt 2021-12-31 05:30          133  s3://02-bucket/local01.txt 2021-12-31 05:27            4  s3://02-bucket/local02.txt 2021-12-31 05:27           12  s3://02-bucket/local03.txt 
 
分片上传 当需要上传的本地文件很大,或者网络状况不理想,往往会出现上传到中途就失败了。此时,如果对已经上传的数据重新上传,既浪费时间又占用了网络资源。Python SDK提供了分片上传接口,用于断点续传本地文件或者并发上传文件不同的区域块来加速上传。
采用分片上传,用户可以对上传做更为精细的控制。这适用于诸如预先不知道文件大小、并发上传、自定义断点续传等场景。一次分片上传可以分为三个步骤:
初始化(createMultipartUpload): 获得Upload ID。 
上传分片(uploadPart) :这一步可以并发执行。 
完成上传(completeMultipartUpload):合并分片,生成文件。 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 ... #初始化session.client() from boto3.session import Session import boto3 #准备上传的文件 file = open('./testfile.docx','rb') part_info = {     'Parts': [] } #分片上传功能的函数 def create_multipart_upld(): 	# step1:初始化     response = s3_client.create_multipart_upload(         Bucket = '02-bucket', Key = 'testfile')       part_no = 1     # 用于标识分片上传时的数据块号,即 PartNumber     with open('/root/testfile.docx', 'rb') as f:         while True:             bytes_data = f.read(1024 * 1024 * 6)             if not bytes_data:                 break             print('upload ID:', response['UploadId'])             #step2:上传分片             part = s3_client.upload_part(                 Body=bytes_data,                 Bucket='02-bucket',                 Key='testfile',                 PartNumber=part_no,     # 这个号要唯一,否则会把前面相同的PartNumber数据覆盖掉                 UploadId=response['UploadId'],             )            # 每个 PartNumber 和 ETag 要对应起来             part_info['Parts'].append({                 'PartNumber': part_no,                 'ETag': part['ETag']             })             part_no += 1     print('part_info:', part_info)     # step3:完成上传     complete_rsp = s3_client.complete_multipart_upload(Bucket='02-bucket',                                                     Key='testfile',                                                     UploadId=response['UploadId'],                                                     MultipartUpload=part_info)     print('complete_rsp:', complete_rsp) if __name__ == '__main__': 	create_multipart_upld() 
 
执行命令:
1 python3 multi_upload_file.py 
 
执行结果:
1 2 3 4 5 6 7 8 9 10 11 12 part_info: {'Parts': [{'PartNumber': 1, 'ETag': '"fad477c7c37c930be51b07f35dfda1de"'}, {'PartNumber': 2, 'ETag': '"eb6cc5a24133f05681cd6725b4e22ca7"'}]} complete_rsp: {'ResponseMetadata': {'RequestId': 'tx000000000000010fa604d-0061d53872- 41dd138-zone-00864d', 'HostId': '', 'HTTPStatusCode': 200, 'HTTPHeaders': {'server':  'openresty/1.13.6.1', 'date': 'Wed, 05 Jan 2022 06:19:30 GMT', 'content-type':  'application/xml', 'content-length': '253', 'connection': 'keep-alive', 'x-amz-request- id': 'tx000000000000010fa604d-0061d53872-41dd138-zone-00864d', 'access-control-allow- origin': '*', 'access-control-allow-methods': 'POST, GET, OPTIONS, DELETE, PUT, PATCH',  'access-control-allow-headers': 'content-type,x-amz-server-side-encryption,range,x-amz- user-agent,x-amz-copy-source,x-amz-content-sha256,x-amz-date,authorization,x-amz- acl,etag,content-encoding,x-requested-with'}, 'RetryAttempts': 0}, 'Bucket': '02-bucket', 'Key': 'testfile', 'ETag': '"282963790da05e43425250eb604359ed-2"'} 
 
2. 下载文件 下载到本地文件 把对象存储服务器上指定bucket里面的文件下载到本地文件
1 2 3 4 5 6 ... #初始化session.client() if __name__ == '__main__':     res = s3_client.get_object(Bucket = '02-bucket', Key = 'local03.txt')     with open('down-local-03.txt', 'wb') as f:         f.write(res['Body'].read()) 
 
执行命令:
1 python3 download_file.py 
 
查看结果:
1 2 $ cat down-local-03.txt 嗨,你好 
 
下载部分内容 下载对象存储服务器上指定bucket里面的文件的前10个字节:
1 2 3 4 5 6 7 ... #初始化session.client() if __name__ == '__main__':     res = s3_client.get_object(Bucket = '02-bucket', Key = 'local03.txt', Range='bytes=0-     4')     with open('down-local-03.txt', 'wb') as f:         f.write(res['Body'].read()) 
 
执行命令:
1 python3 download_part.py 
 
查看结果:
1 2  cat down-local-03.txt 嗨 
 
断点续传 当需要下载的文件很大,或者网络状况不够理想,往往下载到中途就失败了。如果下次重试,还需要重新下载,就会浪费时间和带宽。断点续传的过程大致如下:
1 2 3 1. 在本地创建一个临时文件,文件名由原始文件名加上一个随机的后缀组成。 2. 通过制定HTTP请求的Range头,按照范围读取对象存储服务器上的文件,并写入到文件里相应的位置。 3. 下载完成后,把临时文件重命名为目标文件。 
 
3.查看文件 罗列全部文件列表 查看指定bucket里所有文件列表
1 2 3 4 5 6 ... #初始化session.client() if __name__ == '__main__':     res = s3_client.list_objects(Bucket = '02-bucket')     for obj in res['Contents']:         print (obj['Key']) 
 
执行结果:
1 2 3 4 5 $ python ls_bucket_file.py local.txt local01.txt local02.txt local03.txt 
 
按前缀罗列文件列表 只列举前缀为’local0’的所有文件
1 2 3 4 5 6 ... #初始化session.client() if __name__ == '__main__':     res = s3_client.list_objects(Bucket = '02-bucket', Prefix = 'local0')     for obj in res['Contents']:         print (obj['Key']) 
 
执行结果:
1 2 3 4 $ python3 ls_bucket_file_part.py local01.txt local02.txt local03.txt 
 
模拟文件夹 对象存储的存储空间(Bucket)本身是扁平结构,并没有文件夹或目录的概念。用户可以通过在文件名里加入“/”来模拟文件夹。在列举的时候,则要设置delimiter参数(目录分隔符)为“/”,并通过是否“在CommonPrefixes”来判断是否为文件夹,例如:02-bucket下结构如下:
1 2 3 4 5 6 s3cmd ls s3://02-bucket/                           DIR  s3://02-bucket/test/ 2021-12-31 05:36          133  s3://02-bucket/local.txt 2021-12-31 05:30          133  s3://02-bucket/local01.txt 2021-12-31 05:27            4  s3://02-bucket/local02.txt 2021-12-31 05:27           12  s3://02-bucket/local03.txt 
 
列举02-bucket下的文件夹和文件(不递归列出子文件夹下的文件和子文件夹)
1 2 3 4 5 6 7 8 ... #初始化session.client() if __name__ == '__main__':     res = s3_client.list_objects(Bucket = '02-bucket', Delimiter = '/')     for obj in res.get('CommonPrefixes'):         print(obj.get('Prefix'))     for obj in res.get('Contents'):         print(obj.get('Key')) 
 
执行结果:
1 2 3 4 5 6 7 $ python ls_bucket_file.py / test/ local.txt local01.txt local02.txt local03.txt 
 
列举出02-bucket下的文件夹test下的文件夹和文件(不递归列出文件夹下的文件和文件夹)
1 2 3 4 5 6 7 8 ... #初始化session.client() if __name__ == '__main__':     res = s3_client.list_objects(Bucket = '02-bucket', Delimiter = '/', Prefix='test/')     for obj in res.get('CommonPrefixes'):         print(obj.get('Prefix'))     for obj in res.get('Contents'):         print(obj.get('Key')) 
 
执行结果:
1 2 3 python ls_bucket_file.py test/sub_tags/ test/local0-01.txt 
 
查看文件是否存在 1 2 3 4 5 6 7 8 9 ... #初始化session.client() from botocore.exceptions import ClientError if __name__ == '__main__':     try:         s3_client.head_object(Bucket = '02-bucket', Key = 'local01.txt')         print('EXITS')     except:         print('NOT FOUND') 
 
查询结果:
1 2 $ python has_file.py EXITS 
 
查看文件访问权限 1 2 3 4 5 6 ... #初始化session.client() if __name__ == '__main__':     res = s3_client.get_object_acl(Bucket = '01-bucket', Key = 'local03.txt')     print(res['Grants'])     print(res['Owner']) 
 
执行结果:
1 2 3 4 $  python3 query_acl_file.py  [{'Grantee': {'DisplayName': '****_project', 'ID': '7a3856dc8df348c1ae22dd58d4197749',  'Type': 'CanonicalUser'}, 'Permission': 'FULL_CONTROL'}] {'DisplayName': '****_project', 'ID': '7a3856dc8df348c1ae22dd58d4197749'} 
 
4. 设置文件 设置文件访问权限 1 2 3 4 5 ... #初始化session.client() if __name__ == '__main__':     res = s3_client.put_object_acl(Bucket = '02-bucket', ACL = 'public-read-write',      Key = 'local03.txt') 
 
执行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ s3cmd info s3://02-bucket/local03.txt s3://02-bucket/local03.txt (object):    File size: 12    Last mod:  Fri, 31 Dec 2021 06:42:58 GMT    MIME type: binary/octet-stream    Storage:   STANDARD    MD5 sum:   402cd29fb7d4d73855c517d3e7acee0a    SSE:       none    Policy:    none    CORS:      none    ACL:       *anon*: READ    ACL:       *anon*: WRITE    ACL:       ***_project: FULL_CONTROL    URL:       http://s3.jcloud.sjtu.edu.cn:443/02-bucket/local03.txt 
 
设置元数据 上传文件并且设置元数据
1 2 3 4 5 6 ... #初始化session.client() if __name__ == '__main__':     #上传对象并且设置元数据     s3_client.put_object(Bucket = '02-bucket', Key = 'big-file', Metadata = {      'key1': 'value1', 'key2': 'value2'}, Body = 'big-file.txt') 
 
查看结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 s3cmd info s3://02-bucket/big-file s3://02-bucket/big-file (object):    File size: 12    Last mod:  Fri, 31 Dec 2021 06:25:30 GMT    MIME type: binary/octet-stream    Storage:   STANDARD    MD5 sum:   03b038e5fbb30eaa5077e21ce1df44d2    SSE:       none    Policy:    none    CORS:      none    ACL:       ****_project: FULL_CONTROL    x-amz-meta-key1: value1    x-amz-meta-key2: value2 
 
修改已有对象的元数据
1 2 3 4 5 6 7 8 ... #初始化session.client() if __name__ == '__main__':     #python sdk目前对象没有post方法,因此建议在上传对象时就设置下面使用copy来实现对     #已有的对象的元数据更新     s3_client.copy_object(Bucket = '02-bucket', Key = 'big-file',      CopySource = str('02-bucket/big-file'), MetadataDirective = 'REPLACE', Metadata =       {'key1': 'replace-value1'}) 
 
执行结果
1 2 3 4 5 6 7 8 9 10 11 12 $ s3cmd info s3://02-bucket/big-file s3://02-bucket/big-file (object):    File size: 12    Last mod:  Fri, 31 Dec 2021 06:28:43 GMT    MIME type: binary/octet-stream    Storage:   STANDARD    MD5 sum:   03b038e5fbb30eaa5077e21ce1df44d2    SSE:       none    Policy:    none    CORS:      none    ACL:       ****_project: FULL_CONTROL    x-amz-meta-key1: replace-value1 
 
5. 删除文件 删除单个文件 1 2 3 4 ... #初始化session.client() if __name__ == '__main__':     res = s3_client.delete_object(Bucket = '02-bucket', Key = 'big-file') 
 
执行命令:
1 $ python3 delete_file.py 
 
查看结果:
1 2 3 4 5 $ s3cmd ls s3://02-bucket 2021-12-31 05:36          133  s3://02-bucket/local.txt 2021-12-31 05:30          133  s3://02-bucket/local01.txt 2021-12-31 05:27            4  s3://02-bucket/local02.txt 2021-12-31 05:27           12  s3://02-bucket/local03.txt 
 
删除多个文件 1 2 3 4 5 6 7 8 9 10 11 ... #初始化session.client() if __name__ == '__main__':     objects_to_delete = [{'Key': 'local01.txt'}, {'Key': 'local02.txt'}]     s3_client.delete_objects(         Bucket = '02-bucket',         Delete={             'Objects': objects_to_delete         }     ) 
 
执行命令:
1 $ python3 delete_files.py 
 
查看结果:
1 2 3 $ s3cmd ls s3://02-bucket 2021-12-31 05:36          133  s3://02-bucket/local.txt 2021-12-31 05:27           12  s3://02-bucket/local03.txt 
 
6. 拷贝文件 将02-bucket下的local03.txt文件拷贝到first-bucket下且命名为target.txt文件
1 2 3 4 5 ... #初始化session.client() if __name__ == '__main__':     res = s3_client.copy_object(Bucket = 'first-bucket', Key = 'target.txt',                      CopySource = str('02-bucket' + '/' + 'local03.txt')) 
 
执行命令:
 
查看结果:
1 2 3 4 5 6 $ s3cmd ls s3://first-bucket 2021-12-31 06:02          133  s3://first-bucket/local.txt 2021-12-31 06:04          133  s3://first-bucket/local01.txt 2021-12-31 06:04          133  s3://first-bucket/local02.txt 2021-12-31 06:04          133  s3://first-bucket/local03.txt 2021-12-31 06:37           12  s3://first-bucket/target.txt