当前位置:首页 > 文章列表 > Golang > Go问答 > Go goquery Text 为什么包含子节点中的全部文字

Go goquery Text 为什么包含子节点中的全部文字

来源:17golang原创 2026-10-13 15:40:16 0浏览 收藏

我第一次用 goquery 读取文章卡片标题时,选中的是外层容器,结果拿到的却不只是卡片自己的那一行,而是标题、摘要和嵌套标签里的文字拼在一起。这个结果并不是选择器失效,而是 Selection.Text() 的定义本来就包含后代节点:它会从选中的节点开始递归访问子树,只把遇到的文本节点追加到字符串中。

官方地址:https://github.com/PuerkitoBio/goquery

所以,标题《Go goquery Text 为什么包含子节点中的全部文字》的直接答案是:你选中的元素是一个父节点,Text() 取的是这棵子树的文本内容,不是元素标签直属的那一层文字。要改变结果,应该缩小选择器,或者改用带文本策略的 API。

把 Text() 理解成“读取选中节点及其后代的纯文本”就不会意外。它不会返回 HTML 标签,也不会只保留父元素的直接文本;多个选中节点还会继续按选择顺序合并。

Text 的选择边界其实是整棵子树

goquery 的选择结果是一个 *Selection,其中可以包含一个或多个 HTML 节点。调用 Selection.Text() 时,库会遍历选择中的每个节点;遇到文本节点就写入结果,遇到元素节点则继续访问它的子节点。因此,父元素、子元素和文字之间是“选择边界—递归读取—文本汇总”的关系。

goquery Selection.Text 递归读取父元素、子元素和 TextNode 的横向静态结构图
图1:操作示意图,展示 Selection.Text 为什么会把选中节点后代中的文本节点汇总到同一个字符串;这是静态结构图,不是截图或运行证据。

例如,下面的 HTML 中,article 是父节点,h2、p 和 strong 都在它的后代树里:

Go 入门

掌握 goquery 的文本提取

此时选中 .card,目标是整个卡片的可见文本;选中 .card h2,目标才收窄为标题。真正决定范围的是选择器,Text() 只忠实处理这个范围内的文本节点。

为什么它不会只返回父元素直属文字

HTML 元素通常把文字放在文本节点中,而不是放在元素对象的某个“单行字段”里。父元素的直接文字、子元素中的文字,最终都属于这棵 DOM 子树。goquery 的方法名虽然简短,但语义更接近“把选中节点及后代的文字内容合并起来”。

这个行为也解释了两个常见误会:

  • 它不会把

    这样的标签文本返回出来,标签只是遍历边界;
  • 它不会替你理解“标题”和“摘要”的业务层级,父元素里有什么文本节点,就会按文档顺序汇总什么。

如果目标是标题,不要先选卡片再期待 Text() 自动猜出标题字段。把选择器直接写到标题节点,通常比拿到整块文本后再切字符串更稳:

package main

import (
  "fmt"
  "strings"

  "github.com/PuerkitoBio/goquery"
)

func readHeading(htmlText string) (string, error) {
  // 从字符串创建文档;真实抓取时应先自行处理 HTTP 响应和关闭 Body。
  doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlText))
  if err != nil {
    return "", err
  }

  // 选择器直接落到 h2,避免把卡片中的摘要和强调文字一并取出。
  return doc.Find("article.card h2").First().Text(), nil
}

func main() {
  // 示例只展示调用边界,不把本地运行输出当作文章证据。
  title, err := readHeading(`

Go 入门

摘要

`) if err != nil { panic(err) } fmt.Println(title) }

多个选中节点为什么会继续拼接

还有一种更容易被忽略的情况:选择器本身匹配了多个节点。doc.Find("li").Text() 并不是“每个 li 返回一个字符串”,而是把选择中的节点依次交给同一个文本汇总过程。默认的 Selection.Text() 不会自动插入换行或分隔符。

因此,列表提取时要先明确输出协议。保留每项边界,就逐项遍历;确实要合成一段话,再接受合并语义:

// 逐项读取,保留每个 li 的业务边界。
doc.Find("ul.tags li").Each(func(index int, item *goquery.Selection) {
  // 单个 li 仍然会包含其后代文字,但不会吞掉其他 li。
  text := strings.TrimSpace(item.Text())
  if text != "" {
    fmt.Printf("%d: %s\n", index+1, text)
  }
})

如果结果像“第一项第二项第三项”,优先检查是否把多个节点直接调用了一次 Text()。这不是 goquery 丢了内容,而是 API 没有替你决定项目之间应该用空格、换行还是 JSON 数组表达。

需要分隔符时使用可控的 Text API

当前 goquery 还提供包级函数 goquery.Text,它的默认行为与选择的 Selection.Text() 相近,但可以通过 TextOptions 调整不同文本节点的连接方式。比如把卡片标题和段落合成更适合阅读的一行,可以设置空格分隔并修剪每个文本节点两侧的空白。

goquery Selection.Text 和 TextOptions 文本策略的横向静态关系图
图2:结果示意图,对比基础 Text 与 goquery.Text 的参数职责;这是静态结构图,不是截图或运行证据。
package main

import (
  "fmt"
  "strings"

  "github.com/PuerkitoBio/goquery"
  "golang.org/x/net/html"
)

func readableText(sel *goquery.Selection) string {
  // Separator 负责文本节点之间的连接;Trim 避免缩进空白进入结果。
  return goquery.Text(sel, &goquery.TextOptions{
    Separator: " ",
    Trim:      true,
    Keep: func(node *html.Node) bool {
      // 过滤脚本和样式节点,避免把非正文内容当作文章文本。
      parent := node.Parent
      return parent == nil || parent.Type != html.ElementNode ||
        (parent.Data != "script" && parent.Data != "style")
    },
  })
}

func main() {
  // 文本来源仍由选择器决定,选中 card 就会覆盖它的后代文本。
  doc, err := goquery.NewDocumentFromReader(strings.NewReader(`

Go 入门

掌握 goquery

`)) if err != nil { panic(err) } fmt.Println(readableText(doc.Find("article.card"))) }

Separator、Trim 和 Keep 解决的是文本拼接策略,不会把父选择自动变成“只取直属文字”。如果选择器仍然是外层卡片,得到的仍是卡片后代中符合 Keep 条件的文本。

保留 HTML 结构还是提取纯文本

排查到这里,我通常会先问自己:下游需要的是阅读文本,还是还要知道标签结构?如果要继续判断链接、强调标签或嵌套字段,应该保留节点或使用 Html();如果只要搜索索引、摘要或日志字符串,才使用 Text(),并明确空白和分隔符规则。

目标选择关键边界
整块内容的纯文本Selection.Text()递归包含后代文本,默认不插入分隔符
可读的拼接文本goquery.Text用 TextOptions 控制分隔、修剪和过滤
单个字段把选择器收窄到目标元素不要先取父节点再切字符串
保留标签关系Html() 或继续处理节点纯文本会丢失结构信息

最终可以记成一句话:Text() 的“全部文字”不是额外抓取,而是选中节点子树的自然结果。选择器负责边界,Text API 负责汇总,业务代码负责决定字段和分隔规则。

相关问题

为什么 Text 的结果里没有 HTML 标签?

因为它只写入文本节点的数据,元素标签只是遍历结构,不会作为字符串内容追加。

怎么避免 script 和 style 的内容进入结果?

用更窄的选择器,或使用带 Keep 函数的 goquery.Text,按父节点过滤脚本和样式文本节点。

为什么多个 li 拼成一行?

多个节点被放进同一个 Selection 后,默认 Text 不负责推断项目分隔符;需要保留边界时逐项读取,需要连接时显式设置 Separator。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go goquery 怎么移除副本中的节点而保留原文档Go goquery 怎么移除副本中的节点而保留原文档
上一篇
Go goquery 怎么移除副本中的节点而保留原文档
Go goquery Find 与 Filter 选择节点有什么区别
下一篇
Go goquery Find 与 Filter 选择节点有什么区别
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    460次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    521次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    539次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    487次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    307次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码