使用 go-colly 解析 HTML 并函数 returns 一个空切片
Parsing HTML with go-colly and function returns an empty slice
我正在使用 colly 框架解析网站,但出现错误。我有一个非常基本的功能 getweeks()
可以抓取和 return 一些东西,但我得到的是一个空切片。
func getWeeks(c *colly.Collector) []string {
var wks []string
c.OnHTML("div.ltbluediv", func(div *colly.HTMLElement) {
weekName := div.DOM.Find("span").Text() // a string Week 1, Week 2 etc
wks = append(wks, weekName) // weekName has actual value is not empty
// If `wks` printed here it shows correctly how the slice gets populated on each iteration
})
return wks // returns []
}
func main() {
c := colly.NewCollector(
)
w := getWeeks(c)
fmt.Println(w) // []
c.OnRequest(func(r *colly.Request) {
r.Headers.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64)")
})
c.Visit("target url")
}
tl;dr:切片 header 在 OnHTML
回调中更新,但您在 main
中打印的值是旧值切片 header。您应该改用 *[]string
。
首先,您传递给 c.OnHTML
的回调实际上只会在您调用 c.Visit
之后 运行,因此在 getWeeks
之后立即打印 w
, 在任何情况下都会显示一个空切片。
然而,即使在 c.Visit
之后打印它也会是一个空切片,为什么?
Go 中的切片被实现为一种数据结构 — 称为切片 header(更多信息:1, 2)。
当您分配 getWeeks
的 return 值时,您实际上是在复制切片 header,包括其字段 Data
、Len
和Cap
。您可以通过使用 %p
动词打印切片的地址来在 this playground 中看到它(使用其他一些结构而不是 go-colly 来制作示例 self-contained):
func getWeeks(c *Foo) []string {
var wks []string
c.OnHTML("div.ltbluediv", func(text string) {
weekName := text
wks = append(wks, weekName)
})
fmt.Printf("%p\n", &wks)
return wks
}
func main() {
c := &Foo{}
w := getWeeks(c)
c.Visit("target url")
fmt.Printf("%p\n", &w)
}
打印两个不同的内存地址:
0xc0000ac030
0xc0000ac018
现在,如果您继续在 Stack Overflow 上搜索 slice 和 append
行为,您可能会发现如果 slice 具有足够的容量 (1, 2, 3),则不会重新分配后备数组。
然而,即使您通过使用足够的容量初始化 wks
来确保后备数组相同,w
的值仍然是原始切片 header 的副本,因此 长度为 0。这在 this playground 中进行了演示,它打印出:
in getWeeks reflect.SliceHeader{Data:0xc0000121b0, Len:0, Cap:3}
in callback reflect.SliceHeader{Data:0xc0000121b0, Len:1, Cap:3}
in callback reflect.SliceHeader{Data:0xc0000121b0, Len:2, Cap:3}
in callback reflect.SliceHeader{Data:0xc0000121b0, Len:3, Cap:3}
[]
in main reflect.SliceHeader{Data:0xc0000121b0, Len:0, Cap:3}
您可以通过重新切片 (playground) 来调整 w
的长度:
c.Visit("target url")
w = w[0:3]
fmt.Println(w) // [foo bar baz]
但这意味着您需要事先知道不会导致重新分配的合理容量,以及重新分片的最终长度。
相反,return 一个指向切片的指针:
func getWeeks(c *colly.Collector) *[]string {
wks := &[]string{}
c.OnHTML("div.ltbluediv", func(div *colly.HTMLElement) {
weekName := div.DOM.Find("span").Text()
*wks = append(*wks, weekName)
})
return wks
}
或者传递一个指针到getWeeks
:
func getWeeks(c *colly.Collector, wks *[]string) {
c.OnHTML("div.ltbluediv", func(div *colly.HTMLElement) {
weekName := div.DOM.Find("span").Text()
*wks = append(*wks, weekName)
})
}
我正在使用 colly 框架解析网站,但出现错误。我有一个非常基本的功能 getweeks()
可以抓取和 return 一些东西,但我得到的是一个空切片。
func getWeeks(c *colly.Collector) []string {
var wks []string
c.OnHTML("div.ltbluediv", func(div *colly.HTMLElement) {
weekName := div.DOM.Find("span").Text() // a string Week 1, Week 2 etc
wks = append(wks, weekName) // weekName has actual value is not empty
// If `wks` printed here it shows correctly how the slice gets populated on each iteration
})
return wks // returns []
}
func main() {
c := colly.NewCollector(
)
w := getWeeks(c)
fmt.Println(w) // []
c.OnRequest(func(r *colly.Request) {
r.Headers.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64)")
})
c.Visit("target url")
}
tl;dr:切片 header 在 OnHTML
回调中更新,但您在 main
中打印的值是旧值切片 header。您应该改用 *[]string
。
首先,您传递给 c.OnHTML
的回调实际上只会在您调用 c.Visit
之后 运行,因此在 getWeeks
之后立即打印 w
, 在任何情况下都会显示一个空切片。
然而,即使在 c.Visit
之后打印它也会是一个空切片,为什么?
Go 中的切片被实现为一种数据结构 — 称为切片 header(更多信息:1, 2)。
当您分配 getWeeks
的 return 值时,您实际上是在复制切片 header,包括其字段 Data
、Len
和Cap
。您可以通过使用 %p
动词打印切片的地址来在 this playground 中看到它(使用其他一些结构而不是 go-colly 来制作示例 self-contained):
func getWeeks(c *Foo) []string {
var wks []string
c.OnHTML("div.ltbluediv", func(text string) {
weekName := text
wks = append(wks, weekName)
})
fmt.Printf("%p\n", &wks)
return wks
}
func main() {
c := &Foo{}
w := getWeeks(c)
c.Visit("target url")
fmt.Printf("%p\n", &w)
}
打印两个不同的内存地址:
0xc0000ac030
0xc0000ac018
现在,如果您继续在 Stack Overflow 上搜索 slice 和 append
行为,您可能会发现如果 slice 具有足够的容量 (1, 2, 3),则不会重新分配后备数组。
然而,即使您通过使用足够的容量初始化 wks
来确保后备数组相同,w
的值仍然是原始切片 header 的副本,因此 长度为 0。这在 this playground 中进行了演示,它打印出:
in getWeeks reflect.SliceHeader{Data:0xc0000121b0, Len:0, Cap:3}
in callback reflect.SliceHeader{Data:0xc0000121b0, Len:1, Cap:3}
in callback reflect.SliceHeader{Data:0xc0000121b0, Len:2, Cap:3}
in callback reflect.SliceHeader{Data:0xc0000121b0, Len:3, Cap:3}
[]
in main reflect.SliceHeader{Data:0xc0000121b0, Len:0, Cap:3}
您可以通过重新切片 (playground) 来调整 w
的长度:
c.Visit("target url")
w = w[0:3]
fmt.Println(w) // [foo bar baz]
但这意味着您需要事先知道不会导致重新分配的合理容量,以及重新分片的最终长度。
相反,return 一个指向切片的指针:
func getWeeks(c *colly.Collector) *[]string {
wks := &[]string{}
c.OnHTML("div.ltbluediv", func(div *colly.HTMLElement) {
weekName := div.DOM.Find("span").Text()
*wks = append(*wks, weekName)
})
return wks
}
或者传递一个指针到getWeeks
:
func getWeeks(c *colly.Collector, wks *[]string) {
c.OnHTML("div.ltbluediv", func(div *colly.HTMLElement) {
weekName := div.DOM.Find("span").Text()
*wks = append(*wks, weekName)
})
}