S3文件夹预签名URL无法批量下载文件,如何解决?
首先得说清楚为啥你遇到这个情况:AWS S3本质上没有真正的「文件夹」,所谓的文件夹只是对象的前缀而已。你直接给s3://bucket-name/large-folder/生成预签名URL时,CLI其实是给这个不存在的「虚拟文件夹对象」签了权,所以下载到的是0字节空文件,而单个文件的URL正常是因为指向了实际存在的对象。
下面给你几个可行的解决办法:
方法1:批量生成每个文件的预签名URL
用AWS CLI遍历文件夹下所有文件,逐个生成预签名URL,把这些URL整理到一个文本文件里共享给对方就行。
直接在云Shell里执行这个命令:
aws s3 ls s3://bucket-name/large-folder/ --recursive | awk '{print $4}' | while read file; do if [ -n "$file" ]; then echo "$(aws s3 presign s3://bucket-name/$file --expires-in 604800 --region us-east-1) -> $file" fi done > presigned_urls.txt
执行完后,当前目录会生成presigned_urls.txt,里面每行都是一个文件的预签名URL和对应的文件名,把这个文件发给外部用户,他们就能逐个下载文件了。
方法2:把整个文件夹打包成ZIP再生成预签名URL
如果想让用户一次性下载所有文件,最方便的是把文件夹内容打包成ZIP,再给这个ZIP文件生成预签名URL。
适合小体量文件夹的云Shell直接打包
如果文件夹总文件大小不大(云Shell有存储和带宽限制),直接用下面的命令:
# 把文件夹内容打包成ZIP并上传到S3临时路径 aws s3 cp s3://bucket-name/large-folder/ - --recursive | zip -r - large-folder/ > large-folder.zip aws s3 cp large-folder.zip s3://bucket-name/temp/large-folder.zip # 给这个ZIP生成预签名URL aws s3 presign s3://bucket-name/temp/large-folder.zip --expires-in 604800 --region us-east-1
执行完后会输出ZIP文件的预签名URL,用户下载这个ZIP就能得到所有Parquet文件了。记得之后可以删掉临时的ZIP文件节省存储。
适合大文件夹的Lambda打包方案
如果文件夹里文件太多、总容量大,云Shell搞不定,就用Lambda来生成ZIP:
- 新建一个Python Lambda函数,区域选us-east-1,权限给足S3的读写权限。
- 把下面的代码粘贴进去,替换
bucket和prefix的值:
import boto3 import zipfile from io import BytesIO s3 = boto3.client('s3', region_name='us-east-1') bucket = 'bucket-name' # 替换成你的存储桶名 prefix = 'large-folder/' # 替换成目标文件夹前缀 temp_zip_key = 'temp/large-folder.zip' # 临时ZIP文件的存储路径 # 内存中打包文件 zip_buffer = BytesIO() with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zf: # 分页遍历S3前缀下的所有文件 paginator = s3.get_paginator('list_objects_v2') for page in paginator.paginate(Bucket=bucket, Prefix=prefix): for obj in page.get('Contents', []): # 跳过虚拟文件夹本身 if obj['Key'] == prefix: continue # 获取文件内容并写入ZIP obj_data = s3.get_object(Bucket=bucket, Key=obj['Key']) # 去掉前缀,保留文件相对路径 zf.writestr(obj['Key'].replace(prefix, ''), obj_data['Body'].read()) # 把ZIP上传回S3 zip_buffer.seek(0) s3.put_object(Bucket=bucket, Key=temp_zip_key, Body=zip_buffer) # 生成预签名URL presigned_url = s3.generate_presigned_url('get_object', Params={'Bucket': bucket, 'Key': temp_zip_key}, ExpiresIn=604800) print(presigned_url)
- 执行Lambda函数,控制台会输出ZIP文件的预签名URL,用户下载这个URL就能拿到所有文件了。
方法3:启用静态网站托管共享文件列表(需配置权限)
如果你的存储桶可以开启静态网站托管,也可以通过这种方式让用户看到文件列表并下载:
- 登录S3控制台,找到你的存储桶,开启「静态网站托管」,设置索引文档(比如
index.html)。 - 给存储桶配置权限,允许外部用户的
s3:GetObject和s3:ListBucket操作。 - 生成静态网站前缀的预签名URL:
aws s3 presign https://bucket-name.s3-website-us-east-1.amazonaws.com/large-folder/ --expires-in 604800 --region us-east-1
用户访问这个URL会看到文件夹下的所有文件列表,点击就能下载(前提是每个文件也有对应的访问权限,或者你也可以给每个文件批量生成预签名URL放在列表里)。不过这种方式配置相对麻烦,不如前两种直接。
内容的提问来源于stack exchange,提问作者Ritab

