☰
自建图库第五天:图片审核链路与批量抓取实战
2026/10/2 2:12:42 网站建设 项目流程

做智能协图云图库这个连续开发项目,今天是第五天。前四天把上传、归类、检索、协作这几块打通之后,图库已经能正常运转了,但有一个问题一直悬在头上:用户传进来的图片,怎么保证安全合规?网上找的素材,怎么高效批量捞回来?这两件事不解决,图库就只是个本地文件夹,谈不上“智能”,更谈不上“云图库”。

正好最近后台也收到不少人问“有没有无审核AI生成图片的软件”“一键生成图片无审核哪家好”,我的态度很明确:自建图库绝不能走“无审核”这条路。审核不是给用户添堵,而是给整个图库兜底。这篇就把我第五天做的事情完整拆开讲,图片审核链路怎么搭、批量抓取怎么落地、后续入库巡检怎么做,以及这中间踩过的几个典型的坑,全部记录下来。

1. 图片审核模块的整体考量

1.1 为什么自建图库必须把审核当核心

“智能协图云图库”这个名字听起来很技术,但本质上做的是内容资产的管理。内容资产最怕三样东西:违规图片、侵权图片、重复垃圾图。这三样不处理干净,图库就会变成“内容垃圾桶”,轻则被合作方嫌弃,重则给自己惹麻烦。

有人觉得审核是平台方的事情,自己搭的内部图库不需要。这个想法很容易出问题。只要图库面向的不只是自己一个人,哪怕只是三五人的小团队,就一定会有人传错图、传错内容。更不用说如果后续接入了AI生成图片的能力,AI生成的内容质量参差不齐,其中偶尔会出现非常“出格”的图。如果直接入库,哪天被人截图出去,整个项目都跟着背锅。

我第五天做审核模块的时候,定的目标是:上传的图片在入库前必须经过一次完整检测,正常情况下五秒内返回审核结果,只有检测置信度不高的图片才需要人工介入。这个目标既不追求极端“零审核”,也不搞“全量人工”,而是把机器和人的优势组合起来。

1.2 审核方案选型:机器初筛 + 人工兜底

市面上主流的图片审核方案大致有三类:

方案类型优点缺点适用场景
完全人工审核判断准、能处理复杂语义成本高、速度慢、容易疲劳图片量极小的内部图库
完全机器审核响应快、可扩展误杀率高、边界case处理不了大规模公开平台
机器初筛 + 人工兜底兼顾速度与准确率、成本可控需要维护两套逻辑中小规模垂直图库

我选的是第三类,也是最接近实际生产环境的一种。机器负责处理那些“一眼就知道有问题”的图片,以及“一眼就知道没问题”的图片,剩下大约15%到20%的模糊地带交给人来判断。这个比例不是拍脑袋定的,我拿一个千张图片的测试集跑过,机器直接判定通过的约占70%,直接拦截的约占12%,剩下18%的图片在色情、低俗、敏感场景、水印遮挡等维度上机器置信度不够,进入人工待审队列。

这里要强调一下,机器初筛不是用一套规则打天下,而是需要根据图库的实际图片来源持续校准阈值。比如做设计素材图库和做摄影作品图库,对裸体、暴力、广告水印的判断标准是完全不一样的。阈值放在配置中心,随时可以调,而不是写死在代码里,这是我从第一天就开始坚持的做法。

1.3 审核维度的设计

审核维度我分成三个层级。

第一层是合规性审核,包括色情低俗识别、违禁品识别、以及涉及政治和历史的敏感内容识别。这一层最重要,一旦发现直接拦截,不需要人工复议。涉及政治和历史领域的图片,我采用的是从严策略,哪怕只是疑似,也先进人工复核,由人工确认无误后才能通过。

第二层是质量审核,包括图片的分辨率是否达标、是否严重模糊、是否为纯色占位图、是否带明显的推广水印或二维码。质量不合格的图片不是违规,但入库会拉低整个图库的品质。这类图片有两种处理方式:分辨率不足的自动压缩成缩略图标签,模糊的图片标记为“低质量”并在检索权重里降权,而不是直接删除。

第三层是重复性审核,主要用感知哈希来做。同一个素材被不同用户重复上传,或者AI连续生成多张相似图,如果不做去重,检索结果就会被一堆几乎相同的图淹没。感知哈希的思路是把图片缩小成8x8的灰度图,计算64位指纹,两个指纹的海明距离小于10就判定为近似重复。这个算法非常快,一张普通图片的PHash计算时间大约在二十毫秒左右。

2. 审核规则与核心实现

2.1 机器审核链路怎么做

整个审核流程是一个异步管道。用户上传图片或者从外部抓取图片进入之后,不会直接落库,而是先进入一个待审核队列。队列里每张图片都要经过五个步骤:格式校验、元数据清洗、内容特征提取、规则引擎判定、入库或转人工。

格式校验是最容易被忽略的一步。很多图片上传过来的时候,后缀是jpg,但实际编码是PNG,甚至有些直接把文本文件改了后缀伪装成图片。如果不对格式做校验,后面的图像分析函数很可能因为解码失败直接崩溃。我自己用的是Pillow的verify()方法,在解码前先校验文件头,文件头不符的直接拒绝入库。

元数据清洗这步要处理的是EXIF信息。手机拍摄的照片会带GPS信息、设备信息、拍摄时间等隐私数据。图库如果直接把这些信息暴露给协作者,等于在帮别人泄露隐私。我在这一步做的是把非白名单的EXIF字段全部剥掉,只保留基本的版权信息和色彩描述。

内容特征提取是机器审核的核心,也是耗时最多的环节。我试过直接调第三方审核API,延迟和成本都控制不住,后来改成本地跑轻量级的特征检测模型加规则组合。比如肤色检测,先把图片从RGB转成YCbCr色彩空间,为什么用YCbCr而不是RGB?因为肤色在YCbCr空间里的分布更集中,Cb和Cr分量在特定范围内聚类明显,不受亮度变化影响太大。检测到肤色像素占比超过30%,就标记为高风险图片进入人工队列。

2.2 一个能落地的规则引擎示例

多路检测跑完之后,所有结果汇聚到规则引擎里做综合判断。我给的规则不是简单的加加减减,而是每种检测结果带一个权重,权重之和超过阈值就触发对应动作。伪代码逻辑大概是这样的:

# 检测结果权重配置,实际生产里放配置中心 RULES = { "nude_score": {"weight": 0.4, "threshold": 0.7}, "skin_ratio": {"weight": 0.3, "threshold": 0.6}, "violence_score": {"weight": 0.5, "threshold": 0.8}, "qrcode_detected": {"weight": 0.5, "threshold": 0.7}, "text_politics_score": {"weight": 0.9, "threshold": 0.5}, } def audit_image(image_path): features = extract_features(image_path) total_score = 0 action = "PASS" for rule_name, rule in RULES.items(): score = features.get(rule_name, 0) if score >= rule["threshold"]: total_score += rule["weight"] if total_score >= 1.0: action = "REJECT" elif total_score >= 0.5: action = "REVIEW" return action

这个规则引擎写起来不难,难点在于权重的调校。我踩过的坑是规则之间相互独立,实际图片往往是多特征叠加的。比如一张图片既有一点裸露又带二维码,单个维度都够不上拦截阈值,但综合判断就知道这大概率是低质营销图。后来我在权重设计上加了“综合分叠加”逻辑,两张小问题叠加也会触发拦截,实测效果好了不少。

2.3 人工审核工作台

机器审核判定为REVIEW的图片,会进入一个人工审核队列。人工审核工作台不需要做得花哨,但一定要高效。我做的是一个极简页面,左右两栏布局:左栏是待审图片瀑布流,点击任意一张放大预览;右栏是这张图片的检测结果明细,包括每一项检测得分、图片原始链接、上传者信息和历史操作记录。

操作按钮只有三个:通过、拒绝、删除。通过就直接入库;拒绝的话必须选择拒因,拒因从配置项里下拉选择;删除则是彻底移除并记录审计日志。审核员还支持键盘快捷键,按方向键切换下一张、按快捷键快速操作,目的就是减少鼠标移动距离,一个人一天处理两三千张图片是不成问题的。

我实际用下来的感受是,人工审核队列最大的价值是“校准机器”。每天抽一小部分已通过审核的图片回看,把审核员不认可的结果导出成修正样本,重新喂给规则引擎调阈值。这样跑了两周,机器审核的准确率从最初的78%提升到了93%,人工介入比例从18%降到了11%。

3. 图片批量抓取方案设计

3.1 抓取之前先想清楚三件事

图库里的图片如果全靠用户一张张传,内容增长太慢。批量抓取是图库快速扩充素材库的必经之路。实操之前,有三个问题必须先想清楚:版权、robots协议、抓取频率。

版权问题是头号问题。不是所有网上图片都能随便抓进自己的图库里,很多站点在页面底部明确写了禁止采集。我做图库的原则是优先抓取无版权限制的站点,比如一些明确声明使用CC0协议的高质量图库站;对于有版权声明但允许非商业引用的站点,我只抓取缩略图和元数据,不抓取原图;对于明确禁止爬虫的站点,直接绕过不碰。有些人觉得设置一个随机UA穿越对方反爬就是技术牛,但这个思路本身就是给项目埋雷。

robots协议这关别忽略。抓取之前先用robots.txt检查目标站点的抓取许可,这是一个从业者最基本的职业操守。虽然robots协议没有法律强制力,但它明确表达了站方对自动抓取的态度。做图库不是做抢数据的灰产,这点边界要拎清楚。

抓取频率的设定,核心原则是“不要给对方服务器造成压力”。我给自己定的默认并发数是5个请求,每请求间隔0.5秒,高峰时段降为每秒1个请求。这个频率对绝大多数中型站点来说完全在可承受范围内,同时抓取速度也能满足素材扩充的业务需求。

3.2 抓取框架搭建与源码实现

批量抓取的框架我用了requests加BeautifulSoup加asyncio的组合。为什么不用Scrapy?因为这个项目里抓取只是其中一个模块,不需要上完整框架,简单任务用小工具组合反而更灵活。采集流程是:先从列表页解析出所有详情页URL,再挨个详情页提取图片直链,然后并发下载图片到本地临时目录,最后交给清洗模块。

这里有一个经验:解析图片直链时,不要拿页面上第一个img标签的src当作图片地址。很多站点的图片是懒加载的,真正的图片地址藏在>import asyncio import aiohttp MAX_CONCURRENCY = 5 SEMAPHORE = asyncio.Semaphore(MAX_CONCURRENCY) async def download_one(session, url, save_path): async with SEMAPHORE: try: async with session.get(url, timeout=20) as resp: if resp.status != 200: return False data = await resp.read() if len(data) < 1024 * 50: # 小于50KB直接丢弃 return False with open(save_path, "wb") as f: f.write(data) return True except Exception: return False async def fetch_image_links(session, page_url): # 解析页面,提取图片直链 pass async def run_crawler(start_urls): async with aiohttp.ClientSession(headers={ "User-Agent": "Mozilla/5.0 (compatible; MyStockBot/1.0)" }) as session: tasks = [] for url in start_urls: links = await fetch_image_links(session, url) for index, link in enumerate(links): save_path = f"/data/raw/{len(tasks)}_{index}.jpg" tasks.append(download_one(session, link, save_path)) await asyncio.gather(*tasks)

这里特别要注意User-Agent的设置。伪造一个浏览器UA在文明站点问题不大,但直接用“python-requests”这种裸UA,对方日志一眼就能看到是爬虫,很容易被限制。我用的UA统一带上了项目标识加版本号,这样如果真的给对方带来困扰,对方可以通过UA直接联系到我,比在对方日志里留下一个乱七八糟的UA要体面得多。

3.3 抓回来的图片如何清洗

抓回来的一堆文件不能直接入库,清洗环节决定素材的真实可用性。清洗步骤按顺序做:扩展名自动纠正、解码验证、尺寸筛选、模糊度检测、内容去重。

扩展名自动纠正这步用Pillow重新保存一次就行,把所有图片统一转为RGB模式的JPEG,一来保证图库内格式统一,二来顺手把带透明通道的PNG内容在转为JPEG时处理好背景色,避免透明背景变成黑色块。这个细节我是被坑过才记住的。

尺寸筛选的阈值根据图库的目标用途定。我做的这个图库定位是设计协作素材,使用场景需要清晰大图,所以抓取入库的最低尺寸是1280x720。小于这个分辨率的图片会被降级为“缩略图候选”,只在列表页展示,不参与原图下载。如果你做的是图标库或者背景纹理库,尺寸阈值肯定要下调,这个没有统一标准,按业务实际来。

模糊度检测是最常被忽略的步骤。很多图片看着清晰,实际是网络图压缩过的重影图,放大之后完全不能看。我用的是经典的拉普拉斯方差法:先将图片转灰度,再用拉普拉斯算子卷积,计算方差。方差小于设定阈值就判定为模糊图。这个算法简单有效,一张图耗时不到十毫秒,但能过滤掉大量劣质素材。

3.4 感知哈希去重

抓取阶段如果不做去重,后面入库时会涌入大量重复图。我用的方法前文提到过,感知哈希。具体实现是把彩色图片缩小成8x8像素,转成灰度图,计算64个像素的平均灰度,然后逐个比较每一位与平均值的关系,生成64位二进制哈希串。两张图的海明距离越小,说明图片越相似。

我实际测试下来,海明距离小于10的图片,肉眼看上去几乎是一模一样的;10到20之间,属于同一个素材的不同裁剪版本;大于20则是完全无关的图片。去重的逻辑放在数据库层面,给图片的phash字段建索引,入库前先查一次数据库,有接近重复的图片就不再重复写入。

不过PHash对翻转和镜像的图片是无能为力的,横翻之后的图哈希值完全不同。这个问题的处理我放在后端的相似组检测任务里,用周期性任务定期对已入库图片做镜像哈希比对,识别出横翻图并标记为同一组。日常抓取入库去重用PHash已经足够应对90%以上的情况了。

4. 入库流水线与自动巡检机制

4.1 入库流水线的完整设计

审核通过、清洗干净的图片,还不是真正意义上的“可用素材”,后面还得走一条完整的入库流水线。我把这条流水线分成七个环节:

原始文件接收,所有图片先进临时目录,不做任何处理。审核环节,机器直接过或转人工。清洗环节,格式统一、元数据剥离、尺寸筛选。去重环节,PHash比对、疑似重复标记。入库写入,把图片元数据写入数据库,原图存储到对象存储,生成多个尺寸的缩略图。索引构建,插入标签和描述信息,供搜索模块检索。CDN预热,对于标记为高优先级的图片,主动将缩略图推送到CDN节点,保证协作者访问时不会出现首帧空白。

这套流水线看起来简单,实际跑起来最需要重视的是异常处理。图片在任何环节出现问题都不能让整条链路停止,应该是单张图片进入异常标记状态,同时不影响队列后续图片的处理。我用的是一个任务队列加分布式锁的方案,每个图片任务带独立的超时时间和重试次数,三次重试仍然失败的图片会落进一个专门的问题图库,供人工检查。

4.2 定时巡检:让审核不只是一次性的

图片审核最大的隐患是“入库时安全,使用中出问题”。有些图片单独看没问题,但被AI生成工具二次处理后,就变成了完全不同的内容。所以审核必须是持续性的,不是一次性的。我加了一个定时巡检任务,每天深夜对图库全量图片做一次轻量级扫描。

巡检和初次审核的差别在于目标不同。初次审核要把“疑似违规”的图片拦下来,宁可错杀不可放过;巡检则要优先保证图库稳定性,重点关注“新入库图片在ACE模型下的置信度变化”。具体做法是,把入库时每张图片的特征向量保存下来,每天对特征向量和当天的黑名单特征库做一次增量比对,匹配到的新增风险图片进入下一个人工审核队列。这些变化往往是审核规则更新或者黑名单库扩充导致的,所以巡检线程的日志一定要留好,方便回溯是哪条规则的变化引发了标记。

巡检发现的违规图片不能直接删除,要先下架并通知上传者协作者。下架的意思是从公开检索结果里移出,但保留文件本身,等人工确认之后再决定是彻底删除还是恢复。这个机制能避免因为规则误杀导致正常素材被错误清除。

4.3 审计日志与账号行为追踪

无论机器审核还是人工审核,每一次操作都要落审计日志。日志里至少要包含:操作人ID、图片ID、操作类型(通过、拒绝、删除、下架)、操作时间、操作原因、触发规则版本。这样设计的目的不是为了追责,而是为了审核规则迭代时有据可查。

我见过很多小型项目忽略审计日志,出问题的时候只能拍脑袋定位。我自己的项目中,审计日志和图片本身绑定存储,前端查看图片详情时可以直接看到这张图在生命周期内的所有操作记录。对上传者而言,如果他的图片被拒绝,他能清晰地看到拒绝原因,而不是面对一个笼统的“审核未通过”,这对用户体验很重要。

账号行为追踪方面,如果同一个用户短时间内连续上传大量违规图片,触发设定的阈值时,自动对这个账号进行一天的资源限制。这是比较柔性的处理方式,避免误打击正常用户。

5. 常见问题与排查技巧实录

5.1 抓回来一堆裂图和黑图

批量抓取第一天,我就遇到了裂图问题。链接看起来没问题,下载下来也确实有文件内容,但打开全是黑色底或者灰色条纹。排查之后发现是两类原因造成的。第一类是目标站点对图片做了WebP格式输出,但我的下载代码兼容性没跟上,保存成了JPG后缀,解码失败。解决办法很简单,下载后统一用Pillow验证解码,不能解码就尝试用WebP格式重新打开。

第二类是目标站点的图片是经过JS动态签名生成的临时URL,一次性有效,下载时URL已经过期了。这种情况需要重新解析页面拿到新链接,而不是复用页面缓存里的图片地址。排查方法是抓取时给每张图第一次下载的HTTP响应加一个二进制校验,响应体如果是一段HTML错误页而非图片数据,直接丢弃并记录到日志里,同时触发一次链接重新解析。

5.2 审核误杀正常图片

审核规则阈值太严格的时候,婚纱摄影、艺术人体等正常图片会被机器判定为高风险内容,这是每个内容图库都会遇到的问题。我最初把肤色占比阈值设为20%,结果一小部分海边的游客照也被拦了。后来我把肤色检测单独降权,不能只凭肤色占比就拦截,必须和其他维度比如姿势特征、场景特征结合判断。

这个问题的本质是审核规则场景化不足。解决思路是引入“内容分类前置”模块,在审核之前先对图片做场景分类:人像、风景、美食、建筑、艺术、其他。不同场景使用不同审核规则权重,比如人像类重点关注合规性,风景类重点关注质量,艺术类重点关注版权。场景分类的准确率不用太高,能达到80%以上就够用,因为剩下的20%即使分错场景,也不会直接漏掉审核,只会进入人工队列。

5.3 抓取被目标站点限流

明明把频率控制在了每分钟60个请求以内,还是被限流了。排查发现是我低估了目标站点对相同User-Agent的指纹追踪能力。就算换了UA,其他HTTP头信息比如Accept、Accept-Language、Sec-Fetch这些也能暴露爬虫身份。安全的方向是把关键请求头补全,做到和浏览器一致,而不是只改一个UA自欺欺人。

还有一种情况是目标站点针对单个IP的并发连接数做了限制。解决方法是把并发数降到3,同时设置一个可配置的抓取窗口,只在每日固定的几个时段执行抓取,其他时间完全不碰目标站点。这个方案牺牲了一些抓取速度,但换来了长期稳定运行。数据抓取比拼的不是一时速度,而是能把任务持续跑多久不出问题。

5.4 关于“无审核AI生成图片”需求的正向引导

做审核模块的时候,我收到了不少私信咨询,都是最近搜索量很高的“AI一键生成图片无审核”“无审核AI图片生成哪个软件好”这类问题。我的观点是,严肃做内容产品的人不应该找这种工具。所谓“无审核”意味着没有任何机构对你的内容安全负责,生成出来的图片用到图库里,所有风险都会转移到图库运营者身上。

在这种情况下,正确做法不是去找“无审核”软件,而是搭一套自己的审核管道。你完全可以先用正常的AI图片生成工具批量产出内容,然后对这些内容跑一遍上面设计的审核规则,用算法替代人工筛选,提高出图效率。这套思路本质上就是我第五天做的工作在AI内容上的延伸——审核规则的输入源不只是外部抓取图片,也包括AI生成图片。让AI生成的内容和人工上传的内容走同一条审核链路,才能保证图库在内容来源扩展到AI之后依然保持稳定。

关于最后一点个人经验,规则引擎一定要做成可配置的。我的项目里所有审核阈值、权重、黑白名单全部放在配置中心,每次调整规则不需要重新发布代码,只刷新配置就能生效。这让我在回滚误操作、灰度测试新规则时省了非常多时间。如果你是第一天开始做类似项目,这个建议可以直接用上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询