
Golang 中解析 HTML 的指南
在本文中,我将向你展示如何使用内置的 net/html 包在 Go 中解析 HTML。我们会讲解基础知识,我也会介绍一些可能更适合特定任务的其他库。到最后,你将掌握解析 HTML、提取结构化数据,并使用 Go 像专业人士一样处理网页抓取的技能。让我们开始吧!
先决条件
在深入了解如何在 Go 中解析 HTML 之前,请确保你具备以下条件:
-
已安装 Go:如果你还没有安装 Go,请访问 Go 官方网站并按照安装说明操作。
-
对 Go 语法有基本了解:如果你刚接触 Go,建议先复习一下它的语法,包括如何使用包、函数和错误处理。
-
HTML 知识:理解 HTML 结构至关重要,因为你需要识别想要提取的标签和属性。
-
Go modules:如果你正在启动一个新项目,请通过运行 go mod init 确保项目中已初始化 Go modules。
使用 net/html 解析 HTML
Go 标准库提供了用于解析 HTML 的 net/html 包。它提供了两种主要的解析方式:tokenizer API 和 node parsing API,使其成为最佳 HTML 解析器之一。在本教程中,我们将主要关注 node parsing API,因为它提供了更高层次的抽象,也更容易使用。不过,为了完整性,我们也会简要介绍 tokenizer API。
使用 Go 解析的替代方案
目前最好的替代方案是数据集。可以阅读我关于最佳数据集网站的文章。如果你时间紧,这里是前 5 大数据集提供商的 TL;DR:
-
Bright Data --- 跨行业的可定制和预构建数据集。
-
Statista --- 面向商业和研究的广泛统计数据与报告。
-
Datarade --- 来自各类提供商的优质数据产品市场。
-
AWS Data Exchange --- 与 AWS 服务集成的第三方数据集。
-
Zyte --- 根据业务需求定制的网页抓取和自定义数据集。
我与上述任何提供商都没有关联。
步骤 1:从网页获取 HTML
要解析 HTML,你必须先从网页获取原始 HTML 内容。这是通过向你想要抓取的页面 URL 发起 HTTP 请求完成的。在 Go 中,你可以使用 net/http 包来发起 HTTP 请求。
下面是一个如何发起简单 HTTP GET 请求的示例:
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
url := "https://example.com" // Replace with your target URL
resp, err := http.Get(url)
if err != nil {
fmt.Println("Error fetching URL:", err)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error reading response body:", err)
return
}
fmt.Println("Fetched HTML:")
fmt.Println(string(body))
}
在这段代码中,我们使用 HTTP.Get() 发送 GET 请求并检索响应正文。页面的 HTML 内容会以字符串形式打印出来。
步骤 2:使用 Node Parsing API 解析 HTML
现在我们已经有了原始 HTML,接下来解析它。net/html 包提供了一个名为 html.Parse() 的函数,它可以解析 HTML 文档并返回一个节点树。树中的每个节点都表示一个 HTML 元素、属性或一段文本。
要解析 HTML,我们将响应正文(它是一个 nio.Reader)传递给 html.Parse()。这将返回一个根节点,你可以递归遍历它来提取所需信息。
下面是一个解析 HTML 文档并打印标签名称的示例:
package main
import (
"fmt"
"net/http"
"golang.org/x/net/html"
"io"
)
func main() {
url := "https://example.com"
resp, err := http.Get(url)
if err != nil {
fmt.Println("Error fetching URL:", err)
return
}
defer resp.Body.Close()
doc, err := html.Parse(resp.Body)
if err != nil {
fmt.Println("Error parsing HTML:", err)
return
}
traverse(doc)
}
// Recursively traverse the HTML nodes and print their tag names
func traverse(n *html.Node) {
if n.Type == html.ElementNode {
fmt.Println("Tag:", n.Data)
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
traverse(c)
}
}
这段代码会输出在 HTML 文档中找到的标签名称。
步骤 3:从 HTML 中提取特定数据
要提取特定数据,你需要检查 HTML 结构,并识别包含所需信息的元素。例如,你想从电商页面中提取所有产品名称、价格和图片 URL。你需要查找特定标签(例如用于产品名称的 、用于价格的 ,以及用于图片的 ),并提取其内容。
下面是一个提取产品名称、价格和图片 URL 的示例函数:
package main
import (
"fmt"
"net/http"
"golang.org/x/net/html"
"strings"
"io"
)
func main() {
url := "https://example.com/ecommerce"
resp, err := http.Get(url)
if err != nil {
fmt.Println("Error fetching URL:", err)
return
}
defer resp.Body.Close()
doc, err := html.Parse(resp.Body)
if err != nil {
fmt.Println("Error parsing HTML:", err)
return
}
processProducts(doc)
}
// Recursively process the HTML nodes to extract product details
func processProducts(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "li" {
// Look for product name
var name string
var price string
var imageURL string
for c := n.FirstChild; c != nil; c = c.NextSibling {
switch c.Data {
case "h2":
if c.FirstChild != nil && c.FirstChild.Type == html.TextNode {
name = c.FirstChild.Data
}
case "span":
for _, a := range c.Attr {
if a.Key == "class" && strings.Contains(a.Val, "price") {
if c.FirstChild != nil && c.FirstChild.Type == html.TextNode {
price = c.FirstChild.Data
}
}
}
case "img":
for _, a := range c.Attr {
if a.Key == "src" {
imageURL = a.Val
}
}
}
}
fmt.Println("Product Name:", name)
fmt.Println("Price:", price)
fmt.Println("Image URL:", imageURL)
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
processProducts(c)
}
}
在这个示例中,processProducts 函数会递归搜索 元素。在每个内,它会查找 (产品名称)、(价格)和 (图片 URL)。然后提取相关文本或属性,并打印这些信息。
步骤 4:处理错误和边缘情况
处理 HTML 时,你会遇到一些边缘情况,例如缺少标签、空属性或格式不正确的 HTML。优雅地处理错误并确保你的爬虫工具能够应对这些场景非常重要。
例如,从节点提取文本时,始终检查该节点是否有子节点,以及子节点是否为文本节点。同样,在提取属性(例如用于图片的 src)时,请确保该属性存在后再使用。
if n.FirstChild != nil && n.FirstChild.Type == html.TextNode {
fmt.Println("Text:", n.FirstChild.Data)
}
在 Go 中解析 HTML 的替代库
虽然 net/html 是 Go 中最常用的 HTML 解析包,但其他库也提供了额外功能或替代 API,具体是否有用取决于你的使用场景。
Goquery
Goquery 是一个流行的 Go 库,提供类似 jQuery 的语法,用于遍历和操作 HTML 文档。Goquery 基于 net/html 包构建,但提供了更便捷的 API,让你可以使用 CSS 选择器查询元素。这使得无需手动遍历 HTML 树也能更轻松地提取特定数据。
下面是一个使用 Goquery 从页面中提取产品名称的示例:
package main
import (
"fmt"
"github.com/PuerkitoBio/goquery"
"net/http"
)
func main() {
url := "https://example.com/ecommerce"
res, err := http.Get(url)
if err != nil {
fmt.Println("Error:", err)
return
}
defer res.Body.Close()
doc, err := goquery.NewDocumentFromReader(res.Body)
if err != nil {
fmt.Println("Error parsing HTML:", err)
return
}
doc.Find("li").Each(func(i int, s *goquery.Selection) {
name := s.Find("h2").Text()
fmt.Println("Product Name:", name)
})
}
Goquery 允许你使用 CSS 选择器轻松选择元素,例如 doc.Find("li h2").Text(),让代码更简洁,也更易读。
其他库
-
Go-html-transform:这个库提供了一个 API,可使用 CSS 选择器转换 HTML 文档,类似于 Goquery,但采用不同的方法。不过,它已不再积极维护。
-
Html2go:这个工具主要将 HTML 文件转换为 Go 代码(structs)。如果你需要静态定义 HTML 文档结构,它会很有用。
结论
使用 net/html 包在 Go 中解析 HTML 非常简单。你可以获取原始 HTML,并使用 html.Parse() 遍历 HTML 树,获取所需数据。如果你想要更方便的方式,Goquery 这类库提供了类似 jQuery 的功能,让处理 HTML 变得更轻松。无论你是在构建一个快速的网页爬虫工具,还是投入一个更大型的网页爬取项目,Go 的库和并发特性都使其成为高效处理 HTML 解析的可靠选择。
有问题吗?请在评论中告诉我!
对其他网页抓取指南感兴趣?
更多精彩文章,请访问我的个人资料 --- Data Journal ❤️