案例: 在S3存储桶中,有一个包含大量图像的大型zip文件。是否有一种方法可以不下载整个文件,读取元数据或其他内容,以知道zip文件中有多少个文件?
当文件在本地时,在Python中我可以将其打开为zipfile(),然后调用namelist()方法,该方法返回所有文件的列表,我可以对其进行计数。但是不确定在文件驻留在S3中时如何做到这一点而无需下载它。如果使用Lambda实现可能最好。
案例: 在S3存储桶中,有一个包含大量图像的大型zip文件。是否有一种方法可以不下载整个文件,读取元数据或其他内容,以知道zip文件中有多少个文件?
当文件在本地时,在Python中我可以将其打开为zipfile(),然后调用namelist()方法,该方法返回所有文件的列表,我可以对其进行计数。但是不确定在文件驻留在S3中时如何做到这一点而无需下载它。如果使用Lambda实现可能最好。
我认为这将解决你的问题:
import zlib
import zipfile
import io
def fetch(key_name, start, len, client_s3):
"""
range-fetches a S3 key
"""
end = start + len - 1
s3_object = client_s3.get_object(Bucket=bucket_name, Key=key_name, Range="bytes=%d-%d" % (start, end))
return s3_object['Body'].read()
def parse_int(bytes):
"""
parses 2 or 4 little-endian bits into their corresponding integer value
"""
val = (bytes[0]) + ((bytes[1]) << 8)
if len(bytes) > 3:
val += ((bytes[2]) << 16) + ((bytes[3]) << 24)
return val
def list_files_in_s3_zipped_object(bucket_name, key_name, client_s3):
"""
List files in s3 zipped object, without downloading it. Returns the number of files inside the zip file.
See : https://dev59.com/3J7ha4cB1Zd3GeqPjG7W
Based on : https://dev59.com/GlUK5IYBdhLWcg3wuSFD
bucket_name: name of the bucket
key_name: path to zipfile inside bucket
client_s3: an object created using boto3.client("s3")
"""
bucket = bucket_name
key = key_name
response = client_s3.head_object(Bucket=bucket_name, Key=key_name)
size = response['ContentLength']
eocd = fetch(key_name, size - 22, 22, client_s3)
# start offset and size of the central directory
cd_start = parse_int(eocd[16:20])
cd_size = parse_int(eocd[12:16])
# fetch central directory, append EOCD, and open as zipfile!
cd = fetch(key_name, cd_start, cd_size, client_s3)
zip = zipfile.ZipFile(io.BytesIO(cd + eocd))
print("there are %s files in the zipfile" % len(zip.filelist))
for entry in zip.filelist:
print("filename: %s (%s bytes uncompressed)" % (entry.filename, entry.file_size))
return len(zip.filelist)
if __name__ == "__main__":
import boto3
import sys
client_s3 = boto3.client("s3")
bucket_name = sys.argv[1]
key_name = sys.argv[2]
list_files_in_s3_zipped_object(bucket_name, key_name, client_s3)
我改进了已有的解决方案 - 现在它还可以处理大于4GiB的文件:
import boto3
import io
import struct
import zipfile
s3 = boto3.client('s3')
EOCD_RECORD_SIZE = 22
ZIP64_EOCD_RECORD_SIZE = 56
ZIP64_EOCD_LOCATOR_SIZE = 20
MAX_STANDARD_ZIP_SIZE = 4_294_967_295
def lambda_handler(event):
bucket = event['bucket']
key = event['key']
zip_file = get_zip_file(bucket, key)
print_zip_content(zip_file)
def get_zip_file(bucket, key):
file_size = get_file_size(bucket, key)
eocd_record = fetch(bucket, key, file_size - EOCD_RECORD_SIZE, EOCD_RECORD_SIZE)
if file_size <= MAX_STANDARD_ZIP_SIZE:
cd_start, cd_size = get_central_directory_metadata_from_eocd(eocd_record)
central_directory = fetch(bucket, key, cd_start, cd_size)
return zipfile.ZipFile(io.BytesIO(central_directory + eocd_record))
else:
zip64_eocd_record = fetch(bucket, key,
file_size - (EOCD_RECORD_SIZE + ZIP64_EOCD_LOCATOR_SIZE + ZIP64_EOCD_RECORD_SIZE),
ZIP64_EOCD_RECORD_SIZE)
zip64_eocd_locator = fetch(bucket, key,
file_size - (EOCD_RECORD_SIZE + ZIP64_EOCD_LOCATOR_SIZE),
ZIP64_EOCD_LOCATOR_SIZE)
cd_start, cd_size = get_central_directory_metadata_from_eocd64(zip64_eocd_record)
central_directory = fetch(bucket, key, cd_start, cd_size)
return zipfile.ZipFile(io.BytesIO(central_directory + zip64_eocd_record + zip64_eocd_locator + eocd_record))
def get_file_size(bucket, key):
head_response = s3.head_object(Bucket=bucket, Key=key)
return head_response['ContentLength']
def fetch(bucket, key, start, length):
end = start + length - 1
response = s3.get_object(Bucket=bucket, Key=key, Range="bytes=%d-%d" % (start, end))
return response['Body'].read()
def get_central_directory_metadata_from_eocd(eocd):
cd_size = parse_little_endian_to_int(eocd[12:16])
cd_start = parse_little_endian_to_int(eocd[16:20])
return cd_start, cd_size
def get_central_directory_metadata_from_eocd64(eocd64):
cd_size = parse_little_endian_to_int(eocd64[40:48])
cd_start = parse_little_endian_to_int(eocd64[48:56])
return cd_start, cd_size
def parse_little_endian_to_int(little_endian_bytes):
format_character = "i" if len(little_endian_bytes) == 4 else "q"
return struct.unpack("<" + format_character, little_endian_bytes)[0]
def print_zip_content(zip_file):
files = [zi.filename for zi in zip_file.filelist]
print(f"{len(files)} files: {files}")
parse_little_endian_to_int
应该被解析为unsigned
,否则我们可能会得到cd_start的负值... - Jan Rüeggeocd_record
,例如在return zipfile.ZipFile(io.BytesIO(central_directory + zip64_eocd_record + zip64_eocd_locator + eocd_record))
中...既然我们已经有了zip64_eocd_record
,那么为什么在__zip64__代码块的return
语句末尾还需要eocd_record
呢? - Vivek Puurkayastha [中央目录头n]
[zip64中央目录结束记录]
[zip64中央目录定位器]
[最后的中央目录记录]
包括“标准”EOCD和zip64 EOCD。 - kwiecien您可以尝试下载存档的一部分(例如前1MB),并使用jar
工具查看文件列表和属性:
jar vt < first-part-of-archive.zip
你可以使用 subprocess 模块在 Python 中获取这些数据。
jar
工具允许读取不完整zip文件的内容(Python模块或unzip
工具将无法正常工作)。 - Stanislav Ivanov尝试使用以下s3命令获取gz格式文件的计数
aws s3 cp <s3 file uri> - | gunzip -c | grep -i '<Search String>' | wc -l
例子
aws s3 cp s3://test-bucket/test/test.gz - | gunzip -c | grep -i 'test' | wc -l
目前,您无法在不下载zip文件的情况下获取此类信息。上传到s3时,您可以将所需信息存储为zip文件的元数据。
正如您在问题中提到的那样,使用Python函数,我们能够在不解压缩的情况下获取文件列表。您可以使用相同的方法获取文件计数并将其添加为特定文件的元数据,然后将其上传到S3。
希望这有所帮助,谢谢。