5、goquery

goquery是用 Go 语言编写的一个类似于 jQuery 的库。它基于 HTML 解析库net/html和 CSS 库cascadia,提供与 jQuery 相近的接口。Go 著名的爬虫框架colly就是基于 goquery 的。

安装

go get -u github.com/PuerkitoBio/goquery

由于 net/html 要求使用 UTF-8 编码,goquery 也是如此。我们需要保证传给 goquery 的 HTML 源字符串是 UTF-8 编码的。现在已经很少有非UTF-8 编码的网页了。在早些时候,国内很多网站都是使用 GB2312 或 GBK 编码。如果我们遇到了非 UTF-8 编码的网页该怎么办呢?可以使用iconv-go将字符串的编码转为 UTF-8。

使用

package main

import (
	"fmt"
	"github.com/PuerkitoBio/goquery"
	"log"
	"strings"
)

func main() {
	html := `<html>
			<body>
				<h1 id="title">Test</h1>
				<p class="content1">t1</p>
				<p class="content2">t2</p>
				<p class="content3">t3</p>
			</body>
			</html>
			`
	dom, err := goquery.NewDocumentFromReader(strings.NewReader(html))
	if err != nil {
		log.Fatalln(err)
	}

	dom.Find("p").Each(func(i int, selection *goquery.Selection) {
		fmt.Println(i, selection.Text())
	})
}
/*
0 t1
1 t2
2 t3
*/

NewDocumentFromReader() 返回了一个*Documenterror。Document代表一个将要被操作的HTML文档。

Find()是获取当前匹配元素集中每个元素的子代,参数是x选择器 ,它返回一个包含这些匹配元素的新选择对象。在例子中我们使用的是元素选择器P,它会帮我们匹配出所有的p标签 。

Each() 是迭代器,它会循环遍历选择的节点,它的参数是一个匿名函数,匿名函数拥有2个参数,一个是元素的索引位置,还有一个就是选择的结果集匹配到的内容都在它的里面。

Text() 则是获取匹配元素集中的文本内容。

选择器

上面的例子中,我们使用了元素选择器,goquery跟jquery一样都支持很多选择器,我们简单的介绍下常用的选择器:

基于HTML Element 元素的选择器

就是基于a,p等这些HTML的基本元素进行选择。

使用方法 :使用语法为 dom.Find("p"),匹配文档中所有的p标签。

ID 选择器

ID选择器是我们使用最频繁的,假如我们有2个p元素,其实我们只需要其中的一个,那么我们只需要给这个标记一个唯一的id即可,这样我们就可以使用id选择器,精确定位了。

使用方法 :id选择器以#开头,紧跟着元素id的值,使用语法为dom.Find("#title") ,匹配文档中所有的 id=title的内容

如果多个标签的ID都是title,我们可以指定某一个标签,如dom.Find("p#title")

Class选择器

类选择跟ID选择器一样都是使用很频繁的,我们可以通过类选择器快速筛选到需要的内容。

使用方法 : id选择器以.开头,紧跟着元素class的值,使用语法为dom.Find(".content1"),匹配文档中所有的 id=title的元素。

类选择权器跟ID选择器一样,也可以指定某一个标签dom.Find("div.content1")

属性选择器

一个HTML元素都有自己的属性以及属性值,所以我们也可以通过属性和值筛选元素。

使用方法 :我们可以通过元素的属性和属性值来筛选数据,使用语法为dom.Find("p[class=content1],匹配文档中所有的 p标签的class属性是content1的元素。

当然我们这里以class属性为例,还可以用其他属性,比如href等很多,自定义属性也是可以的。

刚刚我们使用的是完全相等的匹配方式,属性选择器还要很多匹配方式。

选择器 说明
Find(“div[my]“) 筛选含有my属性的div元素
Find(“div[my=zh]“) 筛选my属性为zh的div元素
Find(“div[my!=zh]“) 筛选my属性不等于zh的div元素
Find(“div[my|=zh]“) 筛选my属性为zh或者zh-开头的div元素
Find(“div[my*=zh]“) 筛选my属性包含zh这个字符串的div元素
Find(“div[my~=zh]“) 筛选my属性包含zh这个单词的div元素,单词以空格分开的
Find(“div[my$=zh]“) 筛选my属性以zh结尾的div元素,区分大小写
Find(“div[my^=zh]“) 筛选my属性以zh开头的div元素,区分大小写

parent > child选择器

筛选出某个元素下的子元素。

使用方法:使用>符号连接,使用语法 dom.Find("div>p") , 筛选div标签下的p标签

element + next 相邻选择器

如果要筛选的元素没有规律,但是该元素的上一个元素有规律,我们就可以使用这种下一个相邻选择器来进行选择。

<div>
    <p my="a">a</p>
    <p>b</p>
    <p>c</p>
<div>

我想筛选出b所在的标签

使用方法:dom.Find("p[my=a]+p")筛选出p标签属性my的值为a的相邻p标签。

element~next 兄弟选择器

有时候我们需要筛选同一父元素下,不相邻的标签,可以使用兄弟选择器

比如我想筛选出 b 和c 所在标签

使用方法:dom.Find("p[my=a]~p"),筛选出p标签属性my的值为a的兄弟p标签。

常用方法

// 类似函数的位置操作
Find(selection) *Selection //根据选择器查找节点集
Eq(index int) *Selection //根据索引获取某个节点集
First() *Selection //获取第一个子节点集
Last() *Selection //获取最后一个子节点集
Next() *Selection //获取下一个兄弟节点集
NextAll() *Selection //获取后面所有兄弟节点集
Prev() *Selection //前一个兄弟节点集
Get(index int) *html.Node //根据索引获取一个节点
Index() int //返回选择对象中第一个元素的位置
Slice(start, end int) *Selection //根据起始位置获取子节点集
// 循环遍历选择的节点
Each(f func(int, *Selection)) *Selection //遍历
EachWithBreak(f func(int, *Selection) bool) *Selection //可中断遍历
Map(f func(int, *Selection) string) (result []string) //返回字符串数组
// 检测或获取节点属性值
Attr(), RemoveAttr(), SetAttr() //获取,移除,设置属性的值
AddClass(), HasClass(), RemoveClass(), ToggleClass()
Html() //获取该节点的html
Length() //返回该Selection的元素个数
Text() //获取该节点的文本值
在文档树之间来回跳转(常用的查找节点方法)
Children() //返回selection中各个节点下的孩子节点
Contents() //获取当前节点下的所有节点
Find() //查找获取当前匹配的元素
Next() //下一个元素
Prev() //上一个元素