脚本专家 发表于 3 天前

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

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

文件头特征码的通用原理

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

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

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

下面是一段示例代码,展示了完整的匹配流程:


import os

def get_type_list():
    return {
      "d0cf11e0a1b11ae10000": 'xls',
      '504b030414': 'docx_docx_xlsx',
    }

def bytes2hex(data):
    hex_str = ""
    for b in data:
      h = "%x" % b
      if len(h) % 2:
            hex_str += "0"
      hex_str += h
    return hex_str.upper()

def guess_by_file_header(filename):
    if os.path.getsize(filename) == 0:
      return 'empty'
    with open(filename, 'rb') as f:
      header = f.read(20)
    header_hex = bytes2hex(header).lower()
    type_table = get_type_list()
    for signature, file_type in type_table.items():
      if header_hex.startswith(signature):
            return file_type
    header_prefix = header_hex[:5]
    for signature, file_type in type_table.items():
      if len(signature) > 5 and signature.startswith(header_prefix):
            return file_type
    return 'unknown'

if __name__ == '__main__':
    print(guess_by_file_header(r'D:\tmp\111.xlsx'))


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

更专业的解决方案:filetype 库

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

安装方式很简单:


pip install filetype


通过路径判断文件类型

filetype 的 guess 方法接收文件路径,返回一个包含 extension 和 mime 属性的对象。如果无法识别则返回 None。注意,guess 方法会尝试打开文件并读取头部信息,因此不要传入不存在的路径。


import os
import filetype

def detect_file(path):
    kind = filetype.guess(path)
    if kind is None:
      print('无法识别该文件的类型')
      return
    print('文件名: %s' % os.path.basename(path))
    print('扩展名: %s' % kind.extension)
    print('MIME 类型: %s' % kind.mime)

if __name__ == '__main__':
    detect_file(r'D:\tmp\444.ppt')


如果需要判断的内存数据来自网络请求、表单上传或其他字节流,可以直接使用 guess(bytes_data) 方法,避免先落盘再读取:


import filetype

def detect_from_bytes(data):
    kind = filetype.guess(data)
    if kind is None:
      return 'unknown'
    return kind.extension


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

常见文件头特征参考

如果不想引入第三方库,也可以根据下列常见格式的头字节自行扩展特征表:


JPEG (jpg): FFD8FF
PNG (png): 89504E47
GIF (gif): 47494638
TIFF (tif): 49492A00
BMP (bmp): 424D
DWG (dwg): 41433130
PSD (psd): 38425053
RTF (rtf): 7B5C727466
XML (xml): 3C3F786D6C
HTML (html): 68746D6C3E
Outlook PST (pst): 2142444E
MS Office (xls/doc): D0CF11E0
MDB (mdb): 5374616E64617264204A


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

性能与适用场景小结

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

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

热心网友7 发表于 3 天前

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

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

热心网友7 发表于 3 天前

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

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

热心网友6 发表于 3 天前

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

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