Colly 特性:

  • 清晰的API 快速(单个内核上的请求数大于1k)
  • 管理每个域的请求延迟和最大并发数
  • 自动cookie 和会话处理
  • 同步/异步/并行抓取
  • 高速缓存
  • 自动处理非Unicode的编码
  • 支持Robots.txt 定制Agent信息
  • 定制抓取频次。

特性如此多,引无数程序员竞折腰。下面开始我们的Colly之旅:

go get -u github.com/gocolly/colly/...import "github.com/gocolly/colly"

准备工作已经完成,接下来就看看Colly的使用方法和主要的用途。

colly的主体是Collector对象,管理网络通信和负责在作业运行时执行附加的回调函数。使用colly需要先初始化Collector:

c := colly.NewCollector() 

我们看看NewCollector,它也是变参函数,参数类型为函数类型func(*Collector),主要是用来初始化一个&Collector{}对象。

而在Colly中有好些函数都返回这个函数类型func(*Collector),如UserAgent(us string)用来设置UA。所以,这里其实是一种初始化对象,设置对象属性的一种模式。相比使用方法(set方法)这种传统方式来初始设置对象属性,采用回调函数的形式在Go语言中更灵活更方便:

NewCollector(options ...func(*Collector)) *Collector
UserAgent(ua string) func(*Collector)

一旦得到一个colly对象,可以向colly附加各种不同类型的回调函数(回调函数在Colly中广泛使用),来控制收集作业或获取信息,回调函数的调用顺序如下:

  1. OnRequest 在发起请求前被调用
  2. OnError 请求过程中如果发生错误被调用
  3. OnResponse 收到回复后被调用
  4. OnHTML 在OnResponse之后被调用,如果收到的内容是HTML
  5. OnScraped 在OnHTML之后被调用

下面我们看一个例子:

package main
import (
	"fmt"
	"github.com/gocolly/colly"
)
func main() {
	// NewCollector(options ...func(*Collector)) *Collector
	// 声明初始化NewCollector对象时可以指定Agent,连接递归深度,URL过滤以及domain限制等
	c := colly.NewCollector(
		//colly.AllowedDomains("news.baidu.com"),
		colly.UserAgent("Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.9.168 Version/11.50"))
	// 发出请求时附的回调
	c.OnRequest(func(r *colly.Request) {
		// Request头部设定
		r.Headers.Set("Host", "baidu.com")
		r.Headers.Set("Connection", "keep-alive")
		r.Headers.Set("Accept", "*/*")
		r.Headers.Set("Origin", "")
		r.Headers.Set("Referer", "http://www.baidu.com")
		r.Headers.Set("Accept-Encoding", "gzip, deflate")
		r.Headers.Set("Accept-Language", "zh-CN, zh;q=0.9")
		fmt.Println("Visiting", r.URL)
	})
	// 对响应的HTML元素处理
	c.OnHTML("title", func(e *colly.HTMLElement) {
		//e.Request.Visit(e.Attr("href"))
		fmt.Println("title:", e.Text)
	})
	c.OnHTML("body", func(e *colly.HTMLElement) {
		// <div class="hotnews" alog-group="focustop-hotnews"> 下所有的a解析
		e.ForEach(".hotnews a", func(i int, el *colly.HTMLElement) {
			band := el.Attr("href")
			title := el.Text
			fmt.Printf("新闻 %d : %s - %s\n", i, title, band)
			// e.Request.Visit(band)
		})
	})
	// 发现并访问下一个连接
	//c.OnHTML(`.next a[href]`, func(e *colly.HTMLElement) {
	//	e.Request.Visit(e.Attr("href"))
	//})
	// extract status code
	c.OnResponse(func(r *colly.Response) {
		fmt.Println("response received", r.StatusCode)
		// 设置context
		// fmt.Println(r.Ctx.Get("url"))
	})
	// 对visit的线程数做限制,visit可以同时运行多个
	c.Limit(&colly.LimitRule{
		Parallelism: 2,
		//Delay:      5 * time.Second,
	})
	c.Visit("http://news.baidu.com")
}

上面代码在开始处对Colly做了简单的初始化,增加UserAgent和域名限制,其他的设置可根据实际情况来设置,Url过滤,抓取深度等等都可以在此设置,也可以后运行时在具体设置。

该例只是简单说明了Colly在爬虫抓取,调度管理方面的优势,对此如有兴趣可更深入了解。大家在深入学习Colly时,可自行选择更合适的URL。

程序运行后,开始根据news.baidu.com抓取页面结果,通过OnHTML回调函数分析首页中的热点新闻标题及链接,并可不断地抓取更深层次的新链接进行访问,每个链接的访问结果我们可以通过OnHTML来进行分析,也可通过OnResponse来进行处理,例子中没有进一步展示深层链接的内容,有兴趣的朋友可以继续进一步研究。

我们来看看OnHTML这个方法的定义:

func (c *Collector) OnHTML(goquerySelector string, f HTMLCallback)

直接在参数中标明了 goquerySelector ,上例中我们有简单尝试。这和我们下面要介绍的goquery HTML解析框架有一定联系,我们也可以使用goquery,通过goquery 来更轻松分析HTML代码。