HtmlAgilityPack 抛 NullReferenceException 的主因是未校验 DocumentNode 是否为 null;XPath 匹配失败多因 HTML 结构松散与查询严格性冲突;中文乱码源于编码未显式指定;InnerText 包含 script/style 内容属设计行为;SelectNodes 性能差需手动缓存。

HtmlAgilityPack 加载 HTML 时直接抛出 NullReferenceException
根本原因不是 HTML 有错,而是 HtmlDocument.Load() 或 LoadHtml() 后没检查 DocumentNode 是否为 null —— 尤其当输入为空字符串、null 或网络响应失败(比如 HttpClient.GetStringAsync() 返回空)时,DocumentNode 就是 null,后续任何 SelectNodes() 都崩。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 永远先判断:
if (doc.DocumentNode == null) return;或抛出自定义异常 - 用
LoadHtml(htmlString)前确保htmlString?.Trim() != "" - 从 HTTP 获取内容后,别直接丢给
LoadHtml(),先做非空和状态码校验 - 避免用
Load()读本地文件路径却不处理FileNotFoundException
用 SelectNodes() 找不到节点,但浏览器里明明有
最常见的是忘了 HTML 不是 XML:标签大小写不敏感、属性名无引号、自闭合标签缺失斜杠(如 <img src="x" alt="C#怎么解析HTML网页节点_C#如何使用HtmlAgilityPack【实战】" >)—— HtmlAgilityPack 默认按“宽松模式”解析,但 XPath 查询严格区分结构。比如 //div[@class='content'] 会失败,因为实际 HTML 中可能是 class="content"(双引号),或含多个 class(class="content main")。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 改用
GetAttributeValue("class", "")+Contains("content")替代 class 属性全匹配 - 查子节点优先用
Descendants("a")而非SelectNodes("//a"),更稳定 - 调试时先输出
doc.DocumentNode.OuterHtml.Substring(0, 500),确认实际解析出的 DOM 结构 - 避免在未调用
OptionFixNestedTags = true时解析严重嵌套错误的 HTML(如表格内直接放<div>) <h3>中文乱码或特殊字符显示为 或空格</h3> <p>本质是编码没对齐:网页声明了 <code>charset=utf-8,但HtmlWeb.Load()没传编码参数,它会按系统默认(如 GB2312)解码,导致 UTF-8 字节被错误解释。实操建议:
立即学习“前端免费学习笔记(深入)”;
- 用
HtmlWeb时显式指定编码:web.OverrideEncoding = Encoding.UTF8; - 若用
LoadHtml(),确保原始字符串已是正确解码后的string,而非 byte[] 误转 - 遇到 meta charset 动态变化的页面(如先写
gbk后 JS 改成utf-8),别依赖自动探测,手动按响应头Content-Type设置 - 注意
HtmlDocument.PreserveWhitespace = true对中文换行/缩进的影响,有时看起来像“丢字”,其实是空白被合并了
提取文本时把 script/style 内容也混进来了
InnerText是“所有后代文本拼接”,包括<script>console.log('x')</script>和<style>body{color:red}</style>里的内容 —— 这不是 bug,是设计如此。实操建议:
立即学习“前端免费学习笔记(深入)”;
- 提取可见文本前先移除无关节点:
doc.DocumentNode.SelectNodes("//script | //style | //comment()")?.ToList().ForEach(n => n.Remove()); - 用
InnerHtml+ 正则清理再走文本提取,不如直接用Descendants()过滤标签名 - 需要保留部分格式(如换行、段落)时,别全靠
InnerText,改用递归遍历Nodes()并跳过HtmlNodeType.Comment和HtmlNodeType.Script - 注意
Remove()是就地修改,如果要复用 doc,记得Clone()一份再清理
HtmlAgilityPack 的 DOM 操作看着像 jQuery,但底层没有 CSS 选择器、不支持伪类、也不缓存查询结果 —— 每次
SelectNodes()都是全树扫描。复杂页面里反复查同一类节点,得自己缓存IEnumerable<htmlnode></htmlnode>或提前用Descendants()一次性捞出来分组用。 - 用



















