[每周一更]-(第148期):使用 Go 进行网页抓取:Colly 与 Goquery 的对比与思路
文章目录
- Colly 概述
- Colly 的核心特性
- 适用场景
- Goquery 概述
- Goquery 的核心特性
- 适用场景
- Colly 与 Goquery 的对比
- 何时使用 Colly
- 何时使用 Goquery
- 结合 Colly 和 Goquery
- 网页抓取的最佳实践
- 更多demo
- 1. 标准库 net/http
- 特性
- 适用场景
- 优点与局限
- 2. Chromedp
- 特性
- 适用场景
- 优点与局限
- 3. Ferret
- 特性
- 适用场景
- 优点与局限
- 4. GoCrawl
- 特性
- 适用场景
- 优点与局限
- 5. Surf
- 特性
- 适用场景
- 优点与局限
- 6. 正则表达式与 HTML 解析器
- 特性
- 适用场景
- 优点与局限
- 对比与选择指南
- 需要注意:
- 扩展资源
最近公司新项目需要,会对一些文献网站进行爬取工作,如:https://pmc.ncbi.nlm.nih.gov/articles/PMC12156228/,在进行爬取过程中采用了goquery和colly两种,其实从单个页面来看速度上无差异,就是写法的区别。
本质两者都是解析识别html标签,colly可能更能应对复杂场景。基础逻辑都是针对html标签进行解析操作。
网页抓取是提取网站数据的强大技术,而 Go(Golang)提供了多个优秀的库来简化这一过程。在 Go 生态中,Colly 和 goquery 是两个常用的网页抓取工具。本文将介绍它们的特性、使用场景及对比,并为你的文章提供一些写作思路,帮助你深入探讨 Go 语言在网页抓取中的应用。
Colly 概述
Colly 是一个高层次的 Go 网页抓取框架,设计注重简单性和高效性。它提供了简洁的 API,用于处理 HTTP 请求、管理 Cookie 和解析 HTML 内容。Colly 特别适合抓取大规模网站或多页面数据,因为它内置了并发支持和请求管理功能。
Colly 的核心特性
- HTTP 客户端集成:内置 HTTP 请求处理,支持重试、超时和自定义请求头。
- 并发支持:支持并行请求,可配置速率限制,避免对服务器造成过大压力。
- HTML 解析:内部使用 goquery 进行 DOM 遍历和操作。
- 扩展性:提供请求/响应的中间件支持,并可通过插件扩展功能。
- 易用性:直观的 API,方便定义抓取规则和处理分页。
适用场景
Colly 适合抓取动态网站或大规模数据集,例如从电商网站提取商品信息,或爬取博客的多篇文章页面。
示例代码:
package main import ( "fmt" "github.com/gocolly/colly" ) func main() {c := colly.NewCollector(colly.AllowedDomains("example.com"))c.OnHTML("a[href]", func(e *colly.HTMLElement) { link := e.Attr("href")fmt.Println("发现链接:", link)c.Visit(e.Request.AbsoluteURL(link)) })c.OnRequest(func(r *colly.Request) { fmt.Println("正在访问", r.URL.String()) })c.Visit("http://example.com/")
}
Goquery 概述
Goquery 是一个受 jQuery 启发的 Go 库,专注于 HTML 文档的 DOM 遍历和操作。它不是一个完整的抓取框架,而是擅长解析和查询 HTML 内容。Goquery 适合需要精细控制 DOM 元素且已有 HTML 内容的项目。
Goquery 的核心特性
- 类 jQuery 语法:对熟悉 JavaScript/jQuery 的开发者友好,易于选择和操作 DOM 元素。
- 轻量级:专注于 HTML 解析,无内置 HTTP 客户端或爬取功能。
- 强大选择器:支持 CSS 选择器,精准提取元素。
- 链式方法:代码简洁且易读,适合 DOM 操作。
适用场景
Goquery 适合解析单页 HTML 内容,例如从静态网页提取特定数据点。
示例代码:
package main import ( "fmt" "github.com/PuerkitoBio/goquery" "strings"
) func main() {html := `<html><body><div class="post"><h1>Hello, World!</h1></div></body></html>`doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))if err != nil {fmt.Println("错误:", err)return}doc.Find(".post h1").Each(func(i int, s *goquery.Selection) { fmt.Println("标题:", s.Text()) })
}
Colly 与 Goquery 的对比
特性 | Colly | Goquery |
---|---|---|
用途 | 完整网页抓取框架 | DOM 解析与操作 |
HTTP 客户端 | 内置,支持并发请求 | 无(需外部 HTTP 客户端) |
易用性 | 高层次 API,适合初学者 | 类 jQuery,适合熟悉 jQuery 的用户 |
并发支持 | 内置并行请求处理 | 不适用 |
HTML 解析 | 内部使用 goquery | 核心功能,高度灵活 |
适用场景 | 大规模抓取、爬取多页面 | 单页解析、DOM 操作 |
性能 | 优化多请求处理 | 轻量级,适合解析任务 |
学习曲线 | 中等,因涉及抓取功能 | 较低,特别对 jQuery 用户友好 |
何时使用 Colly
- 需要爬取多个页面或整个网站。
- 要求内置 HTTP 请求管理和并发支持。
- 项目涉及动态内容或复杂导航(如跟踪链接、处理分页)。
何时使用 Goquery
- 已通过其他方式(如 net/http)获取 HTML 内容。
- 需要使用 CSS 选择器进行精细的 DOM 操作。
- 项目专注于静态页面或小规模抓取任务。
结合 Colly 和 Goquery
由于 Colly 内部使用 goquery 进行 HTML 解析,你可以在 Colly 框架中利用 goquery 的强大选择器进行更复杂的 DOM 查询。这种结合在 Colly 的内置解析功能不足以应对复杂需求时特别有用。
示例代码:
package main import ( "fmt" "github.com/PuerkitoBio/goquery" "github.com/gocolly/colly" ) func main() { c := colly.NewCollector() c.OnHTML("div.post", func(e *colly.HTMLElement) { doc := e.DOM // goquery 选择器 title := doc.Find("h1").Text() fmt.Println("文章标题:", title) }) c.Visit("http://example.com/")
}
网页抓取的最佳实践
- 遵守 Robots.txt:谨慎使用 Colly 的 colly.IgnoreRobotsTxt(),并尊重网站的使用条款。
- 速率限制:在 Colly 中配置延迟,避免对服务器造成过大压力(例如:c.Limit(&colly.LimitRule{DomainGlob: “*”, Parallelism: 2, Delay: 1 * time.Second}))。
- 错误处理:为 HTTP 请求和 HTML 解析添加健壮的错误处理逻辑。
- 代理与伪装:对于需要绕过反爬机制的场景,可配置代理或自定义 User-Agent。
- 数据存储:将抓取的数据结构化存储(如 JSON、CSV 或数据库),便于后续分析。
更多demo
1. 标准库 net/http
Go 的标准库 net/http 是最基础的 HTTP 客户端工具,提供了发送 HTTP 请求和处理响应的核心功能。它适合需要高度自定义抓取逻辑的场景。
特性
- 灵活性:支持 GET、POST 等多种 HTTP 方法,可自定义请求头、Cookie 和超时设置。
- 轻量级:无额外依赖,适合简单抓取任务。
- 可扩展:可与 html 包或其他解析库(如 goquery)结合使用。
- 并发支持:结合 Go 的 goroutine 实现高并发请求。
适用场景
- 简单的网页数据抓取,如获取单个页面的 HTML。
- 需要完全控制请求逻辑的场景。
- 与其他解析库结合使用(如 goquery 或正则表达式)。
示例代码:
package mainimport ( "fmt" "io" "net/http" )
func main() {resp, err := http.Get("http://example.com")if err != nil {fmt.Println("错误:", err)return}defer resp.Body.Close()body, err := io.ReadAll(resp.Body)if err != nil {fmt.Println("读取错误:", err)return}fmt.Println(string(body))
}
优点与局限
- 优点:内置于 Go 标准库,无需额外依赖;高度可定制。
- 局限:没有内置的 DOM 解析或爬取框架,需手动处理 HTML 和分页逻辑。
2. Chromedp
Chromedp 是一个基于 Chrome DevTools Protocol 的 Go 库,用于控制 headless Chrome 或 Chromium 浏览器,适合抓取动态渲染的网页(如依赖 JavaScript 的内容)。
特性
- 动态内容抓取:能够执行 JavaScript,获取页面渲染后的 DOM。
- 浏览器自动化:支持模拟用户操作(如点击、输入表单)。
- 截图与 PDF 生成:可生成页面截图或导出为 PDF。
- 并发支持:通过 goroutine 可实现多页面抓取。
适用场景
- 抓取需要 JavaScript 渲染的动态网页(如 SPA 应用)。
- 模拟用户交互的复杂场景(如登录后抓取数据)。
- 需要截图或生成页面快照的任务。
示例代码:
package main import ( "context" "fmt" "github.com/chromedp/chromedp" ) func main() { ctx, cancel := chromedp.NewContext(context.Background()) defer cancel() var title string err := chromedp.Run(ctx, chromedp.Navigate("http://example.com"), chromedp.Text("h1", &title), ) if err != nil { fmt.Println("错误:", err) return } fmt.Println("页面标题:", title)
}
优点与局限
- 优点:处理动态内容能力强,接近真实浏览器行为。
- 局限:依赖 Chrome/Chromium,资源占用较高;学习曲线较陡。
3. Ferret
Ferret 是一个声明式网页抓取框架,结合了 Go 和动态查询语言(类似 SQL),通过嵌入 Chrome 引擎支持动态内容抓取。
特性
- 声明式查询:使用类似 SQL 的语法定义抓取规则,易于维护。
- 动态内容支持:通过内置 Chrome 引擎处理 JavaScript 渲染。
- 模块化:支持多种数据输出格式(如 JSON、CSV)。
- 跨平台:无需手动安装浏览器,内置运行时。
适用场景
- 需要快速定义抓取规则的场景。
- 动态网页抓取,尤其是对非开发人员友好的场景。
- 需要将抓取结果结构化输出的项目。
示例代码(简化的 Ferret 查询):
package main
import ( "context" "fmt" "github.com/MontFerret/ferret/pkg/runtime" ) func main() { query := ` LET doc = DOCUMENT('http://example.com') FOR el IN doc.querySelectorAll('h1') RETURN el.innerText ` prog, err := runtime.Compile(query) if err != nil { fmt.Println("编译错误:", err) return } ctx := context.Background() result, err := prog.Run(ctx) if err != nil { fmt.Println("运行错误:", err) return } fmt.Println(string(result))
}
优点与局限
- 优点:声明式语法降低开发复杂度;支持动态内容。
- 局限:依赖外部运行时,配置稍复杂;社区较小,文档有限。
4. GoCrawl
GoCrawl 是一个高性能的网页爬取框架,专注于分布式爬取和大规模数据采集,适合需要爬取整个网站的任务。
特性
- 分布式爬取:支持多节点并行爬取,适合大规模任务。
- 可配置性:提供灵活的爬取策略(如深度优先、广度优先)。
- HTML 解析:内置简单的解析器,也可与 goquery 结合。
- Robots.txt 支持:默认遵守网站爬取规则。
适用场景
- 爬取整个网站或大量页面(如构建搜索引擎索引)。
- 需要分布式部署的抓取任务。
- 对性能和资源优化要求高的场景。
优点与局限
- 优点:高性能,适合大规模爬取;支持分布式架构。
- 局限:API 较复杂,适合有经验的开发者;社区活跃度较低。
5. Surf
Surf 是一个轻量级的 Go 浏览器库,基于 WebKit 引擎,支持简单的动态内容抓取和页面导航。
特性
- 浏览器模拟:支持 JavaScript 执行和页面导航。
- 简单 API:易于上手,适合中小型抓取任务。
- 表单处理:支持模拟表单提交和用户交互。
适用场景
- 需要轻量级动态内容抓取的场景。
- 模拟浏览器行为但不想依赖 Chrome 的项目。
- 快速原型开发。
优点与局限
- 优点:轻量级,易于使用;支持部分动态内容。
- 局限:功能不如 Chromedp 强大;对复杂 JavaScript 支持有限。
6. 正则表达式与 HTML 解析器
对于简单场景,可以结合 net/http 和 Go 的 regexp 包或 golang.org/x/net/html 进行手动解析。
特性
- 正则表达式:通过 regexp 匹配特定模式,适合提取简单数据。
- HTML 解析器:golang.org/x/net/html 提供低层次的 HTML 解析,适合自定义 DOM 处理。
- 无额外依赖:仅依赖标准库或轻量扩展包。
适用场景
- 提取结构化数据(如特定字段或链接)。
- 对性能要求极高且 HTML 结构简单的场景。
- 不需要复杂爬取逻辑的小型任务。
示例代码(使用 html 解析器):
package main import ( "fmt" "golang.org/x/net/html" "net/http" "strings" ) func main() { resp, err := http.Get("http://example.com")if err != nil { fmt.Println("错误:", err) return } defer resp.Body.Close() doc, err := html.Parse(resp.Body) if err != nil { fmt.Println("解析错误:", err) return } var f func(*html.Node) f = func(n *html.Node) { if n.Type == html.ElementNode && n.Data == "h1" { fmt.Println("标题:", strings.TrimSpace(n.FirstChild.Data)) } for c := n.FirstChild; c != nil; c = c.NextSibling { f(c) } } f(doc)
}
优点与局限
- 优点:高度灵活,适合特定需求;无外部依赖。
- 局限:正则表达式难以维护;手动解析复杂 HTML 费时。
对比与选择指南
工具 | 动态内容支持 | 并发性 | 易用性 | 适用场景 |
---|---|---|---|---|
net/http | 无 | 高 | 中等 | 简单请求,高度自定义 |
Chromedp | 是 | 高 | 较高 | 动态网页,浏览器自动化 |
Ferret | 是 | 中等 | 中等 | 声明式抓取,动态内容 |
GoCrawl | 有限 | 高 | 较高 | 大规模、分布式爬取 |
Surf | 有限 | 中等 | 高 | 轻量级动态内容,快速开发 |
正则/Html | 无 | 高 | 较低 | 简单数据提取,性能敏感场景 |
需要注意:
在你的开发中,可以通过以下方式融入这些技术方案:
- 分类介绍:将工具分为三类:基础请求(net/http)、动态内容(Chromedp、Ferret、Surf)、大规模爬取(GoCrawl、Colly)。
- 场景分析:针对不同场景(如静态网页、动态 SPA、大规模爬取)推荐合适的工具,并说明原因。
- 代码对比:展示同一抓取任务(如提取网页标题)在不同工具中的实现,突出代码复杂度与功能的差异。
- 反爬机制应对:讨论如何结合代理、User-Agent 伪装或 Chromedp 的 headless 模式绕过反爬限制。
- 性能与维护性:分析各工具的性能开销和代码维护成本,例如 net/http 的轻量 vs Chromedp 的资源占用。
- 道德与合规:强调抓取时的合法性问题,建议检查 Robots.txt 和网站条款。
扩展资源
-
net/http 文档:https://golang.org/pkg/net/http/
-
Chromedp GitHub:https://github.com/chromedp/chromedp
-
Ferret 官网:https://www.montferret.dev/
-
GoCrawl GitHub:https://github.com/amir/gocrawl
-
Surf GitHub:https://github.com/headzoo/surf
-
Colly 官网:http://go-colly.org/
-
Goquery GitHub:https://github.com/PuerkitoBio/goquery
-
Go 官方文档:https://golang.org/pkg/net/http/