尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python boto3实战:S3文件上传下载核心操作与最佳实践

Python boto3实战:S3文件上传下载核心操作与最佳实践 1. 从零开始为什么S3是云存储的“瑞士军刀”如果你最近在折腾云原生应用、数据备份或者只是想找个地方存点自己的文件大概率会听到“Amazon S3”这个名字。它几乎是云存储的代名词但很多刚接触的朋友会觉得它有点“重”——又是权限、又是桶、又是区域一堆概念。今天我们不谈那些复杂的架构设计就从一个最朴素的需求出发怎么用最简单、最直接的方式把文件扔进S3再从里面拿出来。这听起来简单但背后藏着不少门道。比如你可能会想我直接用网页控制台拖拽上传不就行了确实可以但这只适合临时操作。一旦涉及到自动化、程序化或者需要集成到你的应用里你就得和它的API打交道。而S3的API设计恰恰是它强大和易用性的核心体现。它把复杂的存储逻辑抽象成了类似“HTTP PUT/GET一个文件到某个网址”这样简单的操作。理解了这个核心你就掌握了使用S3的钥匙。我见过不少项目一开始图省事把文件直接存在应用服务器本地结果服务器一迁移或者一崩溃数据全丢。也见过为了省点小钱用各种网盘API来存业务数据最后在稳定性、速度和安全上栽跟头。S3解决的就是这类问题它提供了一个高可用、高持久、几乎无限扩展的“文件柜”并且全球都有节点访问速度有保障。更重要的是它的计费模式清晰用多少付多少对于个人开发者或初创公司非常友好。所以这篇内容就是带你绕过那些复杂的概念直击要害。我会假设你已经在AWS控制台创建好了一个S3存储桶Bucket并且手头有了一组访问密钥Access Key ID 和 Secret Access Key。我们就用最流行的Python语言配合官方推荐的boto3库一步步实现文件的上传和下载。过程中我会穿插我实际使用中踩过的坑和总结的最佳实践让你不仅能跑通代码更能理解每一步背后的逻辑以后遇到问题也能自己排查。2. 环境准备与boto3配置避开第一个大坑万事开头难和S3打交道的第一步往往不是写代码而是把环境配通。很多新手在这里就会卡住不是因为步骤多复杂而是因为一些细节没注意到。我们一步一步来。2.1 安装boto3与创建安全凭证首先你需要安装boto3库。打开你的终端或命令行执行pip install boto3如果你用的是Python 3可能需要用pip3。安装过程通常很顺利。接下来是最关键的一步配置访问凭证。绝对不要像某些教程里写的那样把Access Key和Secret Key硬编码在代码文件里这是极其危险的做法一旦代码泄露比如上传到GitHub你的AWS账户就门户大开了。正确的方式是使用AWS的凭证文件。在你的用户主目录下Linux/macOS是~/.aws/Windows是C:\Users\你的用户名\.aws\创建两个文件config和credentials。~/.aws/credentials文件内容如下[default] aws_access_key_id YOUR_ACCESS_KEY_ID aws_secret_access_key YOUR_SECRET_ACCESS_KEY~/.aws/config文件内容如下[default] region us-east-1请将YOUR_ACCESS_KEY_ID和YOUR_SECRET_ACCESS_KEY替换成你从AWS IAM控制台获取的真实密钥。region填写你创建S3桶时选择的区域例如ap-southeast-1新加坡、eu-west-1爱尔兰等。这个区域必须和你的桶所在区域一致否则后续操作会失败。注意credentials文件存储的是密钥权限敏感务必确保其文件权限设置正确在Linux/macOS上可以运行chmod 600 ~/.aws/credentials。config文件主要存放区域等配置信息。为什么这么做boto3库在初始化时会自动按照一个标准的“凭证查找链”来寻找配置顺序是1) 代码中传入参数2) 环境变量3) 用户主目录的.aws/文件夹。我们把凭证放在这里代码里就无需显式指定既安全又方便。2.2 初始化S3客户端理解“客户端”与“资源”的区别配置好凭证后我们就可以在Python代码中初始化了。boto3提供了两种主要的接口低级Client和高级Resource。对于简单的上传下载两者都能完成但理解和选择哪一个很重要。Client接口是对AWS服务API的一对一映射。你调用的方法名和参数几乎和官方API文档一模一样。它返回的是原始的字典dict响应你需要自己从中解析数据。它的控制粒度更细性能理论上也稍好一点。Resource接口是更面向对象的抽象。它把S3的桶Bucket、对象Object都建模成了Python对象你可以用更直观的.操作符来调用方法代码写起来更“Pythonic”。对于大多数常见操作Resource接口更简洁易懂。我个人的建议是对于新手从Resource接口开始。它隐藏了很多细节让你更关注业务逻辑。等需要更高级、更底层的控制时再去看Client接口。下面分别是两种方式的初始化# 方式一使用Resource接口推荐新手 import boto3 s3_resource boto3.resource(s3) # 自动从 ~/.aws/ 读取配置 # 方式二使用Client接口 s3_client boto3.client(s3)初始化时boto3会自动读取我们刚才在~/.aws/config里设置的region。如果一切配置正确这行代码执行时不会有任何输出但背后已经建立好了与指定区域AWS服务的连接。一个常见的坑是网络问题。如果你的机器在国内直接连接海外区域如us-east-1的S3端点可能会很慢甚至超时。对于国内业务可以考虑使用AWS中国区宁夏或北京但需要单独申请账户和配置。另一种方案是如果桶的访问策略允许并且你更追求上传下载速度可以为桶配置加速端点Transfer Acceleration但这会产生额外费用。在开发测试阶段如果遇到连接超时可以先检查本机网络是否能正常访问AWS服务。3. 核心操作一上传文件到S3的三种姿势环境配好了客户端也初始化了现在我们来实战上传。上传一个文件到S3远不止一种方法。不同的方法适用于不同的场景选错了可能会影响性能或带来不必要的复杂度。3.1 方法一put_object - 最灵活的基础方法这是最基础、最直接的上传方法对应于HTTP的PUT操作。你可以上传一个文件也可以直接上传一段二进制数据或字符串。def upload_with_put_object(bucket_name, file_path, object_key): 使用put_object方法上传文件。 :param bucket_name: S3桶名 :param file_path: 本地文件路径 :param object_key: 存储在S3中的对象键可以包含路径如 images/photo.jpg with open(file_path, rb) as file_data: response s3_client.put_object( Bucketbucket_name, Keyobject_key, Bodyfile_data ) # 检查响应状态码虽然HTTP错误会以异常形式抛出但成功时也可以查看 print(f文件上传成功。ETag: {response.get(ETag)}) # 调用示例 upload_with_put_object(my-example-bucket-2023, /Users/me/Desktop/my_photo.jpg, uploads/my_photo.jpg)关键参数解析Bucket: 目标桶的名称。这个名字必须全局唯一因为S3的桶名是DNS命名的一部分。Key: 这是对象在桶内的“键”你可以把它理解为文件路径。它决定了文件在S3控制台里显示的层级结构。例如project/docs/readme.txt。Body: 要上传的数据体。这里我们以二进制读取模式rb打开文件将文件对象传入。你也可以直接传入字节串bytes或字符串。为什么用rb模式因为S3传输的是二进制流。用文本模式r打开文件在遇到非UTF-8编码或特定换行符时可能会出错。rbread binary是通用且安全的选择。put_object的优缺点优点控制粒度细。你可以方便地设置对象的元数据如ContentTypeimage/jpeg、存储类别如StorageClassSTANDARD_IA用于不常访问的文件、服务器端加密等。适合上传已知内容的小文件一般建议小于100MB。缺点对于大文件它需要一次性将整个文件读入内存如果文件很大比如几个GB会消耗大量内存甚至导致程序崩溃。此外它不支持上传过程中的进度追踪。3.2 方法二upload_file - 自动处理大文件的“智能”方法这是boto3Resource接口提供的一个高级方法也是我最推荐日常使用的方法。它内部会自动处理大文件上传的复杂性。def upload_with_upload_file(bucket_name, file_path, object_key): 使用upload_file方法上传文件。自动处理多部分上传。 s3_resource.Bucket(bucket_name).upload_file( Filenamefile_path, Keyobject_key ) print(f文件 {file_path} 已上传至 {bucket_name}/{object_key}) # 调用示例 upload_with_upload_file(my-example-bucket-2023, /Users/me/Desktop/big_video.mp4, videos/big_video.mp4)代码简洁得令人感动。你不需要手动打开文件只需要提供本地路径和目标键即可。它神奇在哪里upload_file方法在背后做了大量工作。当文件较小时它可能直接调用put_object。但当文件超过一定阈值默认是8MB时它会自动启动“多部分上传Multipart Upload”。多部分上传会把大文件切割成多个小块分片然后并行上传这些分片最后再通知S3将这些分片组合成完整的对象。这样做有三个巨大好处提升吞吐量并行上传充分利用网络带宽。增强可靠性单个分片上传失败只需重传该分片而不是整个文件。支持断点续传虽然upload_file本身不直接暴露断点续传接口但多部分上传的机制使其成为可能通过boto3.s3.transfer模块可以更精细控制。upload_file方法还支持额外的参数比如设置元数据、存储类别、回调函数等用法和put_object类似。实操心得对于绝大多数上传场景无脑用upload_file就对了。它省心、高效、可靠。只有当你需要上传的“数据”不是来自一个文件而是一段在内存中动态生成的二进制流时才需要考虑put_object。3.3 方法三upload_fileobj - 处理流式数据的利器有时候你的数据源不是一个磁盘上的文件而是一个文件对象file-like object比如由另一个库如PIL打开的图像、pandas生成的CSV流产生的或者来自网络请求。这时upload_fileobj就派上用场了。from io import BytesIO from PIL import Image def upload_in_memory_image(bucket_name): 上传一个在内存中创建的图像。 # 在内存中创建一个简单的图像 img Image.new(RGB, (100, 100), colorred) img_byte_arr BytesIO() img.save(img_byte_arr, formatPNG) img_byte_arr.seek(0) # 将指针移回开头这点非常重要 # 使用upload_fileobj上传 s3_resource.Bucket(bucket_name).upload_fileobj( Fileobjimg_byte_arr, Keygenerated/red_square.png, ExtraArgs{ContentType: image/png} # 记得设置正确的Content-Type ) print(内存图像上传成功。) # 调用示例 upload_in_memory_image(my-example-bucket-2023)核心要点Fileobj: 任何具有read方法的对象都可以比如BytesIO、StringIO或者一个已打开的、以二进制模式读取的文件对象。seek(0)操作至关重要在我们将数据写入BytesIO后它的内部指针已经到了末尾。如果直接上传S3会读取到一个空字节流。seek(0)将指针重置回开头确保上传的是完整数据。这是使用流式上传时最常见的错误之一。由于数据不是来自文件S3无法自动判断其类型务必通过ExtraArgs参数手动设置ContentType否则下载时浏览器可能无法正确识别。三种方法如何选择小文件100MB或需精细控制元数据/加密put_object(Client) 或put_object(Resource的Object.put)。上传本地大文件默认选择upload_file。上传非文件源的二进制流数据upload_fileobj。4. 核心操作二从S3下载文件的三种姿势有上传自然就有下载。下载的逻辑和上传类似也有多种方法对应不同场景。4.1 方法一get_object - 获取文件对象与元数据这是下载的基础方法它返回一个包含对象数据和大量元信息的响应字典。def download_with_get_object(bucket_name, object_key, local_file_path): 使用get_object方法下载文件。 response s3_client.get_object(Bucketbucket_name, Keyobject_key) # 响应体文件内容在 Body 键中它是一个流式对象 object_data response[Body] # 将流写入本地文件 with open(local_file_path, wb) as local_file: # 注意是 wb 二进制写入 local_file.write(object_data.read()) # 可以查看元数据例如文件大小、类型等 print(f文件下载成功。内容类型: {response.get(ContentType)}, 文件大小: {response.get(ContentLength)} bytes) # 调用示例 download_with_get_object(my-example-bucket-2023, uploads/my_photo.jpg, /Users/me/Downloads/downloaded_photo.jpg)关键点解析response[Body]是一个botocore.response.StreamingBody对象它支持read()、readline()等方法。对于大文件你可以分块读取例如read(amt1024*1024)每次读取1MB避免一次性占用过多内存。响应中包含了丰富的HTTP头信息如ContentTypeMIME类型、ContentLength文件大小、LastModified最后修改时间以及你上传时设置的任何自定义元数据。这在需要根据文件信息做逻辑判断时非常有用。同样对于大文件一次性read()到内存可能不适用。此时应该使用流式写入。4.2 方法二download_file - 下载大文件的“智能”方法与upload_file对应download_file是Resource接口提供的下载大文件的利器。它同样会自动处理多部分下载如果对象是通过多部分上传的且桶启用了相关设置并且将文件直接保存到本地路径。def download_with_download_file(bucket_name, object_key, local_file_path): 使用download_file方法下载文件。自动处理可能的多部分下载。 s3_resource.Bucket(bucket_name).download_file( Keyobject_key, Filenamelocal_file_path ) print(f文件 {bucket_name}/{object_key} 已下载至 {local_file_path}) # 调用示例 download_with_download_file(my-example-bucket-2023, videos/big_video.mp4, /Users/me/Downloads/big_video_local.mp4)代码依旧简洁。你只需要关心源桶和键和目标本地路径。boto3会处理好剩下的所有事情包括可能的断点续传通过底层的boto3.s3.transfer模块配置。一个常见的“静默”错误download_file在文件已存在时会直接覆盖不会有任何提示。这在自动化脚本中是优点但也可能导致意外数据丢失。一个良好的实践是在下载前检查本地文件是否存在或者使用一个带时间戳的唯一文件名。import os from pathlib import Path def safe_download(bucket_name, object_key, target_dir): 安全下载避免覆盖已有文件。 target_dir Path(target_dir) target_dir.mkdir(parentsTrue, exist_okTrue) # 从object_key中提取文件名 file_name Path(object_key).name local_path target_dir / file_name # 如果文件已存在添加时间戳后缀 if local_path.exists(): stem local_path.stem suffix local_path.suffix timestamp datetime.now().strftime(%Y%m%d_%H%M%S) local_path target_dir / f{stem}_{timestamp}{suffix} print(f目标文件已存在将下载为: {local_path}) s3_resource.Bucket(bucket_name).download_file(Keyobject_key, Filenamestr(local_path)) return local_path4.3 方法三download_fileobj - 流式下载与直接处理与上传的upload_fileobj对应当你不想把文件保存到磁盘而是想直接下载到内存中进行处理比如图片压缩、文本分析、直接传给前端时就用download_fileobj。import pandas as pd from io import BytesIO, StringIO def download_and_process_csv(bucket_name, object_key): 下载一个CSV文件到内存并用pandas直接处理。 # 准备一个字节缓冲区来接收数据 byte_stream BytesIO() # 下载到缓冲区 s3_resource.Bucket(bucket_name).download_fileobj(Keyobject_key, Fileobjbyte_stream) # 将指针重置到开头 byte_stream.seek(0) # 现在byte_stream里就是CSV文件的原始字节 # 我们可以用pandas直接读取 # 如果是文本CSV可以转换成StringIO # string_io StringIO(byte_stream.read().decode(utf-8)) # df pd.read_csv(string_io) # 更直接的方式如果知道编码pandas可以直接读BytesIO df pd.read_csv(byte_stream) # 假设CSV是UTF-8编码 print(f成功从S3加载数据到DataFrame形状: {df.shape}) # 进行后续处理... return df # 调用示例 # df download_and_process_csv(my-data-bucket, dataset/latest.csv)这种方法非常适合数据管道Data Pipeline场景避免了“下载到临时文件 - 读取 - 删除临时文件”的繁琐步骤提升了效率并减少了磁盘I/O。下载方法选择总结需要获取文件元数据或进行流式分块处理get_object。将S3文件直接保存到本地磁盘最常用download_file。将S3文件加载到内存进行即时处理download_fileobj。5. 实战中必须面对的四个关键问题代码跑起来只是第一步。在实际项目集成中你会遇到比“上传下载”更具体的问题。下面是我在多次项目中总结出的四个关键点。5.1 权限管理IAM策略与桶策略的迷思权限问题是S3访问失败的首要原因。错误信息通常是AccessDenied、403 Forbidden。你需要理解两层权限IAM用户/角色权限你用来生成Access Key的那个IAM实体必须被授予操作特定S3桶和对象的权限。S3桶策略Bucket Policy附加在桶本身上的资源策略可以允许或拒绝其他账户、IAM角色甚至匿名用户的访问。对于我们自己账户下的操作通常只配置IAM权限就够了。一个最小化的、允许对特定桶进行所有操作的IAM策略如下JSON格式{ Version: 2012-10-17, Statement: [ { Effect: Allow, Action: s3:*, Resource: [ arn:aws:s3:::my-example-bucket-2023, arn:aws:s3:::my-example-bucket-2023/* ] } ] }注意Resource部分有两行。第一行arn:aws:s3:::my-example-bucket-2023授予对桶本身的操作权限如列出桶内容ListBucket。第二行arn:aws:s3:::my-example-bucket-2023/*授予对桶内所有对象的操作权限如GetObjectPutObject。很多AccessDenied错误都是因为漏了其中一行导致的。最佳实践遵循最小权限原则。不要直接使用s3:*而是根据实际需要细化Action比如只允许s3:GetObject和s3:PutObject。5.2 内容类型与元数据让浏览器“认识”你的文件你有没有遇到过从S3下载一个图片浏览器却把它当成二进制文件直接下载而不是显示或者上传的CSS/JS文件被服务器以错误的MIME类型返回问题出在Content-Type这个元数据上。S3不会自动检测文件类型。如果你用put_object或upload_file上传时没有指定它的Content-Type默认是binary/octet-stream二进制流。下载时HTTP响应头里就是这个类型浏览器自然不认识。解决方案在上传时显式设置Content-Type。import mimetypes def upload_with_content_type(bucket_name, file_path, object_key): 自动检测并设置文件的内容类型。 # 根据文件扩展名猜测MIME类型 content_type, _ mimetypes.guess_type(file_path) if content_type is None: content_type binary/octet-stream # 兜底类型 extra_args {ContentType: content_type} # 使用upload_file上传 s3_resource.Bucket(bucket_name).upload_file( Filenamefile_path, Keyobject_key, ExtraArgsextra_args ) print(f上传成功Content-Type设置为: {content_type}) # 对于已知类型的文件也可以直接指定 extra_args {ContentType: application/json, ContentDisposition: inline}Python的mimetypes库可以帮你根据文件后缀进行合理猜测。对于网页资源如.html.css.js正确设置Content-Type至关重要。5.3 错误处理与重试机制让程序更健壮网络是不稳定的服务也可能有瞬时故障。你的上传下载代码必须有基本的错误处理和重试能力。boto3内置了重试机制但了解常见错误并主动处理能让你的程序更健壮。import botocore from tenacity import retry, stop_after_attempt, wait_exponential def robust_download(bucket_name, object_key, local_path): 一个带有错误处理和重试的下载函数。 try: s3_resource.Bucket(bucket_name).download_file(Keyobject_key, Filenamelocal_path) print(下载成功。) except botocore.exceptions.ClientError as e: error_code e.response[Error][Code] if error_code 404: print(f错误S3上的对象 {object_key} 不存在。) elif error_code 403: print(错误权限不足请检查IAM策略或桶策略。) else: print(f下载失败AWS错误码: {error_code}, 信息: {e}) # 这里可以记录日志、发送告警等 raise # 重新抛出异常让上层调用者知道 except Exception as e: # 捕获其他非AWS错误如本地磁盘空间不足、网络中断等 print(f下载过程中发生未知错误: {e}) raise # 使用tenacity库实现更优雅的自动重试 retry(stopstop_after_attempt(3), waitwait_exponential(multiplier1, min2, max10)) def download_with_retry(bucket_name, object_key, local_path): 遇到可重试错误如网络超时时自动重试最多3次。 s3_resource.Bucket(bucket_name).download_file(Keyobject_key, Filenamelocal_path)重点处理botocore.exceptions.ClientError并根据其中的错误码如404403NoSuchBucket进行针对性处理。对于网络波动等暂时性错误可以使用tenacity这类重试库来装饰你的函数实现指数退避重试避免雪崩。5.4 成本控制请求、存储与流量的权衡使用S3不是免费的虽然单价很低但用量大了也是一笔开销。主要成本来自三方面存储费用按存储在桶中的数据量每月计费。不同存储类别标准、低频访问、归档价格不同。请求费用每万次PUT、POST、LIST、GET等请求的费用。对于GET请求标准存储和低频访问的费率也不同。数据传输费用数据从S3传出到互联网下载的费用。数据传入S3上传通常是免费的。在不同区域之间传输数据也会产生费用。省钱小技巧选择合适的存储类别对于几乎不访问的日志、归档数据使用GLACIER或DEEP_ARCHIVE归档存储价格极低。对于偶尔访问的备份使用STANDARD_IA低频访问。使用生命周期规则可以自动将旧文件从标准存储转移到低频或归档存储甚至到期删除。这在控制台或代码中都可以配置。减少不必要的LIST请求list_objects_v2这类操作是收费的。不要在循环中频繁调用它。尽量通过明确的对象键来访问而不是先列清单。使用CloudFrontCDN如果你的S3文件主要用于对外提供静态资源如图片、JS、CSS通过CloudFront分发可以降低S3直接传输的出站流量费用并且提升用户访问速度。监控与告警在AWS Cost Explorer中设置预算和告警当月度费用超过预期时及时通知你。6. 进阶场景预签名URL与服务器端加密掌握了基本操作和常见问题处理后我们可以看两个稍微进阶但非常实用的场景。6.1 生成预签名URL安全地分享私有文件你的S3桶默认是私有的。但有时你需要让用户A下载某个文件又不想给他你的AWS访问密钥或者不想把桶设为公开。这时预签名URLPresigned URL就派上用场了。预签名URL是一个包含了你的安全凭证、操作权限和过期时间的特殊URL。你可以在后端生成这个URL然后发给前端或用户。用户在有效期内点击这个URL就可以直接下载或上传文件而无需任何AWS凭证。def generate_presigned_url_download(bucket_name, object_key, expiration3600): 生成一个用于下载的预签名URL有效期默认为1小时。 try: url s3_client.generate_presigned_url( get_object, Params{Bucket: bucket_name, Key: object_key}, ExpiresInexpiration ) except botocore.exceptions.ClientError as e: print(f生成预签名URL时出错: {e}) return None return url def generate_presigned_url_upload(bucket_name, object_key, expiration3600): 生成一个用于上传的预签名URL。 try: url s3_client.generate_presigned_url( put_object, Params{Bucket: bucket_name, Key: object_key}, ExpiresInexpiration ) except Exception as e: print(f生成上传URL时出错: {e}) return None return url # 使用示例 download_url generate_presigned_url_download(my-private-bucket, reports/q3_summary.pdf, 1800) # 30分钟有效 print(f请点击此链接下载报告30分钟内有效: {download_url}) # 前端拿到这个URL后可以直接用fetch或axios发起GET请求下载文件。重要注意事项有效期一定要设置一个合理的、尽可能短的有效期如几分钟到几小时避免URL泄露导致长期风险。权限生成URL的IAM用户/角色必须有对应对象GetObject或PutObject的权限。不可撤销一旦生成并发出在过期前无法主动使其失效。因此对于极高敏感操作需要搭配更精细的权限控制。6.2 启用服务器端加密为数据加把锁数据安全至关重要。S3支持在服务器端自动加密存储你的数据SSE Server-Side Encryption。即使有人非法获取了你的数据存储介质没有密钥也无法解密。推荐的做法是使用SSE-S3S3托管密钥这是最简单且免费的方式。你可以在上传时指定加密方式# 使用SSE-S3加密上传 extra_args { ServerSideEncryption: AES256, # AES-256加密由S3管理密钥 # 还可以同时设置存储类别 # StorageClass: STANDARD_IA } s3_resource.Bucket(bucket_name).upload_file(Filenamefile_path, Keyobject_key, ExtraArgsextra_args)更省心的办法是直接在S3桶的默认设置里启用加密。这样所有新上传的对象都会自动加密无需每次在代码中指定。路径是S3控制台 - 选择桶 - “属性”标签页 - 默认加密 - 启用。对于有更高安全要求的场景还可以使用SSE-KMS使用AWS KMS服务管理密钥或SSE-C客户提供密钥。SSE-S3对于绝大多数场景已经足够。7. 从脚本到生产构建一个健壮的文件传输工具最后我们把上面所有的知识点串联起来构建一个稍微像样点的、可以用于生产环境辅助脚本的小工具。这个工具包含上传、下载、生成分享链接等基本功能并具备错误处理和简单日志。#!/usr/bin/env python3 一个简单的S3文件传输命令行工具。 import argparse import logging import sys from pathlib import Path import boto3 import botocore.exceptions from .utils import guess_content_type # 假设有一个工具函数 logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s) logger logging.getLogger(__name__) class S3FileManager: def __init__(self, bucket_name): self.bucket_name bucket_name self.s3_resource boto3.resource(s3) self.s3_client boto3.client(s3) # 验证桶是否存在且有权限访问 try: self.s3_resource.meta.client.head_bucket(Bucketbucket_name) logger.info(f成功连接到桶: {bucket_name}) except botocore.exceptions.ClientError as e: error_code e.response[Error][Code] if error_code 404: logger.error(f桶 {bucket_name} 不存在。) elif error_code 403: logger.error(f无权访问桶 {bucket_name}请检查权限。) else: logger.error(f连接桶时发生未知错误: {e}) raise def upload_file(self, local_path, s3_key, extra_argsNone): 安全上传文件自动设置Content-Type。 local_path Path(local_path) if not local_path.is_file(): logger.error(f本地文件不存在: {local_path}) return False if extra_args is None: extra_args {} if ContentType not in extra_args: content_type guess_content_type(local_path) if content_type: extra_args[ContentType] content_type try: self.s3_resource.Bucket(self.bucket_name).upload_file( Filenamestr(local_path), Keys3_key, ExtraArgsextra_args ) logger.info(f上传成功: {local_path} - {self.bucket_name}/{s3_key}) return True except Exception as e: logger.error(f上传失败: {e}) return False def download_file(self, s3_key, local_dir, overwriteFalse): 下载文件到指定目录可选择是否覆盖。 local_dir Path(local_dir) local_dir.mkdir(parentsTrue, exist_okTrue) file_name Path(s3_key).name local_path local_dir / file_name if local_path.exists() and not overwrite: logger.warning(f文件已存在且未指定覆盖: {local_path}) return None try: self.s3_resource.Bucket(self.bucket_name).download_file( Keys3_key, Filenamestr(local_path) ) logger.info(f下载成功: {self.bucket_name}/{s3_key} - {local_path}) return local_path except botocore.exceptions.ClientError as e: if e.response[Error][Code] 404: logger.error(fS3对象不存在: {s3_key}) else: logger.error(f下载失败: {e}) return None def get_shareable_link(self, s3_key, expires_in3600): 生成一个用于下载的预签名URL。 try: url self.s3_client.generate_presigned_url( get_object, Params{Bucket: self.bucket_name, Key: s3_key}, ExpiresInexpires_in ) logger.info(f已生成分享链接有效期 {expires_in} 秒。) return url except Exception as e: logger.error(f生成分享链接失败: {e}) return None def main(): parser argparse.ArgumentParser(description简单的S3文件传输工具) parser.add_argument(--bucket, requiredTrue, helpS3桶名) subparsers parser.add_subparsers(destcommand, help子命令) # 上传命令 upload_parser subparsers.add_parser(upload, help上传文件) upload_parser.add_argument(local_file, help本地文件路径) upload_parser.add_argument(s3_key, helpS3对象键) # 下载命令 download_parser subparsers.add_parser(download, help下载文件) download_parser.add_argument(s3_key, helpS3对象键) download_parser.add_argument(--output-dir, default., help本地输出目录) download_parser.add_argument(--overwrite, actionstore_true, help覆盖已存在的文件) # 分享命令 share_parser subparsers.add_parser(share, help生成分享链接) share_parser.add_argument(s3_key, helpS3对象键) share_parser.add_argument(--expires, typeint, default3600, help链接有效期秒) args parser.parse_args() manager S3FileManager(args.bucket) if args.command upload: success manager.upload_file(args.local_file, args.s3_key) sys.exit(0 if success else 1) elif args.command download: local_file manager.download_file(args.s3_key, args.output_dir, args.overwrite) if local_file: print(f文件已下载至: {local_file}) else: sys.exit(1) elif args.command share: url manager.get_shareable_link(args.s3_key, args.expires) if url: print(f分享链接 (有效期 {args.expires} 秒):) print(url) else: sys.exit(1) else: parser.print_help() if __name__ __main__: main()这个工具虽然简单但已经包含了核心的健壮性考虑输入验证、错误处理、日志记录、避免覆盖文件。你可以在此基础上根据需要添加更多功能比如列出桶内文件、删除对象、设置存储类别、多文件并行传输等。走到这里你已经从一个S3的“门外汉”变成了能够熟练使用其核心API进行文件操作的“实践者”。记住云服务的最佳学习方式就是动手去用在真实的项目中遇到问题、解决问题。先从这个小工具开始把它集成到你的下一个项目里相信你会对S3的简单与强大有更深的理解。
返回列表