goquery 是一个为 Go 语言提供类似于 jQuery 的语法以解析 HTML 文档的库,可以通过链式语法来操作和查询 HTML 文档。它并不包含一个功能齐全的 DOM 树,因此 jQuery 的有状态操作函数(如 height()
、css()
、detach())
在这里是不能使用的。
此外,由于 goquery 底层基于 net/html
来做解析,而使用 net/html
的要求是提供的文档输入流必须是 UTF-8 编码,所以 goquery 也是如此。
安装
go get github.com/PuerkitoBio/goquery
API 说明
goquery 主要暴露了两个结构体(Document
和 Selection
)以及一个接口(Matcher
)。与 jQuery 不同,使用 goquery 前需要告知 goquery 是对哪个 HTML 文档进行操作,这就是 Document
的作用。
goquery 基于 Cascadia 提供了一套类似 jQuery 选择器的功能,所以对于原本已熟悉 jQuery 选择器的学习者来说,上手 goquery 更加事半功倍了。不过要注意的是 Cascadia 并不一定能完全支持 jQuery 支持的所有选择器,
示例
下面代码会获取我的博客首页中的文章标题输出到控制台上:
package main
import (
"fmt"
"github.com/PuerkitoBio/goquery"
"log"
"net/http"
)
func main() {
res, err := http.Get("https://www.zze.xyz")
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
if res.StatusCode != 200 {
log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
}
doc, err := goquery.NewDocumentFromReader(res.Body)
if err != nil {
log.Fatal(err)
}
doc.Find(".article .has-link-black-ter").Each(func(idx int, selection *goquery.Selection) {
fmt.Printf("%d: %s\n", idx, selection.Text())
})
}
在上面的示例程序中最后是通过
doc.Find()
方法来找到我们的目标节点,而该方法的参数就是选择器表达式,大部分 CSS 选择器语法都可以在这里使用,可参考:https://www.w3school.com.cn/cssref/css_selectors.asp。
执行结果如下:
0: 三分钟部署多Master高可用Kubernetes集群
1: 二进制部署 1.19.x 版本多Master的Kubernetes集群
2: golang 内置的 rpc 小 demo
3: Istio 快速部署与简单使用
4: Istio 的核心配置对象
5: Istio 的核心组件极其功能
6: Python 中简单实现定时任务的几种方式
7: XXL-JOB 2.2 升级到 2.3 表结构修改
8: 查看 navicat 已保存连接的密码
9: Jenkinsfile 中获取 Git 代码变更用户名和 Email
10: Linux 命令行下优雅的解析 JSON
11: 从云上mysql冷备xb文件恢复到本地
常用方法
在前面的示例中我是使用 http 客户端请求一个 URL 来获取一个 HTML 文档,在下面的测试中为更方便的展示出各方法的特性,我们就直接自定义 HTML 文档来保存到字符串中,就像这样:
htmlStr := `<html>
...
</html>
`
doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlStr))
if err != nil {
log.Fatalf("read document failed, err: %v\n", err)
}
为精简文章篇幅,后续方法示例中都会精简实例化 doc
的部分。
Length
获取匹配到的元素个数。
htmlStr := `
<span>test1</span>
<span>test2</span>
<span>test3</span>
`
...
elementCount := doc.Find("span").Length()
fmt.Println(elementCount)
/*
3
*/
Eq
根据索引获取指定元素,返回值为 Selection
类型。
htmlStr := `
<span>test1</span>
<span>test2</span>
<span>test3</span>
`
...
eq := doc.Find("span").Eq(2)
fmt.Println(eq.Html())
/*
test3 <nil>
*/
First & Last
获取查询结果集中第一个元素(相当于 eq(0)
)和最后一个元素,返回值为 Selection
类型。
htmlStr := `
<span>test1</span>
<span>test2</span>
<span>test3</span>
`
...
firstElement := doc.Find("span").First()
lastElement := doc.Find("span").Last()
fmt.Println(firstElement.Html())
fmt.Println(lastElement.Html())
/*
test1 <nil>
test3 <nil>
*/
et
根据索引获取指定元素,返回值为 *html.Node
类型(源包 golang.org/x/net/html
)。
htmlStr := `
<span>test1</span>
<span>test2</span>
<span>test3</span>
`
...
element := doc.Find("span").Get(0)
fmt.Printf("%T\n", element)
/*
*html.Node
*/
ndex
获取当前元素的索引。
htmlStr := `
<span>test1</span>
<span>test2</span>
<span>test3</span>
`
...
firstElement := doc.Find("span").First()
lastElement := doc.Find("span").Last()
fmt.Printf("firstIndex: %d, lastIndex: %d", firstElement.Index(), lastElement.Index())
/*
firstIndex: 0, lastIndex: 2
*/
Each
循环匹配到的元素。
htmlStr := `
<span>test1</span>
<span id="b">test2</span>
<span>test3</span>
`
...
doc.Find("span").Each(func(idx int, sel *goquery.Selection) {
fmt.Println(idx, sel.Text())
})
/*
0 test1
1 test2
2 test3
*/
Not
从匹配结果中排除指定元素。
htmlStr := `
<span>test1</span>
<span id="b">test2</span>
<span>test3</span>
`
...
doc.Find("span").Not("#b").Each(func(idx int, sel *goquery.Selection) {
fmt.Println(idx, sel.Text())
})
/*
0 test1
1 test3
*/
Prev
获取匹配到的元素的前一个兄弟元素。
htmlStr := `
<span>test1</span>
<span id="b">test2</span>
<span>test3</span>
`
...
fmt.Println(doc.Find("#b").Prev().Html())
/*
test1 <nil>
*/
Prepend
将匹配到的元素移动到文档头部。
htmlStr := `
<span>test1</span>
<span id="b">test2</span>
<span id="c">test3</span>
`
...
fmt.Println(doc.Prepend("#c").Html())
/*
<span id="c">test3</span><html><head></head><body><span>test1</span>
<span id="b">test2</span>
</body></html> <nil>
*/
Remove
将匹配到的元素从文档中移除并返回。
htmlStr := `
<span>test1</span>
<span id="b">test2</span>
<span id="c">test3</span>
`
doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlStr))
if err != nil {
log.Fatalf("read document failed, err: %v\n", err)
}
fmt.Println(doc.Find("#c").Remove().Html())
fmt.Println(doc.Html())
/*
<span id="c">test3</span><html><head></head><body><span>test1</span>
<span id="b">test2</span>
</body></html> <nil>
*/
RemoveAttr
移除匹配到的元素的指定属性。
htmlStr := `
<span>test1</span>
<span id="b">test2</span>
<span id="c">test3</span>
`
...
fmt.Println(doc.Find("#c").RemoveAttr("id"))
fmt.Println(doc.Html())
/*
<span id="c">test3</span><html><head></head><body><span>test1</span>
<span id="b">test2</span>
</body></html> <nil>
*/
Parent
获取指定元素的父元素下的所有元素。
htmlStr := `
<div class='parent'>
<span id="a">test1</span>
<span id="b">test2</span>
<span id="c">test3</span>
</div>
`
...
fmt.Println(doc.Find("#b").Parent().Html())
/*
<span id="a">test1</span>
<span id="b">test2</span>
<span id="c">test3</span>
<nil>
*/
SetHtml
设置指定元素的内容,支持 HTML。
htmlStr := `
<div class='parent'>
<span id="a">test1</span>
<span id="b">test2</span>
<span id="c">test3</span>
</div>
`
...
doc.Find("#b").SetHtml("<a href='www.zze.xyz'>gogogo</a>")
fmt.Println(doc.Html())
/*
<html><head></head><body><div class="parent">
<span id="a">test1</span>
<span id="b"><a href="www.zze.xyz">gogogo</a></span>
<span id="c">test3</span>
</div>
</body></html> <nil>
*/
SetText
设置指定元素的内容,如果内容包含 HTML 字符将会进行转义。
htmlStr := `
<div class='parent'>
<span id="a">test1</span>
<span id="b">test2</span>
<span id="c">test3</span>
</div>
`
doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlStr))
if err != nil {
log.Fatalf("read document failed, err: %v\n", err)
}
doc.Find("#b").SetText("<a href='www.zze.xyz'>gogogo</a>")
fmt.Println(doc.Html())
/*
<html><head></head><body><div class="parent">
<span id="a">test1</span>
<span id="b"><a href='www.zze.xyz'>gogogo</a></span>
<span id="c">test3</span>
</div>
</body></html> <nil>
*/
SetAttr
为匹配到的元素设置属性。
htmlStr := `
<div class='parent'>
<span id="a">test1</span>
<span id="b">test2</span>
<span id="c">test3</span>
</div>
`
doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlStr))
if err != nil {
log.Fatalf("read document failed, err: %v\n", err)
}
doc.Find("#b").SetAttr("class", "blue")
fmt.Println(doc.Html())
/*
<html><head></head><body><div class="parent">
<span id="a">test1</span>
<span id="b" class="blue">test2</span>
<span id="c">test3</span>
</div>
</body></html> <nil>
*/
暂时就介绍这么多,其实就对于 HTML 文档的解析来说,掌握一个
Find
和Each
,然后使用选择器语法就妥妥的能解决 95% 的问题了。
评论区