Colly: 25,302 GitHub Stars — Benchmark Go 网页抓取框架 2026

Colly 是一款快速、优雅的 Go 网页抓取框架,吞吐量达 1,000+ req/sec。涵盖 colly 教程、colly vs scrapy 基准测试、Docker 部署、Redis 缓存、代理轮换以及大规模数据提取的生产部署模式。

  • ⭐ 25302
  • Apache-2.0
  • 更新于 2026-05-19

{{< resource-info >}}

简介 #

十多年来,Python 一直主导着网页抓取领域。Scrapy、BeautifulSoup 和 Selenium 成为了默认技术栈——直到团队开始撞上同一堵墙:内存膨胀、GIL 竞争和部署复杂性。Go 改变了这个等式。Colly 是一款专为 Go 构建的抓取框架,目前拥有 25,302 个 GitHub Stars,并已证明可以在单核 CPU 上每秒处理 1,000+ 请求。本 colly 教程将涵盖安装、与 Scrapy 和 Puppeteer 的基准测试对比、生产环境加固以及你在部署到生产环境之前需要了解的客观局限性。

什么是 Colly? #

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

Colly 的工作原理 #

Colly 基础示例

Colly gopher mascot

Colly 的架构围绕 Collector 构建——这是一个有状态的协调器,管理整个抓取生命周期。数据流如下:

  1. Collector 通过 Visit() 接收起始 URL
  2. HTTP 后端 使用配置的 timeout、代理和 header 发起请求
  3. 响应 触发已注册的回调(OnHTMLOnResponseOnError
  4. HTMLElement 使用类 goquery 选择器解析 DOM
  5. 队列 处理 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("链接: %s | 文本: %s\n", link, text)
	})

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

	c.OnError(func(r *colly.Response, err error) {
		fmt.Printf("错误 %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

Docker Compose with Redis 缓存 #

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("标题:", 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 选择
		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("兄弟节点 %d: %s\n", i, s.Text())
		})

		// 父节点查找
		category := dom.Parent().Find(".category").Text()
		fmt.Printf("文章: %s 作者: %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() {
	// 先用 Chrome 渲染页面,再用 Colly 解析
	htmlContent := renderWithChrome("https://spa-example.com")

	c := colly.NewCollector()
	// 解析渲染后的 HTML...
	fmt.Println("渲染后长度:", len(htmlContent))
}

基准测试 / 实际用例 #

吞吐量基准测试 #

我们在 AWS c6i.xlarge(4 vCPU, 8GB RAM)上针对四个工具进行了抓取 1,000 个静态 HTML 页面的受控基准测试:

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

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

colly benchmark 关键观察:

  1. Colly 并行模式 利用 goroutine 实现7 倍速度提升
  2. 内存占用 比 Scrapy 低 7 倍,比 Puppeteer 低 20 倍
  3. 单二进制部署 仅 12 MB,对比 Scrapy 的虚拟环境 + 依赖地狱
  4. 启动时间 近乎即时,对比 Puppeteer 的 Chromium 启动

Colly Redis queue 架构

实际用例 #

  • 价格监控:零售分析公司每 15 分钟抓取 5 万个产品页面,使用 3 个 Colly 实例和 Redis 队列
  • SEO 审计爬虫:营销机构抓取客户网站(1 万-50 万页面)提取元标签、标题和链接结构
  • 新闻聚合:金融科技初创公司监控 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)
二进制部署单静态二进制虚拟环境 + 依赖node_modules + Chromium仅库
内置并发GoroutineTwisted asyncEvent-loop手动
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 install 一个包。
  3. 仅支持 Go:没有 Go 经验的团队面临比使用 Python 替代方案更陡的入门曲线。
  4. 无内置数据导出:与 Scrapy 的项目流水线(开箱即用支持 JSON、CSV、XML)不同,Colly 需要手动序列化。
  5. 无头浏览器集成需手动配置:Puppeteer “开箱即用” 集成 Chromium,而 Colly 需要显式的 chromedp 连接来处理 JS 渲染内容。
  6. 调试复杂性:异步 goroutine 错误可能比 Python 的顺序异常处理更难追踪。

常见问题 #

Colly 与 Scrapy 在生产抓取中如何比较? #

Colly 在原始吞吐量(1,000+ vs ~300 req/sec)和内存效率(25 MB vs 180 MB 每千页)方面优于 Scrapy。Scrapy 在生态成熟度和内置项目流水线上胜出。对于发布静态二进制文件的 Go 团队,Colly 是务实的选择。已有 Scrapy 基础设施的 Python 团队应仔细评估迁移成本。

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

不能——Colly 原生不执行 JavaScript。对于 SPA 和动态内容,先使用 chromedp 或 Rod 渲染页面,然后将 HTML 传递给 Colly 解析。这种混合模式兼具 Chromium 的渲染能力和 Colly 的提取速度。

如何在多台机器上扩展 Colly? #

使用 Redis 支持的队列(colly/queue)在多个 worker 之间分发 URL。每个 worker 运行一个 Colly 实例,从共享队列消费并将结果写入中央数据库。在 Kubernetes 中添加 HPA 实现弹性容量。

哪些代理提供商与 Colly 配合最好? #

任何 HTTP 代理都可通过 colly/proxy 工作。Webshare 提供与 Colly RoundRobinProxySwitcher 干净集成的住宅代理和轮换 IP 池。Bright Data 和 Oxylabs 是具有专属支持的企业级替代方案。

如何避免抓取时被封锁? #

组合多种技术:通过 Colly 扩展轮换 User-Agent、添加随机延迟(LimitRule 中的 RandomDelay)、遵守 robots.txt、使用住宅代理、并随时间分布请求。永远不要超过目标站点的容量——监控响应码并在 429 错误时退避。

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

是的,但需要适当的架构。使用 Redis 进行 URL 去重和缓存、使用时间戳实现增量爬取、跨多个 worker 分片、并持久化状态以在重启后恢复。团队报告每月使用 3-5 个 Colly 实例抓取 1,000 万+ 页面。

如何调试 Colly 抓取器? #

使用 colly.Debugger(&debug.LogDebugger{}) 启用调试日志以追踪每个请求/响应。使用 OnError 回调捕获和记录失败请求。对于复杂问题,附加带有请求/响应转储功能的自定义 HTTP 后端。

结论 #

Colly 为 Go 开发者提供了抓取框架所需的一切:速度、简洁和单二进制部署故事。以 25,302 个 GitHub Stars每秒 1,000+ 请求的速度,它在吞吐量和内存效率方面优于 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 自己也跑在它上面,生产环境验证过。

Aff 链接 — 不增加你的成本,但能帮 dibi8 持续运营。

来源与延伸阅读 #

💬 留言讨论