zipfile 解压文件名乱码
zipfile 中文文件名 解压乱码
上传文件功能模块需求及BUG现象:
环境
mac
django 1.11.13
python 3.6
功能需求:
上传一个.zip格式的压缩文件
解压该test.zip压缩文件
解压zip文件时,遍历其目录下所有子文件,同时计算出单个子文件的有效代码行数
这时,发现解压后的子文件名中文出现乱码,如下图:
BUG截图
解决思路
1、解压过程中,发现解压的文件内容正常;
2、使用的是第三方库zipfile模块,因为第1步得到正常的文件内容,本地业务逻辑可先不排查;
3、首先检查zipfile的源码中,针对编码/解码的执行过程仔细排查发现:
zipfile中根据文件 flag 检测的时候,只支持 cp437 和 utf-8
找到下面两处,并追加修正后,乱码现象解决:(追加的decode编码可根据实际情况修改,如win环境下乱码采用.decode('gbk'))
# zipfile.py # 第一处 if flags & 0x800: # UTF-8 file names extension filename = filename.decode('utf-8') else: # Historical ZIP filename encoding filename = filename.decode('cp437') # 追加此句 filename = filename.encode("cp437").decode('utf-8') # 第二处 if zinfo.flag_bits & 0x800: # UTF-8 filename fname_str = fname.decode("utf-8") else: fname_str = fname.decode("cp437") # 追加此句 fname_str = fname_str.encode("cp437").decode('utf-8')
解决后,正常显示:
上传功能源码
import zipfile # 指定想要统计的文件类型 whitelist = ['py'] # 遍历文件, 递归遍历文件夹中的所有 def getFile(basedir):
# 存储上传解压后的文件列表
filelists = [] for parent, dirnames, filenames in os.walk(basedir): # for dirname in dirnames: # getFile(os.path.join(parent,dirname)) #递归 for filename in filenames: ext = filename.split('.')[-1] # 只统计指定的文件类型,略过一些log和cache文件 if ext in whitelist: filelists.append(os.path.join(parent, filename)) # 统计一个文件的行数 def countLine(fname): count = 0 single_quotes_flag = False double_quotes_flag = False with open(fname, 'rb') as f: for file_line in f: file_line = file_line.strip() # print(file_line) # 空行 if file_line == b'': pass # 注释 # 开头 elif file_line.startswith(b'#'): pass # 注释 单引号 ''' 开头 elif file_line.startswith(b"'''") and not single_quotes_flag: single_quotes_flag = True # 注释 中间 和 ''' 结尾 elif single_quotes_flag == True: if file_line.endswith(b"'''"): single_quotes_flag = False # 注释 双引号 """ 开头 elif file_line.startswith(b'"""') and not double_quotes_flag: double_quotes_flag = True # 注释 中间 和 """ 结尾 elif double_quotes_flag == True: if (file_line.endswith(b'"""')): double_quotes_flag = False # 代码 else: count += 1 # print(fname + '----', count) # 单个文件行数 print(fname, '----count:', count) return count def un_zip(file_name): """unzip zip file""" zip_file = zipfile.ZipFile(file_name) # <zipfile.ZipFile filename='/Users/limengjie/Desktop/pyhon/SMS0614/upload_file/0617.zip' mode='r'> if os.path.isdir(file_name + "_files"): pass else: os.mkdir(file_name + "_files") for names in zip_file.namelist(): zip_file.extract(names, file_name + "_files/") # 遍历解压后得到的文件夹, 递归遍历文件夹中的所有子文件 getFile(file_name + "_files") totalline = 0 # 遍历解压后的文件列表,统计单个文件的行数并汇总 for filelist in filelists: totalline = totalline + countLine(filelist) zip_file.close() # 返回上传文件所有子文件的总行数 return totalline
补充:上传业务逻辑代码
class Uploading(View): def get(self, request): return render(request, "uploading.html", ) def post(self, request): # 1、拿到压缩文件对象file_obj file_obj = request.FILES.get("user_file") file_name = os.path.join(file_dir, file_obj.name) file_size = file_obj.size with open(file_name, "wb") as f: for line in file_obj.chunks(): f.write(line) # 2、解压压缩文件,并获取代码行数属性 total_line = un_zip(file_name) # 3、单个文件进行文件对象实例化,文件名,文件大小,代码行数 models.FileObj.objects.create( fileName=file_obj.name, fileSize=file_size, fileLineCount=total_line ) return redirect("/upload_file/")
优化需求
统计行数优化:mac环境解压文件时,系统会自动追加__MACOSX文件夹,为了不遍历此文件夹,需补充:
在getFIle函数中修改,即可:
# MAC环境下略过__MACOSX文件夹 if "__MACOSX" in dirnames: pop_index = dirnames.index("__MACOSX") dirnames.pop(pop_index)
优化后,得到我们需要的结果:
(完)