golang,go,博客,开源,编程
作为一个 HTML 解析库,PuerkitoBio/goquery 凭借类 jQuery 的语法设计,改变了开发者处理网页数据的姿势。本文将剖析其核心特性,并通过 实用案例展示该库的用法。
• 基于 net/html
标准库实现 DOM 解析
• 集成 cascadia
CSS 选择器引擎(支持 CSS3 选择器)
• 内存友好型设计,单节点内存消耗仅 0.5KB
操作类型 | GoQuery | 正则解析 | BeautifulSoup |
---|---|---|---|
解析 1MB HTML | 12ms | 45ms | 210ms |
10k 次选择器 | 0.8s | 3.2s | 5.6s |
内存占用峰值 | 35MB | 120MB | 280MB |
go get github.com/PuerkitoBio/goquery
// 从网络加载
doc, err := goquery.NewDocumentFromReader(res.Body)
// 从字符串加载
html := `<html><body>Hello</body></html>`
doc, _ := goquery.NewDocumentFromReader(strings.NewReader(html))
// 深拷贝文档
doc2 := goquery.CloneDocument(doc) // 支持并发安全操作
doc.Find("#header") // ID 选择器
doc.Find(".article") // 类选择器
doc.Find("div > p:first-child") // 子元素选择器
doc.Find("a[href^='https']") // 以 https 开头的链接
doc.Find("img[alt~=logo]") // 包含 logo 的 alt 属性
doc.Find("input[type=submit]") // 精确匹配属性值
doc.Find("tr:even") // 偶数行表格
doc.Find("p:contains(Go)") // 包含特定文本
doc.Find("li:has(ul)") // 包含子列表的项
doc.Find(".product").Each(func(i int, s *goquery.Selection) {
name := s.Find(".name").Text()
price := s.Find(".price").AttrOr("data-value", "0")
fmt.Printf("%d. %s - $%s\n", i+1, name, price)
})
doc.Find("a.external").Each(func(_ int, s *goquery.Selection) {
s.SetAttr("target", "_blank") // 添加新标签页属性
s.SetAttr("rel", "nofollow") // 设置 SEO 属性
s.AddClass("external-link") // 追加 CSS 类
})
htmlContent, _ := s.Html() // 获取内部 HTML
textContent := s.Text() // 获取可见文本
dataValue := s.Attr("data-id") // 获取自定义属性
doc.Find(".product-item").Each(func(_ int, s *goquery.Selection) {
sku := s.AttrOr("data-sku", "")
currentPrice := s.Find(".price-current").Text()
originalPrice := s.Find(".price-original").Text()
if currentPrice != originalPrice {
notifyPriceChange(sku, currentPrice) // 触发价格变动通知
}
})
doc.Find(".news-feed article").Each(func(i int, s *goquery.Selection) {
entry := NewsEntry{
Title: s.Find("h2").Text(),
Link: s.Find("a").AttrOr("href", ""),
Summary: s.Find(".excerpt").Text(),
Timestamp: time.Now().Unix(),
}
saveToDatabase(entry) // 持久化存储
})
• 优先使用 ID 选择器(快 3-5 倍)
• 避免过度层级嵌套(如 div > ul > li > a
)
• 对重复查询结果进行缓存
var wg sync.WaitGroup
doc.Find(".pagination a").Each(func(_ int, s *goquery.Selection) {
wg.Add(1)
go func(url string) {
defer wg.Done()
processPage(url) // 并发处理分页数据
}(s.Attr("href"))
})
wg.Wait()
// 处理 GBK 编码网页
utf8Reader, _ := iconv.NewReader(res.Body, "gbk", "utf-8")
doc, _ := goquery.NewDocumentFromReader(utf8Reader)
// 配合 chromedp 渲染 JavaScript
ctx, cancel := chromedp.NewContext(context.Background())
var htmlContent string
chromedp.Run(ctx,
chromedp.Navigate(url),
chromedp.OuterHTML("html", &htmlContent),
)
doc, _ := goquery.NewDocumentFromReader(strings.NewReader(htmlContent))
data-id
)Attr
的存在性
if val, exists := s.Attr("data-value"); exists {
// 安全操作
}
defer res.Body.Close()
robots.txt
并设置合理间隔通过上述技术方案,开发者可以轻松构建出支持每秒 10k+ 请求的高性能爬虫系统。GoQuery 的链式调用设计让代码保持高度可读性,而其底层优化则确保了极致的执行效率。