go-colly是用Go实现的网络爬虫框架。go-colly快速优雅,在单核上每秒可以发起1K以上请求;以回调函数的形式提供了一组接口,可以实现任意类型的爬虫。
Colly 特性:
清晰的API 快速(单个内核上的请求数大于1k) 管理每个域的请求延迟和最大并发数 自动cookie 和会话处理
同步/异步/并行抓取 高速缓存 自动处理非Unicode的编码 支持Robots.txt 定制Agent信息 定制抓取频次**
下载安装第三方包:
go get -u github.com/gocolly/colly/…
在代码中导入包:
import "github.com/gocolly/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中广泛使用),来控制收集作业或获取信息,回调函数的调用顺序如下:
例子:
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框架可以快速发起请求,接收服务器响应。但如果我们需要分析返回的HTML代码,这时候仅仅使用colly就有点吃力。而goquery库是一个使用Go语言写成的HTML解析库,功能更加强大。
goquery将jQuery的语法和特性引入进来,所以可以更灵活地选择采集内容的数据项,就像jQuery那样的方式来操作DOM文档,使用起来非常的简便。
type Document struct {
*Selection
Url *url.URL
rootNode *html.Node
}
Document 嵌入了Selection 类型,因此,Document 可以直接使用 Selection 类型的方法。我们可以通过下面四种方式来初始化得到*Document对象。
func NewDocumentFromNode(root *html.Node) *Document
func NewDocument(url string) (*Document, error)
func NewDocumentFromReader(r io.Reader) (*Document, error)
func NewDocumentFromResponse(res *http.Response) (*Document, error)
Selection 是重要的一个结构体,解析中最重要,最核心的方法方法都由它提供。
type Selection struct {
Nodes []*html.Node
document *Document
prevSel *Selection
}
首先,要确定已经下载安装这个第三方包:
go get github.com/PuerkitoBio/goquery
接下来在代码中导入包:
import "github.com/PuerkitoBio/goquery"
goquery可以直接发送url请求,获得响应后得到HTML代码。但goquery主要擅长于HTML代码分析,而colly在爬虫抓取管理调度上有优势,所以下面以colly作为爬虫框架,goquery作为HTML分析器,看看怎么抓取并分析页面内容:
package main
import (
"bytes"
"fmt"
"log"
"net/url"
"time"
"github.com/PuerkitoBio/goquery"
"github.com/gocolly/colly"
)
func main() {
urlstr := "https://news.baidu.com"
u, err := url.Parse(urlstr)
if err != nil {
log.Fatal(err)
}
c := colly.NewCollector()
// 超时设定
c.SetRequestTimeout(100 * time.Second)
// 指定Agent信息
c.UserAgent = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.108 Safari/537.36"
c.OnRequest(func(r *colly.Request) {
// Request头部设定
r.Headers.Set("Host", u.Host)
r.Headers.Set("Connection", "keep-alive")
r.Headers.Set("Accept", "*/*")
r.Headers.Set("Origin", u.Host)
r.Headers.Set("Referer", urlstr)
r.Headers.Set("Accept-Encoding", "gzip, deflate")
r.Headers.Set("Accept-Language", "zh-CN, zh;q=0.9")
})
c.OnHTML("title", func(e *colly.HTMLElement) {
fmt.Println("title:", e.Text)
})
c.OnResponse(func(resp *colly.Response) {
fmt.Println("response received", resp.StatusCode)
// goquery直接读取resp.Body的内容
htmlDoc, err := goquery.NewDocumentFromReader(bytes.NewReader(resp.Body))
// 读取url再传给goquery,访问url读取内容,此处不建议使用
// htmlDoc, err := goquery.NewDocument(resp.Request.URL.String())
if err != nil {
log.Fatal(err)
}
// 找到抓取项 <div class="hotnews" alog-group="focustop-hotnews"> 下所有的a解析
htmlDoc.Find(".hotnews a").Each(func(i int, s *goquery.Selection) {
band, _ := s.Attr("href")
title := s.Text()
fmt.Printf("热点新闻 %d: %s - %s\n", i, title, band)
c.Visit(band)
})
})
c.OnError(func(resp *colly.Response, errHttp error) {
err = errHttp
})
err = c.Visit(urlstr)
}