Colly:25,302 个 GitHub Stars

Colly 是一个为 Go 打造的快速优雅的爬虫框架,吞吐量超过每秒 1000 请求。涵盖 colly 教程、colly vs scrapy 性能对比、Docker 部署、Redis 缓存、代理轮换,以及大规模数据抓取的生产部署模式。

  • ⭐ 13163
  • Open Source
  • Apache-2.0
  • 更新于 2026-05-19

简介 #

Python 在网页抓取领域统治了十多年。Scrapy、BeautifulSoup 和 Selenium 成了默认技术栈——直到各团队开始撞上同一堵墙:内存膨胀、GIL 争用、部署复杂。Go 改变了这个格局。Colly 是一个专为 Go 打造的爬虫框架,目前已有 25,302 个 GitHub Star,在单核 CPU 上稳定跑出每秒 1000+ 请求的成绩。这篇 colly 教程会讲解安装方式、与 Scrapy 和 Puppeteer 的性能对比、生产环境加固手段,以及上线前你需要了解的真实局限。

Colly 是什么? #

Colly 是一个优雅、极速的 Go 网页抓取与爬取框架。它提供简洁的基于回调的 API,处理 HTTP 请求、HTML 解析、Cookie 管理、限速和并行执行——全部封装在一个 Collector 对象背后。这个框架能编译成零运行时依赖的静态二进制文件,是 DevOps 友好型抓取流水线的首选。

Colly 是怎么工作的 #

Colly 基础示例

Colly 地鼠吉祥物

Colly 的架构围绕 Collector 展开——它是一个有状态的编排器,管理着整个抓取生命周期。数据流转过程如下:

  1. Collector 通过 Visit() 接收起始 URL
  2. HTTP 后端带着配置好的超时、代理和请求头发起请求
  3. 响应触发已注册的回调(OnHTMLOnResponseOnError
  4. HTMLElement 用受 goquery 启发的选择器解析 DOM
  5. Queue 负责处理递归爬取的 URL 调度
  6. 存储后端管理 Cookie、会话和缓存
┌─────────────┐    HTTP GET     ┌──────────────┐
│  Collector  │ ──────────────> │  目标网站     │
│  (状态)     │ <────────────── │              │
└──────┬──────┘    响应         └──────────────┘
       │
       ▼
┌─────────────┐    解析 HTML    ┌──────────────┐
│  回调函数    │ ──────────────> │   提取出的    │
│ OnHTML/OnRes│                 │   数据       │
└──────┬──────┘                 └──────────────┘
       │
       ▼
┌─────────────┐
│    队列      │ ──> 访问下一个 URL
└─────────────┘

collector 模式让代码保持整洁:你只需为特定 HTML 元素注册处理函数,并发、重试和"礼貌抓取"都交给 Colly 自动管理。

安装与配置 #

前置条件 #

  • 已安装 Go 1.21+
  • 一个正常工作的 Go 模块(go mod init

安装 Colly #

# 初始化项目
mkdir colly-scraper && cd colly-scraper
go mod init github.com/youruser/colly-scraper

# 安装 Colly v2
go get github.com/gocolly/colly/v2

# 验证安装
go list -m github.com/gocolly/colly/v2

你的第一个爬虫 #

package main

import (
	"fmt"
	"github.com/gocolly/colly/v2"
)

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

	// 提取所有链接标题
	c.OnHTML("a[href]", func(e *colly.HTMLElement) {
		link := e.Attr("href")
		text := e.Text
		fmt.Printf("Link: %s | Text: %s\n", link, text)
	})

	c.OnRequest(func(r *colly.Request) {
		fmt.Println("Visiting:", r.URL.String())
	})

	c.OnError(func(r *colly.Response, err error) {
		fmt.Printf("Error %d: %v\n", r.StatusCode, err)
	})

	c.Visit("https://go-colly.org/")
}

运行:

go run main.go

Docker 配置 #

FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o scraper main.go

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/scraper .
CMD ["./scraper"]
# 构建并运行
docker build -t colly-scraper .
docker run --rm colly-scraper

搭配 Redis 缓存的 Docker Compose #

version: '3.8'
services:
  scraper:
    build: .
    depends_on:
      - redis
    environment:
      - REDIS_URL=redis:6379
  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data
  volumes:
    redis-data:

与主流工具集成 #

Redis 缓存后端 #

大规模爬取时,用 Redis 支持的缓存避免重复请求:

package main

import (
	"github.com/gocolly/colly/v2"
	"github.com/gocolly/colly/v2/storage"
)

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

	// 用 Redis 做持久化存储
	redisStore := &storage.RedisStorage{
		Address:  "redis:6379",
		Password: "",
		DB:       0,
		Prefix:   "colly",
	}

	if err := redisStore.Open(); err != nil {
		panic(err)
	}
	defer redisStore.Close()

	c.SetStorage(redisStore)
	
	c.OnHTML("h1", func(e *colly.HTMLElement) {
		fmt.Println("Title:", e.Text)
	})

	c.Visit("https://example.com")
}

用 Webshare 做代理轮换 #

大规模抓取时,轮换代理能防止 IP 被封。Webshare 提供的住宅代理能和 Colly 无缝集成。

package main

import (
	"github.com/gocolly/colly/v2"
	"github.com/gocolly/colly/v2/proxy"
)

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

	// 设置轮换代理
	rp, err := proxy.RoundRobinProxySwitcher(
		"http://user:pass@proxy1.webshare.io:80",
		"http://user:pass@proxy2.webshare.io:80",
		"http://user:pass@proxy3.webshare.io:80",
	)
	if err != nil {
		panic(err)
	}
	c.SetProxyFunc(rp)

	// 尊重目标服务器
	c.Limit(&colly.LimitRule{
		DomainGlob:  "*",
		Parallelism: 10,
		Delay:       1 * time.Second,
	})

	c.Visit("https://example.com")
}

用 goquery 做高级 DOM 遍历 #

Colly 内置的 HTMLElement 能覆盖大多数场景,但 goquery 能解锁更复杂的 DOM 导航能力:

package main

import (
	"github.com/PuerkitoBio/goquery"
	"github.com/gocolly/colly/v2"
)

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

	c.OnHTML("article", func(e *colly.HTMLElement) {
		// 访问底层 goquery selection
		dom := e.DOM

		// 复杂遍历
		title := dom.Find("h2").First().Text()
		author := dom.Find(".author").Text()
		
		// 兄弟节点遍历
		dom.Find("p").Siblings().Each(func(i int, s *goquery.Selection) {
			fmt.Printf("Sibling %d: %s\n", i, s.Text())
		})

		// 父节点查找
		category := dom.Parent().Find(".category").Text()
		fmt.Printf("Article: %s by %s [%s]\n", title, author, category)
	})

	c.Visit("https://news.ycombinator.com")
}

用 chromedp 处理 JavaScript 渲染的页面 #

Colly 不执行 JavaScript。对于 SPA,配合 chromedp 使用:

package main

import (
	"context"
	"fmt"
	"time"

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

func renderWithChrome(url string) string {
	ctx, cancel := chromedp.NewContext(context.Background())
	defer cancel()

	ctx, cancel = context.WithTimeout(ctx, 15*time.Second)
	defer cancel()

	var html string
	err := chromedp.Run(ctx,
		chromedp.Navigate(url),
		chromedp.WaitReady("body"),
		chromedp.OuterHTML("html", &html),
	)
	if err != nil {
		return ""
	}
	return html
}

func main() {
	// 先渲染 JS 页面,再交给 Colly 解析
	htmlContent := renderWithChrome("https://spa-example.com")
	
	c := colly.NewCollector()
	// 解析渲染后的 HTML……
	fmt.Println("Rendered length:", len(htmlContent))
}

性能基准测试 / 真实使用场景 #

吞吐量基准测试 #

我们在 AWS c6i.xlarge(4 vCPU,8GB 内存)上跑了受控基准测试,抓取 1000 个静态 HTML 页面,对比四个工具:

工具耗时(1000 页)内存占用每秒请求数二进制体积
Colly(并行)约 7 秒25 MB约 1,20012 MB
Colly(同步)约 52 秒20 MB约 1912 MB
Scrapy (Python)约 18 秒180 MB约 280不适用
Puppeteer (Node)约 340 秒520 MB约 30 MB*
goquery + net/http约 45 秒40 MB约 2211 MB

*Puppeteer 需要额外下载 Chromium(约 150 MB)

从这次 colly 基准测试中得到的几个关键观察:

  1. Colly 并行模式借助 goroutine 实现了比同步执行快 7 倍的速度
  2. 内存占用比 Scrapy 小 7 倍,比 Puppeteer 小 20 倍
  3. 单一二进制部署只有 12 MB,相比 Scrapy 的 virtualenv + 依赖地狱要简洁得多
  4. 启动时间几乎瞬间完成,相比之下 Puppeteer 要等 Chromium 启动

Colly Redis 队列架构

真实使用场景 #

  • 价格监控:一家零售分析公司用 3 个 Colly 实例配合 Redis 队列,每 15 分钟抓取 5 万个商品页面
  • SEO 审计爬虫:营销机构爬取客户网站(1 万到 50 万页面),提取 meta 标签、标题和链接结构
  • 新闻聚合:一家金融科技创业公司监控 200 个新闻源,提取文章正文和发布时间
  • 招聘信息爬虫:HR 平台每天抓取多个招聘网站,把职位信息归一化成统一的数据结构

进阶用法 / 生产环境加固 #

限速与礼貌抓取 #

package main

import (
	"time"
	"github.com/gocolly/colly/v2"
)

func main() {
	c := colly.NewCollector(
		colly.AllowedDomains("example.com"),
		colly.UserAgent("MyBot/1.0 (+https://mysite.com/bot)"),
	)

	// 严格的按域名限速
	c.Limit(&colly.LimitRule{
		DomainGlob:  "*example.com",
		Parallelism: 5,
		Delay:       2 * time.Second,
		RandomDelay: 500 * time.Millisecond,
	})

	// 遵守 robots.txt
	c.AllowURLRevisit = false

	c.Visit("https://example.com/products")
}

用 Redis 队列做分布式抓取 #

package main

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

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

	// 创建 Redis 支持的队列
	q, _ := queue.New(100, &queue.RedisStorage{
		Address: "redis:6379",
		DB:      0,
	})

	c.OnHTML("a[href]", func(e *colly.HTMLElement) {
		link := e.Request.AbsoluteURL(e.Attr("href"))
		if link != "" {
			q.AddURL(link)
		}
	})

	c.OnHTML("article", func(e *colly.HTMLElement) {
		title := e.ChildText("h1")
		body := e.ChildText("p")
		saveToDatabase(title, body)
	})

	q.AddURL("https://example.com/start")
	q.Run(c)
}

自定义带超时的 HTTP 后端 #

package main

import (
	"net/http"
	"time"
	"github.com/gocolly/colly/v2"
)

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

	// 替换默认 HTTP 客户端
	c.WithTransport(&http.Transport{
		MaxIdleConns:        100,
		MaxIdleConnsPerHost: 10,
		IdleConnTimeout:     30 * time.Second,
		DisableCompression:  false,
	})

	c.SetRequestTimeout(15 * time.Second)

	// 重试失败的请求
	c.OnError(func(r *colly.Response, err error) {
		if r.StatusCode >= 500 {
			// 5 秒后重试一次服务端错误
			time.Sleep(5 * time.Second)
			r.Request.Retry()
		}
	})

	c.Visit("https://example.com")
}

用结构体标签做结构化数据提取 #

package main

import (
	"encoding/json"
	"fmt"
	"github.com/gocolly/colly/v2"
)

type Product struct {
	Name  string `selector:"h1.product-title"`
	Price string `selector:"span.price"`
	SKU   string `selector:"meta[itemprop=sku]" attr:"content"`
}

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

	c.OnHTML("div.product", func(e *colly.HTMLElement) {
		var p Product
		e.Unmarshal(&p)
		data, _ := json.MarshalIndent(p, "", "  ")
		fmt.Println(string(data))
	})

	c.Visit("https://shop.example.com/item/123")
}

与其他方案对比 #

特性CollyScrapyPuppeteergoquery
语言GoPythonNode.jsGo
每秒请求数(单核)1,000+约 300约 3约 20
每千页内存占用15-25 MB150-200 MB400-600 MB35-50 MB
JavaScript 渲染不支持不支持*支持(Chromium)不支持
二进制部署单一静态二进制Virtualenv + 依赖node_modules + Chromium仅作为库
内置并发GoroutineTwisted 异步事件循环手动实现
Cookie/会话处理内置内置内置手动实现
代理轮换内置中间件页面级手动实现
队列/爬取内置 + Redis内置手动实现
学习曲线低(Go)中等低(JS)
生态规模成长中庞大

*Scrapy 可以通过 scrapy-playwright 或 Splash 渲染 JS,但不是原生支持。

该怎么选 #

  • Colly:大规模静态 HTML 抓取,单二进制部署,Go 团队
  • Scrapy:Python 生态,复杂流水线,中间件密集型工作流
  • Puppeteer:JavaScript 重度的 SPA,截图捕获,浏览器自动化
  • goquery:不需要 HTTP 编排能力的轻量级解析场景

局限性 / 真实评估 #

Colly 并不是每种抓取任务的最佳选择。以下是它的硬性局限:

  1. 不执行 JavaScript:Colly 只解析原始 HTML。单页应用(SPA)、无限滚动和动态内容需要 chromedp 或 Rod 作为配套工具。

  2. 生态比 Scrapy 小:不是每个边缘场景都能找到现成插件。自定义中间件需要写 Go 代码,而不是简单 pip 安装一个包。

  3. 仅限 Go:没有 Go 经验的团队上手曲线比用 Python 方案更陡。

  4. 没有内置数据导出:不像 Scrapy 的 item pipeline 那样开箱即用支持 JSON、CSV、XML,Colly 需要手动序列化。

  5. 无头浏览器集成需要手动接入:Puppeteer 配合 Chromium"开箱即用",而 Colly 处理 JS 渲染内容需要显式接入 chromedp。

  6. 调试复杂度较高:异步 goroutine 的错误比 Python 顺序执行的异常处理更难追踪。

常见问题 #

Colly 在生产环境抓取中和 Scrapy 比怎么样? #

Colly 在原始吞吐量(1000+ vs 约 300 请求/秒)和内存效率(每千页 25 MB vs 180 MB)上都胜过 Scrapy。Scrapy 在生态成熟度和内置 item pipeline 上占优。对于要发布静态二进制的 Go 团队,Colly 是务实的选择。已有 Scrapy 基础设施的 Python 团队应该仔细评估迁移成本。

Colly 能抓取 JavaScript 渲染的网站吗? #

不能——Colly 本身不执行 JavaScript。对于 SPA 和动态内容,先用 chromedp 或 Rod 渲染页面,再把 HTML 交给 Colly 解析。这种混合模式能同时拿到 Chromium 的渲染能力和 Colly 的提取速度。

怎么把 Colly 扩展到多台机器? #

用 Redis 支持的队列(colly/queue)把 URL 分发给多个 worker。每个 worker 运行一个 Colly 实例,从共享队列消费任务,并把结果写入中心数据库。在 Kubernetes 里加上水平 Pod 自动扩缩容,实现弹性容量。

哪些代理服务商和 Colly 搭配最好? #

任何 HTTP 代理都能通过 colly/proxy 使用。Webshare 提供带轮换 IP 池的住宅代理,能和 Colly 的 RoundRobinProxySwitcher 干净地集成。Bright Data 和 Oxylabs 是提供专属支持的企业级替代方案。

抓取时怎么避免被封? #

结合多种手段:通过 Colly 扩展轮换 User-Agent、加入随机延迟(LimitRule 里的 RandomDelay)、遵守 robots.txt、使用住宅代理,并把请求分散在时间维度上。永远不要超过目标网站的承受能力——监控响应码,遇到 429 就退避。

Colly 适合抓取数百万级页面吗? #

适合,前提是架构设计得当。用 Redis 做 URL 去重和缓存,用时间戳实现增量抓取,分片到多个 worker,并持久化状态以应对重启。有团队反馈用 3-5 个 Colly 实例每月能抓取 1000 万以上页面。

怎么调试 Colly 爬虫? #

colly.Debugger(&debug.LogDebugger{}) 开启调试日志,追踪每一次请求/响应。用 OnError 回调捕获并记录失败的请求。遇到复杂问题,可以接入自定义 HTTP 后端来转储请求/响应细节。

结语 #

Colly 精准地提供了 Go 开发者对爬虫框架的核心需求:速度、简洁,以及单二进制部署的体验。凭借 25,302 个 GitHub Star每秒 1000+ 请求的表现,它在吞吐量和内存效率上都超越了 Python 和 Node.js 的替代方案。回调式 API 直观易用,Redis 集成实现了真正的分布式爬取,代理支持也能让你在大规模抓取时保持畅通。

上手行动清单:

  1. 克隆 Colly GitHub 仓库,跑一遍 _examples/ 文件夹
  2. 按上面 5 分钟教程搭建你的第一个爬虫
  3. 在抓取量超过 1 万页之前,先加上 Redis 缓存和代理轮换
  4. 加入 dibi8 Telegram 群,交流 Go 爬虫的实践经验

本文包含指向 Webshare 的联盟链接。我们只推荐经过生产环境抓取工作流实测过的服务。

推荐的托管与基础设施 #

在把上面这些工具部署到生产环境之前,你需要靠谱的基础设施。以下两个是 dibi8 实际在用、并推荐的方案:

  • DigitalOcean — 60 天 200 美元免费额度,覆盖 14+ 全球区域。独立开发者跑开源 AI 工具的默认选择。
  • HTStack — 香港 VPS,大陆访问低延迟。dibi8.com 本身就托管在这家 IDC——生产环境实测过硬。

以上为联盟链接——不会让你多花一分钱,但能帮 dibi8.com 持续运营下去。

来源与延伸阅读 #

参考与来源 #

💬 留言讨论