摘要
文章内容是本人基于官方文档以及源码的学习,在学习过程中记录整理。
Colly
是一款基于Go语言实现的轻量级爬虫框架,也几乎是目前Go语言中唯一比较知名的爬虫框架了,结合Go语言高并发的特点,Colly
天生具备非常优秀的高并发能力,同时Colly
的设计非常优雅,开发速度非常快,还具备分布式和强大的扩展能力。
特性
- 简单的API
- 快速(单核上> 1k请求/秒)
- 控制请求延迟和每个域名的最大并发数
- 自动cookie和session处理
- 同步/异步/并行抓取
- 高速缓存
- 对非unicode响应自动编码
- Robots.txt支持
- 分布式抓取
- 支持通过环境变量配置
- 随意扩展
安装
1 | go get -u github.com/gocolly/colly/v2... |
使用go mod
管理
1 | # 设置GO111MODULE开启go mod |
快速上手
导入colly依赖包
1 | import "github.com/gocolly/colly/v2" |
创建Collector
对象
Colly
要做的第一件事就是创建一个 Collector
对象。通过 Collector
管理网络通信并负责在 Collector job
运行时执行附加的回调函数。
1 | c := colly.NewCollector() |
添加回调函数
我们可以把不同类型的回调函数附加到收集器上来监听不同的采集任务,然后通过回调对事件进行处理。
可用的回调函数有:
1 | c.OnRequest(func(r *colly.Request) { |
回调函数的执行顺序
OnRequest
:请求执行之前调用OnError
:请求过程中出现Error时调用OnResponse
:收到response
之后调用OnHTML
:如果收到的内容是HTML,就在onResponse
执行后调用OnXML
:如果收到的内容是HTML或者XML,就在onHTML
执行后调用OnScraped
:在OnXML
执行后调用,完成所有工作后执行
访问目标地址
1 | c.Visit("https://c.isme.pub/") |
其他访问方式
1 | // 通过一个指定的url开始采集器的采集任务,之后会调用设置好的回调函数,请求方式主要为Get请求 |
完整代码
1 | package main |
配置
Colly
是一个高度可定制的爬虫框架。它为开发者提供了大量的配置选项,并且为每个选项提供了比较合理的默认值。
创建不带参数的collector
1 | c := colly.NewCollector() |
创建collector
时设置参数
1 | c := colly.NewCollector( |
创建collector
后随时可以修改参数
1 | c := colly.NewCollector() |
任意位置修改参数
collector
的配置可以在爬虫执行到任何阶段改变。
一个经典的例子:通过随机修改 user-agent
,可以帮助我们实现简单的反爬。
1 | package main |
通过环境变量来修改参数
colly
支持通过设置环境变量来配置collector
的默认配置,这样我们就可以在不重新编译的情况下修改配置了。
需要注意的是环境变量的配置是在 collector 初始化的最后一步生效的,程序正式启动之后,对每个配置的修改,都会覆盖从环境变量中读取到的配置
支持的配置项,如下:
1 | ALLOWED_DOMAINS (字符串切片) 允许访问的域名,比如 []string{"c.isme.pub", "z.isme.pub"} |
HTTP配置
Colly
使用Golang
默认的http客户端作为网络层。可以通过更改默认的HTTP roundtripper来调整HTTP配置
1 | c := colly.NewCollector() |
Debug调试
在部分场景下,在回调函数中通过log.Println()
输出信息就足够了,但是对于一些复杂的场景就明显不够用了。Colly
内置了调试收集器的功能。我们可以使用调试器接口和不同类型的调试器来实现信息的收集工作。
将回调函数附加到收集器
附加一个基本的日志调试器需要 Colly 的 repo 中的debug
( github.com/gocolly/colly/debug
) 包。
1 | import ( |
LogDebugger
LogDebugger
是colly
提供给我们的内置数据结构,我们可以通过定义发现,我们只需要给他提供一个io.Writer
类型的对象就可以进行日志输出了
1 | // LogDebugger is the simplest debugger which prints log messages to the STDERR |
示例
1 | package main |
日志输出
1 | $ tail isme.log |
分布式爬虫
分布式爬虫可以通过爬取任务的要求,通过不同的方法来实现。大多数情况下,爬虫跨站网络层通信就足够了(并发发起网络请求)。
在Colly
中分布式爬虫可以通过几种方式来实现:
- 代理层
- 执行层
- 存储层
代理池
通过设置代理池,我们可以将不同的HTTP请求通过不同的代理进行访问,这样有2个优点:
- 网络层面上可以提高整体的爬取速度
- 防止单个IP访问速度过快导致被封禁
但是使用这种方式,代理池切换器仍然是集中式的,爬取任务的执行还是从单个节点上发起的。
内置代理切换器proxy.RoundRobinProxySwitcher
Colly
有一个内置的代理切换器,可以在每个请求上轮换代理列表。
1 | package main |
自定义代理切换器
Colly
支持用户自定义代理切换器,并且通过SetProxyFunc()
更换代理切换器。
1 | var proxies []*url.URL = []*url.URL{ |
分布式爬虫
对于分布式爬虫,我们需要管理的是调度器和执行器,通过调度器发起任务,将任务发送给部署在不同节点的执行器中,实际的爬取任务让执行节点来执行,这样就可以实现真正意义上的分布式爬虫了。
要实现分布式爬虫,还需要解决几个问题:
- 需要先定义好调度器和执行器之间的通信协议,如:
HTTP
、TCP
、Google App Engine
- 需要集中式存储持久化cookie和访问的url处理结果
Colly 内置了 Google App Engine 的方法。
Collector.Appengine(*http.Request)
HTTP执行器的实现
下面是一个Colly
提供的HTTP执行器的源码示例:
1 | package main |
基于官方提供的示例简单实现一个HTTP调度器
1 | package main |
分布式爬虫的集中存储
我们虽然将执行任务分配到了不同的执行节点,但是请求的cookie、url、处理结果等都临时存储在执行节点的内存中,对于一些简单场景下的任务,这就足够了,但是在处理大规模请求,需要长时间处理大量数据时,这种方式就不能满足使用了,因此我们需要将数据存储在集中存储中,以此来实现不同执行节点的数据共享
内置存储
目前Colly
支持的存储有以下几种:
1 | type Storage interface { |
memory
Colly
默认的存储方式,可以通过collector.SetStorage()
方法修改成其他的存储。
redis
1 | package main |
sqlite3
1 | package main |
mongo
1 | package main |
自定义存储
Colly
还支持用户自定义存储,只需要用户实现colly/storage.Storage接口,就可以当做存储后端来用了,至于如何实现,我们后面会出单独的篇章来讲。
1 | type Storage interface { |
分布式爬虫-队列
Colly
的v2
版本添加了使用存储当做队列的功能,我们可以通过调度器将需要爬取的任务加入到队列中,然后执行节点可以从集中存储中获取爬取任务并执行任务。
Colly
中支持的存储只要实现了以下接口都可以用作队列使用:
1 | type Storage interface { |
memory
队列
1 | package main |
redis
队列
1 | package main |
使用多个采集器
在我们实际的爬取任务中,经常会碰到需要处理不同逻辑的多个页面的情况,比如:通过父页面获取到多个子页面的链接,然后需要用另一种逻辑去处理子页面。
对于这种复杂的爬取任务,只使用一个采集器就不合适了。对于这种场景,我们可以针对不同处理逻辑的页面,定义多个不同的采集器,通过不同的采集器来处理不同的页面。
克隆采集器
通过Clone()
可以将父采集器的配置复制到克隆出来的采集器中,克隆的时候只会克隆配置,不会克隆采集器的回调方法。
1 | c := colly.NewCollector( |
在多个采集器间传递自定义数据
使用收集器的Request()
功能能够与其他收集器共享上下文。
共享上下文示例:
1 | c.OnResponse(func(r *colly.Response) { |
这个 Context 只是 Colly 实现的数据共享的数据结构,并非 Go 标准库中的 Context
官方示例
coursera course scraper,这个示例中使用了两个收集器:
- 一个解析列表视图并处理分页
- 另一个收集课程详细信息
1 | package main |
优化爬虫配置
Colly
的默认配置是针对一些简单、数据量小的场景下的配置,如果你需要对大量的目标进行爬取,就需要对默认的配置进行优化了
持久化集中存储
前面提到过了,我们需要将一些数据存储在集中存储中。
递归启动异步任务
默认情况下,Colly
会阻塞请求直到请求返回,因此Collector.Visit
从回调中递归调用就会产生大量的堆栈,这个时候我们就需要开启异步Collector.Async = true
来避免这种问题。开启异步功能后,需要增加c.Wait()
来等待任务的结束。
禁用KeepAlive连接
Colly
默认会开启KeepAlive
来提高爬取速度,但是这会导致文件描述符的占用增加,我们要么修改系统参数来增加进程可以使用的文件描述符,或者我们可以关闭KeepAlive
来减少文件描述符的增加。
1 | c := colly.NewCollector() |
扩展插件
Colly
还提供了一些扩展功能,主要与爬虫相关的一些常用功能: colly/extensions/
referer
random_user_agent
url_length_filter
以下示例启用随机 User-Agent 切换器和 Referrer 设置器扩展并访问 httpbin.org
两次。
1 | import ( |
扩展插件官方源码
通过阅读官方提供的扩展源码我们可以发现,扩展的实现非常的简单,基本都是基于c.OnResponse
和c.OnRequest
来实现的,这样我们就能很轻松的自定义扩展了。
RandomUserAgent
1 | package extensions |
Referer
1 | package extensions |
URLLengthFilter
1 | package extensions |
更多官方示例
Colly
提供的官方文档内容特别少,但是通过官方文档以及源码的学习,我们就已经能够初步掌握Colly
的使用技巧了。后续除了多加练习以外,官方还提供了一些不同场景的示例以供我们参考、学习。