【Go语言爬虫系列03】Colly高级特性与并发控制

Go 优雅的爬虫框架 - Colly Colly 是一款用 Go 语言编写的优雅网络爬虫框架,速度快、灵活且易于使用配置可以写在里面,也可以写在外面。 阅读详情

📚 原创系列: “Go语言爬虫系列”

🔄 转载说明: 本文最初发布于"Gopher部落"微信公众号,经原作者授权转载。

🔗 关注原创: 欢迎扫描文末二维码,关注"Gopher部落"微信公众号获取第一手Go技术文章。

📑 Go语言爬虫系列导航

本文是【Go语言爬虫系列】的第3篇,点击下方链接查看更多文章

🚀 Go爬虫系列:共12篇
  1. 爬虫入门与Colly框架基础
  2. HTML解析与Goquery技术详解
  3. Colly高级特性与并发控制👈 当前位置
  4. 爬虫架构设计与实现(即将发布)
  5. 反爬虫策略应对技术(即将发布)
  6. 模拟登录与会话维持(即将发布)
  7. 动态网页爬取技术(即将发布)
  8. 分布式爬虫设计与实现(即将发布)
  9. 数据存储与处理(即将发布)
  10. 爬虫性能优化技术(即将发布)
  11. 爬虫安全与合规性(即将发布)
  12. 综合项目实战:新闻聚合系统(即将发布)

📖 文章导读

在前两篇文章中,我们学习了爬虫的基本概念、Colly框架的基础用法以及HTML解析技术。本文作为系列的第三篇,将深入探讨Colly框架的高级特性和并发控制技术,重点介绍:

  1. Colly高级配置与性能调优
  2. 异步并发爬取与协程控制
  3. 自定义中间件开发
  4. 请求限速与防御策略
  5. 错误处理与重试机制
  6. 应对反爬虫技术的实用策略

通过本文的学习,您将掌握构建高效、稳定、大规模爬虫系统的核心技术,能够处理更加复杂的爬取任务,同时有效应对各种网站反爬策略。

一、Colly高级配置与性能优化

1.1 Collector的高级配置项

在前面的文章中,我们简单介绍了Colly的基本用法。实际上,Collector对象支持多种高级配置选项,可以精细控制爬虫行为:

c := colly.NewCollector(
    // 设置用户代理
    colly.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/96.0.4664.110"),
    
    // 允许访问的域名,支持通配符
    colly.AllowedDomains("example.com", "*.example.com"),
    
    // 允许重复访问
    colly.AllowURLRevisit(),
    
    // 忽略robots.txt规则(请谨慎使用)
    colly.IgnoreRobotsTxt(),
    
    // 设置最大深度
    colly.MaxDepth(5),
    
    // 异步模式
    colly.Async(true),
    
    // 设置并发数
    colly.Parallelism(5),
    
    // 自定义请求头
    colly.Headers(map[string]string{
        "Accept": "text/html,application/xhtml+xml,application/xml",
        "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
    }),
    
    // URL过滤器
    colly.URLFilters(
        regexp.MustCompile("https://example.com/path/.*"),
    ),
)

1.2 HTTP客户端定制

Colly允许您自定义底层的HTTP客户端,这对于处理特殊网络环境(如代理、特殊证书等)非常有用:

// 创建自定义Transport
transport := &http.Transport{
    // 设置代理
    Proxy: http.ProxyURL(&url.URL{
        Scheme: "http",
        Host:   "proxy.example.com:8080",
    }),
    
    // TLS配置
    TLSClientConfig: &tls.Config{
        InsecureSkipVerify: true, // 忽略证书验证(仅用于测试)
    },
    
    // 连接池设置
    MaxIdleConnsPerHost: 20,
    DisableKeepAlives:   false,
    
    // 超时设置
    DialContext: (&net.Dialer{
        Timeout:   30 * time.Second,
        KeepAlive: 30 * time.Second,
    }).DialContext,
}

// 设置自定义HTTP客户端
c.WithTransport(transport)

1.3 请求上下文和元数据

Colly提供了请求上下文(Context)功能,允许在不同的回调函数之间传递数据:

c.OnRequest(func(r *colly.Request) {
    // 设置上下文数据
    r.Ctx.Put("key", "value")
    r.Ctx.Put("timestamp", time.Now().Unix())
})

c.OnResponse(func(r *colly.Response) {
    // 获取上下文数据
    key := r.Ctx.Get("key")
    timestamp := r.Ctx.GetAny("timestamp").(int64)
    
    fmt.Printf("请求 %s 处理成功,上下文数据: %s, 时间戳: %d\n", 
        r.Request.URL, key, timestamp)
})

这个特性在处理需要关联请求与响应的场景中非常有用,例如:记录请求时间、传递页面类型信息、关联父子页面等。

1.4 存储后端配置

Colly使用存储后端来记录已访问的URL,防止重复爬取。默认使用内存存储,但对于大型爬虫,可以配置使用其他存储后端:

// 内存存储(默认)
c.SetStorage(colly.NewInMemoryStorage())

// SQLite存储
storage := &colly.SQLiteStorage{
    FileName: "visited_urls.db",
}
if err := storage.Init(); err != nil {
    log.Fatal(err)
}
c.SetStorage(storage)

// Redis存储
storage := &colly.RedisStorage{
    Address:  "127.0.0.1:6379",
    Password: "",
    DB:       0,
    Prefix:   "colly:",
}
if err := storage.Init(); err != nil {
    log.Fatal(err)
}
c.SetStorage(storage)

为大规模爬虫选择合适的存储后端能显著提高效率和可靠性。例如,Redis存储允许多个爬虫实例共享URL访问状态,便于分布式爬取。

二、异步并发爬取与协程控制

2.1 开启异步模式

Colly默认是同步模式,这意味着每个请求会阻塞等待完成。在异步模式下,请求会并发执行,大幅提高爬取效率:

// 方式1:创建时启用异步
c := colly.NewCollector(colly.Async(true))

// 方式2:后续启用异步
c.Async = true

// 设置并发数(默认为1)
c.Parallelism = 5

// 启动多个URL爬取
for _, url := range urls {
    c.Visit(url)
}

// 等待所有请求完成
c.Wait()

关键点说明:

  • 异步模式下必须调用Wait()方法等待所有请求完成
  • Parallelism设置了最大并发请求数
  • 异步模式适合大量页面的并发爬取

2.2 并发控制与限制

虽然高并发可以提高爬取速度,但过高的并发可能导致服务器拒绝请求或IP被封。Colly提供了多种并发控制机制:

// 创建限速器
limiter := colly.NewLimiter(
    // 每域名最大并发数
    colly.IgnoreRegexRules(),
    // 域名限速规则,每5秒最多2个请求
    colly.PerDomain(&colly.LimiterRule{
        DomainGlob:  "*.example.com",
        Parallelism: 2,
        Delay:       2 * time.Second,
        RandomDelay: 1 * time.Second,
    }),
    // 随机延迟
    colly.RandomDelay(500*time.Millisecond),
)

// 应用限速器
c.SetLimiter(limiter)

限速参数详解:

  • Parallelism: 每个域名的最大并发数
  • Delay: 请求之间的固定延迟
  • RandomDelay: 在固定延迟基础上增加的随机延迟
  • DomainGlob: 域名匹配模式,支持通配符

2.3 使用队列管理请求

对于大规模爬虫,使用队列可以更好地管理请求,支持优先级、存储和恢复等功能:

// 创建收集器
c := colly.NewCollector()

// 配置回调函数...

// 创建队列
q, _ := queue.New(
    2, // 线程数
    &queue.InMemoryQueueStorage{MaxSize: 10000}, // 队列存储
)

// 添加URL到队列
for _, url := range urls {
    q.AddURL(url)
}

// 启动队列处理
q.Run(c)

高级队列配置示例:

// 创建基于Redis的队列存储
storage := &queue.RedisQueueStorage{
    Address:  "localhost:6379",
    Password: "",
    DB:       0,
    Prefix:   "crawler_queue:",
}

// 初始化存储
if err := storage.Init(); err != nil {
    log.Fatal(err)
}

// 创建队列
q, _ := queue.New(
    10, // 线程数
    storage,
)

// 添加URL到队列,可设置优先级
q.AddURL("https://example.com/important", queue.Priority{Level: 1})
q.AddURL("https://example.com/normal", queue.Priority{Level: 5})

// 添加请求到队列
request, _ := colly.NewRequest("GET", "https://example.com", nil)
request.Headers.Set("X-Custom-Header", "value")
q.AddRequest(request, queue.Priority{Level: 1})

队列的优势在于:

  • 支持请求优先级
  • 可以暂停和恢复爬取
  • 便于实现分布式爬虫
  • 可以持久化存储请求队列,防止程序崩溃导致任务丢失

2.4 克隆Collector扩展功能

在某些场景下,我们需要为不同域名或不同类型的页面使用不同的配置。Colly支持克隆Collector实现这一需求:

// 创建主收集器
mainCollector := colly.NewCollector(
    colly.AllowedDomains("example.com"),
    colly.Async(true),
)

// 克隆一个子收集器,用于处理不同的域名
productCollector := mainCollector.Clone()
productCollector.AllowedDomains = append(
    productCollector.AllowedDomains, 
    "products.example.com",
)

// 克隆另一个子收集器,用于处理图片
imageCollector := mainCollector.Clone()
imageCollector.AllowedDomains = append(
    imageCollector.AllowedDomains, 
    "images.example.com",
)
imageCollector.DetectCharset = false // 图片无需字符集检测

// 在主收集器中发现产品链接时,交给产品收集器处理
mainCollector.OnHTML("a.product", func(e *colly.HTMLElement) {
    productCollector.Visit(e.Attr("href"))
})

// 在产品收集器中发现图片链接时,交给图片收集器处理
productCollector.OnHTML("img", func(e *colly.HTMLElement) {
    imageCollector.Visit(e.Attr("src"))
})

// 启动爬取
mainCollector.Visit("https://example.com")
mainCollector.Wait()
productCollector.Wait()
imageCollector.Wait()

这种方法特别适合:

  • 处理跨域名的复杂爬取逻辑
  • 对不同类型的资源应用不同的处理策略
  • 实现层次化的爬取结构

五、错误处理与重试机制

5.1 识别和分类常见错误

爬虫开发中常见的错误可分为以下几类:

  1. 网络错误:连接超时、DNS解析失败等
  2. HTTP错误:4xx、5xx状态码
  3. 解析错误:HTML格式错误、选择器不匹配
  4. 资源错误:内存不足、并发过高等

Colly提供了OnError回调来处理这些错误:

c.OnError(func(r *colly.Response, err error) {
    url := r.Request.URL.String()
    statusCode := r.StatusCode
    
    // 网络错误判断
    if statusCode == 0 {
        if strings.Contains(err.Error(), "timeout") {
            log.Printf("连接超时: %s - %v\n", url, err)
        } else if strings.Contains(err.Error(), "no such host") {
            log.Printf("DNS解析失败: %s - %v\n", url, err)
        } else {
            log.Printf("网络错误: %s - %v\n", url, err)
        }
        return
    }
    
    // HTTP错误判断
    switch {
    case statusCode >= 400 && statusCode < 500:
        log.Printf("客户端错误: %s - %d\n", url, statusCode)
    case statusCode >= 500:
        log.Printf("服务器错误: %s - %d\n", url, statusCode)
    default:
        log.Printf("未知错误: %s - %d - %v\n", url, statusCode, err)
    }
})

5.2 高级重试策略

简单的重试可能不足以应对复杂场景,下面是一个更完善的重试策略实现:

// 高级重试中间件
type RetryPolicy struct {
    // 最大重试次数
    MaxRetries int
    // 退避系数(重试间隔倍数)
    BackoffFactor float64
    // 初始重试延迟
    InitialDelay time.Duration
    // 最大重试延迟
    MaxDelay time.Duration
    // 重试的错误类型
    RetryableErrors []string
    // 重试的HTTP状态码
    RetryableStatusCodes []int
    
    // 内部状态
    retryCount map[string]int
    mu         sync.Mutex
}

// 创建默认的重试策略
func NewRetryPolicy() *RetryPolicy {
    return &RetryPolicy{
        MaxRetries:          3,
        BackoffFactor:       2.0,
        InitialDelay:        1 * time.Second,
        MaxDelay:            60 * time.Second,
        RetryableErrors:     []string{"timeout", "connection refused", "no such host", "EOF"},
        RetryableStatusCodes: []int{408, 429, 500, 502, 503, 504},
        retryCount:          make(map[string]int),
    }
}

// 应用重试策略到收集器
func (rp *RetryPolicy) Apply(c *colly.Collector) {
    c.OnError(func(r *colly.Response, err error) {
        rp.mu.Lock()
        defer rp.mu.Unlock()
        
        url := r.Request.URL.String()
        statusCode := r.StatusCode
        
        // 检查是否应该重试
        shouldRetry := false
        
        // 检查HTTP状态码
        for _, code := range rp.RetryableStatusCodes {
            if statusCode == code {
                shouldRetry = true
                break
            }
        }
        
        // 检查错误信息
        if !shouldRetry && err != nil {
            errMsg := err.Error()
            for _, retryableErr := range rp.RetryableErrors {
                if strings.Contains(errMsg, retryableErr) {
                    shouldRetry = true
                    break
                }
            }
        }
        
        // 如果不应该重试,直接返回
        if !shouldRetry {
            log.Printf("不可重试的错误: %s - %d - %v\n", url, statusCode, err)
            return
        }
        
        // 检查重试次数
        count := rp.retryCount[url]
        if count >= rp.MaxRetries {
            log.Printf("达到最大重试次数: %s (%d次)\n", url, count)
            return
        }
        
        // 计算退避延迟
        delay := rp.InitialDelay * time.Duration(math.Pow(rp.BackoffFactor, float64(count)))
        if delay > rp.MaxDelay {
            delay = rp.MaxDelay
        }
        
        // 更新重试计数
        rp.retryCount[url] = count + 1
        
        log.Printf("重试 %s (%d/%d) 延迟 %v\n", 
            url, count+1, rp.MaxRetries, delay)
        
        // 延迟后重试
        time.AfterFunc(delay, func() {
            // 克隆原始请求
            req := r.Request.Clone()
            // 清除一些可能导致重试失败的头信息
            req.Headers.Del("If-None-Match")
            req.Headers.Del("If-Modified-Since")
            c.Request("GET", url, nil, r.Request.Ctx, req.Headers)
        })
    })
}

使用示例:

func main() {
    c := colly.NewCollector(colly.Async(true))
    
    // 应用重试策略
    retryPolicy := NewRetryPolicy()
    retryPolicy.Apply(c)
    
    // ... 其他爬虫逻辑 ...
}

5.3 健壮的错误恢复机制

针对严重错误和不可恢复的情况,我们可以实现崩溃恢复和状态保存:

// 崩溃恢复中间件
func RecoveryMiddleware(c *colly.Collector) {
    // 为每个goroutine添加恢复逻辑
    c.OnRequest(func(r *colly.Request) {
        go func(req *colly.Request) {
            // 延迟处理panic
            defer func() {
                if r := recover(); r != nil {
                    log.Printf("从panic恢复: %v\n请求: %s\n堆栈: %s\n", 
                        r, req.URL, debug.Stack())
                }
            }()
            
            // 这里可以添加其他操作
        }(r)
    })
}

// 状态保存和恢复功能
type CrawlerState struct {
    PendingURLs []string          `json:"pending_urls"`
    VisitedURLs map[string]bool   `json:"visited_urls"`
    Data        map[string]interface{} `json:"data"`
}

func SaveCrawlerState(state *CrawlerState, filename string) error {
    data, err := json.MarshalIndent(state, "", "  ")
    if err != nil {
        return err
    }
    return os.WriteFile(filename, data, 0644)
}

func LoadCrawlerState(filename string) (*CrawlerState, error) {
    data, err := os.ReadFile(filename)
    if err != nil {
        return nil, err
    }
    
    var state CrawlerState
    err = json.Unmarshal(data, &state)
    if err != nil {
        return nil, err
    }
    
    return &state, nil
}

5.4 监控与日志记录

良好的日志记录对于诊断问题和优化爬虫至关重要:

// 详细日志记录器
func DetailedLogger(c *colly.Collector, logFile string) {
    // 创建或打开日志文件
    file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
    if err != nil {
        log.Fatal("无法创建日志文件:", err)
    }
    
    // 创建多输出的日志器
    logger := log.New(io.MultiWriter(os.Stdout, file), "", log.LstdFlags)
    
    // 记录请求
    c.OnRequest(func(r *colly.Request) {
        logger.Printf("[请求] %s %s\n", r.Method, r.URL)
    })
    
    // 记录响应
    c.OnResponse(func(r *colly.Response) {
        logger.Printf("[响应] %s %d %d字节 %.2fs\n", 
            r.Request.URL, r.StatusCode, len(r.Body), 
            time.Since(r.Request.Ctx.GetAny("startTime").(time.Time)).Seconds())
    })
    
    // 记录错误
    c.OnError(func(r *colly.Response, err error) {
        logger.Printf("[错误] %s %v\n", r.Request.URL, err)
    })
    
    // 记录完成的页面
    c.OnScraped(func(r *colly.Response) {
        logger.Printf("[完成] %s\n", r.Request.URL)
    })
    
    // 记录请求开始时间
    c.OnRequest(func(r *colly.Request) {
        r.Ctx.Put("startTime", time.Now())
    })
}

六、应对反爬虫技术

6.1 常见反爬虫技术及应对策略

现代网站采用各种技术来防止爬虫,以下是一些常见技术及应对方法:

反爬虫技术原理应对策略
User-Agent检测识别爬虫使用的默认User-Agent使用随机真实的浏览器User-Agent
IP频率限制限制单IP的请求频率使用代理池、控制请求速率
Cookie/Session验证要求有效的会话状态保存并使用Cookie,模拟登录流程
验证码通过人机验证阻止自动访问接入验证码识别服务或人工处理
JavaScript渲染内容通过JS动态加载使用无头浏览器(如Chromedp)处理
蜜罐陷阱设置隐藏链接诱导爬虫点击分析网页结构,避免访问蜜罐链接
网页指纹识别分析浏览器行为特征模拟真实用户行为和浏览模式

6.2 模拟浏览器指纹

某些网站使用高级指纹识别来检测爬虫,需要模拟更完整的浏览器特征:

// 添加真实的浏览器指纹
func BrowserFingerprint(c *colly.Collector) {
    c.OnRequest(func(r *colly.Request) {
        // 设置常见的请求头
        r.Headers.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36")
        r.Headers.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8")
        r.Headers.Set("Accept-Language", "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7")
        r.Headers.Set("Accept-Encoding", "gzip, deflate, br")
        r.Headers.Set("Connection", "keep-alive")
        r.Headers.Set("Upgrade-Insecure-Requests", "1")
        r.Headers.Set("Cache-Control", "max-age=0")
        
        // 设置一些常见的客户端提示头
        r.Headers.Set("Sec-Ch-Ua", "\"Not A;Brand\";v=\"99\", \"Chromium\";v=\"96\", \"Google Chrome\";v=\"96\"")
        r.Headers.Set("Sec-Ch-Ua-Mobile", "?0")
        r.Headers.Set("Sec-Ch-Ua-Platform", "\"Windows\"")
        r.Headers.Set("Sec-Fetch-Dest", "document")
        r.Headers.Set("Sec-Fetch-Mode", "navigate")
        r.Headers.Set("Sec-Fetch-Site", "none")
        r.Headers.Set("Sec-Fetch-User", "?1")
    })
}

6.3 集成无头浏览器处理JavaScript

对于需要JavaScript渲染的网站,Colly可以与Chromedp集成:

package main

import (
    "context"
    "log"
    "time"

    "github.com/chromedp/chromedp"
    "github.com/gocolly/colly/v2"
)

// 使用Chromedp获取渲染后的HTML
func GetRenderedHTML(url string) (string, error) {
    // 创建上下文
    ctx, cancel := chromedp.NewContext(
        context.Background(),
        // 可选:设置调试端口
        // chromedp.WithDebugf(log.Printf),
    )
    defer cancel()

    // 设置超时
    ctx, cancel = context.WithTimeout(ctx, 30*time.Second)
    defer cancel()

    // 保存HTML的变量
    var html string

    // 运行任务
    err := chromedp.Run(ctx,
        chromedp.Navigate(url),
        // 等待页面加载完成
        chromedp.WaitVisible("body", chromedp.ByQuery),
        // 可选:等待特定元素出现
        // chromedp.WaitVisible("#content", chromedp.ByID),
        // 等待一点时间让JS执行完成
        chromedp.Sleep(2*time.Second),
        // 获取HTML
        chromedp.OuterHTML("html", &html),
    )

    if err != nil {
        return "", err
    }

    return html, nil
}

func main() {
    // 创建收集器
    c := colly.NewCollector()
    
    // 设置常规回调
    c.OnHTML("a[href]", func(e *colly.HTMLElement) {
        link := e.Attr("href")
        log.Println("Found link:", link)
    })
    
    // 处理JS渲染的页面
    url := "https://example.com/js-rendered-page"
    
    // 获取渲染后的HTML
    html, err := GetRenderedHTML(url)
    if err != nil {
        log.Fatal(err)
    }
    
    // 使用渲染后的HTML
    err = c.Request("GET", url, strings.NewReader(html), nil, nil)
    if err != nil {
        log.Fatal(err)
    }
}

6.4 绕过Web应用防火墙(WAF)

一些网站使用防火墙来阻止爬虫,以下是一些常见绕过技术:

// 添加关键头部来绕过一些WAF
func BypassWAF(c *colly.Collector) {
    c.OnRequest(func(r *colly.Request) {
        // 添加Referer,模拟从合法网站点击进入
        r.Headers.Set("Referer", "https://www.google.com/")
        
        // 添加X-Forwarded-For头,可能绕过某些基于IP的限制
        // 注意:这种方法在现代WAF面前可能不太有效
        r.Headers.Set("X-Forwarded-For", "66.249.66.1") // Google爬虫的IP
        
        // 设置DNT (Do Not Track)
        r.Headers.Set("DNT", "1")
        
        // 模拟真实浏览器的其他特征
        r.Headers.Set("Accept-Charset", "UTF-8")
    })
    
    // 处理CF-Challenge等防护
    c.OnResponse(func(r *colly.Response) {
        // 检测是否遇到Cloudflare等WAF的挑战页面
        if r.StatusCode == 403 || r.StatusCode == 503 {
            if bytes.Contains(r.Body, []byte("Cloudflare")) || 
               bytes.Contains(r.Body, []byte("challenge")) {
                log.Println("检测到WAF挑战页面,尝试使用无头浏览器解决...")
                
                // 这里可以调用之前的GetRenderedHTML函数
                // 或者其他专门解决CF挑战的库
            }
        }
    })
}

📝 实战案例:构建健壮的新闻爬虫

让我们将学到的所有技术整合到一个实际案例中——一个健壮、高效的新闻爬虫:

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "math/rand"
    "os"
    "os/signal"
    "strings"
    "sync"
    "syscall"
    "time"

    "github.com/gocolly/colly/v2"
    "github.com/gocolly/colly/v2/queue"
)

// 新闻文章结构
type Article struct {
    Title     string    `json:"title"`
    URL       string    `json:"url"`
    Author    string    `json:"author,omitempty"`
    Date      time.Time `json:"date,omitempty"`
    Content   string    `json:"content"`
    Category  string    `json:"category,omitempty"`
    Source    string    `json:"source"`
}

// 爬虫状态
type CrawlerState struct {
    Articles   []Article         `json:"articles"`
    VisitedURLs map[string]bool  `json:"visited_urls"`
    PendingURLs []string         `json:"pending_urls"`
}

func main() {
    // 初始化随机数生成器
    rand.Seed(time.Now().UnixNano())
    
    // 创建爬虫状态
    state := &CrawlerState{
        Articles:    make([]Article, 0),
        VisitedURLs: make(map[string]bool),
        PendingURLs: make([]string, 0),
    }
    
    // 加载之前的状态(如果存在)
    stateFile := "crawler_state.json"
    if _, err := os.Stat(stateFile); err == nil {
        log.Println("加载之前的爬虫状态...")
        data, err := os.ReadFile(stateFile)
        if err == nil {
            json.Unmarshal(data, state)
        }
    }
    
    // 创建收集器,配置高级选项
    c := colly.NewCollector(
        colly.AllowedDomains("news.example.com"),
        colly.MaxDepth(3),
        colly.Async(true),
    )
    c.Parallelism = 3
    
    // 设置限速
    err := c.Limit(&colly.LimiterRule{
        DomainGlob:  "*",
        Delay:       1 * time.Second,
        RandomDelay: 1 * time.Second,
        Parallelism: 3,
    })
    if err != nil {
        log.Fatal("设置限速器失败:", err)
    }
    
    // 应用中间件
    RandomUserAgent(c)
    retryPolicy := NewRetryPolicy()
    retryPolicy.Apply(c)
    DetailedLogger(c, "crawler.log")
    
    // 数据锁,防止并发修改状态
    var mu sync.Mutex
    
    // 处理文章列表页
    c.OnHTML("article.news-item", func(e *colly.HTMLElement) {
        // 提取文章链接
        link := e.ChildAttr("a.read-more", "href")
        if link == "" {
            return
        }
        
        // 确保是绝对URL
        link = e.Request.AbsoluteURL(link)
        
        // 检查是否已访问
        mu.Lock()
        if state.VisitedURLs[link] {
            mu.Unlock()
            return
        }
        mu.Unlock()
        
        // 访问文章详情页
        detailCollector := c.Clone()
        detailCollector.Visit(link)
    })
    
    // 处理文章详情页
    c.OnHTML("div.article-detail", func(e *colly.HTMLElement) {
        url := e.Request.URL.String()
        
        // 提取文章信息
        article := Article{
            Title:    strings.TrimSpace(e.ChildText("h1.title")),
            URL:      url,
            Content:  strings.TrimSpace(e.ChildText("div.content")),
            Author:   strings.TrimSpace(e.ChildText("span.author")),
            Category: strings.TrimSpace(e.ChildText("span.category")),
            Source:   "news.example.com",
        }
        
        // 解析日期
        dateStr := strings.TrimSpace(e.ChildText("time.published"))
        if dateStr != "" {
            if t, err := time.Parse("2006-01-02 15:04", dateStr); err == nil {
                article.Date = t
            }
        }
        
        // 保存文章
        mu.Lock()
        state.Articles = append(state.Articles, article)
        state.VisitedURLs[url] = true
        mu.Unlock()
        
        log.Printf("保存文章: %s\n", article.Title)
    })
    
    // 发现分页链接
    c.OnHTML("a.pagination[href]", func(e *colly.HTMLElement) {
        link := e.Attr("href")
        if link != "" {
            e.Request.Visit(link)
        }
    })
    
    // 定期保存状态
    ticker := time.NewTicker(1 * time.Minute)
    go func() {
        for range ticker.C {
            saveState(state, stateFile)
        }
    }()
    
    // 捕获终止信号,确保优雅退出
    sigChan := make(chan os.Signal, 1)
    signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
    go func() {
        <-sigChan
        log.Println("接收到终止信号,正在保存状态...")
        saveState(state, stateFile)
        os.Exit(0)
    }()
    
    // 开始爬取
    startUrls := []string{
        "https://news.example.com/latest",
        "https://news.example.com/politics",
        "https://news.example.com/technology",
    }
    
    // 如果有未完成的URL,优先处理它们
    if len(state.PendingURLs) > 0 {
        startUrls = state.PendingURLs
    }
    
    for _, url := range startUrls {
        c.Visit(url)
    }
    
    // 等待所有爬取完成
    c.Wait()
    
    // 保存最终状态
    saveState(state, stateFile)
    
    // 保存文章到JSON文件
    articlesJson, _ := json.MarshalIndent(state.Articles, "", "  ")
    os.WriteFile("articles.json", articlesJson, 0644)
    
    log.Printf("爬取完成,共保存 %d 篇文章\n", len(state.Articles))
}

// 保存爬虫状态
func saveState(state *CrawlerState, filename string) {
    // 添加剩余的待爬取URL
    // 这里简化处理,实际中应该从队列中提取
    
    data, err := json.MarshalIndent(state, "", "  ")
    if err != nil {
        log.Println("保存状态失败:", err)
        return
    }
    
    err = os.WriteFile(filename, data, 0644)
    if err != nil {
        log.Println("写入状态文件失败:", err)
        return
    }
    
    log.Println("爬虫状态已保存")
}

💡 小结

在本文中,我们深入探讨了Colly框架的高级特性和并发控制技术,覆盖了构建高效、稳定爬虫系统所需的关键知识:

  1. 高级配置: 学习了如何精细调整Collector的参数、自定义HTTP客户端、配置存储后端等
  2. 并发控制: 掌握了异步爬取、并发限制、请求队列管理等技术
  3. 中间件开发: 实现了多种实用中间件,如随机User-Agent、Cookie管理、重试策略等
  4. 限速防御: 学习了如何设置智能限速策略,避免被网站封禁
  5. 错误处理: 构建了健壮的错误处理机制和恢复策略
  6. 反爬应对: 了解了常见反爬技术及有效的应对策略

这些技术和策略相互配合,能够帮您构建出适应各种复杂场景的爬虫系统。记住,爬虫开发既是技术挑战,也有伦理边界 - 尊重网站的robots.txt和服务条款,合理控制爬取频率,做一名负责任的爬虫开发者。

下篇预告

在下一篇文章中,我们将探讨爬虫架构设计与实现,包括大规模分布式爬虫架构、数据存储与处理管道、监控告警系统、调度策略等高级话题,帮助您构建企业级爬虫解决方案。敬请期待!

👨‍💻 关于作者与Gopher部落

"Gopher部落"专注于Go语言技术分享,提供从入门到精通的完整学习路线。

🌟 为什么关注我们?

  1. 系统化学习路线:本系列12篇文章循序渐进,带你完整掌握Go开发
  2. 实战驱动教学:理论结合实践,每篇文章都有可操作的代码示例
  3. 持续更新内容:定期分享最新Go生态技术动态与大厂实践经验
  4. 专业技术社区:加入我们的技术交流群,与众多Go开发者共同成长

📱 关注方式

  1. 微信公众号:搜索 “Gopher部落” 或 “GopherTribe”
  2. 优快云专栏:点击页面右上角"关注"按钮

💡 读者福利

关注公众号回复 “Go爬虫” 即可获取:

  • 完整Go爬虫学习资料
  • 本系列示例代码
  • 项目实战源码

期待与您在Go语言的学习旅程中共同成长!

Go语言——爬虫入门1 学习Go语言的第一个爬虫代码 1.第一个爬虫代码 package main import ( "fmt" "io/ioutil" "net/http" ) func main() { //res 为结构体,储存了很多的信息 resp,err := http.Get("https://studygolang.com/pkgdoc") if err!= nil{ fmt.Println(err) } defer resp.Body.Close() if resp.StatusCod 阅读详情

相关推荐

Go语言爬虫框架:Colly 的详细使用及对应采集案例

前言:colly 是 Go 实现的比较有名的一款爬虫框架,而且 Go 在高并发和分布式场景的优势也正是爬虫技术所需要的。它的主要特点是轻量、快速,设计非常优雅,并且分布式的支持也非常简单,易于扩展。框架简介:基于colly框架及net/http进行封装,实现的一款可配置分布式爬虫架构。使用者只需要配置解析、并发数、入库topic、请求方式、请求url等参数即可,其他代码类似于scrapy,不需要单独编写。github地址:colly特性干净的API快速(单核>1k请求/秒)

数据知道的博客 1万+

使用 Colly 在 Golang 中进行网页抓取的步骤

Golang 是最强大的网络爬虫工具之一。而 Colly 在使用 Go 时提供了很大帮助。阅读这篇博文,了解有关 Colly 的最详细的信息,并学习如何使用 Colly 抓取网站。

wellshake的博客 3481

Go-Colly:Golang网络爬虫开发实战框架

Go语言凭借其简洁高效的语法、原生并发支持以及出色的执行性能,逐渐成为网络爬虫开发的重要语言之一。其在高并发场景下的稳定性,特别适合构建大规模、分布式的爬虫系统。Colly作为Go语言中最流行的网络爬虫框架之一,凭借其轻量级架构、模块化设计和强大的扩展能力,深受Gopher社区喜爱。本章将为读者奠定使用Go与Colly构建高效爬虫系统的理论基础,并引导进入实际开发流程。除了使用内置的LimitRule,Colly 还支持开发者通过中间件机制实现更复杂的限流逻辑。

weixin_28931449的博客 1838

Colly 使用教程

Colly 使用教程 【免费下载链接】colly Elegant Scraper and Crawler Framework for Golang 项目地址: https://gitcode.com/gh_mirrors/co/c...

gitblog_01198的博客 1397

使用Kettle进行数据同步(增量)

文章目录kettle介绍kettle安装kettle使用使用kettle同步关系型数据库数据(MySQL示例)1. 创建一个转换2. 选择表输入3. 格式转换4. 执行脚本5. 创建job使用kettle同步NoSql数据(MongoDB示例)创建转换时的注意事项MongoDBInput怎么创建job的创建如何在Linux上面运行已经创建好的job?1、把创建好的job传输到Linux机器上面2、编辑kjb文件3、执行任务 kettle介绍 Kettle 是一款国外开源的 ETL 工具,纯 Java 编写

JenKin的博客 7351

无源定位之时差估计的精确时差估计算法(ETDE)及MATLAB实现程序

时差估计算法之精确时差估计(ETDE)欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入 欢迎使用Markdown编辑器 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想

伊谢尔伦的林中小路 4255

介绍一个基于 Go 语言的爬虫框架 colly

大家好,我是TheWeiJun。很高兴又和大家见面了,国庆假期马上就要结束了,在国庆假期里小编看了下colly框架,故这篇文章中将提到colly的使用及分析;欢迎各位读者多多阅读与交流!特别声明:本公众号文章只作为学术研究,不作为其它不法用途;如有侵权请联系作者删除。这是「进击的Coder」的第728篇技术分享作者:TheWeiJun来源:逆向与爬虫的故事目录一、colly框架简介...

静觅 3025

5分钟搞定SNMP工具安装:从Linux到Windows的snmpwalk/snmpget全攻略

本文提供了SNMP工具(snmpwalk/snmpget)在Linux和Windows系统上的快速安装与使用指南,涵盖从基础安装到高阶应用的全面解析。通过详细的命令示例和故障排查技巧,帮助用户快速掌握网络设备监控与诊断的核心技能,提升运维效率。

weixin_29248313的博客 555

Go 每日一库之 colly

简介colly是用 Go 语言编写的功能强大的爬虫框架。它提供简洁的 API,拥有强劲的性能,可以自动处理 cookie&session,还有提供灵活的扩展机制。首先,我们介绍co...

darjun的博客 1677

利用Civil3D装配设计器(SAC)建设高度形状自适应挡土墙

之前的模型都是点线,最后加一个形状,形成面。再根据现场测得的挡土墙的墙脚的点生成中心线(alignment),在中心线的基础上生成在现有地面(existing ground level)的高程(profile)。简单的说,该墙的墙趾啊,墙踵啊,基底下面那个把儿啊,都是随着高度变化的,而高度是随着下面这个高程图变化的。接着出现角度和Y的文本框,角度92,y 值随便写一个,然后重要的是,高程目标选路面(优先于Y值)。意思是P2的高度,也就是路面的高程,减去P10,墙脚高程,再减去墙脚埋进地里的深度0.8m。

测量老覃 的博客 1127

【机械臂视觉抓取从理论到实战】

首先声明一下,此项目是参考B站哈萨克斯坦UP的【机械臂视觉抓取从理论到实战】 此内容为他研究生生涯的阶段性成果展示和技术分享,所有数据和代码均开源。所以鹏鹏我特此来复现一下,我采用的硬件与之有所不同,UP主使用UR5,我实验室采用的是UR3,下面列出相关材料 UR3CB3.12:https://www.universal-robots.cn/cb3/ 【UR3系统升级到CB3.12附带URcap1.05】硬件支持代码支持 jacquard数据集:https://pan.baidu.com/s/1524HrV

vor234的博客 5184

Colly官方文档入门教程

Colly 的设计非常模块化,通过 Collector 组件管理请求和响应,通过中间件和事件处理器进行灵活的定制。这种设计使得 Colly 成为一个高效且易于扩展的网络爬虫框架。

学海无涯 2801

JAVA:Spring Boot 集成 Forest 优雅的 HTTP 客户端

Forest是一个优雅的Java HTTP客户端框架,通过注解式声明简化HTTP接口调用。它支持多HTTP客户端、自动参数绑定、序列化、重试机制和拦截器等功能。本文演示了如何在Spring Boot中集成Forest,包括配置、接口定义和调用示例,还展示了POST请求、文件上传等常见场景。相比传统HTTP客户端,Forest具有声明简洁、功能完善的特点,特别适合微服务调用和第三方API对接,能显著提升开发效率和代码可维护性。

木头 427

入门级Go语言爬虫开发教程:从零开始的爬虫实战

之后,我们就可以使用类似于jQuery的语法来操作我们的Document对象,获取页面上的元素信息了。接下来,我们需要解析HTML源码,提取出我们需要的信息。这里,我将使用goquery这个第三方包,在获取HTML源码后,解析响应体中的HTML,并提取页面上的标题和链接。只要我们遵守相关法律法规,严格按照爬虫的道德规范进行操作,我们就可以在爬虫的世界里获取到更加丰富的信息了。可以看到,我们使用了http包提供的Get()来获取对应网站的HTML源码,并对响应体进行了简单的处理。

m0_48891301的博客 762

小米15修复“NV损坏” 去除主板校验电阻步骤解析与图示

小米15系列将搭载骁龙8至尊版处理器,采用台积电3nm工艺。文章分析了手机出现"NV损坏"故障的表现及修复方法,包括通信异常、系统启动问题等典型症状,并介绍了去除主板校验电阻和刷写工程固件两种修复方案。重点讲解了去除主板电阻后刷写QCN文件的操作流程,强调在维修过程中需要结合分析与思考。适用于小米15系列特定机型(24129PN74G/I/C)的硬件修复。

小马哥的专栏 313

【Unity 实用工具篇】 | 滚动列表插件FancyScrollView,快速上手使用

FancyScrollView 是一款专为Unity引擎开发的高效滚动列表组件。它具有轻量级、可定制性强、性能优秀等特点。 使用FancyScrollView插件进行Unity项目开发,可以极大提升开发效率,尤其是在需要大量列表展示的场景下,如商城、目录列表、角色选择界面等。开发者可以将注意力集中在业务逻辑的实现上,而将界面渲染和交互部分交由FancyScrollView处理。 本文来介绍一下FancyScrollView的功能展示和基本使用方法。

努力前行,总会成为自己心中的那道光 3万+

go colly 限制请求并发数

import "github.com/gocolly/colly/v2" c := colly.NewCollector() rule := &colly.LimitRule{ RandomDelay: time.Second, Parallelism: 10, //并发数为10 } _ = c.Limit(rule)

takujo的博客 1538

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

gocolly-登录(5)

文章目录简介demo 简介 模拟账号密码登录 cookie登录 以及字符串cookie登录 csrf_token 处理 demo package main import ( "fmt" "github.com/gocolly/colly" "github.com/gocolly/colly/extensions" "log" "net/http" "os" "strings" "time" ) /* 请求执行之前调用 - OnRequest 响应返回之后调用 - OnRespon

清风冷吟 2398

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

轻松上手 Colly:Golang 中的优雅爬虫框架

轻松上手 Colly:Golang 中的优雅爬虫框架 【免费下载链接】colly Elegant Scraper and Crawler Framework for Golang 项目地址: https://gitcode.com...

gitblog_01261的博客 1222

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

Go 爬虫之 colly 从入门到不放弃指南

最近发现知乎上感兴趣的问题越来越少,于是准备聚合下其他平台技术问答,比如 segmentfault、stackoverflow 等。要完成这个工作,肯定是离不开爬虫的。我就顺便抽时间研究了 Go 的一款爬虫框架 colly。概要介绍colly 是 Go 实现的比较有名的一款爬虫框架,而且 Go 在高并发和分布式场景的优势也正是爬虫技术所需要的。它的主要特点是轻量、快速,设...

Seekload 3317

golang学习笔记——爬虫colly入门

很多语言都可以写爬虫,包括python,java、c++、Pythhon等。而Go本身是开源的,很多大佬为Python的功能扩展写了很多成熟的工具,也就是网络上常说的xx库,我们可以利用这些工具快速实现我们的需求,比较好入门。另外,需要强调的是,网络上并不是什么东西都可以爬,针对这个问题,我国有着一套完备的法律。爬了不该爬的内容,比如大量个人信息,那可以快速实现“从入门到入狱”。

mirana的专栏 2101

【亲测免费】 **Colly爬虫框架下载与安装教程**

Colly爬虫框架下载与安装教程 【免费下载链接】colly Elegant Scraper and Crawler Framework for Golang 项目地址: https://gitcode.com/gh_mirror...

gitblog_01256的博客 2186

GO爬虫必备之HTTP请求QuickStart

前几天在 "知乎想法" 谈到了一个话题,如何模仿学习,举了通过 net/http client 模仿 Pyhton 的requests的例子。但并未实践,难道想法真的只能是想法吗?当然不是,于是我决定先暂停一周 GO 笔记,来实践下自己的想法。 有些新的知识,我们可以通过模仿学习 本文将通过 GO 实现 requests 的 quick start 文档中的所有例子,系统学习http client...

weixin_33770878的博客 616

Go爬虫colly官方示例十四【parallel】- 最大爬取并行度

文章目录示例介绍示例代码输出 转载请标明出处: http://blog.youkuaiyun.com/qq_27818541/article/details/112489172 本文出自:【BigManing的博客】 示例介绍 c.Limit(&colly.LimitRule{DomainGlob: "*", Parallelism: 2}) 设置最大爬取并行度 示例代码 package main import ( "fmt" "github.com/gocolly/colly/v2" )

BigManing的博客 1046

Go语言爬虫框架之Colly和Goquery

写在前面 此文翻译自点击阅读原文,建议英语能力好的同学阅读原文进行学习。 点击阅读Colly文档,点击阅读Goquery文档 Go语言爬虫框架之Colly和Goquery Python框架框架比较有BeautifulSoup或Scrapy,基于Go的爬虫框架是比较强健的,尤其Colly和Goquery是比较强大的工具,其灵活性和 表达性都比较优秀。 网络爬虫 网络爬虫是什么?从本质上讲,网络爬虫的...

Hi,你好 1万+

go爬虫练习,爬取菜鸟教程

【代码】go爬虫练习,爬取菜鸟教程。

weixin_47069956的博客 3443
上一篇: 【Go语言爬虫系列02】HTML解析与Goquery技术详解
下一篇: 【Gin框架入门到精通系列01】Gin框架介绍与环境搭建
Gopher部落
博客等级 码龄2年 784粉丝 92原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gopher部落

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值