最稳方案是用 HtmlAgilityPack 解析 HTML 树:安装主包 HtmlAgilityPack,LoadHtml 加载后移除 script/style 节点,再取 InnerText 并用 WebUtility.HtmlDecode 解码,复用 HtmlDocument 实例提升性能。

用 HtmlAgilityPack 剥离标签最稳
直接上结论:别手写正则去删 <.*?>,HTML 结构嵌套、属性带换行、注释、CDATA 会让它漏删或误删。用 HtmlAgilityPack 是 C# 里最可靠的选择——它真把 HTML 当树来解析,不是当字符串来碰运气。
安装 NuGet 包:Install-Package HtmlAgilityPack。注意别装错成带 Lite 或 Core 后缀的变种,主包就叫 HtmlAgilityPack。
常见错误现象:Regex.Replace(html, @"<.*?>", "") 会吃掉 <script>alert("<div>")</script> 里的引号内容,或者把 <!-- <p>注释里有标签</p> --> 当成真实标签干掉。
- 用
HtmlDocument.LoadHtml()加载,不是Load()(后者读文件) - 调用
doc.DocumentNode.InnerText,不是InnerHtml或OuterText - 如果原文含
、©等实体,InnerText默认不解码,得手动调HtmlEntity.DeEntitize(node.InnerText)
WebUtility.HtmlDecode 和解码顺序很重要
InnerText 拿到的是原始 HTML 实体编码文本,比如 “C# & .NET” 会变成 “C# & .NET”。很多人忘了这步,导出后全是 &。
立即学习“前端免费学习笔记(深入)”;
必须在取 InnerText 后立刻用 WebUtility.HtmlDecode(),不能反过来——先解码再解析会破坏结构(比如把 <div> 变成 <code><div>,导致解析器误认为是真实标签)。
- 顺序只能是:
LoadHtml → DocumentNode.InnerText → WebUtility.HtmlDecode() - 不要用
HttpUtility.HtmlDecode(.NET Framework 专属,.NET 5+ 推荐WebUtility) - 如果输入含 UTF-8 BOM 或编码声明(如
<meta charset="gb2312">),HtmlAgilityPack默认按 UTF-8 解析,可能乱码;需显式传入Encoding.GetEncoding("gb2312")到LoadHtml(string, Encoding)
遇到 script/style 标签内容不想留?得手动清空
InnerText 默认保留 <script> 和 <style> 里的文本,但多数场景下你并不想要 JS 代码或 CSS 规则转成的“纯文本”。这不是 bug,是设计如此——它们确实是 DOM 的文本子节点。
得在解析后、取 InnerText 前,先移除这些节点:
var doc = new HtmlDocument();
doc.LoadHtml(html);
foreach (var node in doc.DocumentNode.SelectNodes("//script|//style"))
node.Remove();
string text = WebUtility.HtmlDecode(doc.DocumentNode.InnerText);-
SelectNodes("//script|//style")是 XPath,不是 CSS 选择器,不支持script, style - 别用
RemoveAllChildren()——它只清内容,不删节点本身,InnerText还是会包含换行和空白 - 如果还要过滤注释,加
|//comment()到 XPath 里
性能差?别在循环里反复 new HtmlDocument
单次转换没问题,但如果你在日志处理、批量邮件解析等场景中每条 HTML 都 new 一个 HtmlDocument,GC 压力明显上升,实测比正则慢 3–5 倍——不是算法慢,是对象创建开销大。
真正瓶颈不在解析逻辑,而在初始化。解决办法只有一个:HtmlDocument 实例可复用。
- 把
new HtmlDocument()提到循环外,每次调用doc.LoadHtml(html)覆盖内容 - 不要缓存
DocumentNode,因为LoadHtml会重建整棵树 - 如果线程安全是刚需(比如 ASP.NET Core 中间件),就别复用,改用
HtmlDocument局部实例——并发下的内存节省远不如稳定性重要
复杂点在于:不同 HTML 编码可能不同,而 LoadHtml 不接受编码参数;所以复用前提是你能确保所有输入编码一致,或者自己先统一转成 UTF-8 字符串再喂给它。











