如何快速批量检索损坏的图片文件—python开发学习笔记(一)

浏览: 62 次浏览 作者: 去年夏天 分类: Ubuntu,Windows,佳软推荐,技术文章 发布时间: 2025-03-19 20:20

为了解决几十万量级图片库内异常图片的检测,折腾出了一个基于 python 的图片检测程序。


一、混乱的图片仓库

用前一段发现的本地 AI 图片视频搜索引擎 MaterialSearch 整理十几年间积累的几十万张图片时,遇到了一个令人崩溃的场景:有上百张图片报损坏,经过部分核查,很多文件打开后呈现诡异色块,亦或者只有半截图,还有些文件大小为 0 KB。这些损坏的图片零散的散布在数千个子文件夹中,手动一个一个检查无异于大海捞针,累死也搞不定。于是 VS code 启动!!


二、初版方案

用 Pillow 暴力验证,直接用 Pillow verify()看看是否报错来解决。

代码V1.0方案(基础检测逻辑)

from PIL import Image

def check_img_v1(path):
    try:
        Image.open(path).verify()
        return True
    except:
        return False

V1.0方案的情况
1. 误报文件:很多图片会报损坏,但是用图片浏览器打开却十分正常,经过研究之后才知道,原来大量网站在使用一种叫做渐进式JPEG的技术,通过将图像数据分为多个扫描逐层渲染,可以在网速不好时图片先绘制出低分辨率的模糊轮廓,随着数据被下载逐步变为清晰图像(现代图片编码如WEBP、AVIF也都有类似的渐进式加载机制)。这导致需要完整解码才能验证所有扫描数据。因此被verify()误认为损坏。
2. 漏检文件:未完整下载的图片有时也能通过验证。
3. 性能问题:慢,按照测试计算,10万张图片的检测起码需要耗时4、5小时了。


三、改进方案

经过对 MaterialSearch 日志报错图片的抽查,发现损坏的文件主要是文件不完整导致的半截图,于是我打算改为:先检查文件结尾是否存在结束符来判定图片是否损坏,然后再做进一步检查。

代码V2.0(尾部校验部分代码)

def check_img_v2(path):
    with open(path, 'rb') as f:
        f.seek(-32, 2) #只用获取文件最后32字节就行
        trailer = f.read()
        if path.lower().endswith('.jpg'):
            return trailer.endswith(b'\xff\xd9')  # JPEG的结束符
        elif path.lower().endswith('.png'):
            return trailer.endswith(b'\xaeB`\x82')  # PNG的IEND块

V2.0方案的情况
1. 捕获到了异常文件 :下载一半的文件确实被检测出来了。
2. 检测了个寂寞 :如果图片附加了元数据,图片文件很可能就不是以\xff\xd9结尾了,结果就是1000张的测试图片,在尾部检测部分逻辑,有800多张都报了损坏……想快速检查了个寂寞。


四、再次优化

使用img.load() 强制加载所有数据。对渐进式jpeg图片做特殊处理逻辑。

代码V3.0(验证部分的修改)

def check_img_v3(path):
    try:
        with open(path, 'rb') as f:  
            img = Image.open(f)
            img.load()  # 强制加载完整图片

            # 特殊处理渐进式JPEG
            if img.format == 'JPEG' and 'progressive' in img.info:
                img.tile = []  

            return True
    except Exception as e:
        print(f"损坏文件: {path} | 错误类型: {type(e).__name__}")
        return False

V3.0方案的情况
1. 漏报率下降了很多
2. 渐进式JPEG兼容处理
3. 打印异常类型方便处理
4. 实际代码中自己傻逼了在verify()之后调用load(),导致文件指针不可用,说人话就是:代码逻辑中verify()做完检查后,就把图片文件关闭了,load()啥也获取不到。
5. 性能就很一般了,基本和初版差不多的速度。


五、终局之战

又经过一番研究和查证其实 Pillow verify()对渐进式图片检测是没问题的,误报率并没有我在V1测试时那么高,只是我本地环境的 Pillow 版本不够新而已,但也确实会有漏报。只用load()也会有漏报,有一点误报可以接受,但是漏报就无法接受了,所以还是需要联合检查。

最终决定采用如下逻辑
1. 先检测文件路径是否存在,收集所有路径。
2. img.verify()先上
3. 同一个循环内使用img.load()再来一次检测
4. 并行处理加快处理速度
5. 不在控制台显示扫描 log,毕竟绝大部分图片都是好的,没问题的显示出来无意义,只显示有问题的又很容易看起来像是卡住了,所以用 tqdm 做个进度条。还能大概估计下完成时间。
6. 用 jinja2 做个 html 格式的检测报告,毕竟在终端里复制粘贴起来也不方便。

代码V4.0(完善使用)

实在太长了就放github上了:img_validator.py

命令:python img_validator.py <"目录路径"> [并发数]

ex:python img_validator.py "D:\Download\图片" 8

最终会在脚本的同级目录下生成 html 格式的检测报告image_validation_report.html

  • 路径最好用””框住
  • 并发数可以不填,默认使用CPU全部线程。除非你用非常大的图,不然全线程跑也费不了多少CPU占用。
  • 理论上 10W 张图片在 12 线程下,用时应该半小时都不到,不过实际上受制于仓库盘使用的是 HDD 只能到 20~40files/s,机械硬盘的读出速度跟不上检测速度,特别是到最后20%的时候,速度更是掉到只有个位数了。18W 张图片的文件夹,检测用时接近5个小时,平均速度 ≈ 11 files/s
  • windows 和 unix 系统都可用,代码会自动处理两者的路径差异。

为什么会有这么多图片损坏

自己十年前写的P站抓取代码不完善,如果因为网络超时导致图片下载失败,爬虫会重试,但是之前损坏的图片有可能并不会被正确清除(删除部分代码没有正确处理超长文件名和带特殊符号的文件名),虽然带问题代码只使用了从14年到16年这大约一坤年,但是也积累了接近 600 张问题文件。不过倒是挺奇怪的,抓全年龄的部分出现大量这种问题,抓R18的几乎就没出错过,这是又为什么呢,沉思中…………

6 条评论
  • 石樱灯笼

    2025-03-21 01:49

    我上次遇到图片大范围损坏还是iphone

    1. 去年夏天

      2025-03-24 10:15

      嘛,我遇到最大规模是硬盘炸了……坏扇区上所有东西都坏了。

  • ACEVS

    2025-03-20 10:35

    图片大师这么多图.
    损坏原因是硬盘坏道还是?

    1. 去年夏天

      2025-03-20 10:49

      下载的时候就有问题,就如最后那一段说的:纯粹是代码问题,图片下载出错超时会重试,但是代码里没有正确删除错误的图片(很多P站的图,作者名+作品名会超级长)所以导致实际上是一张好图 + 一张半截图。

      之前因为磁盘坏道损坏过几次(我是炸硬盘体质,每年都要炸一个U盘/硬盘),不过家里NAS和云盘里都还有一份备份,这次属于是最开始的原始文件就有问题……所以备份的也是有问题的版本。

  • obaby

    2025-03-19 20:50

    类似的东西我也写过,不过现在不爬图片了

    1. 去年夏天

      2025-03-19 21:07

      主要是:反正是自动定时爬取的,看不看的先存了再说:joy:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理