浏览器遇到非法实体时静默跳过并原样输出,不报错、不警告、不替换;仅当匹配合法实体格式(如&、<等)时才解码,否则保留原始字符串。

浏览器遇到非法实体时到底做了什么
浏览器不会报错,也不会中断解析,而是静默跳过非法实体并保留原始字符——比如&xyz;、(超出Unicode范围)或GG;(非法十六进制),全部原样输出为&xyz;、、GG;,不替换、不报错、不警告。
这种行为源于 HTML 解析器的状态机设计:只有在文本内容状态或属性值状态下,且匹配到合法的实体格式(如&、<、{、{)时才执行替换;其余一律视为普通文本。
-
&后面没跟合法名称或数字 → 当作普通&字符处理 -
后不是十进制/十六进制有效数字 → 整个字符串保留原样 -
后含非十六进制字符(如G;)→ 停止解析,输出G; - 嵌套实体(如
)→ 先解<code>&得&,再剩lt;,最终显示为<而非(即只解一层)
为什么 DOMParser 也无法识别非法实体错误
DOMParser().parseFromString(htmlStr, 'text/html')永远返回一个Document对象,哪怕输入是&xyz;这种明显越界的实体。它和浏览器渲染引擎一样,不校验实体合法性,只做尽力解析。
这意味着你不能靠DOMParser来“检测”非法实体——它既不抛异常,也不在body里留空或标记错误节点。它的输出 DOM 结构可能和预期一致,但原始字符串里的非法实体依然躺在textContent里没被处理。
立即学习“前端免费学习笔记(深入)”;
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
- 想检查非法实体?必须用正则或专用库(如
he)主动扫描:/&(?!(amp|lt|gt|quot|apos|#\d+|#x[0-9a-fA-F]+);)/g -
DOMParser唯一可用的兜底判断是:doc.body.textContent.includes('&')且已知输入不该含裸&,才可疑 - 注意:
innerHTML赋值后,非法实体仍可见;而textContent设值再读innerHTML会把裸&转成&,但对&xyz;无影响
Python 的 html.parser 对非法实体的处理差异
html.parser.HTMLParser默认开启convert_charrefs=True(3.5+ 版本),但它只转换合法实体;非法的如&xyz;会被当作普通文本传入handle_data(),不会触发任何错误回调。
关键点在于:它不提供“实体验证钩子”,也没有handle_invalid_entity之类的方法。你只能在handle_data()里自己匹配&开头的子串,再用html.unescape()试探性解码,捕获ValueError来识别非法项。
- 安全做法:先用
html.unescape(raw),再用try/except ValueError捕获失败 - 批量处理时,
he库比html.unescape更鲁棒,能容忍ZZ;等错误并返回原串 -
lxml和BeautifulSoup底层依赖 libxml2 或 html5lib,对非法实体更宽容,但同样不报错——它们的目标是“还原可读文本”,不是“校验合规性”
真正需要关注的其实是上下文边界
非法实体本身几乎不引发运行时问题,真正危险的是它出现在不该出现的位置:比如在<script></script>或<style></style>内部,浏览器根本不解析实体,<就只是四个字符;但在属性值或文本节点里,一个看似无害的©若被误写成&cop;,可能让后续"提前闭合属性,导致 XSS 漏洞面扩大。
所以修复重点不在“让非法实体变合法”,而在确保&只出现在明确需要转义的上下文中,并用工具链(如html-validate)拦截&后无分号、无合法后缀的裸写法。
- 模板引擎中关闭自动转义后,务必手动调用
he.escape()而非依赖浏览器纠错 - 富文本编辑器输出的 HTML,应统一走
DOMParser → textContent → innerHTML流程,避免&意外逃逸 - 服务端接收用户提交的 HTML 片段时,别信
DOMParser的“成功”,要额外用正则扫&[^;\s]*;?模式做白名单过滤


















