持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情
本文翻译自官方文档:Scraping Framework for Golang (go-colly.org)
Gopher们的快速优雅的爬虫框架
最佳实践
调试
有时候,在调用回调函数中放置一些 log.Println() 函数就足够了,但是有时候不够。
Colly 内置有用于 collector 调试的能力。有一个调试器接口和不同类型的调试器实现可用。
绑定调试器到 collector
绑定一个基本的日志调试器需要 Colly 仓库的 debug (github.com/gocolly/colly/debug) 包。
import (
"github.com/gocolly/colly"
"github.com/gocolly/colly/debug"
)
func main() {
c := colly.NewCollector(colly.Debugger(&debug.LogDebugger{}))
// [..]
}
实现一个自定义调试器
可以通过实现 debug.Debugger 接口创建任何类型的自定义调试器。一个好的示例是 LogDebugger 。
logdebugger.go:
// Copyright 2018 Adam Tauber
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package debug
import (
"io"
"log"
"os"
"sync/atomic"
"time"
)
// LogDebugger 是最简化的调试器,它打印日志信息到 STDERR (标准错误)
type LogDebugger struct {
// Output 是日志的目标位置,它可以是任何实现 io.Writer 接口的对象。
// 保持空白的话会使用 STDERR
Output io.Writer
// Prefix 在创建的每个日志行开头出现
Prefix string
// Flag 定义日志属性
Flag int
logger *log.Logger
counter int32
start time.Time
}
// Init 初始化 LogDebugger
func (l *LogDebugger) Init() error {
l.counter = 0
l.start = time.Now()
if l.Output == nil {
l.Output = os.Stderr
}
l.logger = log.New(l.Output, l.Prefix, l.Flag)
return nil
}
// Event 招收 Collector 事件然后将它们打印到 STDERR (标准错误)
func (l *LogDebugger) Event(e *Event) {
i := atomic.AddInt32(&l.counter, 1)
l.logger.Printf("[%06d] %d [%6d - %s] %q (%s)\n", i, e.CollectorID, e.RequestID, e.Type, e.Values, time.Since(l.start))
}
分布式爬取
分布式爬取可用不同的方式实现,这取决于爬取任务的需要。使用代理和 Colly 代理切换器可轻易地实现对网络通信层的扩展,大多数情况下,这已够用了。
代理切换器
当 HTTP 请求在多个代理之间分布时,使用代理切换器爬取仍然会保持集中(非分布)方式。 Colly 通过它的 SetProxyFunc() 成员支持代理切换。任何带 func(*http.Request) (*url.URL, error) 签名的自定义函数都可传递给 SetProxyFunc() 。
提示
SSH 服务器可加
-D标志用作 socks5 代理。
Colly 有内置的代理切换器,可以对每个请求轮换代理列表中的代理。
用法
package main
import (
"github.com/gocolly/colly"
"github.com/gocolly/colly/proxy"
)
func main() {
c := colly.NewCollector()
if p, err := proxy.RoundRobinProxySwitcher(
"socks5://127.0.0.1:1337",
"socks5://127.0.0.1:1338",
"http://127.0.0.1:8080",
); err == nil {
c.SetProxyFunc(p)
}
// ...
}
实现自定义代理切换器:
var proxies []*url.URL = []*url.URL{
&url.URL{Host: "127.0.0.1:8080"},
&url.URL{Host: "127.0.0.1:8081"},
}
func randomProxySwitcher(_ *http.Request) (*url.URL, error) {
return proxies[random.Intn(len(proxies))], nil
}
// ...
c.SetProxyFunc(randomProxySwitcher)
分布式爬虫
要管理独立和分布式爬虫,最好的方式是在服务器中包装一下爬虫。服务器可使用任何类型的服务,如 HTTP、TCP 服务器或 Google App Engine 。使用自定义的 存储 可实现集中,并且将 Cookie 和访问过的 URL 处理持久化。
提示 Colly 内置有对Google App Engine的支持。如果从 App Engine 的标准环境使用 Colly ,不要忘记调用
Collector.Appengine(*http.Request)。
这里有一个实现的示例。
分布式存储
访问过的 URL 和 Cookie 默认存储在内存中。对短周期的爬取任务很方便,但是当处理大型的扩展或长周期的爬取任务会有严重的限制。
Colly 可使用基于实现 colly/storage.Storage 接口的任何存储来代替默认的内存存储。 可查看 存在的存储。
后端存储
Colly 有内置的内存存储后端用来存储 Cookie 和 访问过的 URL,但它可以用任何实现colly/storage.Storage的自定义存储后端重写。
已有的存储后端
内存后端
Colly 的默认后端。可使用 collector.SetStorage() 覆写。
Redis后端
查看 redis 示例 了解详细内容。
boltdb 后端
SQLite3 后端
MongoDB 后端
PostgreSQL 后端
使用多个 collector
如果爬取任务很复杂或者有不同种类的子任务,建议对于单个爬取任务使用多个 collector 。
一个不错的示例是 coursera course scraper ,它使用了两个 collector - 一个是解析列出的视图和处理分页,另一个是收集过程的详细内容。
Colly 内置有支持使用多个 collector 的方法。
提示
在调试时使用
collector.ID以区别不同的 collector
克隆 collector
如果 collector 有相似的配置,可以使用 collector 的 Clone() 方法。 Clone() 会用同样的配置复制 collector ,但是不会绑定回调。
c := colly.NewCollector(
colly.UserAgent("myUserAgent"),
colly.AllowedDomains("foo.com", "bar.com"),
)
// 自定义 User-Agent 和允许的域克隆到 c2
c2 := c.Clone()
在 collector 之间解析自定义数据
使用 collector 的 Request() 函数可以和其它 collector 共享上下文。
共享上下文的示例:
c.OnResponse(func(r *colly.Response) {
r.Ctx.Put(r.Headers.Get("Custom-Header"))
c2.Request("GET", "https://foo.com/", nil, r.Ctx, nil)
})
爬虫配置
对于单任务爬取少量的站点,Colly 的默认配置已经优化。如果想要爬取百万级的站点,该设置工作不是最优的。这里有一些调整。
使用持久化存储后端
默认情况下, Colly 在内存中存储 Cookie 和访问过的 URL 。可以用任何自定义的后端代替内存存储后端。更多详细内容查看 这里。
对于使用递归调用的长周期运行的任务使用异步
默认情况下,请求未完成时 Colly 会阻塞,所以从回调中递归地调用 Collector.Visit 会产生持续增长的栈。使用 Collector.Async = true 可以避免该问题。(不要忘记使用 c.Wait() 进行异步)
禁用或限制连接的 keep-alive (保持连接)
Colly 使用 HTTP keep-alive 以加强爬取速度。它需要打开文件描述符,所以在长周期运行的任务中 max-fd (最大文件描述符)的限制可以轻易达到。
可使用以下代码禁用 HTTP Keep-alive:
c := colly.NewCollector()
c.WithTransport(&http.Transport{
DisableKeepAlives: true,
})
扩展
扩展是 Colly 自带的小型辅助工具类。插件列表参考 这里。
用法
下面的示例提供了随机的 User-Agent 切换器 和 Referrer 设定扩展,并访问 httpbin.org 两次。
import (
"log"
"github.com/gocolly/colly"
"github.com/gocolly/colly/extensions"
)
func main() {
c := colly.NewCollector()
visited := false
extensions.RandomUserAgent(c)
extensions.Referer(c)
c.OnResponse(func(r *colly.Response) {
log.Println(string(r.Body))
if !visited {
visited = true
r.Request.Visit("/get?q=2")
}
})
c.Visit("http://httpbin.org/get")
}