最近在看go 然後寫了個爬蟲爬點數據做個後臺然後我安卓客戶端在實現一套,在爬取過程中先後試過了、http/net
獲取body
解析用正則、得到bod
用goquery
創建document
,直接用gocolly
來訪問解析,剛好gocolly
內部是也用了goquery
由於之前沒寫過js
只是對html標籤
有一點了解 所以在網上找到了這篇goquery的選擇器
·文章有部分修改添加
goquery
對爬取到的HTML進行選擇和查找匹配的內容時,goquery
的選擇器讓解析html操作更簡潔
,而且還有很多不常用但又很有用的選擇器,這裏總結下,以供參考。
以下內容轉載自飛雪無情的 golang goquery selector(選擇器) 示例大全
如果大家以前做過前端開發,對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
選擇器的即可。
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())
})
}
如果有相同的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元素,區分大小寫 |
(如果=後面的值是拼接的用單引號包裹即可)
示例
func main() {
//spiderTop250()
//spoderMoiveInfo("https://movie.douban.com/subject/3011235/")
html := `<body>
<script type="application/ld+json">JSDKK</script>
</body>
`
dom, err := goquery.NewDocumentFromReader(strings.NewReader(html))
if err != nil {
log.Fatalln(err)
}
dom.Find("script[type='application/ld+json']").Each(func(i int, selection *goquery.Selection) {
fmt.Println(selection.Text())
})
}
以上是屬性篩選器的用法,都是以一個屬性篩選器爲例,當然你也可以使用多個屬性篩選器組合使用,比如: 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
不同的選擇器,可以讓我們的抓取工作事半功倍,大大提升爬蟲的效率。