侧边栏壁纸
博主头像
张种恩博主等级

一个能运维的 JPG 搬运工

  • 累计撰写 703 篇文章
  • 累计创建 60 个标签
  • 累计收到 24 条评论
Go

使用 goquery 解析 HTML

张种恩
2021-06-30 / 0 评论 / 0 点赞 / 158 阅读 / 7,948 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-02-15,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

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 主要暴露了两个结构体(DocumentSelection)以及一个接口(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">&lt;a href=&#39;www.zze.xyz&#39;&gt;gogogo&lt;/a&gt;</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 文档的解析来说,掌握一个 FindEach,然后使用选择器语法就妥妥的能解决 95% 的问题了。

0

评论区