golang goquery selector(选择器) 示例大全

共 9291字,需浏览 19分钟

 ·

2020-08-28 18:31

最近研究Go爬虫相关的知识,使用到goquery这个库比较多,尤其是对爬取到的HTML进行选择和查找匹配的内容时,goquery的选择器使用尤其多,而且还有很多不常用但又很有用的选择器,这里总结下,以供参考。

如果大家以前做过前端开发,对jquery不会陌生,goquery类似jquery,它是jquery的go版本实现。使用它,可以很方便的对HTML进行处理。

基于HTML Element 元素的选择器

这个比较简单,就是基于a,p等这些HTML的基本元素进行选择,这种直接使用Element名称作为选择器即可。比如dom.Find("div")

func main() {
   html := `

               
DIV1

               
DIV2

               SPAN

           
           `

   dom,err:=goquery.NewDocumentFromReader(strings.NewReader(html))
   if err!=nil{
       log.Fatalln(err)
   }

   dom.Find("div").Each(func(i int, selection *goquery.Selection) {
       fmt.Println(selection.Text())
   })
}

以上示例,可以把div元素筛选出来,而body,span并不会被筛选。

ID 选择器

这个是使用频次最多的,类似于上面的例子,有两个div元素,其实我们只需要其中的一个,那么我们只需要给这个标记一个唯一的id即可,这样我们就可以使用id选择器,精确定位了。

func main() {
   html := `

               
DIV1

               
DIV2

               SPAN

           
           `

   dom,err:=goquery.NewDocumentFromReader(strings.NewReader(html))
   if err!=nil{
       log.Fatalln(err)
   }

   dom.Find("#div1").Each(func(i int, selection *goquery.Selection) {
       fmt.Println(selection.Text())
   })
}

Element ID 选择器

id选择器以#开头,紧跟着元素id的值,使用语法为dom.Find(#id),后面的例子我会简写为Find(#id),大家知道这是代表goquery选择器的即可。

如果有相同的ID,但是它们又分别属于不同的HTML元素怎么办?有好办法,和Element结合起来。比如我们筛选元素为div,并且iddiv1的元素,就可以使用Find(div#div1)这样的筛选器进行筛选。

所以这类筛选器的语法为Find(element#id),这是常用的组合方法,比如后面讲的过滤器也可以采用这种方式组合使用。

Class选择器

class也是HTML中常用的属性,我们可以通过class选择器来快速的筛选需要的HTML元素,它的用法和ID选择器类似,为Find(".class")

func main() {
   html := `

               
DIV1

               
DIV2

               SPAN

           
           `

   dom,err:=goquery.NewDocumentFromReader(strings.NewReader(html))
   if err!=nil{
       log.Fatalln(err)
   }

   dom.Find(".name").Each(func(i int, selection *goquery.Selection) {
       fmt.Println(selection.Text())
   })
}

以上示例中,就筛选出来classname的这个div元素。

Element Class 选择器

class选择器和id选择器一样,也可以结合着HTML元素使用,他们的语法也类似Find(element.class),这样就可以筛选特定element、并且指定class的元素。

属性选择器

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

func main() {
   html := `

               
DIV1

               
DIV2

               SPAN

           
           `

   dom,err:=goquery.NewDocumentFromReader(strings.NewReader(html))
   if err!=nil{
       log.Fatalln(err)
   }

   dom.Find("div[class]").Each(func(i int, selection *goquery.Selection) {
       fmt.Println(selection.Text())
   })
}

示例中我们通过div[class]这个选择器,筛选出Element为div并且有class这个属性的,所以第一个div没有被筛选到。

刚刚上面这个示例是采用是否存在某个属性为筛选器,同理,我们可以筛选出属性为某个值的元素。

    dom.Find("div[class=name]").Each(func(i int, selection *goquery.Selection) {
       fmt.Println(selection.Text())
   })

这样我们就可以筛选出class这个属性值为namediv元素。

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

除了完全相等,还有其他匹配方式,使用方式类似,这里统一列举下,不再举例

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

以上是属性筛选器的用法,都是以一个属性筛选器为例,当然你也可以使用多个属性筛选器组合使用,比如:
Find("div[id][lang=zh]"),用多个中括号连起来即可。当有多个属性筛选器的时候,要同时满足这些筛选器的元素才能被筛选出来。

parent>child选择器

如果我们想筛选出某个元素下符合条件的子元素,我们就可以使用子元素筛选器,它的语法为Find("parent>child"),表示筛选parent这个父元素下,符合child这个条件的最直接(一级)的子元素。

func main() {
   html := `

               
DIV1

               
DIV2

               
DIV3

               
                   
DIV4

               


           
           `

   dom,err:=goquery.NewDocumentFromReader(strings.NewReader(html))
   if err!=nil{
       log.Fatalln(err)
   }

   dom.Find("body>div").Each(func(i int, selection *goquery.Selection) {
       fmt.Println(selection.Text())
   })
}

以上示例,筛选出body这个父元素下,符合条件的最直接的子元素div,结果是DIV1、DIV2、DIV3,虽然DIV4也是body的子元素,但不是一级的,所以不会被筛选到。

那么问题来了,我就是想把DIV4也筛选出来怎么办?就是要筛选body下所有的div元素,不管是一级、二级还是N级。有办法的,goquery考虑到了,只需要把大于号(>)改为空格就好了。比如上面的例子,改为如下选择器即可。

    dom.Find("body div").Each(func(i int, selection *goquery.Selection) {
       fmt.Println(selection.Text())
   })

prev+next相邻选择器

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

func main() {
   html := `

               
DIV1

               

P1


               
DIV2

               
DIV3

               
                   
DIV4

               

               

P2



           
           `

   dom,err:=goquery.NewDocumentFromReader(strings.NewReader(html))
   if err!=nil{
       log.Fatalln(err)
   }

   dom.Find("div[lang=zh]+p").Each(func(i int, selection *goquery.Selection) {
       fmt.Println(selection.Text())
   })
}

这个示例演示了这种用法,我们想选择

P1

这个元素,但是没啥规律,我们发现它前面的
DIV1
很有规律,可以选择,所以我们就可以采用Find("div[lang=zh]+p")达到选择P元素的目的。

这种选择器的语法是("prev+next"),中间是一个加号(+),+号前后也是选择器。

本文为原创文章,转载注明出处,欢迎扫码关注公众号flysnow_org或者网站http://www.flysnow.org/,第一时间看后续精彩文章。一些比较可耻的网站抓取我的文章会去掉版权信息,这里再写一段,大家见谅。

prev~next选择器

有相邻就有兄弟,兄弟选择器就不一定要求相邻了,只要他们共有一个父元素就可以。

    dom.Find("div[lang=zh]~p").Each(func(i int, selection *goquery.Selection) {
       fmt.Println(selection.Text())
   })

刚刚的例子,只需要把+号换成~号,就可以把P2也筛选出来,因为P2P1DIV1都是兄弟。

兄弟选择器的语法是("prev~next"),也就是相邻选择器的`+`换成了`~`。

内容过滤器

有时候我们使用选择器选择出来后后,希望再过滤一下,这时候就用到过滤器了,过滤器有很多,我们先讲内容过滤器这一种。

    dom.Find("div:contains(DIV2)").Each(func(i int, selection *goquery.Selection) {
       fmt.Println(selection.Text())
   })

Find(":contains(text)")表示筛选出的元素要包含指定的文本,我们例子中要求选择出的div元素要包含DIV2文本,那么只有一个DIV2元素满足要求。

此外还有Find(":empty")表示筛选出的元素都不能有子元素(包括文本元素),只筛选那些不包含任何子元素的元素。

Find(":has(selector)")contains差不多,只不过这个是包含的是元素节点。

    dom.Find("span:has(div)").Each(func(i int, selection *goquery.Selection) {
       fmt.Println(selection.Text())
   })

以上示例表示筛选出包含div元素的span节点。

:first-child过滤器

:first-child过滤器,语法为Find(":first-child"),表示筛选出的元素要是他们的父元素的第一个子元素,如果不是,则不会被筛选出来。

func main() {
   html := `

               
DIV1

               

P1


               
DIV2

               
DIV3

               
                   
DIV4

                   
DIV5

               

               

P2


               


           
           `

   dom,err:=goquery.NewDocumentFromReader(strings.NewReader(html))
   if err!=nil{
       log.Fatalln(err)
   }

   dom.Find("div:first-child").Each(func(i int, selection *goquery.Selection) {
       fmt.Println(selection.Html())
   })
}

以上例子中,我们使用Find("div")会筛选出所有的div元素,但是我们加了:first-child后,就只有DIV1DIV4了,因为只有这两个是他们父元素的第一个子元素,其他的DIV都不满足。

:first-of-type过滤器

:first-child选择器限制的比较死,必须得是第一个子元素,如果该元素前有其他在前面,就不能用:first-child了,这时候:first-of-type就派上用场了,它要求只要是这个类型的第一个就可以,我们把上面的例子微调下。

func main() {
   html := `

               
DIV1

               

P1


               
DIV2

               
DIV3

               
                   

P2


                   
DIV5

               

               


           
           `

   dom,err:=goquery.NewDocumentFromReader(strings.NewReader(html))
   if err!=nil{
       log.Fatalln(err)
   }

   dom.Find("div:first-of-type").Each(func(i int, selection *goquery.Selection) {
       fmt.Println(selection.Html())
   })
}

改动很简单,把原来的DIV4换成了P2,如果我们还使用:first-child,DIV5是不能被筛选出来的,因为它不是第一个子元素,它前面还有一个P2。这时候我们使用:first-of-type就可以达到目的,因为它要求是同类型第一个就可以。DIV5就是这个div类型的第一个元素,P2不是div类型,被忽略。

:last-child 和 :last-of-type过滤器

这两个正好和上面的:first-child:first-of-type相反,表示最后一个,这里不再举例,大家可以自己试试。

:nth-child(n) 过滤器

这个表示筛选出的元素是其父元素的第n个元素,n以1开始。所以我们可以知道:first-child:nth-child(1)是相等的。通过指定n,我们就很灵活的筛选出我们需要的元素。

func main() {
   html := `

               
DIV1

               

P1


               
DIV2

               
DIV3

               
                   

P2


                   
DIV5

               

               


           
           `

   dom,err:=goquery.NewDocumentFromReader(strings.NewReader(html))
   if err!=nil{
       log.Fatalln(err)
   }

   dom.Find("div:nth-child(3)").Each(func(i int, selection *goquery.Selection) {
       fmt.Println(selection.Html())
   })
}

这个示例会筛选出DIV2,因为DIV2是其父元素body的第三个子元素。

:nth-of-type(n) 过滤器

:nth-of-type(n)和 :nth-child(n) 类似,只不过它表示的是同类型元素的第n个,所以:nth-of-type(1) 和 :first-of-type是相等的,大家可以自己试试,这里不再举例。

nth-last-child(n) 和:nth-last-of-type(n) 过滤器

这两个和上面的类似,只不过是倒序开始计算的,最后一个元素被当成了第一个。大家自己测试下看看效果,很明显。

:only-child 过滤器

Find(":only-child") 过滤器,从字面上看,可以猜测出来,它表示筛选的元素,在其父元素中,只有它自己,它的父元素没有其他子元素,才会被匹配筛选出来。

func main() {
   html := `
               
DIV1

               
                   
DIV5

               


           
           `

   dom,err:=goquery.NewDocumentFromReader(strings.NewReader(html))
   if err!=nil{
       log.Fatalln(err)
   }

   dom.Find("div:only-child").Each(func(i int, selection *goquery.Selection) {
       fmt.Println(selection.Html())
   })
}

示例中DIV5就可以被筛选出来,因为它是它的父元素span达到唯一子元素,但DIV1就不是,所以不能呗筛选出来。

:only-of-type 过滤器

上面的例子,如果想筛选出DIV1怎么办?可以使用Find(":only-of-type"),因为它是它的父元素中,唯一的div元素,这就是:only-of-type过滤器所要做的,同类型元素只要只有一个,就可以被筛选出来。大家把上面的例子改成:only-of-type试试,看看是否有DIV1

选择器或(|)运算

如果我们想同时筛选出div,span等元素怎么办?这时候可以采用多个选择器进行组合使用,并且以逗号(,)分割,Find("selector1, selector2, selectorN")表示,只要满足其中一个选择器就可以被筛选出来,也就是选择器的或(|)运算操作。

func main() {
   html := `
               
DIV1

               
                   
DIV5

               


           
           `

   dom,err:=goquery.NewDocumentFromReader(strings.NewReader(html))
   if err!=nil{
       log.Fatalln(err)
   }

   dom.Find("div,span").Each(func(i int, selection *goquery.Selection) {
       fmt.Println(selection.Html())
   })
}

小结

goquery 是解析HTML网页必备的利器,在爬虫抓取网页的过程中,灵活的使用goquery不同的选择器,可以让我们的抓取工作事半功倍,大大提升爬虫的效率。




推荐阅读



学习交流 Go 语言,扫码回复「进群」即可


站长 polarisxu

自己的原创文章

不限于 Go 技术

职场和创业经验


Go语言中文网

每天为你

分享 Go 知识

Go爱好者值得关注


浏览 14
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报