不够用;re.sub()仅适用于结构简单、无脚本和嵌套的纯静态HTML片段,面对script/style/pre、自闭合标签、属性含尖括号等场景极易误判,应优先使用html.parser、lxml或Prettier等专业解析器。

用 re.sub() 格式化 HTML 真的够用吗?
够用,但只适用于结构简单、标签嵌套不超过 1 层、且不包含 JavaScript 或 CSS 片段的纯静态 HTML 片段。一旦遇到 <script>console.log("<div>")</script> 或 <pre><code><div></div></code></pre> 这类内容,正则会直接误判闭合关系。
常见错误现象:re.sub(r'<(/?)(\w+)>', r'<\1\2>', html) 会让 <img src="foo<bar"> 崩溃;<br> 类自闭合标签被错误补成 <br></br>;属性值里的引号和尖括号混在一起时,匹配提前终止。
- 只处理已知安全片段,比如服务端返回的、经过去脚本清洗的 HTML 字符串
- 避免匹配属性值内部(用
[^>]*替代.*?,但仍有边界风险) - 优先用
html.parser或lxml解析后再缩进输出,比正则健壮得多
BeautifulSoup 的 prettify() 为什么有时缩进错乱?
因为默认使用 html.parser,它对“哪些标签是 inline”、“哪些能省略闭合”依赖内置规则,而这些规则和浏览器实际解析行为不完全一致。比如 <p>hello<div>world</div></p> 中,<div> 被认为不能出现在 <p> 内,prettify() 可能把它提到外面,导致缩进层级跳变。
使用场景:适合快速查看结构、调试模板输出、或对格式精度要求不高的日志打印。
立即学习“前端免费学习笔记(深入)”;
- 显式指定解析器:
BeautifulSoup(html, 'lxml')比'html.parser'更贴近浏览器行为 - 控制缩进宽度:
soup.prettify(indent_width=2) - 禁用自动修正:
BeautifulSoup(html, 'html.parser', parse_only=SoupStrainer())可减少重排,但无法完全关闭
手动实现最小可行缩进器要注意什么?
核心不是“怎么加空格”,而是“怎么判断当前标签该缩进几层”。关键在状态机:遇到 <tag> 就进一层,遇到 </tag> 或自闭合标签(如 <br>、<img>)就不进层。但必须跳过注释、CDATA 和 <script>/<style> 内容。
容易踩的坑:
- 没识别
<!-- comment -->,把注释里的<当成标签开头 - 把
<![CDATA[<div>]]>当成普通标签处理 - 忽略大小写:
<DIV>和<div>应视为同一类标签 - 属性里含
>(如onclick="alert('>')")导致提前结束标签匹配
一个简短示例(仅示意逻辑,非完整实现):
stack = []
for match in re.finditer(r'<(/?)(\w+)[^>]*>|<!--.*?-->|<!\[CDATA\[.*?\]\]>', html, re.DOTALL | re.IGNORECASE):
if match.group(1) == '/': # 闭合标签
if stack: stack.pop()
elif match.group(2) and not is_void_tag(match.group(2)): # 开放标签且非自闭合
stack.append(match.group(2))
# …后续按 len(stack) 缩进
什么时候该放弃脚本,改用现成工具?
当你开始写第二版正则、或加了三层条件判断来绕过某个 <textarea> 里的 HTML 字符串时,就该停了。真实项目中的 HTML 很少“干净”,而 js-beautify(命令行或 Python 封装)、tidy、甚至 VS Code 的内置格式化(通过 prettier + prettier-plugin-html),都经过大量边缘 case 淬炼。
性能差异其实不大,但兼容性差太多:比如 <colgroup> 在表格里的合法嵌套位置、<rb>/<rt> 的配对规则、HTML5 新增的 <dialog> 行为——这些全靠正则硬编码,维护成本远高于引入一个稳定依赖。
真正难的不是写正则,是定义“格式化”的边界:要不要合并空白?是否重排属性顺序?是否展开自闭合标签?这些决策一旦写死,后面就很难改。



















