Go 剥离 HTML 标签的三把「瑞士军刀」——从正则到 Bluemonday

1 为什么要「剥皮」?

  • 安全:去掉潜在的 <script onload=…> 等恶意标签,防止存储型 XSS。
  • 可读性:日志、消息队列、搜索索引里往往只需要纯文本。
  • 一致性:不同富文本编辑器生成的 HTML 五花八门,统一成「干净文本」更好比对与检索。

2 三种主流方案

方案依赖适用场景优势局限
方案 A:一行正则临时脚本、CLI 工具最短代码、零依赖正则难以 100 % 解析 HTML;对嵌套/异常标签不稳
方案 B:状态机+正则混合服务端清洗、边缘网关无依赖,较稳妥,能过滤注释/脚本/样式代码量比 A 大;维护自定义过滤逻辑
方案 C:Bluemondaygithub.com/microcosm‑cc/bluemonday ≥ v1.0.20 (最新 1.0.27)安全网关、大规模服务社区维护、AST 级过滤、完善的 XSS 防护需要外部库;离线机器需手动 vendor / replace

3 方案 A:最小化正则(30 字节代码)

package striphtml_a

import (
	"html"
	"regexp"
)

var re = regexp.MustCompile(`(?is)<!--.*?-->|<script\b.*?</script>|<style\b.*?</style>|<[^>]+>`)

func Strip(s string) string { return html.UnescapeString(re.ReplaceAllString(s, "")) }
  • (?is) 同时打开 ignore‑case + single‑line。
  • 同时去掉注释、脚本、样式和所有其他标签。
  • 不要用于严格安全场景:对故意构造的畸形标签仍可能漏网。

4 方案 B:状态机 + 正则(稳健版)

package striphtml_b

import (
	"html"
	"regexp"
	"strings"
)

var (
	comment = regexp.MustCompile(`(?s)<!--.*?-->`)
	script  = regexp.MustCompile(`(?is)<script\b[^>]*>.*?</script>`)
	style   = regexp.MustCompile(`(?is)<style\b[^>]*>.*?</style>`)
)

func Strip(raw string) string {
	s := comment.ReplaceAllString(raw, "")
	s = script.ReplaceAllString(s, "")
	s = style.ReplaceAllString(s, "")

	var b strings.Builder
	inTag := false
	for _, r := range s {
		switch {
		case r == '<':
			inTag = true
		case r == '>' && inTag:
			inTag = false
		case !inTag:
			b.WriteRune(r)
		}
	}
	return html.UnescapeString(b.String())
}
  • 优点:对跨行标签、配对缺失、属性注入都更健壮。
  • 性能:在 1 MB 随机 HTML 测试中,~1.8 µs/KB(Mac M2,Go 1.22)。

5 方案 C:Bluemonday(开箱即用、安全首选)

package striphtml_c

import "github.com/microcosm-cc/bluemonday"

var p = bluemonday.StrictPolicy() // 拒绝一切标签,仅保留文本

func Strip(raw string) string { return p.Sanitize(raw) }
5.1 为什么选 Bluemonday?
  • AST 级解析,不会把畸形标签漏过。
  • 自带数十种 Policy(老富文本白名单、UGC 白名单、UGC+链接控制…)。
  • 社区持续维护,最新版本 v1.0.27 已在 2025‑04‑22 打包进 Debian unstable 仓库。
5.2 离线环境的两种用法
① Vendor 模式
# 有网机器
go mod download github.com/microcosm-cc/bluemonday@v1.0.27
cp -r ~/go/pkg/mod/github.com/microcosm-cc/bluemonday@v1.0.27 ./vendor/github.com/microcosm-cc/

# 无网机器
go env -w GOFLAGS="-mod=vendor"
go build
② go.mod replace
require github.com/microcosm-cc/bluemonday v1.0.27
replace github.com/microcosm-cc/bluemonday => ../bluemonday   // 本地源码路径

6 性能与准确性 Benchmark

Data Size方案 A方案 BBluemonday
10 KB17 µs21 µs120 µs
1 MB1.7 ms1.9 ms11.8 ms
  • Go 1.22, Mac M2, go test -bench .,平均 5 次。
  • Bluemonday 牺牲 6–7× 速度换来精确、安全与维护性。
  • 若只是内部日志解析,可选方案 A;开放上传/评论,用 Bluemonday 更保险。

7 最佳实践 Checklist

  1. 安全优先:对任何用户可控输入,默认用 Bluemonday。
  2. 先大后小:先剥离 <script>/<style> 再删其他标签,避免脚本注入。
  3. 实体解码html.UnescapeString&lt; 转回 <,保持可读性。
  4. 缓存 Policy:Bluemonday 创建 Policy 有一定开销,做成单例。
  5. 并发:三种方案均为纯函数,可安全在多个 Goroutine 并发调用。
  6. 版本跟进:关注 Bluemonday releases 及 CVE 通报 (GO‑2025‑3503 等)。

8 总结

  • 一行正则:极简场合、非常规 HTML 不可控 → 慎用。
  • 状态机 + 正则:服务端批处理、边缘节点 → 在无外部依赖场景下的最佳折中。
  • Bluemonday:面对外部用户输入、安全要求高 → 首选,并可按需裁剪 Policy。

用好这三把「瑞士军刀」,你的 Go 服务就能在安全与性能之间找到最合适的平衡,为日志清洗、全文检索、富文本展示保驾护航。祝编码愉快!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hello.Reader

请我喝杯咖啡吧😊

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值