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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 02:06:08