Python 处理中文文件名的3个坑(附 Flask 上传解决函数)
最近在做一个 Flask 文件上传工具用户上传了一个叫产品图片.jpg的文件结果服务器上保存出来变成了空文件名直接报错。排查下来发现是一个老熟人的锅——secure_filename()。坑一secure_filename()会吃掉中文用 Flask 做文件上传官方示例几乎都会这么写fromwerkzeug.utilsimportsecure_filename filenamesecure_filename(产品图片.jpg)print(filename)# 输出.jpg ← 文件名没了secure_filename()的设计目标是过滤掉不安全字符但它的不安全定义里包含了所有非 ASCII 字符中文直接被扔掉。如果文件名全是中文返回的就是一个光秃秃的.jpg后续open()直接报错。坑二os.path在 Windows 上的编码陷阱在 Windows 环境下如果脚本编码和系统编码不一致os.path.exists()、os.rename()等操作中文路径时可能抛出UnicodeEncodeError: gbk codec cant encode character...解决方式是确保脚本头部声明编码并统一使用pathlib.Path替代os.path# -*- coding: utf-8 -*-frompathlibimportPath pPath(D:/上传文件/产品图片.jpg)print(p.exists())# 正常工作坑三open()写文件默认编码不是 UTF-8# 危险写法Windows 下默认 GBKwithopen(结果.txt,w)asf:f.write(中文内容)# 安全写法withopen(结果.txt,w,encodingutf-8)asf:f.write(中文内容)养成习惯凡是涉及中文内容的文件读写显式指定encodingutf-8。解决函数safe_chinese_filename()针对坑一自己封一个替代secure_filename()的函数有需要的可以直接拿去用。保留中文的同时过滤真正危险的字符importreimportuuiddefsafe_chinese_filename(filename:str)-str: 安全处理文件名支持中文。 - 保留中文、英文、数字、点、下划线、连字符 - 过滤路径穿越字符/ \\ .. 等 - 文件名为空时自动生成 UUID 文件名 # 分离文件名和扩展名if.infilename:name,extfilename.rsplit(.,1)ext.re.sub(r[^\w],,ext)# 扩展名只保留字母数字else:name,extfilename,# 只保留中文、字母、数字、下划线、连字符namere.sub(r[^\w\u4e00-\u9fff\-],_,name)namename.strip(_)# 如果处理后为空用 UUID 兜底ifnotname:nameuuid.uuid4().hex[:8]returnnameext# 测试print(safe_chinese_filename(产品图片.jpg))# 产品图片.jpgprint(safe_chinese_filename(../../../etc/passwd))# ______etc_passwdprint(safe_chinese_filename(.jpg))# a1b2c3d4.jpgUUID兜底print(safe_chinese_filename(hello world.png))# hello_world.pngFlask 中替换使用# 原来filenamesecure_filename(file.filename)# 替换为filenamesafe_chinese_filename(file.filename)总结这些坑并填上问题原因解决方案中文文件名上传后消失secure_filename()过滤非 ASCII使用自定义safe_chinese_filename()中文路径报编码错误Windows GBK 与 UTF-8 不一致改用pathlib.Path中文写入文件乱码open()默认编码非 UTF-8显式声明encodingutf-8这三个坑我都在同一个项目里踩过整理出来希望能帮你少走弯路。如果你也在用 Flask 做文件上传safe_chinese_filename()这个函数直接拿去用就行。有问题欢迎评论区交流