您的位置:首页 > 产品设计 > UI/UE

goquery使用

2015-10-03 18:43 525 查看
做过 Web 开发的,应该都用过或听过 jQuery,它提供了方便的操作 DOM 的 API。使用 Go 语言做服务器端开发,有时候需要解析 HTML 文件,比如抓取网站内容、写一个爬虫等。这时候如果有一个类似 jQuery 的库可以使用,操作 DOM 会很方便,而且,上手也会很快。github.com/PuerkitoBio/goquery 这个库就实现了类似 jQuery 的功能,让你能方便的使用 Go 语言操作 HTML 文档。

1 概述

Go 实现了类似 jQuery 的功能,包括链式操作语法、操作和查询 HTML 文档。它基于 Go net/html 包和 CSS 选择器库 cascadia。由于 net/html 解析器返回的是 DOM 节点,而不是完整的 DOM 树,因此,jQuery 的状态操作函数没有实现(像 height(),css(),detach())。

由于 net/html 解析器要求文档必须是 UTF-8 编码,因此 goquery 库也有此要求。如果文档不是 UTF-8 编码,使用者需要自己转换。进行编码转换,可以使用如下库:

iconv 的 Go 封装,如:github.com/djimenez/iconv-go

官方提供的 text 子仓库,text/encoding,用于其他编码和 UTF-8 之间进行转换

除了实现和 jQuery 类似的功能外,在函数名方面,也尽量和 jQuery 保持一致,也支持链式语法。

2 goquery 提供的主要类型和方法

2.1 Document

Document 代表一个将要被操作的 HTML 文档,不过,和 jQuery 不同,它装载的是 DOM 文档的一部分。

type Document struct {
*Selection
Url *url.URL
rootNode *html.Node // 文档的根节点
}


因为 Document 中内嵌了一个 Selection 类型,因此,Document 可以直接使用 Selection 类型的方法。

有五种方法获取一个 Document 实例,分别是从一个 URL 创建、从一个 *html.Node 创建、从一个 io.Reader 创建、从一个 *http.Response 创建和从一个已有的 Document Clone 一个。

2.2 Selection

Selection 代表符合特定条件的节点集合。

type Selection struct {
Nodes []*html.Node
document *Document
prevSel *Selection
}


一般地,得到了 Document 实例后,通过 Dcoument.Find 方法获取一个 Selection 实例,然后像 jQuery 一样使用链式语法和方法操作它。

Selection 类型提供的方法可以分为如下几大类(注意,3个点(…)表示有重载的方法):

1)类似函数的位置操作

Eq()

First()

Get()

Index…()

Last()

Slice()

2)扩大 Selection 集合(增加选择的节点)

Add…()

AndSelf()

Union(), which is an alias for AddSelection()

3)过滤方法,减少节点集合

End()

Filter…()

Has…()

Intersection(), which is an alias of FilterSelection()

Not…()

4)循环遍历选择的节点

Each()

EachWithBreak()

Map()

5)修改文档

After…()

Append…()

Before…()

Clone()

Empty()

Prepend…()

Remove…()

ReplaceWith…()

Unwrap()

Wrap…()

WrapAll…()

WrapInner…()

6)检测或获取节点属性值

Attr(), RemoveAttr(), SetAttr()

AddClass(), HasClass(), RemoveClass(), ToggleClass()

Html()

Length()

Size(), which is an alias for Length()

Text()

7)查询或显示一个节点的身份

Contains()

Is…()

8)在文档树之间来回跳转(常用的查找节点方法)

Children…()

Contents()

Find…()

Next…()

Parent[s]…()

Prev…()

Siblings…()

2.3 Matcher 接口

该接口定义了一些方法,用于匹配 HTML 节点和编译过的选择器字符串。Cascadia’s Selector 实现了该接口。

type Matcher interface {
Match(*html.Node) bool
MatchAll(*html.Node) []*html.Node
Filter([]*html.Node) []*html.Node
}


3 实战演练

该库提供的类型很少,但方法却很多,我们不可能一个个方法讲解。这里通过模拟几个使用场景来讲解该库的使用。

3.1 抓取 Go语言中文网 社区主题 — http://studygolang.com/topics

查看页面 HTML 结构后,就跟使用 jQuery 操作页面一样。这个例子,我们获取社区主题的标题列表。

主要代码如下(为了节省篇幅,包导入等语句省略,完整代码,参见文章最后说明):

func main() {
doc, err := goquery.NewDocument("http://studygolang.com/topics")
if err != nil {
log.Fatal(err)
}
//查找class=topics且其字节点下class=topic的节点
doc.Find(".topics .topic").Each(func(i int, contentSelection *goquery.Selection) {
//查找class=title且其字节点下节点为a的的节点
title := contentSelection.Find(".title a").Text()
log.Println("第", i+1, "个帖子的标题:", title)
})
}


编译、运行输出如下(你看到的内容和当时社区的主题列表一致):

2015/04/06 22:15:24 第 1 个帖子的标题: 问个加载包的问题

2015/04/06 22:15:24 第 2 个帖子的标题: Tango v0.4版本发布,带来统一高性能的新路由

2015/04/06 22:15:24 第 3 个帖子的标题: 创业团队缺后端开发

2015/04/06 22:15:24 第 4 个帖子的标题: 自由是创造力和灵感的催化剂,需要golang后端开发人员
4000

2015/04/06 22:15:24 第 5 个帖子的标题: cgo编译出来的文件怎么用?

2015/04/06 22:15:24 第 6 个帖子的标题: cgo编译问题,Undefined symbols

2015/04/06 22:15:24 第 7 个帖子的标题: 北京 GO 研发程序员,全职,20K+

2015/04/06 22:15:24 第 8 个帖子的标题: Go 1.5 计划启动,使用 Go 来编译 Go

2015/04/06 22:15:24 第 9 个帖子的标题: beego.Error 原理

2015/04/06 22:15:24 第 10 个帖子的标题: 插入数据库操作测试 1分钟一百万条数据 这数据怎么样?

2015/04/06 22:15:24 第 11 个帖子的标题: Go最新资料汇总(十一)

2015/04/06 22:15:24 第 12 个帖子的标题: Azul3D_Go开发的3D游戏引擎简介

2015/04/06 22:15:24 第 13 个帖子的标题: Golang中goroutine线程何时终止问题

2015/04/06 22:15:24 第 14 个帖子的标题: 标准库中文版的testing是不是打不开了?

2015/04/06 22:15:24 第 15 个帖子的标题: 如何使用cgo编译出来的文件

是不是很简单?

这里我们使用了 Each 这个方法。在 jQuery 中,each 迭代时,如果返回 false,可以终止迭代。比如,我们希望遇到标题中包含 cgo 的主题时,停止迭代,可以使用 EachWithBreak(之所以没有使用 Each,是因为迭代终止的功能是后来加入的,为了不改变 Each 的行为,保持兼容性,引入了该方法):

doc.Find(".topics .topic").EachWithBreak(func(i int, contentSelection *goquery.Selection) bool {
title := contentSelection.Find(".title a").Text()
log.Println("第", i+1, "个帖子的标题:", title)
if strings.Contains(title, "cgo") {
return false
}
return true
})


从上面的输出可以看到,Each 遍历是按照页面节点的顺序的。如果我们希望反着处理,也就是先处理页面最底下的节点。查看文档,发现没有直接提供这样的方法。那么该怎么实现呢?

topicsSelection := doc.Find(".topics .topic")
for i := topicsSelection.Length() - 1; i >= 0; i-- {
// 返回的是 *html.Node
topicNode := topicsSelection.Get(i)
title := goquery.NewDocumentFromNode(topicNode).Find(".title a").Text()
log.Println("第", i+1, "个帖子的标题:", title)
}


这里用到了 NewDocumentFromNode,把其中某一块 HTML 当做文档,对其进行操作。

输出如下:

2015/04/06 22:50:28 第 15 个帖子的标题: 如何使用cgo编译出来的文件

2015/04/06 22:50:28 第 14 个帖子的标题: 标准库中文版的testing是不是打不开了?

2015/04/06 22:50:28 第 13 个帖子的标题: Golang中goroutine线程何时终止问题

2015/04/06 22:50:28 第 12 个帖子的标题: Azul3D_Go开发的3D游戏引擎简介

2015/04/06 22:50:28 第 11 个帖子的标题: Go最新资料汇总(十一)

2015/04/06 22:50:28 第 10 个帖子的标题: 插入数据库操作测试 1分钟一百万条数据 这数据怎么样?

2015/04/06 22:50:28 第 9 个帖子的标题: beego.Error 原理

2015/04/06 22:50:28 第 8 个帖子的标题: Go 1.5 计划启动,使用 Go 来编译 Go

2015/04/06 22:50:28 第 7 个帖子的标题: 北京 GO 研发程序员,全职,20K+

2015/04/06 22:50:28 第 6 个帖子的标题: cgo编译问题,Undefined symbols

2015/04/06 22:50:28 第 5 个帖子的标题: cgo编译出来的文件怎么用?

2015/04/06 22:50:28 第 4 个帖子的标题: 自由是创造力和灵感的催化剂,需要golang后端开发人员

2015/04/06 22:50:28 第 3 个帖子的标题: 创业团队缺后端开发

2015/04/06 22:50:28 第 2 个帖子的标题: Tango v0.4版本发布,带来统一高性能的新路由

2015/04/06 22:50:28 第 1 个帖子的标题: 问个加载包的问题

除了获取节点的文本内容,还可以获取节点的属性值、判断是否有某个 class 等,gopher 们可以自己试验。

顺便附上jquery的选择器的格式:



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  html goquery