Golang анализирует HTML, извлекает весь контент с помощью тегов

Как указано в названии. Мне нужно вернуть весь контент в тегах тела html-документа, включая любые последующие тэги html и т.д. Мне любопытно узнать, что лучший способ сделать это. У меня было рабочее решение с пакетом Gokogiri, однако я стараюсь держаться подальше от любых пакетов, зависящих от библиотек C. Есть ли способ сделать это с помощью стандартной библиотеки? или с пакетом, который на 100% уходит?

После публикации моего первоначального вопроса я попытался использовать следующие пакеты, которые не дали никакого разрешения. (Ни один из них, похоже, не возвращает последующих детей или вложенные теги изнутри тела. Например:

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

будет возвращать содержимое тела, игнорируя последующие теги <p> и текст, который они обертывают):

  • pkg/encoding/xml/(стандартная библиотека xml-пакета)
  • golang.org/x/net/html

По всей цели было бы получить строку или контент, который будет выглядеть так:

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

Ответы

Ответ 1

Эту проблему можно решить путем рекурсивного поиска узла body с использованием пакета 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>'

Ответ 2

Это можно сделать с помощью стандартного пакета encoding/xml. Но это немного громоздко. И один из предостережений в этом примере состоит в том, что он не будет включать в себя тег body 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)
}

Пример Runnable:
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)
}

Определения для SkipTillReader и ReadTillReader приведены здесь: https://play.golang.org/p/6THLhRgLOa. (Но в основном пропустите, пока вы не увидите разделитель, а затем прочитайте, пока не увидите разделитель)

Это не будет работать для нечувствительности к регистру (хотя это не сложно будет изменить).