Colly是Golang世界最知名的Web爬蟲框架了,它的API清晰明瞭,高度可配置和可擴展,支持分佈式抓取,還支持多種存儲後端(如內存、Redis、MongoDB等)。這篇文章記錄我學習使用它的的一些感覺和理解。html
首先安裝它:node
❯ go get -u github.com/gocolly/colly/...
複製代碼
這個go get
和以前安裝包不太同樣,最後有...
這樣的省略號,它的意思是也獲取這個包的子包和依賴。git
Colly的文檔寫的算是很詳細很完整的了,並且項目下的_examples目錄裏面也有不少爬蟲例子,上手很是容易。先看個人一個例子:github
package main
import (
"fmt"
"github.com/gocolly/colly"
)
func main() {
c := colly.NewCollector(
colly.UserAgent("Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"),
)
c.OnRequest(func(r *colly.Request) {
fmt.Println("Visiting", r.URL)
})
c.OnError(func(_ *colly.Response, err error) {
fmt.Println("Something went wrong:", err)
})
c.OnResponse(func(r *colly.Response) {
fmt.Println("Visited", r.Request.URL)
})
c.OnHTML(".paginator a", func(e *colly.HTMLElement) {
e.Request.Visit(e.Attr("href"))
})
c.OnScraped(func(r *colly.Response) {
fmt.Println("Finished", r.Request.URL)
})
c.Visit("https://movie.douban.com/top250?start=0&filter=")
}
複製代碼
這個程序就是去找豆瓣電影Top250的所有連接,如OnHTML方法的第一個函數所描述,找類名是paginator的標籤下的a標籤的href屬性值。golang
運行一下:web
❯ go run colly/doubanCrawler1.go
Visiting https://movie.douban.com/top250?start=0&filter=
Visited https://movie.douban.com/top250?start=0&filter=
Visiting https://movie.douban.com/top250?start=25&filter=
Visited https://movie.douban.com/top250?start=25&filter=
...
Finished https://movie.douban.com/top250?start=25&filter=
Finished https://movie.douban.com/top250?start=0&filter=
複製代碼
在Colly中主要實體就是一個Collector對象(用colly.NewCollector建立),Collector管理網絡通訊和對於響應的回調執行。Collector在初始化時能夠接受多種設置項,例如這個例子裏面我就設置了UserAgent的值。其餘的設置項能夠去看官方網站。後端
Collector對象接受多種回調方法,有不一樣的做用,按調用順序我列出來:bash
Called after OnXML callbacks
,實際上對於OnHTML也有效,你們能夠注意一下。仍是以前的需求,先看看豆瓣Top250頁面每一個條目的部分HTML代碼:網絡
<ol class="grid_view">
<li>
<div class="item">
<div class="info">
<div class="hd">
<a href="https://movie.douban.com/subject/1292052/" class="">
<span class="title">肖申克的救贖</span>
<span class="title"> / The Shawshank Redemption</span>
<span class="other"> / 月黑高飛(港) / 刺激 1995(臺)</span>
</a>
<span class="playable">[可播放]</span>
</div>
</div>
</div>
</li>
....
</ol>
複製代碼
看看這個程序怎麼寫的:併發
package main
import (
"log"
"strings"
"github.com/gocolly/colly"
)
func main() {
c := colly.NewCollector(
colly.Async(true),
colly.UserAgent("Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"),
)
c.Limit(&colly.LimitRule{DomainGlob: "*.douban.*", Parallelism: 5})
c.OnRequest(func(r *colly.Request) {
log.Println("Visiting", r.URL)
})
c.OnError(func(_ *colly.Response, err error) {
log.Println("Something went wrong:", err)
})
c.OnHTML(".hd", func(e *colly.HTMLElement) {
log.Println(strings.Split(e.ChildAttr("a", "href"), "/")[4],
strings.TrimSpace(e.DOM.Find("span.title").Eq(0).Text()))
})
c.OnHTML(".paginator a", func(e *colly.HTMLElement) {
e.Request.Visit(e.Attr("href"))
})
c.Visit("https://movie.douban.com/top250?start=0&filter=")
c.Wait()
}
複製代碼
若是你有心運行上面的那個例子,能夠感覺到抓取時同步的,比較慢。而此次在colly.NewCollector
裏面加了一項colly.Async(true)
,表示抓取時異步的。在Colly裏面很是方便控制併發度,只抓取符合某個(些)規則的URLS,有一句c.Limit(&colly.LimitRule{DomainGlob: "*.douban.*", Parallelism: 5})
,表示限制只抓取域名是douban(域名後綴和二級域名不限制)的地址,固然還支持正則匹配某些符合的 URLS,具體的能夠看官方文檔。
另外Limit方法中也限制了併發是5。爲何要控制併發度呢?由於抓取的瓶頸每每來自對方網站的抓取頻率的限制,若是在一段時間內達到某個抓取頻率很容易被封,因此咱們要控制抓取的頻率。另外爲了避免給對方網站帶來額外的壓力和資源消耗,也應該控制你的抓取機制。
這個例子裏面沒有OnResponse方法,主要是裏面沒有實際的邏輯。可是多用了Wait方法,這是由於在Async爲true時須要等待協程都完成再結束。可是呢,有2個OnHTML方法,一個用來確認都訪問那些頁面,另一個裏面就是抓取條目信息的邏輯了。也就是這部分:
c.OnHTML(".hd", func(e *colly.HTMLElement) {
log.Println(strings.Split(e.ChildAttr("a", "href"), "/")[4],
strings.TrimSpace(e.DOM.Find("span.title").Eq(0).Text()))
})
複製代碼
Colly的HTML解析庫用的是goquery,因此寫起來遵循goquery的語法就能夠了。ChildAttr方法能夠得到元素對應屬性的值,另一個沒有列出來的ChildText,用於得到元素的文本內容。可是咱們這個例子中類名爲title的span標籤有2個,用ChildText回直接返回2個標籤的所有的值,可是Colly又沒有提供ChildTexts方法(有ChildAttrs),因此只能看源碼看ChildText實現改爲了strings.TrimSpace(e.DOM.Find("span.title").Eq(0).Text())
,這樣就能夠拿到第一個符合的文本了。
若是你不喜歡goquery這種形式,固然也能夠切換HTML解析方案,看我這個例子:
import "github.com/antchfx/htmlquery"
c.OnResponse(func(r *colly.Response) {
doc, err := htmlquery.Parse(strings.NewReader(string(r.Body)))
if err != nil {
log.Fatal(err)
}
nodes := htmlquery.Find(doc, `//ol[@class="grid_view"]/li//div[@class="hd"]`)
for _, node := range nodes {
url := htmlquery.FindOne(node, "./a/@href")
title := htmlquery.FindOne(node, `.//span[@class="title"]/text()`)
log.Println(strings.Split(htmlquery.InnerText(url), "/")[4],
htmlquery.InnerText(title))
}
})
複製代碼
此次我改在OnResponse方法裏面得到條目ID和標題。htmlquery.Parse
須要接受一個實現io.Reader接口的對象,因此用了strings.NewReader(string(r.Body))
。其餘的代碼是以前 用Golang寫爬蟲(五) - 使用XPath裏面寫過的,直接拷貝過來就能夠了。
試用Colly後就喜歡上了它,你呢?
本文原文地址: strconv.com/posts/use-c…
完整代碼能夠在這個地址找到。