不能用 domdocument 或 html.parse 加载超大 html,因其强制全量加载到内存导致 oom;应选用流式解析方案,如 php 的 masterminds/html5-php(传 stream resource)、go 的 html.newtokenizer(需预解码编码)、node.js 的 sax(比 htmlparser2 更轻量可控)。

直接用 DOM 解析器加载整页 HTML 是内存暴涨的最快路径——DOMDocument::loadHTMLFile、html.Parse、document.write 这类全量构建 DOM 树的操作,100MB 原始 HTML 往往吃掉 256MB+ 内存,且无法流控。必须绕开 DOM 树构建阶段,改用流式 tokenizer 或事件驱动解析。
为什么不能用 DOMDocument / html.Parse 处理大 HTML
它们底层强制调用 io.ReadAll(Go)或 file_get_contents(PHP),把整个文件读进内存再解析。这不是配置能调的,是 API 设计决定的硬伤。哪怕你只想要其中 <title></title> 的文本,它也得先建出全部 HTMLDivElement 和 Text 节点——每个节点至少占 1–2 KB,嵌套越深开销越大。
Node.js 下该选 sax 还是 htmlparser2
sax 更轻、更可控,适合只提取字段的场景;htmlparser2 功能强但默认会缓存标签结构,一不留神就累积节点。
- 别在
onopentag回调里往全局数组push节点——这是最常见泄漏源 - 若只取
<item><title></title></item>,用栈记录路径(如['rss', 'channel', 'item', 'title']),匹配成功立刻处理并清栈 - 禁用
htmlparser2.WritableStream的onend回调返回完整 DOM,否则照样 OOM
PHP 中用 Masterminds/html5-php 必须传 stream resource
它支持流式输入,但必须显式传 fopen() 返回的 resource,不能传字符串或文件路径。
$html5 = new HTML5();
$stream = fopen('huge.html', 'r');
$dom = $html5->loadHTML($stream); // ✅ 正确
fclose($stream);
// ❌ 错误写法(触发全量加载)
$dom = $html5->loadHTML(file_get_contents('huge.html'));
loadHTML() 内部靠 stream_get_contents() 分块读,前提是传入的是真实 stream resource。如果上游是 cURL response,直接传 CURLOPT_FILE 句柄比 curl_exec 后解析安全得多。
Go 里 html.NewTokenizer 的三个关键动作
别混用 xml.NewDecoder,它不识别 HTML 自闭合标签和缺失结束标签,容易错位甚至 panic。
- 用
token := t.Next()循环推进,不是t.Token()(后者不改变内部状态) - 遇到
html.StartTagToken时,用token.Data匹配标签名,别依赖token.DataAtom(自定义标签可能没 atom) - 提取完目标内容后,必须再调一次
t.Next()再break,否则下次 token 从错误位置开始
真正卡住内存的从来不是“没删节点”,而是“根本没让节点出生”——流式解析不建 DOM 树,自然没有 Detached HTMLDivElement 滞留,也没有闭包钉住的幽灵引用。这点在服务端预渲染或爬虫场景里,比前端优化更早见效。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











