Golang爬虫框架colly快速入门

有些人可能认为爬虫框架和 http client 库的功能一样,用 http client 库也可以写爬虫。当然,无论用第三方的 http client 库还是官方的http库,都可以写爬虫。但术业有专攻,爬虫框架专门为批量爬取设计,往往拥有并发控制、队列、缓存、HTML 解析等一系列开箱即用的 API,能大幅简化在爬虫实现过程中的负担

Python 中非常知名的爬虫框架有Scrapy,Go 中也有一些 star 数较高的爬虫框架。colly就是其中的佼佼者,它 API 简洁,性能优良,开箱即用。今天就来快速学习一下吧!

基本使用

首先引入依赖

go 复制代码
go get -u github.com/gocolly/colly/...

之后就可以使用colly,通过Visit函数来告知colly 采集器要访问的 URL

go 复制代码
package main

import (
	"fmt"

	"github.com/gocolly/colly/v2"
)

func main() {
	c := colly.NewCollector()

	c.Visit("http://go-colly.org/")
}

这样就行了么?运行下试试,没有任何输出。

shell 复制代码
$ go run main.go

原因在于代码要求 colly 采集器访问http://go-colly.org/,但没有设定访问 URL 成功或者失败后要执行的动作。colly提供了一系列的回调函数,用于 URL 访问和响应过程中各种情况的处理

例如,可以设定访问 URL 前、响应成功、响应失败时不同逻辑的处理

go 复制代码
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提供的回调和回调的顺序如下图,每个回调可以设置多次,会依次执行

常规配置

配置分两部分,一部分是 colly 采集器的配置,一部分是 HTTP 的配置

🌲 colly 采集器的配置

✨ 新建 colly 采集器时指定配置,例如

go 复制代码
c := colly.NewCollector(
	colly.UserAgent("example.com"),
	colly.DisallowedDomains("baidu.com", "bing.com"),
)

//...
c.Visit("http://baidu.com/")
c.Visit("http://go-colly.org/")

✨ 使用环境变量可以不用重新编译代码。看名字大家应该也能猜到每个环境变量都有什么作用

注意环境变量有固定前缀COLLY_,官方文档里并没有说明(坑!)

  • COLLY_ALLOWED_DOMAINS (逗号分隔)
  • COLLY_CACHE_DIR (string)
  • COLLY_DETECT_CHARSET (y/n)
  • COLLY_DISABLE_COOKIES (y/n)
  • COLLY_DISALLOWED_DOMAINS (逗号分隔)
  • 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)
shell 复制代码
$ 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/

✨ 通过 colly 采集器的属性进行配置

go 复制代码
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 采集器的配置优先级就是上面介绍的顺序:新建 < 环境变量 < 实例属性

🌲 HTTP 的配置

colly 默认使用的是 Go 标准库中的 http client,我们可以进行替换

go 复制代码
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,
}

常见功能

作为一个爬虫,很少会仅抓取一个链接,通常会抓取大量的链接,甚至会不断分析当前页面中的链接,继续进行深度的爬取。代码通常会类似下面

✨ 为了避免无限制的爬取,可以限制爬取的域名范围,和访问深度;

✨ colly 会记录已经爬取过的链接,不会再重复爬取

go 复制代码
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 默认是串行逐个链接进行爬取,想要提高爬取能力最快速简单的方式就是开启并行。

✨ 除了需要设置colly.Async(true)之外,还需要在最后c.Wait()等待所有并发的请求执行完成

c.Limit可以针对某一个域名设置并发度和发起每一个请求的延迟时间

go 复制代码
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()
}

持久化的外部存储

默认情况下已访问的 URL 和 cookie 等信息都是存储在内存中,服务重新启动后将会丢失这部分信息,且无法在多个机器直接共享。

当我们构建一个分布式爬虫时,我们需要一个公共的用于维护状态的持久化存储,例如 redis 等。

go 复制代码
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)
	}

  // 清除之前存储的信息,可选
	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")
}

队列

可以使用队列来控制爬取的速率,默认情况下队列也是在内存中的

go 复制代码
import (
	"fmt"

	"github.com/gocolly/colly/v2"
	"github.com/gocolly/colly/v2/queue"
)

func main() {
	q, _ := queue.New(
		2, // 消费的进程数
		&queue.InMemoryQueueStorage{MaxSize: 10000}, // 默认的队列,内存队列
	)

	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)
}

对于构建分布式爬虫来说,可以借助外部的队列提高整体的消费能力。

go 复制代码
package main

import (
	"fmt"

	"github.com/gocolly/colly/v2"
	"github.com/gocolly/colly/v2/queue"
	"github.com/gocolly/redisstorage"
)

func main() {
	// 创建redis存储
	storage := &redisstorage.Storage{
		Address:  "127.0.0.1:6379",
		Password: "",
		DB:       0,
		Prefix:   "httpbin_test",
	}

	q, err := queue.New(
		2, // 消费的进程数
		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)
}

总结

这篇文章作为一个入门介绍,看完后你应该能 Get 到普通的 http client 库和爬虫库的区别了吧。

colly 作为一个爬虫框架集成了一系列针对爬虫的 API,想要体验 colly 的更多能力,建议还是好好阅读下 colly 的文档

参考

相关推荐
esmember15 小时前
电路研究9.2.6——合宙Air780EP中HTTP——HTTP GET 相关命令使用方法研究
网络·网络协议·http·at指令
霸王蟹21 小时前
http和https的区别?
网络·笔记·网络协议·学习·http·https
精通HelloWorld!21 小时前
使用HttpClient和HttpRequest发送HTTP请求
java·spring boot·网络协议·spring·http
泪不是Web妳而流1 天前
BurpSuite抓包与HTTP基础
网络·经验分享·网络协议·安全·http·网络安全·学习方法
zhuyasen1 天前
多维度详细比较 kratos、go-zero、goframe、sponge 框架
后端·http·微服务·rpc·golang
ybq195133454311 天前
javaEE-6.网络原理-http
网络·网络协议·http
我的青春不太冷1 天前
《深入理解HTTP交互与数据监控:完整流程与优化实践》
网络·经验分享·科技·网络协议·学习·http·架构
A.sir啊1 天前
爬虫基础(五)爬虫基本原理
网络·爬虫·python·网络协议·http·pycharm
烛阴1 天前
Go 语言进阶:打造可复用的模块,导出你的专属包
后端·go
高野4402 天前
【网络】3.HTTP(讲解HTTP协议和写HTTP服务)
网络·http·iphone