網站首頁 編程語言 正文
引言
最近研究Go爬蟲相關的知識,使用到goquery這個庫比較多,尤其是對爬取到的HTML進行選擇和查找匹配的內容時,goquery的選擇器使用尤其多,而且還有很多不常用但又很有用的選擇器,這里總結下,以供參考。
如果大家以前做過前端開發,對jquery不會陌生,goquery類似jquery,它是jquery的go版本實現。使用它,可以很方便的對HTML進行處理。
基于HTML Element 元素的選擇器
這個比較簡單,就是基于a
,p
等這些HTML的基本元素進行選擇,這種直接使用Element名稱作為選擇器即可。
比如dom.Find("div")
。
func main() { html := `<body> <div>DIV1</div> <div>DIV2</div> <span>SPAN</span> </body> ` 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 := `<body> <div id="div1">DIV1</div> <div>DIV2</div> <span>SPAN</span> </body> ` 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
,并且id
是div1
的元素,就可以使用Find(div#div1)
這樣的篩選器進行篩選。
所以這類篩選器的語法為Find(element#id)
,這是常用的組合方法,比如后面講的過濾器也可以采用這種方式組合使用。
Class選擇器
class
也是HTML中常用的屬性,我們可以通過class
選擇器來快速的篩選需要的HTML元素,它的用法和ID
選擇器類似,為Find(".class")
。
func main() { html := `<body> <div id="div1">DIV1</div> <div class="name">DIV2</div> <span>SPAN</span> </body> ` 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()) }) }
以上示例中,就篩選出來class
為name
的這個div
元素。
Element Class 選擇器
class
選擇器和id
選擇器一樣,也可以結合著HTML元素使用,他們的語法也類似Find(element.class)
,這樣就可以篩選特定element、并且指定class的元素。
屬性選擇器
一個HTML元素都有自己的屬性以及屬性值,所以我們也可以通過屬性和值篩選元素。
func main() { html := `<body> <div>DIV1</div> <div class="name">DIV2</div> <span>SPAN</span> </body> ` 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
這個屬性值為name
的div
元素。
當然我們這里以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 := `<body> <div lang="ZH">DIV1</div> <div lang="zh-cn">DIV2</div> <div lang="en">DIV3</div> <span> <div>DIV4</div> </span> </body> ` 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 := `<body> <div lang="zh">DIV1</div> <p>P1</p> <div lang="zh-cn">DIV2</div> <div lang="en">DIV3</div> <span> <div>DIV4</div> </span> <p>P2</p> </body> ` 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()) }) }
這個示例演示了這種用法,我們想選擇<p>P1</p>
這個元素,但是沒啥規律,我們發現它前面的<div lang="zh">DIV1</div>
很有規律,可以選擇,所以我們就可以采用Find("div[lang=zh]+p")
達到選擇P
元素的目的。
這種選擇器的語法是("prev+next")
,中間是一個加號(+),+號前后也是選擇器。
prev~next選擇器
有相鄰就有兄弟,兄弟選擇器就不一定要求相鄰了,只要他們共有一個父元素就可以。
dom.Find("div[lang=zh]~p").Each(func(i int, selection *goquery.Selection) { fmt.Println(selection.Text()) })
剛剛的例子,只需要把+
號換成~
號,就可以把P2
也篩選出來,因為P2
、P1
和DIV1
都是兄弟。
兄弟選擇器的語法是("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 := `<body> <div lang="zh">DIV1</div> <p>P1</p> <div lang="zh-cn">DIV2</div> <div lang="en">DIV3</div> <span> <div style="display:none;">DIV4</div> <div>DIV5</div> </span> <p>P2</p> <div></div> </body> ` 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
后,就只有DIV1
和DIV4
了,因為只有這兩個是他們父元素的第一個子元素,其他的DIV
都不滿足。
:first-of-type過濾器
:first-child
選擇器限制的比較死,必須得是第一個子元素,如果該元素前有其他在前面,就不能用:first-child
了,這時候:first-of-type
就派上用場了,它要求只要是這個類型的第一個就可以,我們把上面的例子微調下。
func main() { html := `<body> <div lang="zh">DIV1</div> <p>P1</p> <div lang="zh-cn">DIV2</div> <div lang="en">DIV3</div> <span> <p>P2</p> <div>DIV5</div> </span> <div></div> </body> ` 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 := `<body> <div lang="zh">DIV1</div> <p>P1</p> <div lang="zh-cn">DIV2</div> <div lang="en">DIV3</div> <span> <p>P2</p> <div>DIV5</div> </span> <div></div> </body> ` 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 := `<body> <div lang="zh">DIV1</div> <span> <div>DIV5</div> </span> </body> ` 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 := `<body> <div lang="zh">DIV1</div> <span> <div>DIV5</div> </span> </body> ` 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不同的選擇器,可以讓我們的抓取工作事半功倍,大大提升爬蟲的效率。
原文鏈接:https://juejin.cn/post/7145373304117788708
相關推薦
- 2022-04-03 Go語言讀取txt文檔的操作方法_Golang
- 2022-07-31 Windows使用cmd命令行查看、修改、刪除與添加環境變量_DOS/BAT
- 2022-08-07 Python算法練習之二分查找算法的實現_python
- 2022-04-25 C#使用游標實現補間函數_C#教程
- 2022-04-24 解決redis在linux上的部署的問題_Redis
- 2023-01-02 正確在Flutter中添加webview實現詳解_Android
- 2022-02-11 SQL中ISNULL函數使用介紹_數據庫其它
- 2022-09-06 C++詳細講解引用類型_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支