Go语言爬虫系列教程(三)HTML解析技术

第3课:HTML解析技术

在上一章中,我们使用正则表达式提取网页内容,但这种方法有局限性。对于复杂的HTML结构,我们需要使用专门的HTML解析库。下面将介绍如何在Go中解析HTML。

1. HTML DOM树结构介绍

1.1 什么是DOM

DOM(Document Object Model)是HTML文档的树形结构表示。每个HTML元素都是一个节点,节点之间存在父子关系。

1.2 DOM树的基本组成

DOM树由多种类型的节点组成:

  • 元素节点:对应HTML标签,如<div><p>
  • 文本节点:包含文本内容
  • 属性节点:元素的属性,如class="container"
  • 注释节点:HTML注释<!-- 注释 -->

1.3 节点关系

DOM树中的节点具有以下关系:

  • 父子关系:包含其他元素的节点是父节点,被包含的是子节点
  • 兄弟关系:共享同一父节点的节点互为兄弟节点
  • 祖先和后代关系:间接的父子关系

1.4 DOM树示例

以下是一个简单HTML文档及其DOM树结构:

<!DOCTYPE html>
<html>
<head>
  <title>示例页面</title>
</head>
<body>
  <div class="container">
    <h1>标题</h1>
    <p>这是<a href="https://example.com">一个</a>段落。</p>
  </div>
</body>
</html>

其树状结构可以表示为:

html
├── head
│   └── title
│       └── "示例页面"
└── body
    └── div.container
        ├── h1
        │   └── "标题"
        └── p
            ├── "这是"
            ├── a[href="https://example.com"]
            │   └── "一个"
            └── "段落。"

1.5 HTML解析的重要性

理解DOM树结构对于HTML解析至关重要,因为它是我们进行网页数据提取的基础。

  • 从网页中提取结构化数据
  • 查找特定的元素和属性
  • 分析网页结构
  • 数据清洗和处理

2. Go中的HTML解析库:goquery

2.1 goquery简介

goquery是Go语言的一个强大HTML解析库,灵感来自jQuery。它基于Go标准库中的net/html包,并提供了类似jQuery的链式API,使HTML文档的遍历和操作变得简单。

2.2 安装goquery

使用以下命令安装goquery:

go get github.com/PuerkitoBio/goquery

2.3 创建文档

在使用goquery之前,首先需要创建一个文档对象。这相当于将HTML转换成可以用代码操作的结构。

从字符串创建文档
package main

import (
	"fmt"
	"github.com/PuerkitoBio/goquery"
	"strings"
)

func main() {
	html := `<html><body><div class="content"><h1>标题</h1><p>这是段落</p></div></body></html>`

	// 将HTML字符串转换为可读取的对象
	reader := strings.NewReader(html)
	doc, err := goquery.NewDocumentFromReader(reader)
	if err != nil {
		fmt.Println("加载HTML出错:", err)
		return
	}

	// 现在我们有了一个doc对象,可以用它来查找元素
	title := doc.Find("h1").Text()
	content := doc.Find("p").Text()
	fmt.Println("标题:", title)
	fmt.Println("内容:", content)
}


得到的结果:

标题: 标题
内容: 这是段落

NewDocumentFromReader()从字符串创建一个新的文档, 返回了一个*Document和error。Document代表一个将要被操作的HTML文档。

Find() 主要是用来查找元素, Find("h1") 即代表获取html中h1标签的元素,包括它的子元素。如果有多个h1标签,默认获取的是最后一个。

Text() 获取元素的纯文本内容

从网络加载文档

如果你还记得上一章的内容,我们使用 https://quotes.toscrape.com/ 作为演示,使用正则表达式获取了引言和作者,现在我们使用goquery代替

package main

import (
	"fmt"
	"github.com/PuerkitoBio/goquery"
	"net/http"
)

func main() {
	// 发送HTTP请求获取网页
	resp, err := http.Get("https://quotes.toscrape.com/")
	if err != nil {
		fmt.Println("请求网页失败:", err)
		return
	}
	defer resp.Body.Close() // 记得关闭连接

	// 检查HTTP状态码
	if resp.StatusCode != 200 {
		fmt.Printf("状态码不对: %d %s\n", resp.StatusCode, resp.Status)
		return
	}

	// 从响应创建文档
	doc, err := goquery.NewDocumentFromReader(resp.Body)
	if err != nil {
		fmt.Println("解析HTML失败:", err)
		return
	}

	// 使用文档
	quote := doc.Find(".text").Text()

	author := doc.Find(".author").Text()
	fmt.Println("引言:", quote)
	fmt.Println("作者:", author)
}

.text.author 都是类选择器,代表class="quote"class="author" 类名前加.表示类

2.4 CSS选择器

CSS选择器是一种用于选择HTML元素的语法。在goquery中,我们可以使用CSS选择器来定位需要的元素。上面的例子中,我们使用了元素选择器(h1和p)类选择器(.text和.author) ,goquery跟jquery一样都支持很多选择器,以下是常用的CSS选择器:

  1. 元素选择器

选择指定标签的所有元素,例如:p 选择所有段落元素

doc.Find("p")   //相当于查找 <p></p>的元素
  1. 类选择器
    选择有指定class的所有元素 ,例如:.quote 选择所有class为"quote"的元素,id选择器以.开头,紧跟着元素class的值。
doc.Find(".quote")    //相当于查找 class="quote"的元素
  1. ID选择器
    选择有指定id的元素, 例如:#author 选择id为"author"的元素 ,id选择器以#开头,紧跟着元素id的值。
doc.Find("#author")    //相当于查找 id="author"的元素
  1. 后代选择器
    选择某元素后代中的元素 ,例如:.quote .text 选择class为"quote"的元素中的class为"text"的元素
doc.Find(".quote .text")   //相当于查找 class="quote text" 元素
  1. 属性选择器

选择具有特定属性的元素,一个HTML元素都有自己的属性以及属性值,所以我们也可以通过属性和值筛选元素。

例如:a[href="https://example.com"] 选择href属性值为"https://example.com" 的链接,这个使用的是完全相等的匹配方式,属性选择器还要很多匹配方式。

选择器说明
Find(“div[my]“)筛选含有my属性的div元素
Find(“div[my=zh]“)筛选my属性为zh的div元素
Find(“div[my!=zh]“)筛选my属性不等于zh的div元素
Find(“div[my¦=zh]“)筛选my属性为zh或者zh-开头的div元素
Find(“div[my*=zh]“)筛选my属性包含zh这个字符串的div元素
Find(“div[my~=zh]“)筛选my属性包含zh这个单词的div元素,单词以空格分开的
Find(“div[my$=zh]“)筛选my属性以zh结尾的div元素,区分大小写
Find(“div[my^=zh]“)筛选my属性以zh开头的div元素,区分大小写
  1. 子元素选择器
    选择某元素的直接子元素, 例如:.quote > .text 选择class为"quote"的元素的直接子元素中class为"text"的元素 , 子元素选择是使用父>子代表父子关系
doc.Find(".quote >.text")   //相当于查找 class="quote"元素下的class="text" 元素
  1. 相邻选择器
    如果要筛选的元素没有规律,但是该元素的上一个元素有规律,我们就可以使用这种下一个相邻选择器来进行选择,例如:p[my=a]+p筛选出p标签属性my的值为a的相邻p标签。使用+号表示相邻。
doc.Find("p[my=a]+p")
  1. 兄弟选择器
    有时候我们需要筛选同一父元素下,所有同级元素,可以使用兄弟选择器,例如:p[my=a]~p,筛选出p标签属性my的值为a的兄弟p标签。使用~号表示兄弟。
doc.Find("p[my=a]~p")

2.5 过滤器(伪类选择器)

有时候我们选择出来的结果,并不是我们心目中的最优结果,我们希望对其进行过滤。

  1. :contains 过滤器 - 按文本内容筛选

筛选出包含指定文本的元素,就像在页面上按Ctrl+F搜索特定文字,:contains可以找出所有包含这个文字的元素。

// 筛选出包含"文章:"文本的段落
doc.Find("p:contains(文章:)")

  1. :has 过滤器
    选择至少有一个符合 selector 的后代元素的 element

// :has查找包含<a>标签的段落(不关心文本内容)
 doc.Find("p:has(a)")   
  1. :empty 过滤器

用于选择没有子元素且没有文本内容的元素

// 查找没有任何内容的元素(既没有文本也没有子元素)
 doc.Find(":empty")

// 通常用于查找空div或空容器
doc.Find("div:empty")

  1. :first-child 与 :first-of-type 过滤器 - 获取首个元素
  • :first-child:选择作为父元素第一个子元素的元素,就像"班里第一排第一位学生"
  • :first-of-type:选择作为父元素中同类型的第一个元素,就像"班里所有姓张的学生,找到其中座位最前面的一个"
// 查找所有作为第一个子元素的p标签
doc.Find("p:first-child")

// 查找所有在父元素中作为第一个p类型的标签
doc.Find("p:first-of-type")
  1. :last-child 与 :last-of-type 过滤器 - 获取最后元素
  • :last-child:选择作为父元素最后一个子元素的元素,就像"班里最后一排最后一位学生"
  • :last-of-type:选择作为父元素中同类型的最后一个元素,就像"班里所有姓张的学生,找到其中座位最后面的一个"
// 查找所有作为最后一个子元素的li标签
doc.Find("li:last-child")

// 查找所有在父元素中作为最后一个p类型的标签
doc.Find("p:last-of-type")
  1. :nth-child(n) 过滤器和:nth-of-type(n) - 按位置选择元素
    选择作为父元素的第n个子元素的元素(n从1开始),就像"请第3排的学生起立",无论他们是什么性别或姓名。
// 查找所有父元素下的第2个子元素
doc.Find(":nth-child(2)")

// 查找所有父元素下的第2个li元素
doc.Find("li:nth-child(2)")

  1. :nth-last-child(n) 与 :nth-last-of-type(n) 过滤器 - 从末尾开始计数
  • :nth-last-child(n):从最后一个开始倒数,选择第n个子元素
  • :nth-last-of-type(n):从同类型的最后一个开始倒数,选择第n个元素
// 查找倒数第2个子元素
doc.Find(":nth-last-child(2)")

// 查找倒数第2个li元素
 doc.Find("li:nth-last-child(2)")

// 查找每个父元素下倒数第1个p标签
doc.Find("p:nth-last-of-type(1)") // 等同于p:last-of-type
  1. :only-child 与 :only-of-type 过滤器 - 唯一子元素
  • :only-child:选择作为父元素唯一子元素的元素
  • :only-of-type:选择在父元素中是唯一类型的元素
// 查找作为唯一子元素的段落
doc.Find("p:only-child")

// 查找在其父元素中是唯一p类型的段落
doc.Find("p:only-of-type")

2.5 goquery常用方法介绍

Find方法 - 查找所有匹配元素

Find方法是最基础也是最常用的方法,它可以查找符合CSS选择器的所有元素。

// 查找所有段落
paragraphs := doc.Find("p")

// 获取找到的元素数量
count := paragraphs.Length()
fmt.Printf("找到了%d个段落\n", count)

// 获取第一个段落的文本
firstParagraph := paragraphs.First().Text()
fmt.Printf("第一个段落内容: %s\n", firstParagraph)
  • Length()告诉我们找到了多少个元素
  • First()取出第一个找到的元素

Text方法 - 获取文本内容

// 获取h1标签的文本
title := doc.Find("h1").Text()
fmt.Println("标题文本:", title)
// 输出: 标题文本: 欢迎来到我的网站

  • Text()方法提取元素内的所有文本,包括子元素的文本
  • 它会自动去除HTML标签,只保留纯文本
  • 它会合并所有文本节点,中间可能有空格

Html方法 - 获取HTML内容

// 获取元素的HTML
contentHtml, err := doc.Find(".content").Html()
if err == nil {
    fmt.Println("内容区HTML:")
    fmt.Println(contentHtml)
    // 输出会包含所有HTML标签和内容
}

  • Html()方法获取元素的完整HTML代码,包括所有标签
  • 如果你需要保留原始格式,比如需要分析HTML结构,这很有用

Attr方法 - 获取属性

// 获取链接的href属性
doc.Find("a").Each(func(i int, s *goquery.Selection) {
    // Attr返回两个值:属性值和是否存在该属性
    href, exists := s.Attr("href")
    if exists {
        fmt.Printf("链接 #%d 指向: %s\n", i+1, href)
        
        // 获取链接文本
        text := s.Text()
        fmt.Printf("链接文本: %s\n", text)
    }
})
  • s.Attr("href")尝试获取元素的href属性
  • 它返回两个值:属性的值和一个布尔值表示属性是否存在
  • exists告诉我们属性是否存在,防止我们使用不存在的属性

Each方法 - 遍历所有元素
刚刚我们获取quotes的例子中,我们想要获取到页面所有的.text.author 元素,Each是一个不二的选择,简单的修改下代码:

	// 从响应创建文档
	doc, err := goquery.NewDocumentFromReader(resp.Body)
	if err != nil {
		fmt.Println("解析HTML失败:", err)
		return
	}

	// 使用文档
	doc.Find(`.quote`).Each(func(i int, s *goquery.Selection) {
		quote := s.Find(".text").Text()
		author := s.Find(".author").Text()
		fmt.Printf("第%d个: \n", i)
		fmt.Println("引言:", quote)
		fmt.Println("作者:", author)
		fmt.Println()
	})


  • Each方法就像是一个循环,会依次处理每个找到的元素,
  • 函数func(i int, s *goquery.Selection)中:
    • i是当前处理的是第几个元素(从0开始计数)
    • s就是当前正在处理的元素, 示例代码中s代表.quote及下面的子元素

筛选元素方法
筛选元素的方法有很多个,我一起介绍

items := doc.Find("li")

// 获取第一个元素
first := items.First()
fmt.Println("第一个菜单项:", first.Text())

// 获取最后一个元素
last := items.Last()
fmt.Println("最后一个菜单项:", last.Text())

// 获取特定索引的元素(从0开始)
second := items.Eq(1) // 第二个元素
fmt.Println("第二个菜单项:", second.Text())

// 过滤有特定类的元素
selected := items.Filter(".selected")
fmt.Println("选中的菜单项:", selected.Text())

// 排除特定元素
notSelected := items.Not(".selected")
fmt.Printf("未选中的菜单项有%d个\n", notSelected.Length())

  • First():拿出第一个元素
  • Last():拿出最后一个元素
  • Eq(1):拿出索引为1的元素(实际上是第2个,因为索引从0开始)
  • Filter(".selected"):只保留有class="selected"的元素
  • Not(".selected"):排除有class="selected"的元素,只留下其他的

2.6 完整实例

package main

import (
	"fmt"
	"github.com/PuerkitoBio/goquery"
	"strings"
)

func main() {
	// 一个包含各种元素的HTML示例
	html := `
    <!DOCTYPE html>
    <html>
    <head>
        <title>我的图书列表</title>
    </head>
    <body>
        <div id="header">
            <h1>我收藏的图书</h1>
            <p>这是我最喜欢的一些书籍</p>
        </div>
        
        <div class="book-list">
            <div class="book">
                <h2 class="title">Go语言编程</h2>
                <p class="author">作者: 张三</p>
                <p class="year">出版年份: 2022</p>
                <p class="description">这是一本关于<b>Go语言</b>的入门书籍</p>
                <span class="rating">评分: 4.5/5</span>
                <a href="https://example.com/go-book" class="link">查看详情</a>
            </div>
            
            <div class="book">
                <h2 class="title">Python数据分析</h2>
                <p class="author">作者: 李四</p>
                <p class="year">出版年份: 2021</p>
                <p class="description">这本书讲解了Python在<b>数据分析</b>中的应用</p>
                <span class="rating">评分: 4.8/5</span>
                <a href="https://example.com/python-book" class="link">查看详情</a>
            </div>
            
            <div class="book">
                <h2 class="title">JavaScript高级编程</h2>
                <p class="author">作者: 王五</p>
                <p class="year">出版年份: 2023</p>
                <p class="description">深入讲解<b>JavaScript</b>的高级特性</p>
                <span class="rating">评分: 4.2/5</span>
                <a href="https://example.com/js-book" class="link">查看详情</a>
            </div>


            <div class="book">
                <h2 class="title">Python入门</h2>
                <p class="author"></p>
                <p class="year">出版年份: 2023</p>
                <p class="description">深入讲解<b>Python</b>的高级特性</p>
                <span class="rating">评分: 4.3/5</span>
                <a href="https://example.com/js-book" class="link">查看详情</a>
            </div>
        </div>
        
        <div id="footer">
            <p>更新时间: 2025年3月15日</p>
        </div>
    </body>
    </html>
    `

	// 创建goquery文档
	reader := strings.NewReader(html)
	doc, err := goquery.NewDocumentFromReader(reader)
	if err != nil {
		fmt.Println("解析HTML失败:", err)
		return
	}

	// 1. 提取页面标题
	fmt.Println("=== 页面信息 ===")
	pageTitle := doc.Find("title").Text()
	headerTitle := doc.Find("#header h1").Text()
	fmt.Printf("页面标题: %s\n", pageTitle)
	fmt.Printf("主标题: %s\n", headerTitle)

	// 2. 提取所有图书信息
	fmt.Println("\n=== 图书列表 ===")
	doc.Find(".book").Each(func(i int, book *goquery.Selection) {
		// 提取图书标题
		title := book.Find(".title").Text()

		// 提取作者(使用替代方法)
		authorElem := book.Find(".author")
		author := authorElem.Text()
		// 清理"作者: "前缀
		author = strings.TrimPrefix(author, "作者: ")

		// 提取评分(使用属性选择器)
		ratingText := book.Find(".rating").Text()
		// 使用strings包处理字符串
		rating := strings.TrimPrefix(ratingText, "评分: ")

		// 提取链接URL和文本
		linkElem := book.Find(".link")
		linkText := linkElem.Text()
		linkHref, _ := linkElem.Attr("href")

		// 输出图书信息
		fmt.Printf("图书 #%d:\n", i+1)
		fmt.Printf("  标题: %s\n", title)
		fmt.Printf("  作者: %s\n", author)
		fmt.Printf("  评分: %s\n", rating)
		fmt.Printf("  链接: %s (%s)\n", linkText, linkHref)

		// 检查描述中是否有强调内容
		desc := book.Find(".description")
		boldText := desc.Find("b").Text()
		if boldText != "" {
			fmt.Printf("  重点内容: %s\n", boldText)
		}

		fmt.Println() // 添加空行分隔不同图书
	})

	// 3. 统计信息
	fmt.Println("=== 统计信息 ===")
	bookCount := doc.Find(".book").Length()
	fmt.Printf("图书总数: %d本\n", bookCount)

	// 统计高评分(>4.5)的书籍
	highRatedBooks := 0
	doc.Find(".book").Each(func(i int, s *goquery.Selection) {
		ratingText := s.Find(".rating").Text()
		// 提取评分数字
		ratingStr := strings.TrimPrefix(ratingText, "评分: ")
		ratingStr = strings.TrimSuffix(ratingStr, "/5")

		// 简单转换为浮点数进行比较
		var rating float64
		fmt.Sscanf(ratingStr, "%f", &rating)

		if rating > 4.5 {
			highRatedBooks++
		}
	})
	fmt.Printf("高评分图书(>4.5): %d本\n", highRatedBooks)

	// 获取页脚信息
	footerText := doc.Find("#footer").Text()
	fmt.Printf("页脚信息: %s\n", strings.TrimSpace(footerText))

	fmt.Println("=== 分隔符 ===")

	// 4. 查找并修改元素 ,查找作者为空的元素,填充作者
	doc.Find(".author:empty").SetHtml(`老六`)
	
	//查找.book ,第四个元素
	newSelection := doc.Find(".book").Eq(3)

	title := newSelection.Find(".title").Text()

	// 提取作者(使用替代方法)
	authorElem := newSelection.Find(".author")
	author := authorElem.Text()
	// 清理"作者: "前缀
	author = strings.TrimPrefix(author, "作者: ")

	// 提取评分(使用属性选择器)
	ratingText := newSelection.Find(".rating").Text()
	// 使用strings包处理字符串
	rating := strings.TrimPrefix(ratingText, "评分: ")

	// 提取链接URL和文本
	linkElem := newSelection.Find(".link")
	linkText := linkElem.Text()
	linkHref, _ := linkElem.Attr("href")

	// 输出图书信息
	fmt.Printf("  标题: %s\n", title)
	fmt.Printf("  作者: %s\n", author)
	fmt.Printf("  评分: %s\n", rating)
	fmt.Printf("  链接: %s (%s)\n", linkText, linkHref)
}

3. XPath查询

XPath(XML Path Language)是一种用于在XML/HTML文档中定位节点的查询语言。尽管goquery主要使用CSS选择器,但在某些场景下,XPath可能更为强大或直观。

3.1XPath语法基础

基本路径表达式

  • / - 从根节点选择
  • // - 从当前节点选择文档中符合条件的所有节点
  • . - 选择当前节点
  • .. - 选择当前节点的父节点
  • @ - 选择属性

XPath高级功能

  1. 谓语(筛选条件)
  • //li[1] - 选择第一个li元素
  • //li[last()] - 选择最后一个li元素
  • //div[count(p) > 2] - 选择包含超过2个段落的div元素
  1. 轴(指定节点关系方向)
  • //h1/following-sibling::p - 选择h1后的所有兄弟段落
  • //li/ancestor::div - 选择li的所有div祖先元素
  • //a/parent::div - 选择a的父元素中的div
  1. 函数
  • string(//h1) - 获取第一个h1元素的文本
  • contains(//p, '文本') - 检查段落是否包含"文本"
  • count(//li) - 计算li元素的数量

实例演示

XPath表达式描述
/html/body/div选择html下的body元素下的所有div元素
//div选择文档中的所有div元素
//div[@class="header"]选择所有class为header的div元素
//a/@href选择所有a元素的href属性值
//div[contains(@class, "item")]选择class属性包含"item"的所有div元素

3.2 在Go中使用XPath

要在Go中使用XPath,可以使用github.com/antchfx/htmlquery库:

go get github.com/antchfx/htmlquery

3.3 简单示例

package main

import (
    "fmt"
    "log"
    "strings"
    
    "github.com/antchfx/htmlquery"
    "golang.org/x/net/html"
)

func xpathExamples() {
    htmlStr := `
    <books>
        <book id="1" category="fiction">
            <title>Go编程</title>
            <author>作者1</author>
            <price>59.90</price>
        </book>
        <book id="2" category="technical">
            <title>数据结构</title>
            <author>作者2</author>
            <price>79.90</price>
        </book>
        <book id="3" category="fiction">
            <title>算法导论</title>
            <author>作者3</author>
            <price>99.90</price>
        </book>
    </books>
    `
    
    doc, err := html.Parse(strings.NewReader(htmlStr))
    if err != nil {
        log.Fatal(err)
    }
    
    // 1. 基本路径表达式
    fmt.Println("=== 基本XPath ===")
    // 选择所有书的标题
    titles := htmlquery.Find(doc, "//title")
    for _, title := range titles {
        fmt.Printf("Title: %s\n", htmlquery.InnerText(title))
    }
    
    // 2. 属性选择
    fmt.Println("\n=== 属性选择 ===")
    // 选择category为fiction的书
    fictionBooks := htmlquery.Find(doc, "//book[@category='fiction']/title")
    for _, book := range fictionBooks {
        fmt.Printf("Fiction book: %s\n", htmlquery.InnerText(book))
    }
    
    // 3. 位置选择
    fmt.Println("\n=== 位置选择 ===")
    // 选择第一本书
    firstBook := htmlquery.FindOne(doc, "//book[1]/title")
    if firstBook != nil {
        fmt.Printf("First book: %s\n", htmlquery.InnerText(firstBook))
    }
    
    // 选择最后一本书
    lastBook := htmlquery.FindOne(doc, "//book[last()]/title")
    if lastBook != nil {
        fmt.Printf("Last book: %s\n", htmlquery.InnerText(lastBook))
    }
    
    // 4. 条件表达式
    fmt.Println("\n=== 条件表达式 ===")
    // 价格大于60的书
    expensiveBooks := htmlquery.Find(doc, "//book[price>60]/title")
    for _, book := range expensiveBooks {
        fmt.Printf("Expensive book: %s\n", htmlquery.InnerText(book))
    }
    
    // 5. 轴运算
    fmt.Println("\n=== 轴运算 ===")
    // 选择作者为"作者2"的书的下一个兄弟节点
    nextBook := htmlquery.FindOne(doc, "//author[text()='作者2']/parent::book/following-sibling::book[1]/title")
    if nextBook != nil {
        fmt.Printf("Next book after 作者2's book: %s\n", htmlquery.InnerText(nextBook))
    }
}

4.CSS选择器与XPath比较

CSS选择器等效XPath描述
div//div选择所有div元素
div.content//div[@class='content']选择class为content的div元素
#header//*[@id='header']选择id为header的元素
div > p//div/p选择div的直接子元素p
div p//div//p选择div的所有后代p元素
a[href]//a[@href]选择有href属性的所有a元素
使用CSS选择器的场景
  • 熟悉前端开发,更习惯CSS语法
  • 需要简洁的选择表达式
  • 使用goquery库

使用XPath的场景

  • 需要更复杂的查询能力(如祖先/后代选择)
  • 需要访问元素的函数(如count()、position())
  • 需要基于文本内容筛选元素
  • 使用htmlquery库
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值