web-dev-qa-db-fra.com

Golang analyser HTML, extraire tout le contenu avec les balises <body> </ body>

Comme indiqué dans le titre. J'ai besoin de renvoyer tout le contenu contenu dans les balises body d'un document html, y compris toutes les balises html ultérieures, etc. J'avais une solution de travail avec le paquet Gokogiri, mais j'essaie de rester à l'écart de tout paquet qui dépend des bibliothèques C. Est-il possible d'accomplir cela avec la bibliothèque standard go? ou avec un paquet qui est 100% aller? 

Depuis la publication de ma question initiale, j'ai tenté d'utiliser les packages suivants qui n'ont pas permis de résoudre le problème. (Ni l'un ni l'autre ne semble renvoyer les enfants ou les balises imbriquées ultérieurs de l'intérieur du corps. Par exemple: 

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

renverra le contenu du corps, en ignorant les balises <p> suivantes et le texte qu'elles enveloppent):

  • pkg/encoding/xml/(paquet XML de la bibliothèque standard)
  • golang.org/x/net/html

Le but ultime serait d’obtenir une chaîne ou un contenu qui ressemblerait à ceci:

<body>
    body content 
    <p>more content</p>
</body>
12
user2737876

Cela peut être résolu en recherchant récursivement le nœud du corps, à l'aide du package html, puis en rendant le code html à partir de ce nœud.

package main

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

func getBody(doc *html.Node) (*html.Node, error) {
    var b *html.Node
    var f func(*html.Node)
    f = func(n *html.Node) {
        if n.Type == html.ElementNode && n.Data == "body" {
            b = n
        }
        for c := n.FirstChild; c != nil; c = c.NextSibling {
            f(c)
        }
    }
    f(doc)
    if b != nil {
        return b, 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 := getBody(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>`
23
Joachim Birche

Cela peut être fait en utilisant le package encoding/xml standard. Mais c'est un peu lourd. Et un inconvénient dans cet exemple est qu'il n'inclura pas la balise body englobante, mais qu'il contiendra tous ses enfants.

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)
}

Exemple exécutable:
http://play.golang.org/p/ZH5iKyjRQp

8
fredrik

Puisque vous n'avez pas montré le code source de votre tentative avec le paquet HTML, je vais devoir deviner ce que vous faisiez, mais je suppose que vous utilisiez le tokenizer plutôt que l'analyseur. Voici un programme qui utilise l'analyseur et fait ce que vous cherchiez:

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)
}
5
andybalholm

Vous pouvez aussi le faire uniquement avec des chaînes:

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)
}

Les définitions de SkipTillReader et ReadTillReader se trouvent ici: https://play.golang.org/p/6THLhRgLOa . (Mais fondamentalement, sautez jusqu'à ce que vous voyiez le délimiteur, puis lisez-le jusqu'à ce que vous voyiez le délimiteur)

Cela ne fonctionnera pas pour l'insensibilité à la casse (bien que cela ne soit pas difficile à changer). 

0
Caleb