ZetCode

Go goquery

last modified April 25, 2026

In this article, we demonstrate how to perform web scraping and HTML parsing in Go using the goquery library. Goquery provides a convenient, jQuery‑like API that makes it easy to traverse HTML documents, extract elements, and manipulate content using familiar CSS selectors.

Under the hood, goquery builds on two core components from the Go ecosystem: the standard library's net/html package, which parses HTML into a DOM tree, and the cascadia library, which implements CSS selector matching. Together, they allow goquery to offer a high-level, expressive interface while relying on robust, battle-tested parsing tools.

To install goquery, run:

go get github.com/PuerkitoBio/goquery

This command downloads the goquery package and makes it available for use in your project.

Go goquery get title

The following example, we get a title of a webpage.

main.go
package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/PuerkitoBio/goquery"
)

func main() {

    webPage := "https://example.com"
    resp, err := http.Get(webPage)

    if err != nil {
        log.Fatal(err)
    }

    defer resp.Body.Close()

    if resp.StatusCode != 200 {
        log.Fatalf("failed to fetch data: %d %s", resp.StatusCode, resp.Status)
    }

    doc, err := goquery.NewDocumentFromReader(resp.Body)

    if err != nil {
        log.Fatal(err)
    }

    title := doc.Find("title").Text()
    fmt.Println(title)
}

We generate a GET request to the specified webpage and retrieve its contents. From the body of the response, we generate a goquery document. From this document, we retrieve the title.

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

The Find method returns a set of matched elements. In our case, it is one title tag. With Text, we get the text content of the tag.

$ go run main.go 
Example Domain

Go goquery read local file

The following example reads a local HTML file.

index.html
<!DOCTYPE html>
<html lang="en">

<body>
<main>
    <h1>My website</h1>

    <p>
        I am a Go programmer.
    </p>

    <p>
        My hobbies are:
    </p>

    <ul>
        <li>Swimming</li>
        <li>Tai Chi</li>
        <li>Running</li>
        <li>Web development</li>
        <li>Reading</li>
        <li>Music</li>
    </ul>
</main>
</body>

</html>

This is a simple HTML file.

main.go
package main

import (
    "fmt"
    "log"
    "os"
    "regexp"
    "strings"

    "github.com/PuerkitoBio/goquery"
)

func main() {

    data, err := os.ReadFile("index.html")

    if err != nil {
        log.Fatal(err)
    }

    doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(data)))

    if err != nil {
        log.Fatal(err)
    }

    text := doc.Find("h1,p").Text()

    re := regexp.MustCompile(`\s{2,}`)
    fmt.Println(re.ReplaceAllString(text, "\n"))
}

We get the text contents of two tags.

data, err := os.ReadFile("index.html")

We read the file.

doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(data)))

We generate a new goquery document with NewDocumentFromReader.

text := doc.Find("h1,p").Text()

We get the text contents of two tags: h1 and p.

re := regexp.MustCompile(`\s{2,}`)
fmt.Println(re.ReplaceAllString(text, "\n"))

Using a regular expression, we remove excessive white space.

$ go run main.go
My website
I am a Go programmer.
My hobbies are:

Go goquery read from HTML string

In the next example, we process a built-in HTML string.

get_words.go
package main

import (
    "fmt"
    "log"
    "strings"

    "github.com/PuerkitoBio/goquery"
)

func main() {

    data := `
<html lang="en">
<body>
<p>List of words</p>
<ul>
    <li>dark</li>
    <li>smart</li>
    <li>war</li>
    <li>cloud</li>
    <li>park</li>
    <li>cup</li>
    <li>worm</li>
    <li>water</li>
    <li>rock</li>
    <li>warm</li>
</ul>
<footer>footer for words</footer>
</body>
</html>
`

    doc, err := goquery.NewDocumentFromReader(strings.NewReader(data))

    if err != nil {
    log.Fatal(err)
    }

    words := doc.Find("li").Map(func(i int, sel *goquery.Selection) string {
        return fmt.Sprintf("%d: %s", i+1, sel.Text())
    })

    fmt.Println(words)
}

We get the words from the HTML list.

words := doc.Find("li").Map(func(i int, sel *goquery.Selection) string {
    return fmt.Sprintf("%d: %s", i+1, sel.Text())
})

With Find, we get all the li elements. The Map method is used to build a string that contains the word and its index in the list.

$ go run get_words.go
[1: dark 2: smart 3: war 4: cloud 5: park 6: cup 7: worm 8: water 9: rock 10: warm]

Go goquery document node

When goquery parses an HTML document, it wraps everything in a virtual document node. This node is not an HTML element; its Data field is an empty string. The real page elements are its children.

The internal tree looks like this:

DocumentNode  ("")          ← doc.Selection.Get(0)
├── DoctypeNode ("html")    ← <!DOCTYPE html>
└── ElementNode ("html")    ← <html lang="en">
    ├── <head>
    └── <body>

Because doc.Selection wraps the document node, calling doc.Selection.Get(0).Data returns an empty string. To get the root element tag name we must step into the document node's children.

main.go
package main

import (
    "fmt"
    "log"
    "strings"

    "github.com/PuerkitoBio/goquery"
)

func main() {

    data := `<!DOCTYPE html>
<html lang="en">
<head><title>Test</title></head>
<body><p>Hello</p></body>
</html>`

    doc, err := goquery.NewDocumentFromReader(strings.NewReader(data))

    if err != nil {
        log.Fatal(err)
    }

    // doc.Selection is a DocumentNode — Data is empty
    fmt.Printf("doc.Selection Data: %q\n", doc.Selection.Get(0).Data)

    // Step into children to reach the <html> element
    rootTag := doc.Selection.Children().First().Get(0).Data
    fmt.Printf("root element tag: %q\n", rootTag)
}

The example demonstrates the difference between the document node and the root HTML element.

fmt.Printf("doc.Selection Data: %q\n", doc.Selection.Get(0).Data)

Printing doc.Selection.Get(0).Data yields an empty string because doc.Selection wraps the virtual document node, which has no tag name.

rootTag := doc.Selection.Children().First().Get(0).Data

Children returns the direct children of the document node — the doctype declaration and the <html> element. First picks the first child element, and Get(0) retrieves the underlying *html.Node. Its Data field holds the tag name.

$ go run main.go
doc.Selection Data: ""
root element tag: "html"

Go goquery filter words

The following example filters words.

main.go
package main

import (
    "fmt"
    "log"
    "strings"

    "github.com/PuerkitoBio/goquery"
)

func main() {

    data := `
<html lang="en">
<body>
<p>List of words</p>
<ul>
    <li>dark</li>
    <li>smart</li>
    <li>war</li>
    <li>cloud</li>
    <li>park</li>
    <li>cup</li>
    <li>worm</li>
    <li>water</li>
    <li>rock</li>
    <li>warm</li>
</ul>
<footer>footer for words</footer>
</body>
</html>
`

    doc, err := goquery.NewDocumentFromReader(strings.NewReader(data))

    if err != nil {
        log.Fatal(err)
    }

    f := func(i int, sel *goquery.Selection) bool {
        return strings.HasPrefix(sel.Text(), "w")
    }

    var words []string

    doc.Find("li").FilterFunction(f).Each(func(_ int, sel *goquery.Selection) {
        words = append(words, sel.Text())
    })

    fmt.Println(words)
}

We retrieve all words starting with 'w'.

f := func(i int, sel *goquery.Selection) bool {
    return strings.HasPrefix(sel.Text(), "w")
}

This is a predicate function that returns a boolean true for all words that begin with 'w'.

doc.Find("li").FilterFunction(f).Each(func(_ int, sel *goquery.Selection) {
    words = append(words, sel.Text())
})

We locate the set of matching tags with Find. We filter the set with FilterFunction and go over the filtered results with Each. We add each filtered word to the words slice.

fmt.Println(words)

Finally, we print the slice.

$ go run main.go
[war worm water warm]

Go goquery union words

With Union, we can combine selections.

main.go
package main

import (
    "fmt"
    "log"
    "strings"

    "github.com/PuerkitoBio/goquery"
)

func main() {

    data := `
<html lang="en">
<body>
<p>List of words</p>
<ul>
    <li>dark</li>
    <li>smart</li>
    <li>war</li>
    <li>cloud</li>
    <li>park</li>
    <li>cup</li>
    <li>worm</li>
    <li>water</li>
    <li>rock</li>
    <li>warm</li>
</ul>
<footer>footer for words</footer>
</body>
</html>
`
    doc, err := goquery.NewDocumentFromReader(strings.NewReader(data))

    if err != nil {
        log.Fatal(err)
    }

    var words []string

    sel1 := doc.Find("li:first-child, li:last-child")
    sel2 := doc.Find("li:nth-child(3), li:nth-child(7)")

    sel1.Union(sel2).Each(func(_ int, sel *goquery.Selection) {
        words = append(words, sel.Text())
    })

    fmt.Println(words)
}

The example combines two selections.

sel1 := doc.Find("li:first-child, li:last-child")

The first selection contains the first and the last element.

sel2 := doc.Find("li:nth-child(3), li:nth-child(7)")

The second selection contains the third and the seventh element.

sel1.Union(sel2).Each(func(_ int, sel *goquery.Selection) {
    words = append(words, sel.Text())
})

We combine the two selections with Union.

$ go run main.go
[dark warm war worm]

Go goquery get links

The following example retrieves links from a webpage.

get_links.go
package main

import (
    "fmt"
    "log"
    "net/http"
    "strings"

    "github.com/PuerkitoBio/goquery"
)

func getLinks() {

    webPage := "https://golang.org"

    resp, err := http.Get(webPage)

    if err != nil {
        log.Fatal(err)
    }

    defer resp.Body.Close()

    if resp.StatusCode != 200 {
        log.Fatalf("status code error: %d %s", resp.StatusCode, resp.Status)
    }

    doc, err := goquery.NewDocumentFromReader(resp.Body)

    if err != nil {
        log.Fatal(err)
    }

    f := func(i int, s *goquery.Selection) bool {

        link, _ := s.Attr("href")
        return strings.HasPrefix(link, "https")
    }

    doc.Find("body a").FilterFunction(f).Each(func(_ int, tag *goquery.Selection) {

        link, _ := tag.Attr("href")
        linkText := tag.Text()
        fmt.Printf("%s %s\n", linkText, link)
    })
}

func main() {
    getLinks()
}

The example retrieves external links to secured web pages.

f := func(i int, s *goquery.Selection) bool {

    link, _ := s.Attr("href")
    return strings.HasPrefix(link, "https")
}

In the predicate function we ensure that the link has the https prefix.

doc.Find("body a").FilterFunction(f).Each(func(_ int, tag *goquery.Selection) {

    link, _ := tag.Attr("href")
    linkText := tag.Text()
    fmt.Printf("%s %s\n", linkText, link)
})

We find all the anchor tags, filter them, and then print the filtered links to the console.

Go goquery DOM traversal

goquery provides methods to navigate the DOM tree relative to a matched element: Parent, Prev, Next, and NextAll/PrevAll.

main.go
package main

import (
    "fmt"
    "log"
    "strings"

    "github.com/PuerkitoBio/goquery"
)

func main() {

    data := `
<html lang="en">
<body>
<ul id="langs">
    <li>Go</li>
    <li>Rust</li>
    <li>Python</li>
    <li>Java</li>
    <li>C++</li>
</ul>
</body>
</html>`

    doc, err := goquery.NewDocumentFromReader(strings.NewReader(data))

    if err != nil {
        log.Fatal(err)
    }

    third := doc.Find("li:nth-child(3)")

    fmt.Println("Current:  ", third.Text())
    fmt.Println("Previous: ", third.Prev().Text())
    fmt.Println("Next:     ", third.Next().Text())
    fmt.Println("Parent id:", third.Parent().AttrOr("id", ""))

    fmt.Print("Following siblings: ")
    third.NextAll().Each(func(_ int, s *goquery.Selection) {
        fmt.Print(s.Text(), " ")
    })
    fmt.Println()
}

The example selects the third list item and navigates around it.

third := doc.Find("li:nth-child(3)")

We select the third li element using the CSS pseudo-class :nth-child.

fmt.Println("Previous: ", third.Prev().Text())
fmt.Println("Next:     ", third.Next().Text())

Prev and Next return the immediately adjacent sibling elements.

fmt.Println("Parent id:", third.Parent().AttrOr("id", ""))

Parent moves up one level in the tree. AttrOr reads an attribute value and returns the supplied default when the attribute is absent.

third.NextAll().Each(func(_ int, s *goquery.Selection) {

NextAll returns all following siblings in document order.

$ go run main.go
Current:   Python
Previous:  Rust
Next:      Java
Parent id: langs
Following siblings: Java C++ 

Go goquery Has and Not

Has narrows a selection to elements that contain a given descendant, while Not excludes elements that match a selector. Together they let you split a set into two complementary subsets.

main.go
package main

import (
    "fmt"
    "log"
    "strings"

    "github.com/PuerkitoBio/goquery"
)

func main() {

    data := `
<html lang="en">
<body>
<ul>
    <li><a href="https://golang.org">Go</a></li>
    <li>Rust</li>
    <li><a href="https://python.org">Python</a></li>
    <li>Java</li>
    <li><a href="https://isocpp.org">C++</a></li>
</ul>
</body>
</html>`

    doc, err := goquery.NewDocumentFromReader(strings.NewReader(data))

    if err != nil {
        log.Fatal(err)
    }

    fmt.Println("Items with a link:")
    doc.Find("li").Has("a").Each(func(_ int, s *goquery.Selection) {
        link, _ := s.Find("a").Attr("href")
        fmt.Printf("  %-8s %s\n", s.Text(), link)
    })

    fmt.Println("Items without a link:")
    doc.Find("li").Not(":has(a)").Each(func(_ int, s *goquery.Selection) {
        fmt.Println(" ", s.Text())
    })
}

The example separates list items that contain an anchor from those that do not.

doc.Find("li").Has("a").Each(...)

Has("a") keeps only li elements that have at least one descendant a tag.

doc.Find("li").Not(":has(a)").Each(...)

Not(":has(a)") is the complement — it discards any li that contains an anchor, leaving the plain-text items.

$ go run main.go
Items with a link:
  Go       https://golang.org
  Python   https://python.org
  C++      https://isocpp.org
Items without a link:
  Rust
  Java

Go goquery parse HTML table

A common scraping task is extracting data from an HTML table into a slice of structs. goquery makes this straightforward by letting you iterate over rows and cells.

main.go
package main

import (
    "fmt"
    "log"
    "strings"

    "github.com/PuerkitoBio/goquery"
)

type Person struct {
    Name string
    Age  string
    City string
}

func main() {

    data := `
<html lang="en">
<body>
<table>
    <thead>
        <tr><th>Name</th><th>Age</th><th>City</th></tr>
    </thead>
    <tbody>
        <tr><td>Alice</td><td>30</td><td>New York</td></tr>
        <tr><td>Bob</td><td>25</td><td>London</td></tr>
        <tr><td>Carol</td><td>35</td><td>Sydney</td></tr>
        <tr><td>David</td><td>28</td><td>Berlin</td></tr>
    </tbody>
</table>
</body>
</html>`

    doc, err := goquery.NewDocumentFromReader(strings.NewReader(data))

    if err != nil {
        log.Fatal(err)
    }

    var people []Person

    doc.Find("tbody tr").Each(func(_ int, row *goquery.Selection) {

        var cols []string
        row.Find("td").Each(func(_ int, td *goquery.Selection) {
            cols = append(cols, strings.TrimSpace(td.Text()))
        })

        if len(cols) == 3 {
            people = append(people, Person{cols[0], cols[1], cols[2]})
        }
    })

    fmt.Printf("%-8s %-5s %s\n", "Name", "Age", "City")
    fmt.Println(strings.Repeat("-", 25))

    for _, p := range people {
        fmt.Printf("%-8s %-5s %s\n", p.Name, p.Age, p.City)
    }
}

The example parses an HTML table into a slice of Person structs.

doc.Find("tbody tr").Each(func(_ int, row *goquery.Selection) {

We iterate over every data row inside tbody, skipping the header row which lives in thead.

row.Find("td").Each(func(_ int, td *goquery.Selection) {
    cols = append(cols, strings.TrimSpace(td.Text()))
})

For each row we collect the text of every cell into a string slice. The order of cells matches the column order in the HTML.

if len(cols) == 3 {
    people = append(people, Person{cols[0], cols[1], cols[2]})
}

We guard against malformed rows before constructing the struct and appending it to the slice.

$ go run main.go
Name     Age   City
-------------------------
Alice    30    New York
Bob      25    London
Carol    35    Sydney
David    28    Berlin

Go goquery ancestor traversal

Besides moving down the tree with Find, goquery lets you move up the tree with Parents, ParentsFiltered, and Closest. Closest walks up from the current element and returns the nearest ancestor that matches a selector, including the element itself.

main.go
package main

import (
    "fmt"
    "log"
    "strings"

    "github.com/PuerkitoBio/goquery"
)

func main() {

    data := `
<html lang="en">
<body>
<article id="post-1">
    <section class="content">
        <p>
            See the <a href="/glossary" class="ref">glossary</a> for details.
        </p>
    </section>
</article>
</body>
</html>`

    doc, err := goquery.NewDocumentFromReader(strings.NewReader(data))

    if err != nil {
        log.Fatal(err)
    }

    link := doc.Find("a.ref")

    // Nearest ancestor that is a section
    section := link.Closest("section")
    fmt.Println("Closest section class:", section.AttrOr("class", ""))

    // Nearest ancestor that is an article
    article := link.Closest("article")
    fmt.Println("Closest article id:  ", article.AttrOr("id", ""))

    // All ancestors, outermost last
    fmt.Print("All ancestor tags: ")
    link.Parents().Each(func(_ int, s *goquery.Selection) {
        fmt.Print(goquery.NodeName(s), " ")
    })
    fmt.Println()

    // Only ancestor tags that are block-level containers
    fmt.Print("Filtered ancestors (section, article, body): ")
    link.ParentsFiltered("section, article, body").Each(func(_ int, s *goquery.Selection) {
        fmt.Print(goquery.NodeName(s), " ")
    })
    fmt.Println()
}

The example starts from a deeply nested anchor tag and climbs the DOM tree in several ways.

section := link.Closest("section")

Closest starts at the element itself and walks up, returning the first match. Here it finds the enclosing <section>.

link.Parents().Each(func(_ int, s *goquery.Selection) {
    fmt.Print(goquery.NodeName(s), " ")
})

Parents returns all ancestors from the immediate parent up to the document root. goquery.NodeName is a helper that returns the lowercase tag name of a selection.

link.ParentsFiltered("section, article, body").Each(...)

ParentsFiltered is like Parents but keeps only ancestors that match the supplied selector.

$ go run main.go
Closest section class:  content
Closest article id:     post-1
All ancestor tags:  p section article body html 
Filtered ancestors (section, article, body):  section article body 

Go goquery modify document

goquery is not limited to reading HTML — it can also mutate the parsed tree. SetAttr and RemoveAttr change element attributes, while SetHtml replaces an element's inner HTML. The modified document can be serialised back to a string with goquery.OuterHtml.

main.go
package main

import (
    "fmt"
    "log"
    "strings"

    "github.com/PuerkitoBio/goquery"
)

func main() {

    data := `
<html lang="en">
<body>
<ul id="links">
    <li><a href="http://golang.org">Go</a></li>
    <li><a href="http://rust-lang.org">Rust</a></li>
    <li><a href="http://python.org">Python</a></li>
</ul>
</body>
</html>`

    doc, err := goquery.NewDocumentFromReader(strings.NewReader(data))

    if err != nil {
        log.Fatal(err)
    }

    // Upgrade every http:// link to https://
    doc.Find("a[href]").Each(func(_ int, s *goquery.Selection) {
        href, _ := s.Attr("href")
        if strings.HasPrefix(href, "http://") {
            s.SetAttr("href", strings.Replace(href, "http://", "https://", 1))
        }
    })

    // Add a rel="noopener" attribute to all links
    doc.Find("a").SetAttr("rel", "noopener")

    // Replace the inner HTML of the list caption
    doc.Find("ul#links").SetHtml(`<li><a href="https://go.dev" rel="noopener">Go (updated)</a></li>`)

    // Serialise the modified ul back to a string
    html, err := goquery.OuterHtml(doc.Find("ul#links"))

    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(html)
}

The example upgrades all links from HTTP to HTTPS, adds a security attribute, then replaces the list contents entirely.

s.SetAttr("href", strings.Replace(href, "http://", "https://", 1))

SetAttr sets an attribute value on every node in the selection. Here we rewrite the href to use a secure scheme.

doc.Find("a").SetAttr("rel", "noopener")

Calling SetAttr on a multi-element selection applies the change to all matched elements at once.

doc.Find("ul#links").SetHtml(`<li>...</li>`)

SetHtml replaces the inner HTML of the matched element, discarding its previous children.

html, err := goquery.OuterHtml(doc.Find("ul#links"))

goquery.OuterHtml serialises the element including its own opening and closing tags back into an HTML string.

Go goquery StackOverflow questions

We are going to get the latest StackOverflow questions for the Raku tag.

get_qs.go
package main

import (
    "fmt"
    "log"
    "net/http"

    "github.com/PuerkitoBio/goquery"
)

func main() {

    webPage := "https://stackoverflow.com/questions/tagged/raku"

    resp, err := http.Get(webPage)

    if err != nil {
        log.Fatal(err)
    }

    defer resp.Body.Close()

    if resp.StatusCode != 200 {
        log.Fatalf("failed to fetch data: %d %s", resp.StatusCode, resp.Status)
    }

    doc, err := goquery.NewDocumentFromReader(resp.Body)

    if err != nil {
        log.Fatal(err)
    }

    doc.Find(".question-summary .summary").Each(func(i int, s *goquery.Selection) {

        title := s.Find("h3").Text()
        fmt.Println(i+1, title)
    })
}

In the code example, we print the last fifty titles of the StackOverflow questions on the Raku programming language.

doc.Find(".question-summary .summary").Each(func(i int, s *goquery.Selection) {

    title := s.Find("h3").Text()
    fmt.Println(i+1, title)
})

We locate the questions and print their titles; the title is in the h3 tag.

$ go run get_qs.go
1 Raku pop() order of execution
2 Does the `do` keyword run a block or treat it as an expression?
3 Junction ~~ Junction behavior
4 Is there a way to detect whether something is immutable?
5 Optimize without sacrificing usual workflow: arguments, POD etc
6 Find out external command runability
...

Go goquery get earthquakes

In the next example, we fetch data about earthquakes.

$ go get github.com/olekukonko/tablewriter

We use the tablewriter package to display data in tabular format.

earthquakes.go
package main

import (
    "log"
    "net/http"
    "os"
    "strings"

    "github.com/PuerkitoBio/goquery"
    "github.com/olekukonko/tablewriter"
    "github.com/olekukonko/tablewriter/tw"
)

type Earthquake struct {
    Date      string
    Latitude  string
    Longitude string
    Magnitude string
    Depth     string
    Location  string
    IrisId    string
}

var quakes []Earthquake

func fetchQuakes() {

    webPage := "https://www.iris.edu/app/seismic-monitor/table?lat=16.4657&lng=-67.5192&zoom=2"

    resp, err := http.Get(webPage)

    if err != nil {
        log.Fatal(err)
    }

    defer resp.Body.Close()

    if resp.StatusCode != 200 {
        log.Fatalf("failed to fetch data: %d %s", resp.StatusCode, resp.Status)
    }

    doc, err := goquery.NewDocumentFromReader(resp.Body)

    if err != nil {
        log.Fatal(err)
    }

    doc.Find("tbody tr").Each(func(j int, tr *goquery.Selection) {

        if j >= 10 {
            return
        }

        e := Earthquake{}

        tr.Find("td").Each(func(ix int, td *goquery.Selection) {
            switch ix {
            case 0:
                e.Date = td.Text()
            case 1:
                e.Latitude = td.Text()
            case 2:
                e.Longitude = td.Text()
            case 3:
                e.Magnitude = td.Text()
            case 4:
                e.Depth = td.Text()
            case 5:
                e.Location = strings.TrimSpace(td.Text())
            case 6:
                e.IrisId = td.Text()
            }
        })

        quakes = append(quakes, e)

    })

    table := tablewriter.NewWriter(os.Stdout)
    table.Header([]string{"Date", "Location", "Magnitude", "Longitude",
        "Latitude", "Depth", "IrisId"})

    table.Caption(tw.Caption{
        Text: "Last ten earthquakes",
        // Optional: choose placement, alignment, and width
        Spot:  tw.SpotBottomLeft, // or tablewriter.Top
        Align: tw.AlignLeft,      // or Left, Right
        Width: 80,                // custom width if needed
    })

    for _, quake := range quakes {

        s := []string{
            quake.Date,
            quake.Location,
            quake.Magnitude,
            quake.Longitude,
            quake.Latitude,
            quake.Depth,
            quake.IrisId,
        }

        table.Append(s)
    }

    table.Render()
}

func main() {

    fetchQuakes()
}

The example retrieves ten latest earthquakes from the Iris database. It prints the data in a tabular format.

type Earthquake struct {
    Date      string
    Latitude  string
    Longitude string
    Magnitude string
    Depth     string
    Location  string
    IrisId    string
}

The data is grouped in the Earthquake structure.

var quakes []Earthquake

The structures will be stored in the quakes slice.

doc.Find("tbody tr").Each(func(j int, tr *goquery.Selection) {

Locating data is simple; we go for the tr tags inside the tbody tag.

e := Earthquake{}

tr.Find("td").Each(func(ix int, td *goquery.Selection) {
    switch ix {
    case 0:
        e.Date = td.Text()
    case 1:
        e.Latitude = td.Text()
    case 2:
        e.Longitude = td.Text()
    case 3:
        e.Magnitude = td.Text()
    case 4:
        e.Depth = td.Text()
    case 5:
        e.Location = strings.TrimSpace(td.Text())
    case 6:
        e.IrisId = td.Text()
    }
})

quakes = append(quakes, e)

We create a new Earthquake structure, fill it with table row data and put the structure into the quakes slice.

table := tablewriter.NewWriter(os.Stdout)
table.Header([]string{"Date", "Location", "Magnitude", "Longitude",
    "Latitude", "Depth", "IrisId"})

table.Caption(tw.Caption{
    Text: "Last ten earthquakes",
    // Optional: choose placement, alignment, and width
    Spot:  tw.SpotBottomLeft, // or tablewriter.Top
    Align: tw.AlignLeft,      // or Left, Right
    Width: 80,                // custom width if needed
})

We create a new table for displaying our data. The data will be shown in the standard output (console). We create a header and a caption for the table.

for _, quake := range quakes {

    s := []string{
        quake.Date,
        quake.Location,
        quake.Magnitude,
        quake.Longitude,
        quake.Latitude,
        quake.Depth,
        quake.IrisId,
    }

    table.Append(s)
}

The table takes a string slice as a parameter; therefore, we transform the structure into a slice and append the slice to the table with Append.

table.Render()

In the end, we render the table.

Source

Goquery - Github page

In this article we have scraped web/parsed HTML in Go with the goquery package.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Go tutorials.