You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用gocolly异步模式/队列解析时结果不一致的原因排查

问题分析与修复方案

你的问题核心是并发场景下的资源竞争未处理,以及错误被忽略导致请求丢失,具体原因和修复如下:

1. 核心问题点

(1) 非线程安全的数据操作

cards切片在多个goroutine中被append,这会导致数据竞争,不仅可能丢失元素,还会破坏切片结构,间接让你误以为visited计数不符合预期(虽然visited用了atomic,但卡片没正确添加会干扰判断)。

(2) 错误被忽略

  • 队列模式中,queue.New和queue.AddURL的错误被直接忽略,可能导致部分初始URL未加入队列;
  • 异步模式中,c.Visit的错误未捕获,部分请求可能根本没发出去;
  • 现有OnError仅处理收到响应的错误,像DNS失败、连接拒绝这类未收到响应的错误,不会触发OnError,导致你看不到异常。

(3) 队列模式的配置缺失

队列模式下,你没有将Collector与队列绑定,导致通过e.Request.Visit触发的新请求不会进入队列,而是直接并发执行,和队列的调度逻辑冲突,部分请求可能被丢弃。

2. 修复后的代码示例

队列模式修复版

package main

import (
	"fmt"
	"strings"
	"sync"
	"sync/atomic"
	"time"

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

func main() {
	c := colly.NewCollector()

	c.SetRequestTimeout(time.Minute * 5)

	// 捕获队列创建错误
	q, err := queue.New(8, &queue.InMemoryQueueStorage{MaxSize: 1000})
	if err != nil {
		fmt.Println("队列创建失败:", err)
		return
	}
	// 将Collector与队列绑定,确保所有请求走队列调度
	c.SetQueue(q)

	var (
		visited int64
		cards   []map[string]string
		cardMu  sync.Mutex // 保护cards的互斥锁
	)

	c.OnHTML("a.css-rc5s2u", func(e *colly.HTMLElement) {
		// 捕获触发新请求的错误
		if err := e.Request.Visit(e.Attr("href")); err != nil {
			fmt.Println("触发新请求失败:", err)
		}
	})

	c.OnError(func(r *colly.Response, err error) {
		fmt.Println("请求失败 URL:", r.Request.URL, "错误:", err)
	})

	// 增加响应日志,确认请求都收到反馈
	c.OnResponse(func(r *colly.Response) {
		fmt.Println("收到响应 URL:", r.Request.URL)
	})

	c.OnHTML("ul.css-sfcl1s", func(e *colly.HTMLElement) {
		atomic.AddInt64(&visited, 1)
		card := make(map[string]string)
		e.ForEach("p.css-b5m1rv", func(_ int, elem *colly.HTMLElement) {
			text := strings.Split(elem.Text, ":")
			if len(text) > 1 {
				card[text[0]] = text[1]
			} else {
				card["type"] = text[0]
			}
		})
		// 加锁保护cards的append操作
		cardMu.Lock()
		cards = append(cards, card)
		cardMu.Unlock()
	})

	c.OnRequest(func(r *colly.Request) {
		fmt.Println("正在访问", r.URL)
	})

	const (
		baseURL = "some_url"
		maxPage = 5
	)

	for p := 1; p <= maxPage; p++ {
		urlPath := fmt.Sprintf("%s&page=%d", baseURL, p)
		// 捕获添加URL到队列的错误
		if err := q.AddURL(urlPath); err != nil {
			fmt.Println("添加URL到队列失败:", urlPath, "错误:", err)
		}
	}

	q.Run(c)

	fmt.Println("已处理页面数:", visited)
	fmt.Println("已收集卡片数:", len(cards))
}

异步模式修复版

package main

import (
	"fmt"
	"strings"
	"sync"
	"sync/atomic"
	"time"

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

func main() {
	c := colly.NewCollector(colly.Async(true))

	c.SetRequestTimeout(5 * time.Minute)

	var (
		visited int64
		cards   []map[string]string
		cardMu  sync.Mutex // 保护cards的互斥锁
	)

	c.OnHTML("a.css-rc5s2u", func(e *colly.HTMLElement) {
		if err := e.Request.Visit(e.Attr("href")); err != nil {
			fmt.Println("触发新请求失败:", err)
		}
	})

	c.OnError(func(r *colly.Response, err error) {
		fmt.Println("请求失败 URL:", r.Request.URL, "错误:", err)
	})

	c.OnResponse(func(r *colly.Response) {
		fmt.Println("收到响应 URL:", r.Request.URL)
	})

	c.OnHTML("ul.css-sfcl1s", func(e *colly.HTMLElement) {
		atomic.AddInt64(&visited, 1)
		card := make(map[string]string)
		e.ForEach("li>p.css-b5m1rv", func(_ int, elem *colly.HTMLElement) {
			text := strings.Split(elem.Text, ":")
			if len(text) > 1 {
				card[text[0]] = text[1]
			} else {
				card["type"] = text[0]
			}
		})
		cardMu.Lock()
		cards = append(cards, card)
		cardMu.Unlock()
	})

	c.OnRequest(func(r *colly.Request) {
		fmt.Println("正在访问", r.URL)
	})

	const (
		baseURL = "some_url"
		maxPage = 5
	)

	for p := 1; p <= maxPage; p++ {
		urlPath := fmt.Sprintf("%s&page=%d", baseURL, p)
		// 捕获发起请求的错误
		if err := c.Visit(urlPath); err != nil {
			fmt.Println("发起请求失败:", urlPath, "错误:", err)
		}
	}

	c.Wait()

	fmt.Println("已处理页面数:", visited)
	fmt.Println("已收集卡片数:", len(cards))
}

3. 额外优化建议

  • 可以给visited和卡片数加一致性校验,爬取结束后对比两者是否匹配,排查是否有页面未解析到目标元素;
  • 如果目标网站有反爬,异步/队列模式下并发过高可能被限流,可降低队列并发数(queue.New第一个参数),或增加请求间隔(c.Limit(&colly.LimitRule{DomainGlob: "*", Delay: 1 * time.Second}));
  • 若目标页面是动态加载的,gocolly默认无法处理,可结合colly/headless启用无头浏览器渲染。

内容的提问来源于stack exchange,提问作者Don Draper

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 17:10:18