【问题标题】:Golang parse HTML, extract all content with <body> </body> tagsGolang 解析 HTML,提取所有带有 <body> </body> 标签的内容
【发布时间】:2015-05-07 18:34:04
【问题描述】:

如标题所述。我需要返回 html 文档的 body 标签中的所有内容,包括任何后续的 html 标签等。我很想知道最好的方法是什么。我有一个使用 Gokogiri 包的工作解决方案,但是我试图远离任何依赖 C 库的包。 go标准库有没有办法做到这一点?还是使用 100% 成功的包裹?

自从发布我最初的问题以来,我尝试使用以下未产生任何解决方案的软件包。 (两者似乎都不会从正文中返回后续子项或嵌套标签。例如:

<!DOCTYPE html>
<html>
    <head>
        <title>
            Title of the document
        </title>
    </head>
    <body>
        body content 
        <p>more content</p>
    </body>
</html>

将返回正文内容,忽略随后的&lt;p&gt; 标签和它们换行的文本):

  • pkg/encoding/xml/(标准库xml包)
  • golang.org/x/net/html

总体目标是获得如下所示的字符串或内容:

<body>
    body content 
    <p>more content</p>
</body>

【问题讨论】:

  • 我很确定标准 xml 包可以做到这一点。如果没有,请尝试 goquery github.com/PuerkitoBio/goquery
  • @Not_a_Golfer 我尝试使用标准 XML 包,但是我无法让它以字符串形式读取正文中的子/嵌套标签。例如: 文档标题正文内容

    更多内容

    将返回正文内容,忽略随后的

    标记及其包装的文本。

  • 你可以试试golang.org/x/net/html 包。
  • @DaveC 我也一直在尝试这个。对于我的生活,我无法让它以我期望的方式返回嵌套标签。在我对 Not_a_Golfer 的回复中也是同样的问题
  • @user2737876 您可能应该edit 将问题包含在您的第一条评论中。

标签: html go


【解决方案1】:

这可以通过递归查找主体节点来解决,使用 html 包,然后从该节点开始渲染 html。

package main

import (
    "bytes"
    "errors"
    "fmt"
    "golang.org/x/net/html"
    "io"
    "strings"
)

func Body(doc *html.Node) (*html.Node, error) {
    var body *html.Node
    var crawler func(*html.Node)
    crawler = func(node *html.Node) {
        if node.Type == html.ElementNode && node.Data == "body" {
            body = node
            return
        }
        for child := node.FirstChild; child != nil; child = child.NextSibling {
            crawler(child)
        }
    }
    crawler(doc)
    if body != nil {
        return body, nil
    }
    return nil, errors.New("Missing <body> in the node tree")
}

func renderNode(n *html.Node) string {
    var buf bytes.Buffer
    w := io.Writer(&buf)
    html.Render(w, n)
    return buf.String()
}

func main() {
    doc, _ := html.Parse(strings.NewReader(htm))
    bn, err := Body(doc)
    if err != nil {
        return
    }
    body := renderNode(bn)
    fmt.Println(body)
}

const htm = `<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
    body content
    <p>more content</p>
</body>
</html>`

【讨论】:

  • IMO 这应该是公认的答案。 HTML 不是 XML 的子集,如果源 HTML 包含非 XML(例如 &lt;meta charset="UTF-8"&gt;),则基于 encoding/xml 的方法将失败。
  • 我认为我们可以在闭包体中的b = n 之后添加return 以防止不必要的工作
  • 您可能需要事先go get golang.org/x/net/html
【解决方案2】:

可以使用标准的encoding/xml 包来完成。但这有点麻烦。此示例中的一个警告是,它不会包含封闭的 body 标签,但会包含它的所有子标签。

package main

import (
    "bytes"
    "encoding/xml"
    "fmt"
)

type html struct {
    Body body `xml:"body"`
}
type body struct {
    Content string `xml:",innerxml"`
}

func main() {
    b := []byte(`<!DOCTYPE html>
<html>
    <head>
        <title>
            Title of the document
        </title>
    </head>
    <body>
        body content 
        <p>more content</p>
    </body>
</html>`)

    h := html{}
    err := xml.NewDecoder(bytes.NewBuffer(b)).Decode(&h)
    if err != nil {
        fmt.Println("error", err)
        return
    }

    fmt.Println(h.Body.Content)
}

可运行示例:
http://play.golang.org/p/ZH5iKyjRQp

【讨论】:

    【解决方案3】:

    由于您没有使用 html 包显示您尝试的源代码,我不得不猜测您在做什么,但我怀疑您使用的是标记器而不是解析器。这是一个使用解析器并执行您正在寻找的程序的程序:

    package main
    
    import (
        "log"
        "os"
        "strings"
    
        "github.com/andybalholm/cascadia"
        "golang.org/x/net/html"
    )
    
    func main() {
        r := strings.NewReader(`<!DOCTYPE html>
    <html>
        <head>
            <title>
                Title of the document
            </title>
        </head>
        <body>
            body content 
            <p>more content</p>
        </body>
    </html>`)
        doc, err := html.Parse(r)
        if err != nil {
            log.Fatal(err)
        }
    
        body := cascadia.MustCompile("body").MatchFirst(doc)
        html.Render(os.Stdout, body)
    }
    

    【讨论】:

      【解决方案4】:

      你也可以纯粹用字符串来做到这一点:

      func main() {
          r := strings.NewReader(`
      <!DOCTYPE html>
      <html>
          <head>
              <title>
                  Title of the document
              </title>
          </head>
          <body>
              body content
              <p>more content</p>
          </body>
      </html>
      `)
          str := NewSkipTillReader(r, []byte("<body>"))
          rtr := NewReadTillReader(str, []byte("</body>"))
          bs, err := ioutil.ReadAll(rtr)
          fmt.Println(string(bs), err)
      }
      

      SkipTillReaderReadTillReader 的定义在这里:https://play.golang.org/p/6THLhRgLOa。 (但基本上是跳过,直到看到分隔符,然后阅读,直到看到分隔符)

      这不适用于不区分大小写的情况(尽管这不难改变)。

      【讨论】:

      • 这是一个非常有趣的解决方案,除了它在后台使用bufio。这并不完全适合导致额外的内存分配。您知道任何类似的解决方案,但没有上述缺陷吗?
      • 然后你尝试从这个完全有效的 HTML 中解析出正文并惨遭失败:&lt;html&gt;&lt;head&gt;&lt;!-- &lt;body&gt;haha&lt;/body&gt; --&gt;&lt;/head&gt;&lt;body&gt;&lt;/body&gt;&lt;/html&gt;
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多