You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Golang中x/net/html的Token().Attr空切片len返回非零值?

Golang中html.Token().Attr出现len非零但切片为空的异常原因

问题现象

使用golang.org/x/net/html库解析HTML时,出现矛盾行为:

  • 直接调用len(h.Token().Attr)返回非零值,但紧接着打印h.Token().Attr却显示为空切片
  • 将h.Token().Attr的结果保存到变量后,len()和打印内容均正常,符合预期

问题复现代码

package main

import (
    "fmt"
    "log"
    "net/http"

    "golang.org/x/net/html"
)

const url = "https://google.com"

func main() {
    resp, err := http.Get(url)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != 200 {
        log.Fatalf("Status code error: %d %s", resp.StatusCode, resp.Status)
    }

    h := html.NewTokenizer(resp.Body)

    for {
        if h.Next() == html.ErrorToken {
            break
        }

        l := len(h.Token().Attr)

        if l != 0 {
            fmt.Println("=======")
            fmt.Println("Length", l) // 结果大于0
            fmt.Println("Attr", h.Token().Attr) // 始终为空
        }
    }
}

异常输出

=======
Length 2
Attr []
typeof Attr []html.Attribute
=======
Length 8
Attr []
typeof Attr []html.Attribute
=======
Length 1
Attr []
typeof Attr []html.Attribute
=======
Length 1
Attr []
typeof Attr []html.Attribute

Go版本

go version go1.17.7 linux/amd64

修复后的代码

package main

import (
    "fmt"
    "log"
    "net/http"

    "golang.org/x/net/html"
)

const url = "https://google.com"

func main() {
    resp, err := http.Get(url)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    if resp.StatusCode != 200 {
        log.Fatalf("Status code error: %d %s", resp.StatusCode, resp.Status)
    }

    h := html.NewTokenizer(resp.Body)

    for {
        if h.Next() == html.ErrorToken {
            break
        }

        attrs := h.Token().Attr // 保存结果后复用
        l := len(attrs)

        if l != 0 {
            fmt.Println("=======")
            fmt.Println("Length", l)
            fmt.Println("Attr", attrs)
        }
    }
}

正常输出

Length 3
Attr [{ value AJiK0e8AAAAAYtZT7PXDBRBC2BJawIxezEfmIL6Aw5Uy} { name iflsig} { type hidden}]
=======
Length 4
Attr [{ class fl sblc} { align left} { nowrap } { width 25%}]
=======
Length 1
Attr [{ href /advanced_search?hl=en-IN&authuser=0}]
=======
Length 4
Attr [{ id gbv} { name gbv} { type hidden} { value 1}]

原因分析

该异常是由于旧版本golang.org/x/net/html库的Tokenizer.Token()方法实现导致的:

  1. 旧版本中,Token()返回的Attr切片直接引用了Tokenizer内部的attrs字段(未创建副本)
  2. Tokenizer内部的attrs切片会被复用:每次调用Next()时,该切片会被重置长度为0,再填充当前token的属性
  3. 当你两次调用h.Token().Attr时,第一次获取的是当前有效属性的引用,但第二次调用时,内部缓冲区已被复用逻辑修改,返回的引用指向了已被重置为空的切片
  4. 将h.Token().Attr保存到变量后,相当于保留了当前token属性的独立引用/副本,后续内部缓冲区的变化不会影响该变量的值

后续该库的新版本已修复此问题,Token()方法会创建Attr切片的副本并返回,避免了引用内部缓冲区带来的状态不一致问题。

内容的提问来源于stack exchange,提问作者vadasambar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 04:39:20