查看: 22|回复: 3

Python用filetype库判断文件真实类型的方法

[复制链接]
发表于 2 小时前 | 显示全部楼层 |阅读模式
在日常开发中,判断一个文件的真实类型并不像看后缀名那么简单。用户重命名文件、从网络下载资源、上传附件等场景下,后缀可能是错的,甚至完全没有后缀。Windows 系统下如果扩展名与实际格式不匹配,很多文件会无法正常打开。可靠的方案是读取文件头部字节的“特征码”,也就是所谓的魔数(Magic Number),再用这些特征去匹配文件类型。

文件头特征码的通用原理

每种文件格式的设计者通常会在文件起始位置写入固定字节,用来标识文件格式。例如 JPEG 图片以 FFD8FF 开头,PNG 图片以 89504E47 开头,GIF 以 47494638 开头,而老的 Office 文档(如 xls、doc)则以 D0CF11E0A1B11AE10000 开头。这些头长度并不统一,短的可能只需要 2 个字节,长的需要 8 个甚至更多字节。因此判断逻辑上,应该先读取足够长度的文件头,再与已有的特征码表进行前缀匹配。

基于文件头的自实现 Python 判断逻辑

可以自己写一个简化的版本,核心步骤是:读取文件前 20 个字节、将其转换成十六进制字符串、与特征码表比对,如果完全匹配则直接返回类型;如果全码匹配不到,再用前 5 位去尝试匹配码表里的前缀,避免特征码过长导致的误判。

下面是一段示例代码,展示了完整的匹配流程:
  1. import os
  2. def get_type_list():
  3.     return {
  4.         "d0cf11e0a1b11ae10000": 'xls',
  5.         '504b030414': 'docx_docx_xlsx',
  6.     }
  7. def bytes2hex(data):
  8.     hex_str = ""
  9.     for b in data:
  10.         h = "%x" % b
  11.         if len(h) % 2:
  12.             hex_str += "0"
  13.         hex_str += h
  14.     return hex_str.upper()
  15. def guess_by_file_header(filename):
  16.     if os.path.getsize(filename) == 0:
  17.         return 'empty'
  18.     with open(filename, 'rb') as f:
  19.         header = f.read(20)
  20.     header_hex = bytes2hex(header).lower()
  21.     type_table = get_type_list()
  22.     for signature, file_type in type_table.items():
  23.         if header_hex.startswith(signature):
  24.             return file_type
  25.     header_prefix = header_hex[:5]
  26.     for signature, file_type in type_table.items():
  27.         if len(signature) > 5 and signature.startswith(header_prefix):
  28.             return file_type
  29.     return 'unknown'
  30. if __name__ == '__main__':
  31.     print(guess_by_file_header(r'D:\tmp\111.xlsx'))
复制代码

这个实现适合特征码表比较少的场景。如果文件类型很多,自己维护一个庞大的特征码表就会变得低效,而且容易漏掉各种小众格式。此时可以改用现成的第三方库,让代码更简洁、覆盖面更广。

更专业的解决方案:filetype 库

filetype 是一个轻量级、无第三方依赖的 Python 库,专门用来根据文件的“魔数”推断文件类型和 MIME 类型。它支持 60 余种常见文件格式,包括图片、视频、音频、Office 文档、PDF、压缩包等,同时兼容文件路径、文件对象和二进制缓冲区。

安装方式很简单:
  1. pip install filetype
复制代码

通过路径判断文件类型

filetype 的 guess 方法接收文件路径,返回一个包含 extension 和 mime 属性的对象。如果无法识别则返回 None。注意,guess 方法会尝试打开文件并读取头部信息,因此不要传入不存在的路径。
  1. import os
  2. import filetype
  3. def detect_file(path):
  4.     kind = filetype.guess(path)
  5.     if kind is None:
  6.         print('无法识别该文件的类型')
  7.         return
  8.     print('文件名: %s' % os.path.basename(path))
  9.     print('扩展名: %s' % kind.extension)
  10.     print('MIME 类型: %s' % kind.mime)
  11. if __name__ == '__main__':
  12.     detect_file(r'D:\tmp\444.ppt')
复制代码

如果需要判断的内存数据来自网络请求、表单上传或其他字节流,可以直接使用 guess(bytes_data) 方法,避免先落盘再读取:
  1. import filetype
  2. def detect_from_bytes(data):
  3.     kind = filetype.guess(data)
  4.     if kind is None:
  5.         return 'unknown'
  6.     return kind.extension
复制代码

这种基于字节流的判断方式很适合 Web 开发中的文件上传校验。后端在接收到文件数据时,不要盲目信任前端传来的 Content-Type 或后缀,而是先截取文件头部字节,交给 filetype 判断真实格式,再决定是否允许保存。

常见文件头特征参考

如果不想引入第三方库,也可以根据下列常见格式的头字节自行扩展特征表:
  1. JPEG (jpg): FFD8FF
  2. PNG (png): 89504E47
  3. GIF (gif): 47494638
  4. TIFF (tif): 49492A00
  5. BMP (bmp): 424D
  6. DWG (dwg): 41433130
  7. PSD (psd): 38425053
  8. RTF (rtf): 7B5C727466
  9. XML (xml): 3C3F786D6C
  10. HTML (html): 68746D6C3E
  11. Outlook PST (pst): 2142444E
  12. MS Office (xls/doc): D0CF11E0
  13. MDB (mdb): 5374616E64617264204A
复制代码

实际使用时,特征码匹配一般做前缀匹配,同时要注意文件过短或为空的情况。空文件直接返回空类型,不要继续读取。另外在 Python 3 中,文件必须以二进制模式打开,否则字节读取结果会不符合预期。

性能与适用场景小结

自实现方案适合特征表固定、格式单一的内部工具,但如果需要支持大量常见格式,filetype 库无疑是更好的选择。它的实现基于内置的魔数字典,匹配速度很快,代码可读性也更高。在文件上传服务、爬虫下载资源归类、安全检测脚本中,filetype 都能有效提升文件类型判断的准确性和开发效率。

判断结果可以作为后续处理的依据,但也要注意,基于文件头的判断只能说明“文件的开头字节符合某类格式特征”,并不代表整个文件内容完整有效。对于重要文件,还应结合格式解析器做进一步验证。
回复

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python用filetype库判断文件真实类型的方法

学到不少,之前确实吃过后缀名不对的亏。自实现那段看了下,思路清晰,适合定制;后面引到 filetype 库也挺自然的,以后处理上传文件可以省不少事。
回复 支持 反对

使用道具 举报

发表于 2 小时前 | 显示全部楼层

Re: Python用filetype库判断文件真实类型的方法

帖子讲得很实用,尤其是强调不能光看后缀名这一点,做后端上传校验时特别容易踩坑。我自己之前就遇到过用户把txt改成png传上来,结果存到服务器之后图片打不开。用filetype库确实省事很多,不用自己维护一堆魔数表。 另外想补充一个小点:如果是在生产环境用filetype,建议把数据从请求流里读出来之后直接传bytes,避免先写临时文件再读一遍,既能减少IO又能避免并发时候的文件名冲突。还有,判断结果最好落到白名单机制上,比如只允许jpg、png、pdf几种,这样即使识别不了也能安全兜底。
回复 支持 反对

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Python用filetype库判断文件真实类型的方法

学到了,filetype库确实比手写魔数匹配省心很多。之前都是自己维护特征码表,遇到冷门格式就头疼,这个库能覆盖60多种常见格式,日常够用了。特别赞同你说的上传校验不能信任前端传的Content-Type这一点,很多安全问题就是这么来的。建议还可以加一句实际使用时记得处理下文件大小限制,避免恶意大文件直接读到内存里。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-8-25 15:14 , Processed in 0.022943 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部