因为html.parse接收io.reader而非字符串,传字符串字面量会编译失败;正确做法是用strings.newreader包装,如html.parse(strings.newreader(...))。

为什么不能直接用 html.Parse 读取字符串?
因为 html.Parse 接收的是 io.Reader,不是字符串。传入字符串字面量会编译失败;常见错误是直接写 html.Parse("..."),报错 cannot use "..." (type string) as type io.Reader。
正确做法是用 strings.NewReader 包一层:
doc, err := html.Parse(strings.NewReader(`<div><p>hello</p></div>`))
if err != nil {
log.Fatal(err)
}
注意:如果 HTML 来自网络响应(如 *http.Response.Body),它本身已是 io.Reader,可直传,但记得检查 resp.StatusCode 和 resp.Header.Get("Content-Type") 是否含 text/html,否则可能解析乱码或 panic。
如何安全地遍历所有 <p></p> 标签并提取文本?
别用递归函数硬刚整个树——x/net/html 的节点结构容易漏掉 Text 节点或陷入空节点循环。推荐用深度优先遍历 + 类型断言判断 *html.Node 的 Type 字段。
关键点:
-
node.Type == html.ElementNode && node.Data == "p"才是目标标签 - 文本内容不在
node.Data,而在子节点中类型为html.TextNode的节点的Data字段 - 必须跳过换行、缩进等空白文本节点(
strings.TrimSpace后判空)
简例:
func findParagraphText(n *html.Node) []string {
var texts []string
if n.Type == html.ElementNode && n.Data == "p" {
for c := n.FirstChild; c != nil; c = c.NextSibling {
if c.Type == html.TextNode {
s := strings.TrimSpace(c.Data)
if s != "" {
texts = append(texts, s)
}
}
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
texts = append(texts, findParagraphText(c)...)
}
return texts
}
html.Node 的 Data 和 Attr 到底存什么?
Data 不是“HTML 标签内容”,而是节点名或文本值,具体取决于 Type:
-
ElementNode→Data是标签名,如"div"、"a" -
TextNode→Data是纯文本(含空白、换行) -
CommentNode→Data是注释内容(不含<!--和-->)
Attr 是 []html.Attribute,每个 html.Attribute 有 Key(如 "href")和 Val(如 "https://example.com")。获取 href 不能写 node.Attr["href"] —— 它是切片,得遍历找:
for _, a := range node.Attr {
if a.Key == "href" {
fmt.Println(a.Val)
break
}
}
常见坑:a.Val 不自动解码 URL 编码(如 %20 还是 %20),需要手动调 url.PathUnescape 或 url.QueryUnescape。
为什么遍历时常遇到 panic: runtime error: invalid memory address?
几乎全是没判空就访问 FirstChild 或 NextSibling。比如写 c := n.FirstChild; c.Data == "span",但 n 可能根本没有子节点,c 是 nil,解引用就崩。
安全写法永远带空检查:
- 遍历子节点:用
for c := n.FirstChild; c != nil; c = c.NextSibling - 取属性:先
if len(node.Attr) > 0再遍历 - 取文本:只对
TextNode取Data,且建议if c.Type == html.TextNode && c.Data != ""
另一个隐藏雷区:文档未闭合(如 <div><p>foo),<code>html.Parse 会尽力修复并插入缺失标签,导致实际 DOM 结构和预期不一致——调试时用 golang.org/x/net/html/atom 或打印节点路径比对更可靠。











