Go语言使用cron定时运行Colly网络爬虫不触发后续任务问题求助
问题根因
- 回调注册逻辑位置错误:
OnHTML、OnRequest等Colly回调只需给Collector实例注册一次即可,你将注册逻辑放在了每次定时触发的scrapePls方法中,重复注册不会生效,还会引发逻辑异常。 - Colly默认禁止重复访问同一URL:Collector内置已访问URL缓存,第一次请求
www.example.com完成后,该地址会被标记为已访问,后续调用Visit方法会直接跳过,不会发起新请求。 - 目标URL缺少协议头:你调用
Visit传入的参数是www.example.com,没有带http://或https://协议头,首次请求本身也会触发报错,可能影响后续任务执行。
修复方案
方案一:复用Collector实例,开启重复访问权限
将回调注册逻辑移到初始化阶段,同时配置Collector允许重复访问URL,修改后代码如下:
package main import ( "log" "os" "os/signal" "syscall" "github.com/gocolly/colly/v2" "github.com/go-redis/redis/v8" "github.com/robfig/cron/v3" ) type scraper struct { coll *colly.Collector rc *redis.Client } func newScraper(c *colly.Collector, rc *redis.Client) scraper { return scraper{coll: c, rc: rc} } func main() { rc := redis.NewClient(&redis.Options{ Addr: "localhost:3000", Password: "", DB: 0, }) // 初始化Collector时开启允许重复访问 coll := colly.NewCollector( colly.AllowURLRevisit(), ) scrape := newScraper(coll, rc) // 回调注册移到这里,只执行一次 scrape.coll.OnHTML(`body`, func(e *colly.HTMLElement) { // 提取需要的内容 // 写入Redis }) scrape.coll.OnRequest(func(r *colly.Request) { log.Println("Visiting", r.URL) }) c := cron.New() c.AddFunc("@every 10s", scrape.scrapePls) c.Start() sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) <-sig c.Stop() } func (sc scraper) scrapePls() { // 补全协议头 err := sc.coll.Visit("https://www.example.com") if err != nil { log.Println("请求失败:", err) } }
方案二:每次定时任务新建Collector实例
如果不同次爬取需要完全隔离的上下文,可以每次执行scrapePls时新建Collector,不需要配置重复访问权限:
package main import ( "log" "os" "os/signal" "syscall" "github.com/gocolly/colly/v2" "github.com/go-redis/redis/v8" "github.com/robfig/cron/v3" ) type scraper struct { rc *redis.Client } func newScraper(rc *redis.Client) scraper { return scraper{rc: rc} } func main() { rc := redis.NewClient(&redis.Options{ Addr: "localhost:3000", Password: "", DB: 0, }) scrape := newScraper(rc) c := cron.New() c.AddFunc("@every 10s", scrape.scrapePls) c.Start() sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) <-sig c.Stop() } func (sc scraper) scrapePls() { // 每次新建Collector coll := colly.NewCollector() coll.OnHTML(`body`, func(e *colly.HTMLElement) { // 提取内容、写入Redis }) coll.OnRequest(func(r *colly.Request) { log.Println("Visiting", r.URL) }) err := coll.Visit("https://www.example.com") if err != nil { log.Println("请求失败:", err) } }
其他实现思路
- 轻量定时可以不用cron库,直接用
time.Ticker实现固定间隔执行,依赖更少,适合简单场景。 - 可以给爬取逻辑加panic捕获,避免单次爬取任务崩溃导致整个定时调度进程退出。
- 爬取逻辑和数据落地逻辑分离,用channel异步传递爬取结果,单独开goroutine处理Redis写入,避免IO操作阻塞定时调度。
内容的提问来源于stack exchange,提问作者Adith Dev Reddy
相关产品推荐
相关产品推荐

