如何实现Go Colly深度1并行爬取多链接?解决并行失效问题
解决Colly爬虫并行爬取且单站MaxDepth=1的问题
给你两种可行的解决思路:
方案一:单Collector实例+自定义深度控制
复用单个Collector实现全局并行,同时通过请求上下文(Context)为每个城市的爬取任务单独设置深度限制,替代全局MaxDepth的约束。
代码示例
import ( "log" "github.com/gocolly/colly/v2" "github.com/gocolly/colly/v2/extensions" ) func main() { dbutil.Init() defer dbutil.Close() db := dbutil.GetDB() // 初始化单个异步Collector,不设置全局MaxDepth c := colly.NewCollector(colly.Async(true)) extensions.RandomUserAgent(c) // 设置全局并行度为20,统一控制所有请求的并发数 c.Limit(&colly.LimitRule{DomainGlob: "*", Parallelism: 20}) // 自定义深度校验:每个请求携带自己的最大深度限制 c.OnRequest(func(r *colly.Request) { maxDepth, ok := r.Ctx.Get("max_depth").(int) if !ok { maxDepth = 1 } // Colly中初始请求深度为0,子请求深度递增 if r.Depth > maxDepth { r.Abort() return } }) // 绑定你的爬虫处理逻辑(需调整spider函数,不再传入单个Collector) spider(c, db) // 从数据库读取城市链接 rows, err := db.Query("SELECT id, link FROM cities_table") if err != nil { log.Fatal(err) } defer rows.Close() var cities []struct{ ID int; Link string } for rows.Next() { var id int var link string if err := rows.Scan(&id, &link); err != nil { log.Println(err) continue } cities = append(cities, struct{ ID int; Link string }{id, link}) } // 遍历城市发起请求,每个请求携带专属的深度限制和城市ID for _, city := range cities { baseURL := ThroughProxy(city) ctx := colly.NewContext() ctx.Put("max_depth", 1) ctx.Put("city_id", city.ID) if err := c.Request("GET", baseURL.String(), nil, ctx, nil); err != nil { log.Println(err) } } c.Wait() }
说明
- 所有请求复用同一个Collector,全局并发度由
Parallelism:20统一控制,避免资源浪费 - 通过请求上下文为每个城市任务单独设置深度限制,保证单站的爬取深度不超过1
- 需调整
spider函数,通过r.Ctx.Get("city_id")获取当前请求对应的城市信息进行后续处理
方案二:Goroutine并行运行多个Collector
如果不想大幅修改现有代码,可以将每个城市的Collector实例化和爬取逻辑放到goroutine中,用WaitGroup等待所有任务完成,实现多Collector并行。
代码示例
import ( "log" "sync" "github.com/gocolly/colly/v2" "github.com/gocolly/colly/v2/extensions" ) func main() { dbutil.Init() defer dbutil.Close() db := dbutil.GetDB() rows, err := db.Query("SELECT id, link FROM cities_table") if err != nil { log.Fatal(err) } defer rows.Close() var cities []struct{ ID int; Link string } for rows.Next() { var id int var link string if err := rows.Scan(&id, &link); err != nil { log.Println(err) continue } cities = append(cities, struct{ ID int; Link string }{id, link}) } var wg sync.WaitGroup wg.Add(len(cities)) for _, city := range cities { // 启动goroutine处理单个城市的爬取 go func(city struct{ ID int; Link string }) { defer wg.Done() c := colly.NewCollector( colly.MaxDepth(1), colly.Async(true), ) extensions.RandomUserAgent(c) // 单个Collector的并行度建议调低,避免全局并发过高(比如设为2,10个goroutine总并发就是20) c.Limit(&colly.LimitRule{DomainGlob: "*", Parallelism: 2}) spider(c, db, city) baseURL := ThroughProxy(city) c.Visit(baseURL.String()) c.Wait() }(city) // 传值避免循环变量引用问题 } wg.Wait() }
说明
- 每个城市对应一个独立Collector,保留原有的
MaxDepth:1设置,无需修改深度逻辑 - 通过goroutine实现多Collector并行,WaitGroup确保所有任务完成后再退出
- 注意控制单个Collector的并行度,避免全局并发数过高触发反爬或耗尽本地资源
内容的提问来源于stack exchange,提问作者Farshad
相关产品推荐
相关产品推荐

