Alt 文本通过自动检查,不代表质量达标
alt="image"、原始文件名,或与相邻图片重复的描述。
自动化工具能可靠地标记缺失的 alt 文本,但在修复质量差的替代文本方面则力有不逮。大多数 alt 文本检查器只测试图片是否存在无障碍名称,而非检查提供的文本是否对关联图片有任何实际描述作用——这是刻意的設計选择:带有误报的质量导向规则,团队往往会把它关掉。因此 alt="IMG_2847.png" 能通过检查。五个不同的星形图标上使用同样的 alt="3/5 stars" 也能通过。
我们专门为 GitHub Accessibility Scanner 构建了一个 alt 文本插件,旨在帮助你改进 alt 文本。本文会介绍我们在检查器能证实什么、只能怀疑什么之间划线的依据,为什么我们最严重的那个 bug 实际上是布局问题而非解析问题,以及引入模型后发生了什么变化。
如果你也在构建自动化检查(无论用于无障碍访问还是其他用途),这些权衡经验同样适用。
无需看图即可证明字符串错误
alt 文本的存在是一个客观事实;属性要么在那里,要么不在。而质量往往是主观判断。机器无法从标记中证明一个句子是否在上下文中充分描述了图片。 不过,并非所有质量都是主观的。仅凭 alt 文本本身,就能执行几个检查,无需参考图片内容:- 属性缺失(非空)或仅包含空白。
- alt 是文件名,例如
hero.png、IMG_2847.jpg。 - alt 是某人打算替换的占位符,例如
TODO、tbd。
image、logo、chart。以上判断都是针对字符串本身的检查,这也成为了我们的分界线。五个确定性规则默认运行,不需要凭证即可调用 AI 模型或发起网络请求。另一条可选规则则会把图像内容和上下文传入模型,用于判断那些仅靠 alt 字符串无法做出的决策。
第一步,我们要确定扫描页面时该判定哪些图片。我们用 Playwright 基于角色的定位器而非 querySelectorAll('img'),因此任何不在浏览器无障碍树中的元素都会被排除,包括带有 alt="" 的图片。这一条排除规则最为关键:空 alt 意味着作者明确表示该图片为装饰用途,若对此进行标记,反而会给作者希望鼓励的行为惩罚。
那么,严格程度该如何把握?质量检查器的口碑全由误报率决定,所以我们选择了闭集规则而非巧妙的启发式方法。模糊 alt 规则会对字符串进行标准化,然后与一份精心筛选的无信息词表进行匹配,仅在精确命中时触发:
alt="image"会被标记。alt="image of the login screen with the SSO button highlighted"不会。
这样刻板的规则确实会漏掉大量糟糕的 alt 文本。但我们宁可漏检也不误报,因为一个开发者真正愿意开启的可靠检查器,远比一个容易被关掉的好用检查器有价值。
重复是布局问题,不是 DOM 问题
重复的 alt 文本提出了一个有趣的难题。想象一行五个星形图标,每个都写着 "3/5 stars"。屏幕阅读器用户会听到五次相同的内容,其中四次毫无信息增量。
我们的初版按文档顺序遍历图片,标记出任何共享相同标准化 alt 的连续图片序列。这带来了不该出现的情况。比如页脚的 "GitHub" logo 和页头的 "GitHub" logo,在提取列表中可能相邻,但在屏幕上却相去甚远,用户根本不会将它们视为一组。
关键不在于图片在标记中的位置,而在于它在屏幕上的布局。因此现在规则会检查页面布局,只有当两个边界框之间的间距相对于框本身较小时,才会把它们归为一个序列:const gap = Math.max(horizontalGap, verticalGap)
const largerDim = Math.max(a.boundingBox.width, a.boundingBox.height,
b.boundingBox.width, b.boundingBox.height)
return gap > GAP_MULTIPLIER * largerDim
有两点值得注意:
- 倍数是基于经验判断的,不是从任何公式推导出来的数值。这类值需要根据实际页面进行调优,不能照搬规范。
- 如果任一图片没有可测量的边界框,检查会放行,序列继续拼接。漏报是无形的,误报则不然。
让模型扮演审核者,而非评论家
确定性规则只需要 alt 字符串。更智能的检查需要了解页面整体内容,而这些信息并不记录在图片元素里。`alt="a smiling person"` 是否合适,完全取决于它周围的上下文:放在一张泛用氛围图下,大概没问题;但如果出现在点明具体人物姓名的标题下方,提供的细节就不够了。
在可选的 alt-text-quality 检查中,我们会针对每张图片同时提取页面上下文:最近的标题、页面标题、任何 <figcaption>、图片是否嵌套在链接或按钮内,以及附近最多 600 个字符的正文内容。
链接信号最为重要,因为当图片是链接的唯一内容时,它的 alt 就成了链接的可访问名称。此时正确的 alt 应该描述目标去向,而非描述图片本身。
一点注意事项:插件只记录了图片嵌套在链接内,并未检查它是否确实是链接的唯一内容——而这正是让 alt 变成链接名称的关键条件。所以目前模型眼中,这两种情况的表现完全相同。
将上下文字段、alt 文本与图片本身,经由 GitHub Models 一并送入视觉模型。我们的排查结果显示,模型极少出现误读图片的情况,真正的问题往往是它“爱提意见”。即便 alt 文本已经写得很好,初版检查器仍会建议重写,因为“还能更好吗?”这类问题,语言模型永远会回答“可以”。结果每张图都会触发告警,有效信号也就被淹没了。
三项改动解决了这个问题:
- 以决策流程取代开放式指令。提示词会按顺序遍历四个步骤,命中第一个便立即停止,输出对应判定:装饰性、与图注重复、功能性或信息性。
- 明确的防过度挑剔规则。尊重作者的原有表述,区分冗余前缀(如“图片:……”)与实质前缀(如“摄影作品:……”)。若正文已对图片进行分析,则短 alt 直接视为正确。
- 带强制字段顺序的结构化输出,确保
reasoning在verdict之前生成,迫使模型先给出推理过程,再下结论。
上述改动无法让模型百分百准确,但足以保证行为稳定,支持后续迭代优化。仓库内附带一套离线评测工具,其标准源自公开教学资料:WebAIM、W3C 图片教程 以及 POET。检测规则与评测工具共用同一套提示词,这意味着离线调优的内容会直接同步到 CI 环境中运行。但这套工具仅验证模型的判断逻辑,并未覆盖完整流水线。某个案例在离线测试中能拿满分,却在真实扫描中根本无法到达模型。
将图片发送给模型,本质上是隐私与成本的权衡
只要检测逻辑带着网页数据去调用外部模型,它就不再是一个简单的 lint 规则,而必须经过严谨的数据流设计。由此衍生出几点注意事项:
- 规则默认关闭。只有你在插件配置中显式开启才会生效,且必须配备具有 GitHub Models 访问权限的 token。
- URL 会被脱敏处理。图片 URL 和链接的
href通常携带签名 CDN token 或会话标识符,因此进入模型上下文或规则错误日志时,查询参数和片段部分都会被剥离。出于同样的原因,我们发送给模型的标记中会将src和srcset替换为(omitted)。 - 上下文窗口中的所有输入都视为不可信数据。标题、小标题和正文都来自被扫描的页面,而页面可能包含专门用来操纵模型的文本。结构化输出只能约束响应的格式,无法约束推理过程本身。
有一点需要提醒,因为这条清单容易被过度解读:检测结果仍会将真实页面 URL 和原始 HTML 传入扫描器的正常报告管道。这是有意为之——毕竟你无法修复无法定位的图片。脱敏处理只收窄了进入模型和日志的数据,不会影响最终落到你Issue列表里的内容。另外,如果你配置了 Azure AI Vision 凭据,可选的 OCR 预检步骤会将图片字节发送给另一个处理路径。Azure 并非强制要求,但数据流审查需要同时覆盖这两条路径。
成本方面遵循同样的规律。在常见情况下,每次扫描每个图片只需一次模型调用,而在图片密集型的站点上,这会占据整个运行成本的大部分。这也是建议将它加入定时任务而非每个commit触发的重要原因。
它还做不到的事
- 确定性规则是按字面匹配的。它们能捕获明显缺失的 alt 文本,但无法识别措辞流畅但内容错误的 alt 文本。此外,它们读取的是
alt属性而非计算后的可访问名称,因此用aria-label解决问题的话,规则检测依然不会停止报错。 - 模型支持的规则会产生误报。每一条检测结果都只是给人关注的提示,而非最终结论。
- 静默不代表覆盖完整。该规则会在浏览器会话之外重新获取图片,因此任何需要认证的图片都可能加载失败。获取错误和模型错误会被记录并跳过,这意味着一个页面可能因为什么都没被检查而返回"干净"的结果。
- 建议的 alt 文本只是草稿。一个能看到图片及周围少量文字的模型,无法考虑你的受众群体、编辑风格指南,以及这张图片在整个页面中承担的职责。
- 部分发现与扫描器的内置检查重复,因为我们的
missing-alt规则覆盖了相同内容。 - 我们仅检查 HTML
<img>标签。SVG、role="img"容器、CSS背景图和canvas尚未覆盖。 - 这是新代码,实际反馈有限。这类规则在实际网站的各种标记和内容中会得到完善。本插件尚未经历这些,请酌情对待早期发现。
- 通过不等于合规。自动化检查只是底线,面向使用辅助技术的人进行测试才是目标。
如果你要构建类似的东西,我们会告诉你
区分你能证明的和只能怀疑的,并赋予它们不同的默认设置。证明型检查应当廉价、可预测且默认开启;怀疑型检查应当由用户主动选择加入,读起来是建议而非判决。然后,多问用户实际体验到了什么,少问 DOM 声明了什么。本插件中所有仍未闭合的差距都属于后者——我们记录的是图片在链接内部,而非它就是链接;我们读取的是属性值,而非计算后的名称。
那种距离才是真正的边界,更好的模型也无法消弭它。判断一张图片对无法看见它的用户意味着什么,仍然需要人类的判断力。自动化为你带来的,是确保那个做出判断的人能给正确的图片一次二次审视的机会。
在你的无障碍扫描流程中试用 alt-text 插件。如果它告诉你错误的内容,请向我们报告。在Issue中附上该发现,以及受影响页面的链接(如果可以公开访问的话)。