Golang中各种配置的规范和最佳实践
Golang中各种配置的规范和最佳实践
我正在使用 colly 进行数据抓取工作,由于有多个网站需要抓取,我希望复用一些设置。我创建了一个名为 client 的包,包含以下代码:
package client
import (
"bufio"
"fmt"
"github.com/gocolly/colly"
"log"
"math/rand"
"os"
"time"
)
var userAgents []string
func readUserAgents() []string {
path, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
file, err := os.Open(fmt.Sprintf("%s/client/agents.txt", path))
if err != nil {
log.Fatal(err)
}
defer file.Close()
var agents []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
agents = append(agents, scanner.Text())
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
return agents
}
var c *colly.Collector
func init() {
// 播种一个持续变化的数字
rand.Seed(time.Now().UnixNano())
userAgents = readUserAgents()
}
func NewClient() *colly.Collector {
c = colly.NewCollector(
colly.MaxDepth(2),
)
c.Limit(&colly.LimitRule{
RandomDelay: 10 * time.Second,
Parallelism: 2,
})
c.OnRequest(func(r *colly.Request) {
r.Headers.Set("User-Agent", userAgents[rand.Intn(len(userAgents))])
})
c.OnError(func(_ *colly.Response, err error) {
log.Fatal(err)
})
c.OnScraped(func(r *colly.Response) {
log.Println(fmt.Sprintf("Done %s", r.Request.URL))
})
return c
}
现在,在使用这个客户端时,我可以调用 NewClient(),它会返回一个 *colly.NewCollector(),并附带一些用于设置头部和错误处理的样板代码。
但是,在使用这个客户端包时,我仍然需要做很多样板工作。对于每个网站,我都必须设置 AllowedDomains、DomainGlob 以及其他可能特定的配置。
所以我的问题是,Go 语言中处理这种情况的惯用方式是什么?是创建一个仅存储所有配置的包,而不是采用“包装器”式的方法吗?也就是说,基本上就是一堆常量?
更多关于Golang中各种配置的规范和最佳实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html
1 回复
更多关于Golang中各种配置的规范和最佳实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Go中处理配置的惯用方式是使用结构体来封装配置选项,并通过函数选项模式(Functional Options Pattern)提供灵活的配置方式。对于你的爬虫客户端,可以这样重构:
package client
import (
"bufio"
"fmt"
"log"
"math/rand"
"os"
"time"
"github.com/gocolly/colly"
)
type Config struct {
MaxDepth int
RandomDelay time.Duration
Parallelism int
AllowedDomains []string
DomainGlob []string
UserAgentFile string
}
type Option func(*Config)
func WithMaxDepth(depth int) Option {
return func(c *Config) {
c.MaxDepth = depth
}
}
func WithRateLimit(delay time.Duration, parallelism int) Option {
return func(c *Config) {
c.RandomDelay = delay
c.Parallelism = parallelism
}
}
func WithAllowedDomains(domains ...string) Option {
return func(c *Config) {
c.AllowedDomains = domains
}
}
func WithDomainGlob(globs ...string) Option {
return func(c *Config) {
c.DomainGlob = globs
}
}
func WithUserAgentFile(file string) Option {
return func(c *Config) {
c.UserAgentFile = file
}
}
func NewCollector(opts ...Option) *colly.Collector {
rand.Seed(time.Now().UnixNano())
// 默认配置
config := &Config{
MaxDepth: 2,
RandomDelay: 10 * time.Second,
Parallelism: 2,
UserAgentFile: "client/agents.txt",
}
// 应用选项
for _, opt := range opts {
opt(config)
}
// 读取User-Agent
userAgents := readUserAgents(config.UserAgentFile)
// 创建Collector
collectorOpts := []colly.CollectorOption{
colly.MaxDepth(config.MaxDepth),
}
if len(config.AllowedDomains) > 0 {
collectorOpts = append(collectorOpts, colly.AllowedDomains(config.AllowedDomains...))
}
if len(config.DomainGlob) > 0 {
collectorOpts = append(collectorOpts, colly.DomainGlob(config.DomainGlob...))
}
c := colly.NewCollector(collectorOpts...)
c.Limit(&colly.LimitRule{
RandomDelay: config.RandomDelay,
Parallelism: config.Parallelism,
})
c.OnRequest(func(r *colly.Request) {
if len(userAgents) > 0 {
r.Headers.Set("User-Agent", userAgents[rand.Intn(len(userAgents))])
}
})
c.OnError(func(_ *colly.Response, err error) {
log.Printf("Request error: %v", err)
})
c.OnScraped(func(r *colly.Response) {
log.Printf("Done %s", r.Request.URL)
})
return c
}
func readUserAgents(filename string) []string {
file, err := os.Open(filename)
if err != nil {
log.Printf("Warning: cannot open user-agent file: %v", err)
return []string{}
}
defer file.Close()
var agents []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
agents = append(agents, scanner.Text())
}
if err := scanner.Err(); err != nil {
log.Printf("Warning: error reading user-agent file: %v", err)
}
return agents
}
使用示例:
package main
import (
"time"
"yourproject/client"
)
func main() {
// 使用默认配置
defaultCollector := client.NewCollector()
// 自定义配置
customCollector := client.NewCollector(
client.WithMaxDepth(3),
client.WithRateLimit(5*time.Second, 4),
client.WithAllowedDomains("example.com", "test.com"),
client.WithDomainGlob("*.example.com"),
client.WithUserAgentFile("custom_agents.txt"),
)
// 使用collector进行爬取
_ = defaultCollector
_ = customCollector
}
对于跨项目的通用配置,可以创建独立的配置包:
package config
import "time"
type CrawlerConfig struct {
MaxDepth int
RandomDelay time.Duration
Parallelism int
AllowedDomains []string
DomainGlob []string
UserAgentFile string
Timeout time.Duration
CacheDir string
Debug bool
}
func NewDefaultConfig() *CrawlerConfig {
return &CrawlerConfig{
MaxDepth: 2,
RandomDelay: 10 * time.Second,
Parallelism: 2,
UserAgentFile: "agents.txt",
Timeout: 30 * time.Second,
CacheDir: ".cache",
Debug: false,
}
}
然后在客户端包中使用:
package client
import (
"yourproject/config"
// ... 其他导入
)
func NewCollectorFromConfig(cfg *config.CrawlerConfig) *colly.Collector {
// 基于配置创建collector
// ...
}
这种模式提供了类型安全的配置方式,良好的可测试性,并且保持了API的简洁性。

