使用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