goquery - 有点像那个j东西,只不过是用Go语言实现的
goquery为Go语言带来了类似jQuery的语法和一系列功能。它基于Go的net/html包和CSS选择器库cascadia。由于net/html解析器返回的是节点而非完整的DOM树,jQuery的有状态操作函数(如height()、css()、detach())被省略了。
此外,由于net/html解析器要求UTF-8编码,goquery也是如此:确保源文档提供UTF-8编码的HTML是调用者的责任。有关各种实现方法,请参阅wiki。
在语法上,它尽可能接近jQuery,尽可能使用相同的函数名,并保留了那种温暖而模糊的可链式接口。鉴于jQuery是如此流行的库,我觉得编写一个类似的HTML操作库最好遵循其API,而不是重新开始(与Go的fmt
包精神相同),尽管它的一些方法不太直观(说的就是你,index()...)。
目录
安装
请注意,从goquery的v1.9.0
版本开始,由于使用了泛型,需要Go 1.18+。对于之前的goquery版本,由于net/html
依赖,需要Go 1.1+版本。正在进行的goquery开发在最新的两个Go版本上进行测试。
$ go get github.com/PuerkitoBio/goquery
(可选)运行单元测试:
$ cd $GOPATH/src/github.com/PuerkitoBio/goquery
$ go test
(可选)运行基准测试(警告:需要几分钟时间):
$ cd $GOPATH/src/github.com/PuerkitoBio/goquery
$ go test -bench=".*"
更新日志
请注意,goquery的API现已稳定,不会发生破坏性变更。
- 2024-04-29 (v1.9.2) : 更新
go.mod
依赖。 - 2024-02-29 (v1.9.1) : 改进
Map
函数和Selection.Map
方法的内存分配和性能,更好地记录cascadia的差异(感谢 @jwilsson)。 - 2024-02-22 (v1.9.0) : 添加通用
Map
函数,goquery现在需要Go 1.18+版本(感谢 @Fesaa)。 - 2023-02-18 (v1.8.1) : 更新
go.mod
依赖,更新CI工作流程。 - 2021-10-25 (v1.8.0) : 添加
Render
函数,将Selection
渲染到io.Writer
(感谢 @anthonygedeon)。 - 2021-07-11 (v1.7.1) : 更新go.mod依赖并添加dependabot配置(感谢 @jauderho)。
- 2021-06-14 (v1.7.0) : 添加
Single
和SingleMatcher
函数以优化首次匹配选择(感谢 @gdollardollar)。 - 2021-01-11 (v1.6.1) : 修复在包含非元素节点的
Selection
上调用{Prepend,Append,Set}Html
时的崩溃问题。 - 2020-10-08 (v1.6.0) : 对所有处理html字符串的函数(
AfterHtml
、AppendHtml
等)在容器节点的上下文中解析html。感谢 @thiemok 和 @davidjwilkins 的贡献。 - 2020-02-04 (v1.5.1) : 更新模块依赖。
- 2018-11-15 (v1.5.0) : 支持Go模块(感谢 @Zaba505)。
- 2018-06-07 (v1.4.1) : 添加
NewDocumentFromReader
示例。 - 2018-03-24 (v1.4.0) : 废弃
NewDocument(url)
和NewDocumentFromResponse(response)
。 - 2018-01-28 (v1.3.0) : 在
Slice
中添加ToEnd
常量,用于选择到末尾(感谢 @davidjwilkins 提出问题)。 - 2018-01-11 (v1.2.0) : 添加
AddBack*
并废弃AndSelf
(感谢 @davidjwilkins)。 - 2017-02-12 (v1.1.0) : 添加
SetHtml
和SetText
(感谢 @glebtv)。 - 2016-12-29 (v1.0.2) : 优化
Selection.Text
的内存分配(感谢 @radovskyb)。 - 2016-08-28 (v1.0.1) : 优化大型文档的性能。
- 2016-07-27 (v1.0.0) : 标记1.0.0版本。
- 2016-06-15 : 无效的选择器字符串内部编译为永不匹配任何节点的
Matcher
实现(而不是引发panic)。例如,doc.Find("~")
返回一个空的*Selection
对象。 - 2016-02-02 : 添加
NodeName
实用函数,类似于DOM的nodeName
属性。它返回选择中第一个元素的标签名,以及非元素节点的其他相关值(详见文档)。添加OuterHtml
实用函数,类似于DOM的outerHTML
属性(命名为OuterHtml
,小写以保持与现有Selection
上的Html
方法一致)。 - 2015-04-20 : 添加
AttrOr
辅助方法,返回属性值或缺失时的默认值。感谢 piotrkowalczuk。 - 2015-02-04 : 添加更多操作函数 - Prepend* - 再次感谢 Andrew Stone。
- 2014-11-28 : 添加更多操作函数 - ReplaceWith*、Wrap* 和 Unwrap - 再次感谢 Andrew Stone。
- 2014-11-07 : 添加操作函数(感谢 Andrew Stone)和
*Matcher
函数,接收已编译的cascadia选择器而非选择器字符串,从而避免goquery通过cascadia.MustCompile
调用可能引发的panic。这提高了性能(选择器可以编译一次并重复使用)并使错误处理更符合惯例(您可以处理cascadia的编译错误,而不是从panic中恢复,这一直困扰着我)。注意,实际期望的类型是Matcher
接口,cascadia.Selector
实现了该接口。可以使用其他匹配器实现。 - 2014-11-06 : 将net/html的导入路径更改为golang.org/x/net/html(参见 https://groups.google.com/forum/#!topic/golang-nuts/eD8dh3T9yyA)。当您使用
html.Node
调用goquery时,请确保更新代码以使用新的导入路径。 - v0.3.2 : 添加
NewDocumentFromReader()
(感谢jweir),允许从io.Reader创建goquery文档。 - v0.3.1 : 添加
NewDocumentFromResponse()
(感谢assassingj),允许从http响应创建goquery文档。 - v0.3.0 : 添加
EachWithBreak()
,允许通过返回false来跳出Each()
循环。添加此函数而不是更改现有的Each()
以避免破坏兼容性。 - v0.2.1 : 使其可通过go get获取,现在go.net/html与Go1.0兼容(感谢@matrixik指出这一点)。
- v0.2.0 :在Slice()中添加对负索引的支持。重大变更 移除了
Document.Root
,Document
现在本身就是一个Selection
(单一选择,即根元素,与之前的Document.Root
相同)。添加了jQuery的Closest()方法。 - v0.1.1 :添加基准测试作为重构的基线,重构Next...()和Prev...()方法以使用新的html包的链表特性(Next/PrevSibling, FirstChild)。性能显著提升(在某些情况下提升40%以上)。
- v0.1.0 :初始发布。
API
与jQuery不同,goquery不是作为DOM文档的一部分加载,也不会作用于包含它的文档。因此,goquery需要被告知要操作哪个HTML文档。这就是Document
类型的用途。它持有根文档节点作为初始的Selection值进行操作。
goquery暴露了两个结构体Document
和Selection
,以及Matcher
接口。jQuery通常对同一个函数有多个变体(无参数、选择器字符串参数、jQuery对象参数、DOM元素参数等)。goquery没有使用带有可变空接口参数的单一方法来提供相同的功能,而是使用静态类型的签名,遵循以下命名约定:
- 当jQuery等效函数可以无参数调用时,无参数签名与jQuery使用相同的名称(如
Prev()
),而带选择器字符串参数的版本称为XxxFiltered()
(如PrevFiltered()
) - 当jQuery等效函数必须有一个参数时,选择器字符串版本使用与jQuery相同的名称(如
Is()
) - jQuery中接受jQuery对象作为参数的签名在goquery中定义为
XxxSelection()
,并接受*Selection
对象作为参数(如FilterSelection()
) - jQuery中接受DOM元素作为参数的签名在goquery中定义为
XxxNodes()
,并接受*html.Node
类型的可变参数(如FilterNodes()
) - jQuery中接受函数作为参数的签名在goquery中定义为
XxxFunction()
,并接受一个函数作为参数(如FilterFunction()
) - 可以用选择器字符串调用的goquery方法有对应的版本,接受
Matcher
接口,定义为XxxMatcher()
(如IsMatcher()
)
在jQuery中不存在但在Go中有用的实用函数被实现为函数(接受*Selection
作为参数),以避免与*Selection
的方法(保留用于jQuery等效行为)可能发生的命名冲突。
完整的包参考文档可以在这里找到。
请注意,Cascadia的选择器不一定匹配jQuery(Sizzle)支持的所有选择器。详情请参见cascadia项目。此外,选择器的工作方式更像DOM的querySelectorAll
,而不是jQuery的匹配器 - 它们没有上下文匹配的概念(关于这意味着什么的一些具体例子,请参见此票据)。实践中,这通常不太重要,但值得一提。无效的选择器字符串会编译成一个无法匹配任何节点的Matcher
。接受选择器字符串作为参数的各种函数的行为遵循这一事实,例如(其中~
是一个无效的选择器字符串):
Find("~")
返回一个空选择,因为选择器字符串不匹配任何内容。Add("~")
返回一个新的选择,持有与原始选择相同的节点,因为它没有添加任何节点(选择器字符串不匹配任何内容)。ParentsFiltered("~")
返回一个空选择,因为选择器字符串不匹配任何内容。ParentsUntil("~")
返回选择的所有父元素,因为选择器字符串没有匹配到任何元素来停止在顶层元素之前。
示例
在wiki中查看一些技巧和窍门。
改编自example_test.go:
package main
import (
"fmt"
"log"
"net/http"
"github.com/PuerkitoBio/goquery"
)
func ExampleScrape() {
// 请求HTML页面
res, err := http.Get("http://metalsucks.net")
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
if res.StatusCode != 200 {
log.Fatalf("状态码错误: %d %s", res.StatusCode, res.Status)
}
// 加载HTML文档
doc, err := goquery.NewDocumentFromReader(res.Body)
if err != nil {
log.Fatal(err)
}
// 查找评论项目
doc.Find(".left-content article .post-title").Each(func(i int, s *goquery.Selection) {
// 对于找到的每个项目,获取标题
title := s.Find("a").Text()
fmt.Printf("评论 %d: %s\n", i, title)
})
}
func main() {
ExampleScrape()
}
相关项目
- Goq,一个基于goquery和结构体标签的HTML反序列化和抓取库。
- andybalholm/cascadia,goquery使用的CSS选择器库。
- suntong/cascadia,cascadia CSS选择器库的命令行界面,用于测试选择器。
- gocolly/colly,一个快速优雅的网络爬虫框架。
- gnulnx/goperf,一个网站性能测试工具,也可以获取静态资源。
- MontFerret/ferret,声明式网络爬虫。
- tacusci/berrycms,一个现代简单易用的CMS,易于编写插件。
- Dataflow kit,Go语言的Web爬虫框架。
- Geziyor,一个快速的Go语言网络爬虫和抓取框架。支持JS渲染。
- Pagser,一个简单、易用、可扩展、可配置的HTML解析器,基于goquery和结构体标签。
- stitcherd,一个使用CSS选择器和DOM更新进行服务器端包含的服务器。
- goskyr,一个易于配置的Go语言命令行爬虫。
- goGetJS,一个用于提取、搜索和保存JavaScript文件的工具(可选无头浏览器)。
- fitter,一个用于从JSON、XML、HTML和XPath格式页面中选择值的工具。
- seltabl,一个类似ORM的包和支持语言服务器,用于从HTML中提取值。
支持
您可以通过以下几种方式支持本项目:
- 使用它、为它加星、用它构建项目、传播它!
- 如果您确实用它构建了开源或其他公开可见的项目,请告诉我,我可以将其添加到相关项目部分!
- 提出问题以改进项目(注意:文档错别字和澄清也是问题!)
- 在提出新问题之前请先搜索现有问题 - 可能已经有人提出过了。
- 拉取请求:除非修复非常简单,否则请先在问题中讨论新代码。
- 确保新代码经过测试。
- 注意现有代码 - 破坏现有代码的PR很可能会被拒绝,除非它修复了一个严重问题。
- 赞助开发者
- 查看Github仓库顶部的赞助按钮
- 或通过下方的BuyMeACoffee.com
许可证
BSD 3-Clause许可证,与Go语言相同。Cascadia的许可证在这里。