Go語(yǔ)言colly框架的快速入門(mén)
有些人可能認(rèn)為爬蟲(chóng)框架和 http client 庫(kù)的功能一樣,用 http client 庫(kù)也可以寫(xiě)爬蟲(chóng)。當(dāng)然,無(wú)論用第三方的 http client 庫(kù)還是官方的http庫(kù),都可以寫(xiě)爬蟲(chóng)。但術(shù)業(yè)有專(zhuān)攻,爬蟲(chóng)框架專(zhuān)門(mén)為批量爬取設(shè)計(jì),往往擁有并發(fā)控制、隊(duì)列、緩存、HTML 解析等一系列開(kāi)箱即用的 API,能大幅簡(jiǎn)化在爬蟲(chóng)實(shí)現(xiàn)過(guò)程中的負(fù)擔(dān)
Python 中非常知名的爬蟲(chóng)框架有Scrapy,Go 中也有一些 star 數(shù)較高的爬蟲(chóng)框架。colly就是其中的佼佼者,它 API 簡(jiǎn)潔,性能優(yōu)良,開(kāi)箱即用。今天就來(lái)快速學(xué)習(xí)一下吧!
基本使用
首先引入依賴(lài)
go get -u github.com/gocolly/colly/...
之后就可以使用colly,通過(guò)Visit函數(shù)來(lái)告知colly 采集器要訪問(wèn)的 URL
package main
import (
"fmt"
"github.com/gocolly/colly/v2"
)
func main() {
c := colly.NewCollector()
c.Visit("http://go-colly.org/")
}這樣就行了么?運(yùn)行下試試,沒(méi)有任何輸出。
$ go run main.go
原因在于代碼要求 colly 采集器訪問(wèn)http://go-colly.org/,但沒(méi)有設(shè)定訪問(wèn) URL 成功或者失敗后要執(zhí)行的動(dòng)作。colly提供了一系列的回調(diào)函數(shù),用于 URL 訪問(wèn)和響應(yīng)過(guò)程中各種情況的處理
例如,可以設(shè)定訪問(wèn) URL 前、響應(yīng)成功、響應(yīng)失敗時(shí)不同邏輯的處理
package main
import (
"fmt"
"github.com/gocolly/colly/v2"
)
func main() {
c := colly.NewCollector()
c.OnRequest(func(r *colly.Request) {
fmt.Println("Visiting", r.URL)
})
c.OnResponse(func(r *colly.Response) {
fmt.Println("Visited", r.Request.URL)
})
c.OnError(func(_ *colly.Response, err error) {
fmt.Println("Something went wrong:", err)
})
c.Visit("http://go-colly.org/")
}colly提供的回調(diào)和回調(diào)的順序如下圖,每個(gè)回調(diào)可以設(shè)置多次,會(huì)依次執(zhí)行

常規(guī)配置
配置分兩部分,一部分是 colly 采集器的配置,一部分是 HTTP 的配置
colly 采集器的配置
新建 colly 采集器時(shí)指定配置,例如
c := colly.NewCollector(
colly.UserAgent("example.com"),
colly.DisallowedDomains("baidu.com", "bing.com"),
)
//...
c.Visit("http://baidu.com/")
c.Visit("http://go-colly.org/")使用環(huán)境變量可以不用重新編譯代碼??疵执蠹覒?yīng)該也能猜到每個(gè)環(huán)境變量都有什么作用
注意環(huán)境變量有固定前綴COLLY_,官方文檔里并沒(méi)有說(shuō)明(坑?。?/p>
COLLY_ALLOWED_DOMAINS(逗號(hào)分隔)COLLY_CACHE_DIR(string)COLLY_DETECT_CHARSET(y/n)COLLY_DISABLE_COOKIES(y/n)COLLY_DISALLOWED_DOMAINS(逗號(hào)分隔)COLLY_IGNORE_ROBOTSTXT(y/n)COLLY_MAX_BODY_SIZE(int)COLLY_MAX_DEPTH(0 代表不限深度)COLLY_PARSE_HTTP_ERROR_RESPONSE(y/n)COLLY_USER_AGENT(string)
$ COLLY_DISALLOWED_DOMAINS=baidu.com,bing.com go run main.go
Visiting http://go-colly.org/
Visited http://go-colly.org/
Finished http://go-colly.org/
通過(guò) colly 采集器的屬性進(jìn)行配置
c := colly.NewCollector()
c.UserAgent = "example.com"
//...
c.Visit("http://go-colly.org/")
c.DisallowedDomains = []string{"baidu.com", "bing.com"}
c.Visit("http://baidu.com/")colly 采集器的配置優(yōu)先級(jí)就是上面介紹的順序:新建 < 環(huán)境變量 < 實(shí)例屬性
HTTP 的配置
colly 默認(rèn)使用的是 Go 標(biāo)準(zhǔn)庫(kù)中的 http client,我們可以進(jìn)行替換
c := colly.NewCollector()
c.WithTransport(&http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}常見(jiàn)功能
作為一個(gè)爬蟲(chóng),很少會(huì)僅抓取一個(gè)鏈接,通常會(huì)抓取大量的鏈接,甚至?xí)粩喾治霎?dāng)前頁(yè)面中的鏈接,繼續(xù)進(jìn)行深度的爬取。代碼通常會(huì)類(lèi)似下面
為了避免無(wú)限制的爬取,可以限制爬取的域名范圍,和訪問(wèn)深度;
colly 會(huì)記錄已經(jīng)爬取過(guò)的鏈接,不會(huì)再重復(fù)爬取
func main() {
c := colly.NewCollector(
colly.AllowedDomains("httpbin.org"),
colly.MaxDepth(2),
)
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
link := e.Attr("href")
fmt.Printf("Link found: %q -> %s\n", e.Text, link)
c.Visit(link)
})
c.OnError(func(_ *colly.Response, err error) {
fmt.Println("Something went wrong:", err)
})
c.Visit("http://httpbin.org/links/20/3")
}并行抓取
colly 默認(rèn)是串行逐個(gè)鏈接進(jìn)行爬取,想要提高爬取能力最快速簡(jiǎn)單的方式就是開(kāi)啟并行。
除了需要設(shè)置colly.Async(true)之外,還需要在最后c.Wait()等待所有并發(fā)的請(qǐng)求執(zhí)行完成
c.Limit可以針對(duì)某一個(gè)域名設(shè)置并發(fā)度和發(fā)起每一個(gè)請(qǐng)求的延遲時(shí)間
func main() {
c := colly.NewCollector(
colly.AllowedDomains("httpbin.org"),
colly.MaxDepth(2),
colly.Async(true),
)
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
link := e.Attr("href")
fmt.Printf("Link found: %q -> %s\n", e.Text, link)
c.Visit(link)
})
c.Limit(&colly.LimitRule{
DomainGlob: "*httpbin.*",
Parallelism: 2,
Delay: 5 * time.Second,
})
c.Visit("http://httpbin.org/links/20/3")
c.Wait()
}持久化的外部存儲(chǔ)
默認(rèn)情況下已訪問(wèn)的 URL 和 cookie 等信息都是存儲(chǔ)在內(nèi)存中,服務(wù)重新啟動(dòng)后將會(huì)丟失這部分信息,且無(wú)法在多個(gè)機(jī)器直接共享。
當(dāng)我們構(gòu)建一個(gè)分布式爬蟲(chóng)時(shí),我們需要一個(gè)公共的用于維護(hù)狀態(tài)的持久化存儲(chǔ),例如 redis 等。
package main
import (
"fmt"
"github.com/gocolly/colly/v2"
"github.com/gocolly/redisstorage"
)
func main() {
c := colly.NewCollector(
colly.AllowedDomains("httpbin.org", "go-colly.org"),
colly.MaxDepth(2),
)
storage := &redisstorage.Storage{
Address: "127.0.0.1:6379",
Password: "",
DB: 0,
Prefix: "httpbin_test",
}
err := c.SetStorage(storage)
if err != nil {
panic(err)
}
// 清除之前存儲(chǔ)的信息,可選
if err := storage.Clear(); err != nil {
panic(err)
}
defer storage.Client.Close()
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
link := e.Attr("href")
fmt.Printf("Link found: %q -> %s\n", e.Text, link)
c.Visit(link)
})
// ...
c.Visit("http://httpbin.org/links/20/3")
}隊(duì)列
可以使用隊(duì)列來(lái)控制爬取的速率,默認(rèn)情況下隊(duì)列也是在內(nèi)存中的
import (
"fmt"
"github.com/gocolly/colly/v2"
"github.com/gocolly/colly/v2/queue"
)
func main() {
q, _ := queue.New(
2, // 消費(fèi)的進(jìn)程數(shù)
&queue.InMemoryQueueStorage{MaxSize: 10000}, // 默認(rèn)的隊(duì)列,內(nèi)存隊(duì)列
)
c := colly.NewCollector(
colly.AllowedDomains("httpbin.org", "go-colly.org"),
colly.MaxDepth(2),
)
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
link := e.Attr("href")
fmt.Printf("Link found: %q -> %s\n", e.Text, link)
q.AddURL(link)
})
q.AddURL("http://go-colly.org/")
q.Run(c)
}對(duì)于構(gòu)建分布式爬蟲(chóng)來(lái)說(shuō),可以借助外部的隊(duì)列提高整體的消費(fèi)能力。
package main
import (
"fmt"
"github.com/gocolly/colly/v2"
"github.com/gocolly/colly/v2/queue"
"github.com/gocolly/redisstorage"
)
func main() {
// 創(chuàng)建redis存儲(chǔ)
storage := &redisstorage.Storage{
Address: "127.0.0.1:6379",
Password: "",
DB: 0,
Prefix: "httpbin_test",
}
q, err := queue.New(
2, // 消費(fèi)的進(jìn)程數(shù)
storage,
)
if err != nil{
panic(err)
}
c := colly.NewCollector(
colly.AllowedDomains("httpbin.org", "go-colly.org"),
colly.MaxDepth(2),
)
err = c.SetStorage(storage)
if err != nil {
panic(err)
}
c.OnHTML("a[href]", func(e *colly.HTMLElement) {
link := e.Attr("href")
fmt.Printf("Link found: %q -> %s\n", e.Text, link)
q.AddURL(link)
})
q.AddURL("http://go-colly.org/")
q.Run(c)
}總結(jié)
這篇文章作為一個(gè)入門(mén)介紹,看完后你應(yīng)該能 Get 到普通的 http client 庫(kù)和爬蟲(chóng)庫(kù)的區(qū)別了吧。
colly 作為一個(gè)爬蟲(chóng)框架集成了一系列針對(duì)爬蟲(chóng)的 API,想要體驗(yàn) colly 的更多能力,建議還是好好閱讀下 colly 的文檔
到此這篇關(guān)于Go語(yǔ)言colly框架的快速入門(mén)的文章就介紹到這了,更多相關(guān)Go colly框架內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang利用redis和gin實(shí)現(xiàn)保存登錄狀態(tài)校驗(yàn)登錄功能
這篇文章主要介紹了golang利用redis和gin實(shí)現(xiàn)保存登錄狀態(tài)校驗(yàn)登錄功能,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-01-01
Go?interface{}?轉(zhuǎn)切片類(lèi)型的實(shí)現(xiàn)方法
本文主要介紹了Go?interface{}?轉(zhuǎn)切片類(lèi)型的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02
Go?數(shù)據(jù)結(jié)構(gòu)之二叉樹(shù)詳情
這篇文章主要介紹了?Go?數(shù)據(jù)結(jié)構(gòu)之二叉樹(shù)詳情,二叉樹(shù)是一種數(shù)據(jù)結(jié)構(gòu),在每個(gè)節(jié)點(diǎn)下面最多存在兩個(gè)其他節(jié)點(diǎn)。即一個(gè)節(jié)點(diǎn)要么連接至一個(gè)、兩個(gè)節(jié)點(diǎn)或不連接其他節(jié)點(diǎn),下文基于GO語(yǔ)言展開(kāi)二叉樹(shù)結(jié)構(gòu)詳情,需要的朋友可以參考一下2022-05-05
Go語(yǔ)言官方依賴(lài)注入工具Wire的使用教程
依賴(lài)注入是一種實(shí)現(xiàn)控制反轉(zhuǎn)且用于解決依賴(lài)性問(wèn)題的設(shè)計(jì)模式。Golang?中常用的依賴(lài)注入工具主要有?Inject?、Dig?等。但是今天主要介紹的是?Go?團(tuán)隊(duì)開(kāi)發(fā)的?Wire,一個(gè)編譯期實(shí)現(xiàn)依賴(lài)注入的工具,感興趣的可以了解一下2022-09-09
Golang常用環(huán)境變量說(shuō)明與設(shè)置詳解
這篇文章主要介紹了Golang常用環(huán)境變量說(shuō)明與設(shè)置,需要的朋友可以參考下2020-02-02
Go如何實(shí)現(xiàn)HTTP請(qǐng)求限流示例
本篇文章主要介紹了Go如何實(shí)現(xiàn)HTTP請(qǐng)求限流示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04
一文帶你感受Go語(yǔ)言空結(jié)構(gòu)體的魔力
在?Go?語(yǔ)言中,有一種特殊的用法可能讓許多人感到困惑,那就是空結(jié)構(gòu)體,本文將對(duì)Go空結(jié)構(gòu)體進(jìn)行詳解,準(zhǔn)備一杯你最喜歡的飲料或茶,隨著本文一探究竟吧2023-05-05
Golang報(bào)“import cycle not allowed”錯(cuò)誤的2種解決方法
這篇文章主要給大家介紹了關(guān)于Golang報(bào)"import cycle not allowed"錯(cuò)誤的2種解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以們下面隨著小編來(lái)一起看看吧2018-08-08
Go語(yǔ)言實(shí)現(xiàn)JSON解析的方法詳解
在日常項(xiàng)目中,使用Json格式進(jìn)行數(shù)據(jù)封裝是比較常見(jiàn)的操作。本文將詳細(xì)講解如何利用Go語(yǔ)言實(shí)現(xiàn)JSON的解析,感興趣的小伙伴可以學(xué)習(xí)一下2022-04-04

