html是静态文本,dom树是浏览器按规范解析后在内存中构建的动态对象树;二者不等价,混淆会导致选择器失效、事件错位或爬虫提取失败。

HTML文档结构是静态文本,DOM树是浏览器在内存中构建的动态对象树;二者不是同一回事,混淆它们会导致 selector 失效、事件绑定错位、或爬虫提取不到目标节点。
HTML源码和DOM树根本不是一回事
HTML是服务器返回的一串字符串,比如 <div><p>hello</p></div> —— 它本身没有“嵌套合法性”概念,浏览器也不会照单全收。而 DOM 树是解析器按规范重建的结果:遇到不闭合标签、错位结束符(如 <div><p></p></div>),会自动修复为合法结构,最终生成的 DOM 可能是 <div><p></p></div>。
这意味着:
- 你写的 HTML 源码 ≠ 浏览器实际构建的 DOM 树
- 用
document.querySelector('p')查不到东西,未必是选择器写错,很可能是 DOM 还没生成,或被自动修正过结构 - 服务端渲染(SSR)输出的 HTML 字符串,和客户端 hydration 后的 DOM 节点可能有细微差异,尤其涉及自闭合标签或缺失结束标签时
DOM树构建过程依赖栈驱动解析器
浏览器不会一次性读完全部 HTML 再建树,而是边流式解析、边用栈维护当前上下文。核心逻辑近似:
function HTMLSyntaticalParser() {
let stack = [new HTMLDocument()];
this.receiveInput = (token) => {
if (token.type === 'startTag') {
const element = new Element(token.name);
stack[stack.length - 1].childNodes.push(element);
stack.push(element);
} else if (token.type === 'endTag') {
stack.pop();
} else if (token.type === 'text') {
const textNode = new TextNode(token.data);
stack[stack.length - 1].childNodes.push(textNode);
}
};
}
这个机制直接导致几个关键行为:
- 连续文本节点会被合并(
"a"<br>"b"解析后可能只有一个Text节点,中间换行/空格被压缩) <table> 类特殊标签有隐式包裹规则(如 <code><tr> 必须在 <code><tbody> 下,即使你没写,DOM 里也会自动补上)<li>解析中断(如脚本阻塞)时,栈状态决定当前挂起位置,影响后续节点挂载顺序</li> <h3>不同工具解析 HTML 得到的 DOM 树可能不一致</h3> <p>浏览器原生 DOM 构建遵循 HTML5 规范,但 Node.js 侧的 <code>cheerio、Python 的BeautifulSoup或lxml使用各自解析器,对容错、标签推断、自闭合处理策略不同。
Article To Html下载文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
典型差异点:
-
<img src="x">在浏览器中是自闭合,lxml默认要求<img src="x">,否则可能把后续内容吞进img节点内 -
<div><p>A</p></div> <p>B</p>:浏览器会自动闭合<p></p>并调整层级;BeautifulSoup(默认html.parser)可能保留扁平结构,lxml则更接近浏览器行为 - 注释节点
<!-- test -->:浏览器中是独立Comment节点;部分解析器默认忽略或转成文本节点
所以,用
requests + BeautifulSoup抓取页面后调.select('div p')拿不到结果,不一定是 selector 错,先检查它实际解析出的结构是否和浏览器开发者工具里看到的一致 —— 推荐用str(soup)或soup.prettify()对比原始 HTML。document 对象只是 DOM 树的入口,不是全部
document是 DOM 树的根节点(Document类型),但它不等于整棵树的“快照”。很多操作是实时、响应式的:-
document.body返回的是当前已挂载的元素节点,但如果 JS 在中执行,此时body还未解析,值为null -
document.querySelectorAll返回的是静态 NodeList,不响应后续 DOM 变更;而document.getElementById每次都查最新状态 -
document.documentElement指向元素,但它的innerHTML不包含 DOCTYPE 声明 —— 因为DOCTYPE是独立的DocumentType节点,不在元素子树里
真正容易被忽略的是:DOM 树里存在多种节点类型,但大多数选择器(如
querySelector)只匹配元素节点(Element),完全无视Text、Comment、DocumentType等节点。想遍历全部结构,必须手动递归childNodes,而不是只看children。-
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










