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

为什么不能用 DOMDocument 或 html.Parse 加载超大 HTML?
因为它们会强制把整个文档读进内存再构建 DOM 树。100MB 的 HTML 文件,DOMDocument 常占 256MB+ 内存,html.Parse 在 Go 中也一样——底层调用 io.ReadAll,一读就 OOM。这不是配置能调的,是 API 设计决定的:只要用了 DOMDocument::loadHTMLFile 或 html.Parse,你就已经掉进内存陷阱里了。
Node.js 下该用 htmlparser2 + Stream 还是 sax?
选 sax 更轻、更可控;htmlparser2 功能全但默认仍会累积节点(除非你禁用 handler 的 onopentag 缓存逻辑)。实际踩坑点:
- 别在
onopentag回调里push到全局数组——这是最常见内存泄漏源 - 若只取
<item></item>下的<title></title>,用栈记录路径(如['feed', 'entry', 'title']),匹配成功立刻处理并清栈 -
htmlparser2.WritableStream虽支持流式写入,但若传入的cbs里有onend并试图返回完整 DOM,照样爆内存
PHP 里怎么避免 Masterminds/html5-php 吃光内存?
它本身支持流式输入,但必须显式传 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 body,直接传
$ch的CURLOPT_FILE句柄比先curl_exec再解析安全得多 - DiDOM 更快更省,但它不支持流式输入——只能靠预切片:用
strtok按<record></record>分块,逐段喂给new DiDOM\Document()
Go 里用 html.NewTokenizer 还是 xml.NewDecoder?
HTML 就用 html.NewTokenizer,别混用 xml.NewDecoder——后者不识别 <br> 自闭合、<div> 缺结束标签等 HTML 特性,会错位甚至 panic。正确姿势:
<ul>
<li>用 <code>token := t.Next() 循环,不是 t.Token()(后者不推进状态)
html.StartTagToken 时,用 token.Data 匹配标签名,别依赖 token.DataAtom(部分自定义标签没 atom)t.Next() 至少一次再 break,否则下个 token 从错误位置开始for ; token.Type != html.EndTagToken || token.Data != "target"; token = t.Next() {},别幻想 tokenizer.Skip(它不存在)真正容易被忽略的是:Tokenizer 不做字符编码自动转换,UTF-8 BOM 或 GBK 文件得先用 golang.org/x/text/encoding 解码成 UTF-8 io.Reader 再喂进去,否则乱码+提前终止。











