Documentation
¶
Overview ¶
Package sitemap fetches and parses sitemaps and collects the URLs they list.
A call to S.Parse or S.ParseContext takes the URL of a robots.txt, of a sitemap index or of a sitemap. It follows the sitemaps a robots.txt names and the ones a sitemap index lists, and collects the pages of every sitemap it reaches:
s := sitemap.New()
if _, err := s.Parse("https://www.sitemaps.org/sitemap.xml", nil); err != nil {
log.Fatal(err)
}
for _, u := range s.GetURLs() {
fmt.Println(u.Loc)
}
The document at the URL can be handed to the call in place of nil. It is not fetched then; the sitemaps it lists are.
The sitemaps the call found on its way, the ones a robots.txt names and a sitemap index lists, are returned by S.GetSitemaps, whether or not they were fetched, and with the date a sitemap index gives for them, see Sitemap.
S.GetURLs returns the list the instance keeps, which is to be read and not to be changed. S.URLs is an iterator over the same URLs that yields each of them as a copy of the caller's own, and S.Errors is one over the errors S.GetErrors returns. A list of the URLs to sort or to change is collected from the iterator:
urls := slices.Collect(s.URLs())
slices.SortFunc(urls, func(a, b sitemap.URL) int {
return strings.Compare(a.Loc, b.Loc)
})
Formats ¶
A sitemap is a <urlset> of the sitemaps.org protocol, an RSS 2.0 feed, an Atom 1.0 feed, or a plain text file with a URL on a line. A sitemap and a sitemap index may be gzip compressed. An entry of a <urlset> is returned with its <lastmod>, <changefreq> and <priority>, and with what the image, news, video and hreflang extensions say about the page, see URL. Of a feed, the location of a page and the date it was last modified are taken: the <pubDate> of an RSS item and the <updated> of an Atom entry. A text file gives nothing but the location.
Configuration ¶
An instance is configured with its Set methods, which can be chained:
s := sitemap.New().
SetUserAgent("my-crawler/1.0").
SetFetchTimeout(10).
SetFollow([]string{`^https://example\.com/sitemaps/`})
A Set method that is called with a value it does not accept records a ConfigError. Nothing is parsed while one is outstanding; calling the method again with a valid value clears it.
The parser is in tolerant mode unless S.SetStrict says otherwise. Tolerant mode makes the best of a document that does not keep to the protocol: a relative URL, for one, is resolved against the URL of the sitemap. Strict mode holds a document to the protocol, and skips and reports the entries that do not keep to it.
What a call may take up is bounded: the size of a response by S.SetMaxResponseSize, the depth of nested sitemap indexes by S.SetMaxDepth, the number of sitemaps it fetches and of sitemaps it lists by S.SetMaxSitemaps, the number of URLs it collects by S.SetMaxURLs, the number of validation errors it records by S.SetMaxValidationErrors, and the sitemaps it works on at a time by S.SetMaxConcurrency. S.SetFollow restricts the sitemaps that are fetched, S.SetRules the URLs that are collected.
Errors ¶
Parse and ParseContext return an error when the call fails for the URL it was made for: the URL is not valid, or the document at it cannot be fetched or parsed. What goes wrong further on does not fail the call and is reported by S.GetErrors only: a sitemap that cannot be fetched or parsed, an entry that is not valid, a limit that is reached. A nil error therefore does not mean that nothing was skipped.
The errors are of the types ConfigError, NetworkError, ParseError and ValidationError, to be told apart with errors.As.
The error of a limit that was reached wraps the error that stands for the limit, so errors.Is tells which limit it was: ErrMaxResponseSize, ErrMaxDepth, ErrMaxSitemaps, ErrMaxURLs or ErrMaxValidationErrors.
for _, err := range s.GetErrors() {
if errors.Is(err, sitemap.ErrMaxURLs) {
log.Print("the URLs are not all there: ", err)
}
}
Concurrency ¶
The methods of an instance may be called from several goroutines. An instance carries out one Parse or ParseContext call at a time, a second one waits for the first to end; use an instance for each of the calls that are to run side by side. An instance can be used for any number of calls one after the other: its results are those of the most recent one.
Example ¶
A document that is handed to the call is parsed as the one at the URL, without being fetched. With nil in its place, the document is fetched, see the example of Parse.
package main
import (
"fmt"
"log"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>https://example.com/</loc><lastmod>2024-03-01</lastmod></url>
<url><loc>https://example.com/about</loc></url>
</urlset>`
s := sitemap.New()
if _, err := s.Parse("https://example.com/sitemap.xml", &content); err != nil {
log.Fatal(err)
}
fmt.Println(s.GetURLCount(), "URLs")
for _, u := range s.GetURLs() {
fmt.Println(u.Loc)
}
}
Output: 2 URLs https://example.com/ https://example.com/about
Index ¶
- Variables
- type AlternateLink
- type ConfigError
- type Image
- type LastModTime
- type NetworkError
- type News
- type NewsPublication
- type ParseError
- type S
- func (s *S) Errors() iter.Seq[error]
- func (s *S) GetErrors() []error
- func (s *S) GetErrorsCount() int64
- func (s *S) GetFetchTimeout() uint16
- func (s *S) GetFollow() []string
- func (s *S) GetHTTPClient() *http.Client
- func (s *S) GetMaxConcurrency() int
- func (s *S) GetMaxDepth() int
- func (s *S) GetMaxResponseSize() int64
- func (s *S) GetMaxSitemaps() int
- func (s *S) GetMaxURLs() int
- func (s *S) GetMaxValidationErrors() int
- func (s *S) GetMultiThread() bool
- func (s *S) GetRandomURLs(n int) []URL
- func (s *S) GetRules() []string
- func (s *S) GetSitemapCount() int64
- func (s *S) GetSitemaps() []Sitemap
- func (s *S) GetStrict() bool
- func (s *S) GetURLCount() int64
- func (s *S) GetURLs() []URL
- func (s *S) GetUserAgent() string
- func (s *S) Parse(url string, urlContent *string) (*S, error)
- func (s *S) ParseContext(ctx context.Context, url string, urlContent *string) (*S, error)
- func (s *S) SetFetchTimeout(fetchTimeout uint16) *S
- func (s *S) SetFollow(regexes []string) *S
- func (s *S) SetHTTPClient(client *http.Client) *S
- func (s *S) SetMaxConcurrency(maxConcurrency int) *S
- func (s *S) SetMaxDepth(maxDepth int) *S
- func (s *S) SetMaxResponseSize(maxResponseSize int64) *S
- func (s *S) SetMaxSitemaps(maxSitemaps int) *S
- func (s *S) SetMaxURLs(maxURLs int) *S
- func (s *S) SetMaxValidationErrors(maxValidationErrors int) *S
- func (s *S) SetMultiThread(multiThread bool) *S
- func (s *S) SetRules(regexes []string) *S
- func (s *S) SetStrict(strict bool) *S
- func (s *S) SetUserAgent(userAgent string) *S
- func (s *S) URLs() iter.Seq[URL]
- type Sitemap
- type URL
- type URLChangeFreq
- type ValidationError
- type Video
- type VideoPlatform
- type VideoRestriction
- type VideoUploader
Examples ¶
- Package
- AlternateLink
- ConfigError
- Image
- LastModTime
- NetworkError
- New
- News
- ParseError
- S.Errors
- S.GetErrors
- S.GetRandomURLs
- S.GetSitemaps
- S.Parse
- S.Parse (Rss)
- S.Parse (Text)
- S.ParseContext
- S.SetFollow
- S.SetHTTPClient
- S.SetMaxURLs
- S.SetRules
- S.SetStrict
- S.URLs
- Sitemap
- URL
- URLChangeFreq
- ValidationError
- Video
Constants ¶
This section is empty.
Variables ¶
var ( // ErrMaxResponseSize is wrapped by the error of a document that is larger than the limit // set with SetMaxResponseSize: the *NetworkError of a response that exceeds it, and the // *ParseError of gzip content that exceeds it once it is decompressed. ErrMaxResponseSize = errors.New("sitemap: maximum response size exceeded") // ErrMaxDepth is wrapped by the *ParseError of a sitemap index whose sitemaps were not // followed because the limit set with SetMaxDepth is reached. ErrMaxDepth = errors.New("sitemap: maximum depth reached") // ErrMaxSitemaps is wrapped by the *ParseError which tells that the limit set with // SetMaxSitemaps kept the call from fetching or from listing a sitemap. ErrMaxSitemaps = errors.New("sitemap: maximum number of sitemaps reached") // ErrMaxURLs is wrapped by the *ParseError which tells that the limit set with SetMaxURLs // kept the call from collecting a URL. ErrMaxURLs = errors.New("sitemap: maximum number of URLs reached") // ErrMaxValidationErrors is wrapped by the *ParseError which tells that the limit set with // SetMaxValidationErrors kept the call from recording a *ValidationError. ErrMaxValidationErrors = errors.New("sitemap: maximum number of validation errors reached") )
The errors below stand for the limits of a Parse or ParseContext call. The error that is recorded when a limit is reached wraps the one of that limit, so errors.Is tells which limit it was without the message of the error being read:
for _, err := range s.GetErrors() {
if errors.Is(err, sitemap.ErrMaxURLs) {
fmt.Println("the URLs are not all there:", err)
}
}
The message of the recorded error tells the limit and its value, as in `parse "https://example.com/sitemap.xml": limit of 100000 URLs reached`.
Functions ¶
This section is empty.
Types ¶
type AlternateLink ¶ added in v1.0.0
type AlternateLink struct {
// Rel is the relation of the link to the page, the value of the rel attribute:
// "alternate" for another version of the page. Strict mode leaves out and reports
// a link with any other value, an empty one included.
Rel string `xml:"rel,attr"`
// Hreflang names the language of the version, and the region it is for if it is
// for one, the value of the hreflang attribute: "de", "de-CH", or "x-default" for
// the version of no language in particular. Strict mode leaves out and reports a
// link without one.
Hreflang string `xml:"hreflang,attr"`
// Href is the URL of the version, the value of the href attribute. It is never
// empty, and it may be on another host than the page. Strict mode requires an
// absolute HTTP or HTTPS URL: a link whose Href is not one is left out and
// reported.
Href string `xml:"href,attr"`
}
AlternateLink represents an alternate version of a page (hreflang) per the XHTML standard used in sitemaps. Reference: https://developers.google.com/search/docs/specialty/international/localized-versions#sitemap
A link is known by its Href, and one without an Href is not among the links of a URL.
Example ¶
The alternate links of a page name its versions in other languages.
package main
import (
"fmt"
"log"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>https://example.com/en/about</loc>
<xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/ueber-uns"/>
<xhtml:link rel="alternate" hreflang="fr" href="https://example.com/fr/a-propos"/>
</url>
</urlset>`
s := sitemap.New()
if _, err := s.Parse("https://example.com/sitemap.xml", &content); err != nil {
log.Fatal(err)
}
for _, u := range s.GetURLs() {
for _, link := range u.Hreflangs {
fmt.Println(link.Hreflang, link.Href)
}
}
}
Output: de https://example.com/de/ueber-uns fr https://example.com/fr/a-propos
type ConfigError ¶ added in v0.9.0
type ConfigError struct {
// Field is the configuration field name (e.g. "maxDepth", "follow", "rules").
Field string
// Err is the underlying validation error.
Err error
}
ConfigError is returned when a configuration setter receives an invalid value. Callers can inspect the Field to determine which configuration setting caused the error.
Example usage:
var cfgErr *sitemap.ConfigError
if errors.As(err, &cfgErr) {
fmt.Println("bad config field:", cfgErr.Field)
}
Example ¶
A Set method that is given a value it does not accept records an error. Nothing is parsed while such an error is outstanding; setting a valid value clears it.
package main
import (
"errors"
"fmt"
"log"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
s := sitemap.New().SetMaxDepth(0)
for _, err := range s.GetErrors() {
var configErr *sitemap.ConfigError
if errors.As(err, &configErr) {
fmt.Println(configErr.Field, "-", configErr.Err)
}
}
content := `<urlset><url><loc>https://example.com/</loc></url></urlset>`
if _, err := s.Parse("https://example.com/sitemap.xml", &content); err != nil {
fmt.Println(err)
}
s.SetMaxDepth(5)
if _, err := s.Parse("https://example.com/sitemap.xml", &content); err != nil {
log.Fatal(err)
}
fmt.Println(s.GetURLCount(), "URL")
}
Output: maxDepth - must be greater than 0, got 0 errors occurred before parsing, see GetErrors() for details 1 URL
func (*ConfigError) Error ¶ added in v0.9.0
func (e *ConfigError) Error() string
Error returns the message of the error, which names the configuration field and tells what is wrong with the value it was given: `config "maxDepth": must be greater than 0, got 0`.
func (*ConfigError) Unwrap ¶ added in v0.9.0
func (e *ConfigError) Unwrap() error
Unwrap returns the underlying error, enabling errors.Is / errors.As traversal.
type Image ¶ added in v0.7.0
type Image struct {
// Loc is the URL of the image, the content of <image:loc>. It is never empty, and it
// may be on another host than the page. Strict mode requires an absolute HTTP or
// HTTPS URL: an image whose Loc is not one is left out and reported.
Loc string `xml:"http://www.google.com/schemas/sitemap-image/1.1 loc"`
// Title is the title of the image, the content of <image:title>.
Title string `xml:"http://www.google.com/schemas/sitemap-image/1.1 title"`
// Caption is the caption of the image, the content of <image:caption>.
Caption string `xml:"http://www.google.com/schemas/sitemap-image/1.1 caption"`
// GeoLocation is the place the image shows, the content of <image:geo_location>,
// such as "Limerick, Ireland".
GeoLocation string `xml:"http://www.google.com/schemas/sitemap-image/1.1 geo_location"`
// License is the URL of the licence of the image, the content of <image:license>.
// Strict mode reports one that is no absolute HTTP or HTTPS URL; the image is
// returned all the same, with the value as the document gives it.
License string `xml:"http://www.google.com/schemas/sitemap-image/1.1 license"`
}
Image is a structure of <image:image> in <url>, per the Google Image Sitemap extension. Reference: https://developers.google.com/search/docs/crawling-indexing/sitemaps/image-sitemaps
An image is known by its Loc, and one without a Loc is not among the images of a URL. Every other field is empty when the image has no element for it.
Example ¶
The images of a page are those of its entry that have a location.
package main
import (
"fmt"
"log"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
<url>
<loc>https://example.com/gallery</loc>
<image:image>
<image:loc>https://cdn.example.net/harbour.jpg</image:loc>
<image:title>The harbour at dawn</image:title>
</image:image>
<image:image>
<image:loc>https://cdn.example.net/lighthouse.jpg</image:loc>
</image:image>
</url>
</urlset>`
s := sitemap.New()
if _, err := s.Parse("https://example.com/sitemap.xml", &content); err != nil {
log.Fatal(err)
}
for _, u := range s.GetURLs() {
for _, image := range u.Images {
fmt.Printf("%s %q\n", image.Loc, image.Title)
}
}
}
Output: https://cdn.example.net/harbour.jpg "The harbour at dawn" https://cdn.example.net/lighthouse.jpg ""
type LastModTime ¶ added in v1.0.0
type LastModTime struct {
// Time is the date and time the element gives. Where the element gives a day and no
// time, the time is midnight in UTC; where it gives a month or a year and no more,
// the date is the first day of that month or of that year.
time.Time
}
LastModTime is the date, or the date and time, that an element of a document holds: a <lastmod> of a sitemap or of a sitemap index, a date element of an extension, the <pubDate> of an RSS item or the <updated> of an Atom entry. It embeds time.Time.
In the URLs and the sitemaps that Parse and ParseContext collect, a field of this type is nil when the element is absent, when it is empty, and when what it holds cannot be parsed. A field that is set therefore holds the date the document gives.
Example ¶
A date is given as a year, a month, a day, or a day and a time with its time zone. LastModTime embeds time.Time, whose methods it therefore has.
package main
import (
"fmt"
"log"
"time"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>https://example.com/year</loc><lastmod>2024</lastmod></url>
<url><loc>https://example.com/day</loc><lastmod>2024-03-01</lastmod></url>
<url><loc>https://example.com/time</loc><lastmod>2024-03-01T10:30:00+01:00</lastmod></url>
</urlset>`
s := sitemap.New()
if _, err := s.Parse("https://example.com/sitemap.xml", &content); err != nil {
log.Fatal(err)
}
since := time.Date(2024, time.February, 1, 0, 0, 0, 0, time.UTC)
for _, u := range s.GetURLs() {
fmt.Println(u.LastMod.Format(time.RFC3339), u.LastMod.After(since))
}
}
Output: 2024-01-01T00:00:00Z false 2024-03-01T00:00:00Z true 2024-03-01T10:30:00+01:00 true
func (*LastModTime) UnmarshalXML ¶ added in v1.0.0
func (l *LastModTime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error
UnmarshalXML implements xml.Unmarshaler, for code that decodes sitemap XML with encoding/xml itself. It accepts a date or a date and time in one of the W3C formats the sitemaps.org protocol allows, and fails for anything else.
An element that is empty, or holds only whitespace, is not an error and leaves l as it is. Note that encoding/xml allocates a pointer field for every element it meets, so a *LastModTime field decoded this way is not nil for an empty element: it holds the zero time, which IsZero tells. Parse and ParseContext do not read dates this way; in the URLs they collect, the field of an empty element is nil.
type NetworkError ¶ added in v0.9.0
type NetworkError struct {
// URL is the URL that was being fetched when the error occurred.
URL string
// Err is the underlying network or HTTP error.
Err error
}
NetworkError is returned when an HTTP fetch fails. Callers can inspect URL to determine which resource could not be retrieved.
A response that is larger than the limit set with SetMaxResponseSize is such a failure: Err wraps ErrMaxResponseSize then, which errors.Is matches.
Example usage:
var netErr *sitemap.NetworkError
if errors.As(err, &netErr) {
fmt.Println("failed to fetch:", netErr.URL)
}
Example ¶
A document that cannot be fetched is reported by a NetworkError. It fails the call when it is the document the call was made for.
package main
import (
"errors"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
server := httptest.NewServer(http.NotFoundHandler())
defer server.Close()
s := sitemap.New()
_, err := s.Parse(server.URL+"/sitemap.xml", nil)
var networkErr *sitemap.NetworkError
if errors.As(err, &networkErr) {
fmt.Println(strings.TrimPrefix(networkErr.URL, server.URL))
fmt.Println(networkErr.Err)
}
}
Output: /sitemap.xml received HTTP status 404
func (*NetworkError) Error ¶ added in v0.9.0
func (e *NetworkError) Error() string
Error returns the message of the error, which names the URL and tells why it could not be fetched: `fetch "https://example.com/sitemap.xml": received HTTP status 404`.
func (*NetworkError) Unwrap ¶ added in v0.9.0
func (e *NetworkError) Unwrap() error
Unwrap returns the underlying error, enabling errors.Is / errors.As traversal.
type News ¶ added in v0.7.0
type News struct {
// Publication is the publication the article appears in, the <news:publication> of
// the entry.
Publication NewsPublication `xml:"http://www.google.com/schemas/sitemap-news/0.9 publication"`
// PublicationDate is the date the article was published, the content of
// <news:publication_date>. It is nil if the element is absent, if it is empty, and
// if what it holds cannot be parsed, which is reported in both modes. Strict mode
// reports news without one.
PublicationDate *LastModTime `xml:"http://www.google.com/schemas/sitemap-news/0.9 publication_date"`
// Title is the title of the article, the content of <news:title>. It is empty if
// the element is absent. Strict mode reports news without one.
Title string `xml:"http://www.google.com/schemas/sitemap-news/0.9 title"`
}
News is a structure of <news:news> in <url>, per the Google News Sitemap extension. Reference: https://developers.google.com/search/docs/crawling-indexing/sitemaps/news-sitemap
Strict mode reports each of the fields the extension requires and the entry lacks: the name and the language of the publication, the publication date and the title. The news is returned all the same, with what the entry does give.
Example ¶
The news of a page is nil unless its entry has a <news:news> element.
package main
import (
"fmt"
"log"
"time"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:news="http://www.google.com/schemas/sitemap-news/0.9">
<url>
<loc>https://example.com/news/harbour-reopens</loc>
<news:news>
<news:publication>
<news:name>The Example Times</news:name>
<news:language>en</news:language>
</news:publication>
<news:publication_date>2024-03-01</news:publication_date>
<news:title>The harbour reopens</news:title>
</news:news>
</url>
</urlset>`
s := sitemap.New()
if _, err := s.Parse("https://example.com/sitemap.xml", &content); err != nil {
log.Fatal(err)
}
for _, u := range s.GetURLs() {
if u.News == nil {
continue
}
fmt.Println(u.News.Title)
fmt.Println(u.News.Publication.Name, u.News.Publication.Language)
fmt.Println(u.News.PublicationDate.Format(time.DateOnly))
}
}
Output: The harbour reopens The Example Times en 2024-03-01
type NewsPublication ¶ added in v0.7.0
type NewsPublication struct {
// Name is the name of the publication, the content of <news:name>. It is empty if
// the element is absent. Strict mode reports news without one.
Name string `xml:"http://www.google.com/schemas/sitemap-news/0.9 name"`
// Language is the language of the publication, the content of <news:language>: a
// language code such as "en". It is empty if the element is absent. Strict mode
// reports news without one.
Language string `xml:"http://www.google.com/schemas/sitemap-news/0.9 language"`
}
NewsPublication is a structure of <news:publication> in <news:news>.
type ParseError ¶ added in v0.9.0
type ParseError struct {
// URL is the sitemap URL that was being parsed when the error occurred. For a sitemap
// reached through a redirect it is the URL the sitemap was served from.
// When the depth limit is reached, it is the sitemap index whose sitemaps were not
// followed. When the limit on the sitemaps, on the URLs or on the validation errors of a
// call is reached, or the call was cut short, it is the URL the call was made for.
URL string
// Err is the underlying parse error.
Err error
}
ParseError is returned when XML or gzip parsing of a sitemap document fails. Callers can inspect URL to determine which sitemap could not be parsed.
It is also the error of a parse that was not carried through for another reason: the sitemaps a sitemap index lists were not followed because the depth limit is reached, sitemaps, URLs or validation errors were left out because the limit on the sitemaps, on the URLs or on the validation errors of a call is reached, or the call was cut short because its context is done. In the latter case Err is the error of the context, so errors.Is matches context.Canceled and context.DeadlineExceeded. When a limit is reached, Err wraps the error that stands for the limit, which errors.Is matches as well: ErrMaxDepth, ErrMaxSitemaps, ErrMaxURLs or ErrMaxValidationErrors, and ErrMaxResponseSize for gzip content that is too large.
Example usage:
var parseErr *sitemap.ParseError
if errors.As(err, &parseErr) {
fmt.Println("failed to parse sitemap:", parseErr.URL)
}
Example ¶
A document that cannot be parsed is reported by a ParseError.
package main
import (
"errors"
"fmt"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `<html><body>Not found</body></html>`
s := sitemap.New()
_, err := s.Parse("https://example.com/sitemap.xml", &content)
var parseErr *sitemap.ParseError
if errors.As(err, &parseErr) {
fmt.Println(parseErr.URL)
fmt.Println(parseErr.Err)
}
}
Output: https://example.com/sitemap.xml unrecognized sitemap format (root element: "html")
func (*ParseError) Error ¶ added in v0.9.0
func (e *ParseError) Error() string
Error returns the message of the error, which names the URL and tells why the parse failed or was not carried through: `parse "https://example.com/sitemap.xml": sitemap content is empty`.
func (*ParseError) Unwrap ¶ added in v0.9.0
func (e *ParseError) Unwrap() error
Unwrap returns the underlying error, enabling errors.Is / errors.As traversal.
type S ¶
type S struct {
// contains filtered or unexported fields
}
S is a sitemap parser. An instance holds the configuration of the parser, which its Set methods change and its Get methods of the same names return, and the results of its most recent Parse or ParseContext call: the URLs the call collected, the sitemaps it found on its way and the errors it recorded, see GetURLs, GetSitemaps and GetErrors.
An instance is made with New, which gives the configuration its defaults. The zero value has none of them and is not meant to be used.
The methods of an instance may be called from several goroutines. An instance carries out one call at a time, and can be used for any number of calls one after the other.
func New ¶
func New() *S
New creates a new instance of the S structure. It initializes the structure with default configuration values and returns a pointer to the created instance.
Example ¶
The Set methods return the instance, so they can be chained.
package main
import (
"fmt"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
s := sitemap.New().
SetUserAgent("my-crawler/1.0").
SetFetchTimeout(10).
SetMaxDepth(3)
fmt.Println(s.GetUserAgent())
fmt.Println(s.GetFetchTimeout())
fmt.Println(s.GetMaxDepth())
fmt.Println(s.GetStrict())
}
Output: my-crawler/1.0 10 3 false
func (*S) Errors ¶ added in v1.3.0
Errors returns an iterator over the errors GetErrors returns, in the same order:
for err := range s.Errors() {
log.Print(err)
}
It goes through the errors as they are when the loop starts. Errors that are recorded while the loop runs are not among them, and a Parse or ParseContext call that starts in the meantime does not disturb it. No lock is held while the loop runs, so the loop may call every method of s. The iterator can be used for any number of loops, each of which starts anew.
The errors are the values the instance keeps, the very error Parse or ParseContext returned among them, so that they can be compared with it. The list is not handed out: nothing a loop does changes which errors the instance returns, or their order. The iterator yields nothing for a nil *S.
Example ¶
The iterator yields the errors GetErrors returns.
package main
import (
"fmt"
"log"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>ftp://example.com/files</loc></url>
<url><loc>mailto:someone@example.com</loc></url>
</urlset>`
s := sitemap.New()
if _, err := s.Parse("https://example.com/sitemap.xml", &content); err != nil {
log.Fatal(err)
}
for err := range s.Errors() {
fmt.Println(err)
}
}
Output: validate "ftp://example.com/files": unsupported scheme "ftp" validate "mailto:someone@example.com": unsupported scheme "mailto"
func (*S) GetErrors ¶
GetErrors returns the errors of the most recent Parse or ParseContext call, together with the configuration errors that are outstanding: those recorded by a Set method that was called with a value it does not accept, and not again with a valid one since. The errors of a call are discarded when the next call starts; configuration errors are kept until the setting is corrected.
Each error is a *ConfigError, a *NetworkError, a *ParseError or a *ValidationError, to be told apart with errors.As. They are in the order they were recorded in. With multi-threading on, see SetMultiThread, that order is not the same from one call to the next for the errors of different sitemaps.
An error that tells a limit of the call was reached wraps the error that stands for the limit, to be told with errors.Is: ErrMaxResponseSize, ErrMaxDepth, ErrMaxSitemaps, ErrMaxURLs or ErrMaxValidationErrors.
No more *ValidationError values are among them than the limit set with SetMaxValidationErrors allows. If a call had more to record, it left the later ones out and recorded the error of that limit.
The error Parse or ParseContext returned is among them, unless it tells that configuration errors are outstanding: then the configuration errors are. A call that returns a nil error may have recorded errors as well, about sitemaps and entries it had to skip and about limits it reached.
While a call is running, GetErrors returns the errors recorded so far; those recorded afterwards are not added to the slice it returned.
The slice that is returned is the list the instance keeps, not a copy of it. It must not be modified: an error that is set or moved in it is set or moved for every other caller as well. Appending to it is safe, as the slice has no capacity to spare and append therefore copies it. Errors goes through the same errors without handing the list out, and slices.Collect(s.Errors()) returns a list the caller may sort or change. The slice is nil for a nil *S.
Example ¶
The errors of a call are of four types, which errors.As tells apart. An entry or a value that is not valid does not fail the call: it is left out, and reported here.
package main
import (
"errors"
"fmt"
"log"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>https://example.com/</loc></url>
<url><loc>ftp://example.com/files</loc></url>
<url><loc>https://example.com/about</loc><lastmod>yesterday</lastmod></url>
</urlset>`
s := sitemap.New()
if _, err := s.Parse("https://example.com/sitemap.xml", &content); err != nil {
log.Fatal(err)
}
fmt.Println(s.GetURLCount(), "URLs,", s.GetErrorsCount(), "errors")
for _, err := range s.GetErrors() {
var validationErr *sitemap.ValidationError
if errors.As(err, &validationErr) {
fmt.Printf("%s: %v\n", validationErr.URL, validationErr.Err)
}
}
}
Output: 2 URLs, 2 errors ftp://example.com/files: unsupported scheme "ftp" https://example.com/about: invalid <lastmod> value "yesterday"
func (*S) GetErrorsCount ¶
GetErrorsCount returns the number of errors GetErrors returns. It returns 0 for a nil *S.
func (*S) GetFetchTimeout ¶ added in v1.0.0
GetFetchTimeout returns the current fetch timeout in seconds.
func (*S) GetFollow ¶ added in v1.0.0
GetFollow returns a copy of the current follow regex pattern strings.
func (*S) GetHTTPClient ¶ added in v1.0.0
GetHTTPClient returns the custom HTTP client, or nil if the default client behaviour is used.
func (*S) GetMaxConcurrency ¶ added in v1.0.0
GetMaxConcurrency returns the maximum number of sitemaps fetched or parsed at the same time. A value of 0 means unlimited concurrency.
func (*S) GetMaxDepth ¶ added in v1.0.0
GetMaxDepth returns the maximum recursion depth for following sitemap indexes.
func (*S) GetMaxResponseSize ¶ added in v1.0.0
GetMaxResponseSize returns the maximum allowed HTTP response size in bytes.
func (*S) GetMaxSitemaps ¶ added in v1.2.0
GetMaxSitemaps returns the maximum number of sitemaps a Parse or ParseContext call fetches, which is the maximum number of sitemaps it lists as well, see SetMaxSitemaps. A value of 0 means no limit.
func (*S) GetMaxURLs ¶ added in v1.2.0
GetMaxURLs returns the maximum number of URLs a Parse or ParseContext call collects. A value of 0 means no limit.
func (*S) GetMaxValidationErrors ¶ added in v1.3.0
GetMaxValidationErrors returns the maximum number of validation errors a Parse or ParseContext call records, see SetMaxValidationErrors. A value of 0 means no limit.
func (*S) GetMultiThread ¶ added in v1.0.0
GetMultiThread returns whether multi-threaded fetching and parsing is enabled.
func (*S) GetRandomURLs ¶
GetRandomURLs returns a slice of randomly selected URLs from the S object's URL list. The number of URLs to select is specified by the parameter n. If n exceeds the number of URLs in the list, all of them are returned, in random order. If n is zero or negative, or the S object is nil, an empty slice is returned. The function creates a copy of the original URLs list and randomly selects n URLs from it, removing them to avoid duplicates. The selected URLs are returned as a new slice. Each of them is a copy that is the caller's own, with everything it points to, like the ones URLs yields.
Example ¶
No URL is returned twice, and no more of them than there are.
package main
import (
"fmt"
"log"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>https://example.com/1</loc></url>
<url><loc>https://example.com/2</loc></url>
<url><loc>https://example.com/3</loc></url>
</urlset>`
s := sitemap.New()
if _, err := s.Parse("https://example.com/sitemap.xml", &content); err != nil {
log.Fatal(err)
}
fmt.Println(len(s.GetRandomURLs(2)))
for _, u := range s.GetRandomURLs(5) {
fmt.Println(u.Loc)
}
}
Output: 2 https://example.com/1 https://example.com/2 https://example.com/3
func (*S) GetRules ¶ added in v1.0.0
GetRules returns a copy of the current URL filter regex pattern strings.
func (*S) GetSitemapCount ¶ added in v1.3.0
GetSitemapCount returns the number of sitemaps GetSitemaps returns. It returns 0 for a nil *S.
func (*S) GetSitemaps ¶ added in v1.3.0
GetSitemaps returns the sitemaps that the documents of the most recent Parse or ParseContext call list: the entries of the sitemap indexes the call read, and the Sitemap lines of the robots.txt it was made for.
A sitemap is among them whether or not the call fetched it. One that the patterns set with SetFollow leave out is, and so is one that could not be fetched, and one that a limit of the call kept from being fetched. A location that is no URL to request is not: it is reported in GetErrors instead. Neither is the document the call was made for, unless a document of the call lists it.
Each sitemap is returned once, however many documents list it, and by the URL it is requested by, see Sitemap. The sitemaps of a document are in the order the document lists them. With multi-threading off, the documents are read one after the other, so the order is the same from one call to the next; with multi-threading on, see SetMultiThread, it is not for the sitemaps of different documents.
No more sitemaps are returned than the limit set with SetMaxSitemaps allows. If the documents list more, the first ones the call found are returned, and the error that tells the limit was reached is recorded. The sitemaps that are listed and the ones that are fetched are counted apart, so the ones returned then need not be the ones that were fetched.
A sitemap that a sitemap index lists comes with the date the index gives for it, see Sitemap.LastMod.
While a call is running, GetSitemaps returns the sitemaps found so far. The slice that is returned is a copy the caller may modify, the dates in it included. It is never nil, and empty for a nil *S.
Example ¶
The sitemaps a sitemap index lists can be read without any of them being fetched, with a follow pattern that no URL matches.
package main
import (
"fmt"
"log"
"time"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>https://example.com/sitemap-pages.xml</loc><lastmod>2024-03-01</lastmod></sitemap>
<sitemap><loc>https://example.com/sitemap-blog.xml</loc></sitemap>
</sitemapindex>`
s := sitemap.New().SetFollow([]string{`^$`})
if _, err := s.Parse("https://example.com/sitemap-index.xml", &content); err != nil {
log.Fatal(err)
}
fmt.Println(s.GetSitemapCount(), "sitemaps")
for _, listed := range s.GetSitemaps() {
if listed.LastMod == nil {
fmt.Println(listed.Loc, "(no date)")
continue
}
fmt.Println(listed.Loc, listed.LastMod.Format(time.DateOnly))
}
}
Output: 2 sitemaps https://example.com/sitemap-pages.xml 2024-03-01 https://example.com/sitemap-blog.xml (no date)
func (*S) GetStrict ¶ added in v1.0.0
GetStrict returns whether strict URL validation mode is enabled.
func (*S) GetURLCount ¶
GetURLCount returns the count of URLs in the S struct.
func (*S) GetURLs ¶
GetURLs returns the URLs of the most recent Parse or ParseContext call: the pages that the sitemaps it read list, less the ones the patterns set with SetRules leave out.
The URLs of a document are in the order the document lists them. With multi-threading off, the documents are read one after the other, so the order is the same from one call to the next; with multi-threading on, see SetMultiThread, it is not for the URLs of different documents. No more URLs are returned than the limit set with SetMaxURLs allows.
While a call is running, GetURLs returns the URLs collected so far; those collected afterwards are not added to the slice it returned.
The slice that is returned is the list the instance keeps, not a copy of it, so a call costs the same however many URLs there are. It must not be modified: a URL that is set or moved in it, as sorting it does, or changed through one of its pointers, is changed for every other caller as well, and for GetRandomURLs and URLs. Appending to it is safe, as the slice has no capacity to spare and append therefore copies it. URLs goes through the same URLs as copies, and slices.Collect(s.URLs()) returns a list the caller may sort or change. The slice is never nil, and empty for a nil *S.
func (*S) GetUserAgent ¶ added in v1.0.0
GetUserAgent returns the current user agent string used for HTTP requests.
func (*S) Parse ¶
Parse is a method of the S structure. It parses the given URL and its content.
Parse is a backward-compatible wrapper around ParseContext that uses context.Background(). For new code, prefer ParseContext so that callers can propagate cancellation and deadlines.
An instance can be reused: every call first discards the URLs, sitemaps and errors collected by the previous call. Configuration errors recorded by the Set* methods are the exception. They persist across calls, and while any of them is outstanding Parse does not parse anything and returns an error with the message "errors occurred before parsing, see GetErrors() for details". Calling the same setter again with a valid value clears its error.
A sitemap that is reached through a redirect is located at the URL it was served from, not at the URL that was requested: relative URLs in it are resolved against that URL, strict mode compares the URLs it lists with that URL, and the errors about the document name it.
It sets the mainURL field to the given URL. The content is the given URL content or, if that is nil, what the URL serves. If the URL is that of a robots.txt, it parses the robots.txt file and fetches URLs from the sitemap files mentioned in the robots.txt. A URL is that of a robots.txt when the last segment of its path is "robots.txt", in whichever letter case it is written and whether or not a query or a fragment follows it: "https://example.com/robots.txt?v=2" is one. If the URL is not that of a robots.txt, the content is checked and unzipped if necessary, then parsed and fetched. The content of a document is not kept once the document is parsed.
A call fetches no more sitemaps than SetMaxSitemaps allows, and collects no more URLs than SetMaxURLs allows, so that it comes to an end whatever the documents list. It records no more validation errors than SetMaxValidationErrors allows.
The sitemaps the documents of the call list are returned by GetSitemaps, whether or not they were fetched.
It returns the S structure, and a nil error if the document at the given URL was fetched and parsed. Otherwise the error tells why it was not:
- a configuration error is outstanding, see above;
- the URL is not valid: a *ValidationError;
- the document could not be fetched: a *NetworkError;
- the document could not be parsed: a *ParseError.
Except for the first, the error returned is the one GetErrors() holds about it. What goes wrong further on is in GetErrors() only and does not fail the call: a sitemap the document lists that cannot be fetched or parsed, a limit that is reached, an entry that is not valid. The error of a limit wraps the error that stands for it, such as ErrMaxURLs, so errors.Is tells which limit was reached.
Example ¶
A call follows the sitemaps the document at its URL lists: here a robots.txt names a sitemap index, which lists two sitemaps. The site is served by a local test server, and "{site}" stands for its address.
package main
import (
"fmt"
"log"
"net/http"
"net/http/httptest"
"strings"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
documents := map[string]string{
"/robots.txt": "User-agent: *\nSitemap: {site}/sitemap-index.xml\n",
"/sitemap-index.xml": `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>{site}/sitemap-pages.xml</loc></sitemap>
<sitemap><loc>{site}/sitemap-blog.xml</loc></sitemap>
</sitemapindex>`,
"/sitemap-pages.xml": `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>{site}/</loc></url>
<url><loc>{site}/about</loc></url>
</urlset>`,
"/sitemap-blog.xml": `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>{site}/blog/first-post</loc></url>
</urlset>`,
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, strings.ReplaceAll(documents[r.URL.Path], "{site}", "http://"+r.Host))
}))
defer server.Close()
// The sitemaps are fetched one at a time, so that their URLs are in the same order
// every time.
s := sitemap.New().SetMultiThread(false)
if _, err := s.Parse(server.URL+"/robots.txt", nil); err != nil {
log.Fatal(err)
}
for _, u := range s.GetURLs() {
fmt.Println(strings.TrimPrefix(u.Loc, server.URL))
}
}
Output: / /about /blog/first-post
Example (Rss) ¶
The pages of an RSS feed are its items. The date of a page is the <pubDate> of its item.
package main
import (
"fmt"
"log"
"time"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>Example blog</title>
<item>
<link>https://example.com/blog/first-post</link>
<pubDate>Mon, 15 Jan 2024 10:30:00 GMT</pubDate>
</item>
<item>
<link>https://example.com/blog/second-post</link>
</item>
</channel>
</rss>`
s := sitemap.New()
if _, err := s.Parse("https://example.com/feed.xml", &content); err != nil {
log.Fatal(err)
}
for _, u := range s.GetURLs() {
if u.LastMod == nil {
fmt.Println(u.Loc, "(no date)")
continue
}
fmt.Println(u.Loc, u.LastMod.Format(time.RFC3339))
}
}
Output: https://example.com/blog/first-post 2024-01-15T10:30:00Z https://example.com/blog/second-post (no date)
Example (Text) ¶
A text sitemap has a URL on a line. Empty lines and comments are skipped.
package main
import (
"fmt"
"log"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `# pages
https://example.com/
https://example.com/about
https://example.com/contact
`
s := sitemap.New()
if _, err := s.Parse("https://example.com/sitemap.txt", &content); err != nil {
log.Fatal(err)
}
for _, u := range s.GetURLs() {
fmt.Println(u.Loc)
}
}
Output: https://example.com/ https://example.com/about https://example.com/contact
func (*S) ParseContext ¶ added in v0.4.0
ParseContext parses the given URL and its content, honoring the supplied context for cancellation and deadlines.
The context is propagated through every HTTP request issued by the parser (both the initial fetch and the recursive sitemap-index/urlset fetches), so cancelling ctx aborts in-flight downloads and prevents new ones from starting. Already-parsed URLs accumulated in s.urls before cancellation remain available via GetURLs().
If ctx is done by the time the call ends, ParseContext returns a *ParseError that names the given URL and wraps the error of ctx, so errors.Is matches context.Canceled and context.DeadlineExceeded. The same error is recorded in the error list, once for the call: whether the sitemaps are fetched concurrently or one at a time, and however many of them were not fetched. A request that was cut short is recorded as the *NetworkError of that sitemap in addition.
A call that fails for the given URL itself, as described at Parse, returns and records that error alone. This is the case when it is the request for the given URL that is cut short: the error is the *NetworkError of that request, which wraps the error of ctx as well.
All other semantics match Parse.
Example ¶
A call is cut short when its context is done. The error it returns then wraps the error of the context, so errors.Is matches it.
package main
import (
"context"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"time"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
// The server does not answer before the request is given up.
server := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
<-r.Context().Done()
}))
defer server.Close()
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
s := sitemap.New()
_, err := s.ParseContext(ctx, server.URL+"/sitemap.xml", nil)
fmt.Println(errors.Is(err, context.DeadlineExceeded))
}
Output: true
func (*S) SetFetchTimeout ¶
SetFetchTimeout sets the fetch timeout for the Sitemap Parser. The fetch timeout determines how long the parser will wait for an HTTP request to complete. It should be specified in seconds as a uint16 value and must be greater than 0. Invalid values are ignored and a *ConfigError is recorded; a later call with a valid value clears it. Note: when a custom HTTP client is set via SetHTTPClient, this value has no effect. The function returns a pointer to the S structure to allow method chaining.
func (*S) SetFollow ¶ added in v0.1.3
SetFollow sets the follow patterns using the provided list of regex strings and compiles them into regex objects. When patterns are set, only the sitemaps whose URL matches one of them are fetched, whether a sitemap index lists them or a robots.txt names them. The URL passed to Parse is always fetched. Patterns longer than maxRegexPatternLength characters are rejected with a *ConfigError. Any errors encountered during compilation are recorded as *ConfigError values. Each call replaces both the patterns and the errors recorded by the previous call. The function returns a pointer to the S structure to allow method chaining.
Example ¶
Only the sitemaps whose URL matches a pattern are fetched. The others are still among the sitemaps the call lists.
package main
import (
"fmt"
"log"
"net/http"
"net/http/httptest"
"strings"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
documents := map[string]string{
"/sitemap-index.xml": `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>{site}/sitemap-products.xml</loc></sitemap>
<sitemap><loc>{site}/sitemap-blog.xml</loc></sitemap>
</sitemapindex>`,
"/sitemap-products.xml": `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>{site}/products/1</loc></url>
</urlset>`,
"/sitemap-blog.xml": `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>{site}/blog/first-post</loc></url>
</urlset>`,
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, strings.ReplaceAll(documents[r.URL.Path], "{site}", "http://"+r.Host))
}))
defer server.Close()
s := sitemap.New().SetFollow([]string{`/sitemap-blog\.xml$`})
if _, err := s.Parse(server.URL+"/sitemap-index.xml", nil); err != nil {
log.Fatal(err)
}
for _, u := range s.GetURLs() {
fmt.Println("URL:", strings.TrimPrefix(u.Loc, server.URL))
}
for _, listed := range s.GetSitemaps() {
fmt.Println("sitemap:", strings.TrimPrefix(listed.Loc, server.URL))
}
}
Output: URL: /blog/first-post sitemap: /sitemap-products.xml sitemap: /sitemap-blog.xml
func (*S) SetHTTPClient ¶ added in v0.6.0
SetHTTPClient sets a custom HTTP client for the Sitemap Parser. When set, the provided client is used for all HTTP requests instead of the internally created default client. This allows callers to configure custom transports, proxies, TLS settings, authentication, or timeout strategies. When a custom client is provided, SetFetchTimeout has no effect; the client's own Timeout field controls the request deadline. Pass nil to reset to the default client behaviour. The function returns a pointer to the S structure to allow method chaining.
Example ¶
A client of the caller's own is used for every request of the instance. Here it is the client of a test server that speaks HTTPS, which trusts the certificate of that server.
package main
import (
"fmt"
"log"
"net/http"
"net/http/httptest"
"strings"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `<urlset><url><loc>https://%s/page</loc></url></urlset>`, r.Host)
}))
defer server.Close()
s := sitemap.New().SetHTTPClient(server.Client())
if _, err := s.Parse(server.URL+"/sitemap.xml", nil); err != nil {
log.Fatal(err)
}
for _, u := range s.GetURLs() {
fmt.Println(strings.TrimPrefix(u.Loc, server.URL))
}
}
Output: /page
func (*S) SetMaxConcurrency ¶ added in v0.4.0
SetMaxConcurrency sets the maximum number of sitemaps that are worked on at the same time when multi-threaded parsing is enabled. The default is 16. A value of 0 means unlimited concurrency. A positive value caps the number of sitemaps that are being fetched or parsed at any one time across the recursive sitemap-index traversal: a sitemap counts from the moment its request is sent until its content is parsed. That bounds the connections in use and the documents held in memory, which is recommended for very large sitemap indexes to avoid goroutine and connection blow-up. Negative values are rejected and a *ConfigError is recorded; a later call with a valid value clears it. The function returns a pointer to the S structure to allow method chaining.
func (*S) SetMaxDepth ¶ added in v0.3.0
SetMaxDepth sets the maximum recursion depth for following sitemap indexes. A sitemap index may reference other sitemap indexes; this limits how many levels deep the parser will follow. The default is 10. When the limit is reached, a *ParseError is recorded for every sitemap index whose sitemaps are not followed. It names that sitemap index and wraps ErrMaxDepth, which errors.Is matches. The value must be greater than 0; invalid values are ignored and a *ConfigError is recorded. A later call with a valid value clears it. The function returns a pointer to the S structure to allow method chaining.
func (*S) SetMaxResponseSize ¶ added in v0.3.0
SetMaxResponseSize sets the maximum allowed HTTP response size in bytes. Responses exceeding this limit are rejected with a *NetworkError. The same limit caps the decompressed size of gzip-compressed content, of all the members of a gzip file together, whether it was fetched or supplied through the urlContent argument of Parse; content that expands beyond it is rejected with a *ParseError. Both errors wrap ErrMaxResponseSize, which errors.Is matches. The default is 50 MB, matching the sitemaps.org protocol limit. The value must be greater than 0; invalid values are ignored and a *ConfigError is recorded. A later call with a valid value clears it. Every value greater than 0 is a limit, math.MaxInt64 included: nothing can exceed that one, so it is the way to have no limit in effect. The function returns a pointer to the S structure to allow method chaining.
func (*S) SetMaxSitemaps ¶ added in v1.2.0
SetMaxSitemaps sets the maximum number of sitemaps a Parse or ParseContext call fetches. The default is 50,000, which is as many sitemaps as a sitemap index may list according to the sitemaps.org protocol. A value of 0 means no limit.
The sitemaps a robots.txt or a sitemap index lists count, on every level together: each one the call requests, whether or not the request succeeds. A sitemap that is listed more than once is requested and counted once. The document the call is made for does not count.
Once the limit is reached, the sitemaps that are left are not fetched, and a *ParseError naming the URL the call was made for is recorded, once for the call. It wraps ErrMaxSitemaps, which errors.Is matches. The call does not fail: what the sitemaps fetched until then yielded is returned. With multi-threading off, the sitemaps are fetched in the order they are listed, so it is the first ones that are fetched; with multi-threading on, which ones are depends on how fast the server answers. Nothing is recorded if no sitemap had to be left out.
The same limit is on the sitemaps GetSitemaps returns, which are the ones the documents list whether or not they are fetched: no more of them are kept than the limit allows, and the same error is recorded if one had to be left out of them. A call that fetches fewer sitemaps than it finds, as one with patterns set with SetFollow does, can therefore reach the limit by the sitemaps it finds.
The limit bounds the number of requests a document can make the parser send, and the memory the sitemaps of a call take up. Without it, a server can keep a call busy for as long as it likes by listing sitemaps that list ever further sitemaps.
Negative values are rejected and a *ConfigError is recorded; a later call with a valid value clears it. The value that applies to a call is the one set when the call starts. The function returns a pointer to the S structure to allow method chaining.
func (*S) SetMaxURLs ¶ added in v1.2.0
SetMaxURLs sets the maximum number of URLs a Parse or ParseContext call collects. The default is 10,000,000. A value of 0 means no limit.
The URLs that GetURLs returns count. An entry that is not valid does not, and neither does a URL that the patterns set with SetRules leave out.
Once the limit is reached, the call stops collecting: what is left of the document that reached the limit is left out unread, so it adds no errors either, the sitemaps that are left are not fetched, and a *ParseError naming the URL the call was made for is recorded, once for the call. It wraps ErrMaxURLs, which errors.Is matches. The call does not fail: the URLs collected until then are returned. With multi-threading off, they are the first ones in the order the sitemaps list them; with multi-threading on, which ones they are depends on how fast the server answers. Nothing is recorded if nothing had to be left out.
The limit bounds the memory the URLs of a call take up, whatever the documents list. The default is a last resort rather than a tight bound: ten million URLs take up gigabytes. Set a lower limit for documents that are not trusted.
Negative values are rejected and a *ConfigError is recorded; a later call with a valid value clears it. The value that applies to a call is the one set when the call starts. The function returns a pointer to the S structure to allow method chaining.
Example ¶
A call collects no more URLs than the limit allows, and records that it had to leave some out. errors.Is tells the error of a limit from the others.
package main
import (
"errors"
"fmt"
"log"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>https://example.com/1</loc></url>
<url><loc>https://example.com/2</loc></url>
<url><loc>https://example.com/3</loc></url>
</urlset>`
s := sitemap.New().SetMaxURLs(2)
if _, err := s.Parse("https://example.com/sitemap.xml", &content); err != nil {
log.Fatal(err)
}
fmt.Println(s.GetURLCount(), "URLs")
for _, err := range s.GetErrors() {
if errors.Is(err, sitemap.ErrMaxURLs) {
fmt.Println(err)
}
}
}
Output: 2 URLs parse "https://example.com/sitemap.xml": limit of 2 URLs reached
func (*S) SetMaxValidationErrors ¶ added in v1.3.0
SetMaxValidationErrors sets the maximum number of validation errors a Parse or ParseContext call records. The default is 100,000. A value of 0 means no limit.
What is wrong with an entry of a document is recorded as a *ValidationError: a location that is no URL, a value that cannot be parsed, a field strict mode requires. How many entries of a document are not valid is up to the server, and such an entry does not count towards the limit set with SetMaxURLs. The errors of this type that GetErrors returns count, whatever they are about.
Once the limit is reached, the validation errors that are left are not recorded, and a *ParseError naming the URL the call was made for is recorded, once for the call. It wraps ErrMaxValidationErrors, which errors.Is matches. Nothing else changes: the documents are read to their end, and the URLs and the sitemaps of the call are the ones a call without the limit returns. With multi-threading off, the errors recorded are the first ones in the order the sitemaps list the entries; with multi-threading on, which ones they are depends on how fast the server answers. Nothing is recorded if no error had to be left out.
The errors of the other types do not count and are recorded whatever the limit is: a *NetworkError and a *ParseError are about a sitemap or about the call as a whole, so there is at most one of them for a sitemap the call fetches, and a few for the call. A call therefore still tells every sitemap it could not fetch or parse.
The limit bounds the memory the errors of a call take up, whatever the documents hold, together with the limit set with SetMaxSitemaps.
Negative values are rejected and a *ConfigError is recorded; a later call with a valid value clears it. The value that applies to a call is the one set when the call starts. The function returns a pointer to the S structure to allow method chaining.
func (*S) SetMultiThread ¶ added in v0.1.2
SetMultiThread sets the multi-threading for the Sitemap Parser. The multi-threading flag determines whether the parser should fetch and parse the sitemaps concurrently using goroutines. When it is off, the sitemaps are fetched one at a time and in the order they are listed, those a robots.txt names as well as those of a sitemap index, and SetMaxConcurrency has no effect. The function returns a pointer to the S structure to allow method chaining.
func (*S) SetRules ¶ added in v0.1.3
SetRules sets the rules patterns using the provided list of regex strings and compiles them into regex objects. Patterns longer than maxRegexPatternLength characters are rejected with a *ConfigError. Any errors encountered during compilation are recorded as *ConfigError values. Each call replaces both the patterns and the errors recorded by the previous call. The function returns a pointer to the S structure to allow method chaining.
Example ¶
Only the URLs that match a pattern are collected.
package main
import (
"fmt"
"log"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>https://example.com/</loc></url>
<url><loc>https://example.com/blog/first-post</loc></url>
<url><loc>https://example.com/products/1</loc></url>
<url><loc>https://example.com/blog/second-post</loc></url>
</urlset>`
s := sitemap.New().SetRules([]string{`^https://example\.com/blog/`})
if _, err := s.Parse("https://example.com/sitemap.xml", &content); err != nil {
log.Fatal(err)
}
for _, u := range s.GetURLs() {
fmt.Println(u.Loc)
}
}
Output: https://example.com/blog/first-post https://example.com/blog/second-post
func (*S) SetStrict ¶ added in v0.3.0
SetStrict enables or disables strict mode for URL validation. In strict mode, all URLs in sitemap <loc> elements must be absolute HTTP(S) URLs on the same host and protocol as the sitemap file, must hold neither a space, nor a control character, nor one of the characters " < > \ ^ ` { | }, which a URL has to give percent-encoded, and must not exceed 2048 characters, as required by the sitemaps.org specification. The host is compared without regard to letter case, and the default port of the protocol is the same as no port. In tolerant mode (default), relative URLs are resolved against the parent sitemap URL, and a space in a URL is percent-encoded. In both modes a URL has to name a host: one that names none is skipped and reported. The function returns a pointer to the S structure to allow method chaining.
Example ¶
Tolerant mode, the default, makes the best of a sitemap that does not keep to the protocol. Strict mode skips and reports the entries that do not.
package main
import (
"fmt"
"log"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>https://example.com/</loc></url>
<url><loc>/about</loc></url>
<url><loc>https://example.com/summer sale</loc></url>
<url><loc>https://other.example/page</loc></url>
</urlset>`
for _, strict := range []bool{false, true} {
s := sitemap.New().SetStrict(strict)
if _, err := s.Parse("https://example.com/sitemap.xml", &content); err != nil {
log.Fatal(err)
}
fmt.Println("strict:", strict)
for _, u := range s.GetURLs() {
fmt.Println(" URL:", u.Loc)
}
for _, err := range s.GetErrors() {
fmt.Println(" error:", err)
}
}
}
Output: strict: false URL: https://example.com/ URL: https://example.com/about URL: https://example.com/summer%20sale URL: https://other.example/page strict: true URL: https://example.com/ error: validate "/about": strict mode: unsupported scheme "" error: validate "https://example.com/summer sale": strict mode: URL contains a space error: validate "https://other.example/page": strict mode: host "other.example" does not match sitemap host "example.com"
func (*S) SetUserAgent ¶
SetUserAgent sets the user agent for the Sitemap Parser. The user agent is used for making HTTP requests when parsing and fetching URLs. It should be a string representing the user agent header value. The function returns a pointer to the S structure to allow method chaining.
func (*S) URLs ¶ added in v1.3.0
URLs returns an iterator over the URLs GetURLs returns, in the same order:
for u := range s.URLs() {
fmt.Println(u.Loc)
}
Each URL is a copy that is the caller's own. Everything it points to is copied with it, its dates and values and what the extensions say about the page, so nothing that is done to it reaches the URLs the instance keeps. The list itself is not copied: a loop takes up the memory of one URL at a time, however many there are.
slices.Collect(s.URLs()) returns the URLs as a list of such copies, which the caller may sort and change. It cannot tell how long the list is going to be and grows it as it goes, which takes several times the memory of the list where the list is long. Giving the list its capacity spares that:
urls := slices.AppendSeq(make([]sitemap.URL, 0, s.GetURLCount()), s.URLs())
It goes through the URLs as they are when the loop starts. URLs that are collected while the loop runs are not among them, and a Parse or ParseContext call that starts in the meantime does not disturb it. No lock is held while the loop runs, so the loop may call every method of s. The iterator can be used for any number of loops, each of which starts anew. The iterator yields nothing for a nil *S.
Example ¶
The iterator yields the URLs as copies, so they can be collected into a list that is the caller's own to sort, which the slice GetURLs returns is not.
package main
import (
"fmt"
"log"
"slices"
"strings"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>https://example.com/news</loc><priority>0.8</priority></url>
<url><loc>https://example.com/about</loc><priority>0.3</priority></url>
<url><loc>https://example.com/</loc><priority>1.0</priority></url>
</urlset>`
s := sitemap.New()
if _, err := s.Parse("https://example.com/sitemap.xml", &content); err != nil {
log.Fatal(err)
}
for u := range s.URLs() {
fmt.Println(u.Loc)
}
sorted := slices.SortedFunc(s.URLs(), func(a, b sitemap.URL) int {
return strings.Compare(a.Loc, b.Loc)
})
fmt.Println("sorted:")
for _, u := range sorted {
fmt.Println(u.Loc)
}
}
Output: https://example.com/news https://example.com/about https://example.com/ sorted: https://example.com/ https://example.com/about https://example.com/news
type Sitemap ¶ added in v1.3.0
type Sitemap struct {
// Loc is the URL the sitemap is requested by. It is never empty.
Loc string
// LastMod is the date the sitemap was last modified according to the sitemap index
// that lists it. It is nil if no date is given, or none that can be parsed.
LastMod *LastModTime
}
Sitemap is a sitemap that a document lists: an entry of a sitemap index, or a Sitemap line of a robots.txt. GetSitemaps returns the ones a call found.
Loc is the URL the sitemap is located at, the one it is requested by. It is an absolute URL that passed the checks a URL has to pass to be requested. In tolerant mode it can be requested as it is: a relative URL is resolved against the URL of the document that lists it, a space or a character that is not in ASCII is percent-encoded, and an internationalised host name is in its Punycode form. Strict mode returns it as the document gives it. A sitemap that is reached through a redirect is served from another URL than this one.
LastMod is the date the sitemap was last modified, as the sitemap index that lists it gives it: the <lastmod> of its entry. It is nil if the entry has none, if the element is empty, and if what it holds cannot be parsed, which is reported in GetErrors. It is nil for a sitemap that only a robots.txt names as well, which gives no date. If the documents of a call give more than one date for a sitemap, it is the latest of them.
Example ¶
A sitemap that a sitemap index lists comes with the date the index gives for it, which tells the sitemaps that changed since an earlier visit from the rest.
package main
import (
"fmt"
"log"
"time"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap><loc>https://example.com/sitemap-2023.xml</loc><lastmod>2023-12-31</lastmod></sitemap>
<sitemap><loc>https://example.com/sitemap-2024.xml</loc><lastmod>2024-03-01</lastmod></sitemap>
<sitemap><loc>https://example.com/sitemap-pages.xml</loc></sitemap>
</sitemapindex>`
// No sitemap is fetched: no URL matches the follow pattern.
s := sitemap.New().SetFollow([]string{`^$`})
if _, err := s.Parse("https://example.com/sitemap-index.xml", &content); err != nil {
log.Fatal(err)
}
lastVisit := time.Date(2024, time.January, 15, 0, 0, 0, 0, time.UTC)
for _, listed := range s.GetSitemaps() {
// A sitemap without a date may have changed as well.
if listed.LastMod == nil || listed.LastMod.After(lastVisit) {
fmt.Println(listed.Loc)
}
}
}
Output: https://example.com/sitemap-2024.xml https://example.com/sitemap-pages.xml
type URL ¶
type URL struct {
// Loc is the URL of the page: the content of the <loc> of a <url>, the <link> of an
// RSS item, the link of an Atom entry, or a line of a text sitemap. It is never
// empty: an entry without a location is reported, not returned.
Loc string `xml:"loc"`
// LastMod is the date the page was last modified. It is nil if the entry gives no
// date, if the element is empty, and if what it holds cannot be parsed, which is
// reported. Strict mode skips a <url> whose <lastmod> cannot be parsed.
LastMod *LastModTime `xml:"lastmod"`
// ChangeFreq tells how often the page is likely to change, the content of the
// <changefreq> of a <url>. It is nil if the element is absent or empty. A value
// that is set is to be compared with the ChangeFreq constants, see URLChangeFreq
// for what the two modes make of a value that is none of them.
ChangeFreq *URLChangeFreq `xml:"changefreq"`
// Priority is the priority of the page among the pages of its site, the content of
// the <priority> of a <url>: between 0.0 and 1.0 by the protocol. It is nil if the
// element is absent, and if what it holds is no number, which is reported. Tolerant
// mode returns a value outside that range as it is. Strict mode skips and reports a
// <url> whose <priority> is outside it or is no number.
Priority *float32 `xml:"priority"`
// Images are the images on the page, one for each <image:image> of the <url> that
// has a location, in the order of the document. It is empty or nil if there is
// none.
Images []Image `xml:"http://www.google.com/schemas/sitemap-image/1.1 image"`
// News is what the <url> tells about the page as a news article, its <news:news>.
// It is nil if the <url> has none.
News *News `xml:"http://www.google.com/schemas/sitemap-news/0.9 news"`
// Videos are the videos on the page, one for each <video:video> of the <url> that
// has a thumbnail location, in the order of the document. It is empty or nil if
// there is none.
Videos []Video `xml:"http://www.google.com/schemas/sitemap-video/1.1 video"`
// Hreflangs are the versions of the page in other languages and for other regions,
// one for each <xhtml:link> of the <url> that has an href, in the order of the
// document. It is empty or nil if there is none.
Hreflangs []AlternateLink `xml:"http://www.w3.org/1999/xhtml link"`
}
URL is a structure of <url> in <urlset>. It is also what a page of a sitemap in another format is returned as: an item of an RSS feed, an entry of an Atom feed, a line of a text sitemap.
LastMod is the date the page was last modified: the <lastmod> of a <url>, the <pubDate> of an RSS item and the <updated> of an Atom entry. A text sitemap gives no date. No other field but Loc is set for a page of a feed or of a text sitemap.
In the URLs that Parse and ParseContext collect, no text value has whitespace around it: the whitespace that surrounds the content of an element or the value of an attribute is not taken for a part of it. This goes for the fields of the extensions as well.
In tolerant mode Loc is a URL that can be requested as it is: a space or a character that is not in ASCII is percent-encoded in it, and an internationalised host name is in its Punycode form. A URL of an extension comes back the same way if it is an absolute HTTP or HTTPS URL, and as the document gives it otherwise: Image.Loc and Image.License, Video.ThumbnailLoc, Video.ContentLoc, Video.PlayerLoc and VideoUploader.Info, and AlternateLink.Href. Strict mode returns every URL as the document gives it.
Example ¶
The fields of a URL other than its location are pointers, which are nil for what the entry does not give.
package main
import (
"fmt"
"log"
"time"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/</loc>
<lastmod>2024-03-01</lastmod>
<changefreq>daily</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://example.com/about</loc>
</url>
</urlset>`
s := sitemap.New()
if _, err := s.Parse("https://example.com/sitemap.xml", &content); err != nil {
log.Fatal(err)
}
for _, u := range s.GetURLs() {
fmt.Println(u.Loc)
if u.LastMod != nil {
fmt.Println(" last modified:", u.LastMod.Format(time.DateOnly))
}
if u.ChangeFreq != nil {
fmt.Println(" changes:", *u.ChangeFreq)
}
if u.Priority != nil {
fmt.Println(" priority:", *u.Priority)
}
}
}
Output: https://example.com/ last modified: 2024-03-01 changes: daily priority: 0.8 https://example.com/about
type URLChangeFreq ¶ added in v0.3.0
type URLChangeFreq string
URLChangeFreq represents the frequency at which a URL should be crawled and indexed. Possible values are: "always", "hourly", "daily", "weekly", "monthly", "yearly", and "never".
In the URLs that Parse and ParseContext collect, a field of this type is nil when the <changefreq> element is absent or empty. In tolerant mode one of the values above that is written in another letter case, such as "Daily", is replaced by the value itself, so that it equals its constant, and any other value is kept as the document gives it. Strict mode skips and reports an entry whose value is not one of the values above as they are written here, so a field that is set equals one of the constants there.
Example ¶
A change frequency is compared with the constants of its type. Tolerant mode reads a value of the protocol in whichever letter case it is written.
package main
import (
"fmt"
"log"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>https://example.com/news</loc><changefreq>hourly</changefreq></url>
<url><loc>https://example.com/blog</loc><changefreq>Daily</changefreq></url>
<url><loc>https://example.com/about</loc><changefreq>yearly</changefreq></url>
</urlset>`
s := sitemap.New()
if _, err := s.Parse("https://example.com/sitemap.xml", &content); err != nil {
log.Fatal(err)
}
for _, u := range s.GetURLs() {
switch *u.ChangeFreq {
case sitemap.ChangeFreqAlways, sitemap.ChangeFreqHourly, sitemap.ChangeFreqDaily:
fmt.Println(u.Loc, "changes often")
default:
fmt.Println(u.Loc, "changes seldom")
}
}
}
Output: https://example.com/news changes often https://example.com/blog changes often https://example.com/about changes seldom
const ( // ChangeFreqAlways represents the "always" value for URLChangeFreq. ChangeFreqAlways URLChangeFreq = "always" // ChangeFreqHourly represents the "hourly" value for URLChangeFreq. ChangeFreqHourly URLChangeFreq = "hourly" // ChangeFreqDaily represents the "daily" value for URLChangeFreq. ChangeFreqDaily URLChangeFreq = "daily" // ChangeFreqWeekly represents the "weekly" value for URLChangeFreq. ChangeFreqWeekly URLChangeFreq = "weekly" // ChangeFreqMonthly represents the "monthly" value for URLChangeFreq. ChangeFreqMonthly URLChangeFreq = "monthly" // ChangeFreqYearly represents the "yearly" value for URLChangeFreq. ChangeFreqYearly URLChangeFreq = "yearly" // ChangeFreqNever represents the "never" value for URLChangeFreq. ChangeFreqNever URLChangeFreq = "never" )
type ValidationError ¶ added in v0.9.0
type ValidationError struct {
// URL is the URL value being validated. When it is not a URL that was rejected, it is the
// URL the rejected value belongs to: the page for a value of a <url> entry and for the
// date of an entry of a feed, the sitemap a sitemap index lists for the date the index
// gives for it, and the sitemap an entry stands in for an entry without a location. For
// a sitemap reached through a redirect the latter is the URL the sitemap was served from.
// May be empty for field-level errors where no specific URL is available.
URL string
// Err is the underlying validation error.
Err error
}
ValidationError is returned when a URL or field value fails validation. Callers can inspect URL to determine which value was rejected.
How many entries of a document are not valid is up to the document, so a call records no more errors of this type than the limit set with SetMaxValidationErrors allows.
Example usage:
var valErr *sitemap.ValidationError
if errors.As(err, &valErr) {
fmt.Println("invalid URL:", valErr.URL)
}
Example ¶
An entry that is not valid is reported by a ValidationError, which names the URL that was rejected, or the page a rejected value belongs to.
package main
import (
"errors"
"fmt"
"log"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>ftp://example.com/files</loc></url>
<url><loc>https://example.com/about</loc><priority>high</priority></url>
</urlset>`
s := sitemap.New()
if _, err := s.Parse("https://example.com/sitemap.xml", &content); err != nil {
log.Fatal(err)
}
for _, err := range s.GetErrors() {
var validationErr *sitemap.ValidationError
if errors.As(err, &validationErr) {
fmt.Println(validationErr.URL, "-", validationErr.Err)
}
}
}
Output: ftp://example.com/files - unsupported scheme "ftp" https://example.com/about - invalid <priority> value "high"
func (*ValidationError) Error ¶ added in v0.9.0
func (e *ValidationError) Error() string
Error returns the message of the error, which names the URL and tells what is not valid: `validate "ftp://example.com/page": unsupported scheme "ftp"`.
func (*ValidationError) Unwrap ¶ added in v0.9.0
func (e *ValidationError) Unwrap() error
Unwrap returns the underlying error, enabling errors.Is / errors.As traversal.
type Video ¶ added in v0.8.0
type Video struct {
// ThumbnailLoc is the URL of the thumbnail image of the video, the content of
// <video:thumbnail_loc>. It is never empty, and it may be on another host than the
// page. Strict mode requires an absolute HTTP or HTTPS URL: a video whose
// ThumbnailLoc is not one is left out and reported.
ThumbnailLoc string `xml:"http://www.google.com/schemas/sitemap-video/1.1 thumbnail_loc"`
// Title is the title of the video, the content of <video:title>. Strict mode
// reports a video without one.
Title string `xml:"http://www.google.com/schemas/sitemap-video/1.1 title"`
// Description is the description of the video, the content of
// <video:description>. Strict mode reports a video without one.
Description string `xml:"http://www.google.com/schemas/sitemap-video/1.1 description"`
// ContentLoc is the URL of the video file itself, the content of
// <video:content_loc>. Strict mode reports a video that has neither a ContentLoc
// nor a PlayerLoc, and a ContentLoc that is no absolute HTTP or HTTPS URL.
ContentLoc string `xml:"http://www.google.com/schemas/sitemap-video/1.1 content_loc"`
// PlayerLoc is the URL of a player for the video, the content of
// <video:player_loc>. Strict mode reports one that is no absolute HTTP or HTTPS
// URL, see ContentLoc.
PlayerLoc string `xml:"http://www.google.com/schemas/sitemap-video/1.1 player_loc"`
// Duration is the length of the video in seconds, the content of <video:duration>.
// Strict mode reports a value that is not between 1 and 28800, which is eight
// hours.
Duration *int `xml:"http://www.google.com/schemas/sitemap-video/1.1 duration"`
// ExpirationDate is the date after which the video is no longer available, the
// content of <video:expiration_date>. It is nil for an element that is empty as
// well.
ExpirationDate *LastModTime `xml:"http://www.google.com/schemas/sitemap-video/1.1 expiration_date"`
// Rating is the rating of the video, the content of <video:rating>. Strict mode
// reports a value that is not between 0.0 and 5.0.
Rating *float32 `xml:"http://www.google.com/schemas/sitemap-video/1.1 rating"`
// ViewCount is the number of times the video was viewed, the content of
// <video:view_count>.
ViewCount *int `xml:"http://www.google.com/schemas/sitemap-video/1.1 view_count"`
// PublicationDate is the date the video was first published, the content of
// <video:publication_date>. It is nil for an element that is empty as well.
PublicationDate *LastModTime `xml:"http://www.google.com/schemas/sitemap-video/1.1 publication_date"`
// FamilyFriendly tells whether the video is fit to be shown with a filter for
// adult content on, the content of <video:family_friendly>: "yes" or "no" by the
// extension. Neither mode checks the value.
FamilyFriendly string `xml:"http://www.google.com/schemas/sitemap-video/1.1 family_friendly"`
// Restriction tells the countries the video may or may not be shown in, the
// <video:restriction> of the video.
Restriction *VideoRestriction `xml:"http://www.google.com/schemas/sitemap-video/1.1 restriction"`
// Platform tells the platforms the video may or may not be shown on, the
// <video:platform> of the video.
Platform *VideoPlatform `xml:"http://www.google.com/schemas/sitemap-video/1.1 platform"`
// RequiresSubscription tells whether the video can only be viewed with a
// subscription, the content of <video:requires_subscription>: "yes" or "no" by the
// extension. Neither mode checks the value.
RequiresSubscription string `xml:"http://www.google.com/schemas/sitemap-video/1.1 requires_subscription"`
// Uploader is who uploaded the video, the <video:uploader> of the video.
Uploader *VideoUploader `xml:"http://www.google.com/schemas/sitemap-video/1.1 uploader"`
// Live tells whether the video is a live stream, the content of <video:live>:
// "yes" or "no" by the extension. Neither mode checks the value.
Live string `xml:"http://www.google.com/schemas/sitemap-video/1.1 live"`
// Tags are the tags of the video, one for each <video:tag> and in the order of
// the document. It is nil if the video has none. Strict mode reports a video with
// more than 32 of them.
Tags []string `xml:"http://www.google.com/schemas/sitemap-video/1.1 tag"`
}
Video is a structure of <video:video> in <url>, per the Google Video Sitemap extension. Reference: https://developers.google.com/search/docs/crawling-indexing/sitemaps/video-sitemaps
A video is known by its ThumbnailLoc, and one without a ThumbnailLoc is not among the videos of a URL. A text field is empty, and a pointer is nil, when the video has no element for it. A number or a date that cannot be parsed is nil as well, and is reported in both modes; the video is returned without it.
Strict mode reports what the extension requires and a video lacks or has out of range, as the fields below tell. A video whose ThumbnailLoc is accepted is returned whatever else is reported about it.
Example ¶
The numbers and the dates of a video are pointers, which are nil for what the video does not give.
package main
import (
"fmt"
"log"
"time"
"github.com/aafeher/go-sitemap-parser"
)
func main() {
content := `<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
<url>
<loc>https://example.com/videos/harbour</loc>
<video:video>
<video:thumbnail_loc>https://cdn.example.net/harbour.jpg</video:thumbnail_loc>
<video:title>A walk around the harbour</video:title>
<video:description>The harbour at dawn, from the pier to the lighthouse.</video:description>
<video:content_loc>https://cdn.example.net/harbour.mp4</video:content_loc>
<video:duration>600</video:duration>
<video:restriction relationship="allow">IE GB</video:restriction>
<video:tag>harbour</video:tag>
<video:tag>walk</video:tag>
</video:video>
</url>
</urlset>`
s := sitemap.New()
if _, err := s.Parse("https://example.com/sitemap.xml", &content); err != nil {
log.Fatal(err)
}
for _, u := range s.GetURLs() {
for _, video := range u.Videos {
fmt.Println(video.Title)
fmt.Println(video.ContentLoc)
if video.Duration != nil {
fmt.Println(time.Duration(*video.Duration) * time.Second)
}
if video.Restriction != nil {
fmt.Println(video.Restriction.Relationship, video.Restriction.Value)
}
if video.Rating == nil {
fmt.Println("no rating")
}
fmt.Println(video.Tags)
}
}
}
Output: A walk around the harbour https://cdn.example.net/harbour.mp4 10m0s allow IE GB no rating [harbour walk]
type VideoPlatform ¶ added in v0.8.0
type VideoPlatform struct {
// Relationship tells whether the video may be shown on the platforms Value names or
// may not be shown there, the value of the relationship attribute: "allow" or
// "deny" by the extension. Neither mode checks the value.
Relationship string `xml:"relationship,attr"`
// Value names the platforms, as the content of the element gives them: "web",
// "mobile" and "tv" with a space between them by the extension, such as "web tv".
Value string `xml:",chardata"`
}
VideoPlatform is a structure of <video:platform> in <video:video>. It captures the element text and the required "relationship" attribute.
type VideoRestriction ¶ added in v0.8.0
type VideoRestriction struct {
// Relationship tells whether the video may be shown in the countries Value names or
// may not be shown there, the value of the relationship attribute: "allow" or
// "deny" by the extension. Neither mode checks the value.
Relationship string `xml:"relationship,attr"`
// Value names the countries, as the content of the element gives them: country
// codes with a space between them by the extension, such as "IE GB US".
Value string `xml:",chardata"`
}
VideoRestriction is a structure of <video:restriction> in <video:video>. It captures the element text and the required "relationship" attribute.
type VideoUploader ¶ added in v0.8.0
type VideoUploader struct {
// Info is the URL of a page that tells more about the uploader, the value of the
// info attribute. It is empty if the element has no such attribute. Strict mode
// reports one that is no absolute HTTP or HTTPS URL; the video is returned all the
// same, with the value as the document gives it.
Info string `xml:"info,attr"`
// Value is the name of the uploader, the content of the element.
Value string `xml:",chardata"`
}
VideoUploader is a structure of <video:uploader> in <video:video>. It captures the uploader name and the optional "info" URL attribute.
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
advanced
command
|
|
|
atom
command
|
|
|
context
command
|
|
|
encoding
command
|
|
|
errors
command
|
|
|
follow
command
|
|
|
getrandomurls
command
|
|
|
geturls
command
|
|
|
gzip
command
|
|
|
hreflang
command
|
|
|
httpclient
command
|
|
|
idn
command
|
|
|
image
command
|
|
|
iterators
command
|
|
|
lastmod
command
|
|
|
maxconcurrency
command
|
|
|
maxdepth
command
|
|
|
maxresponsesize
command
|
|
|
maxsitemaps
command
|
|
|
maxurls
command
|
|
|
maxvalidationerrors
command
|
|
|
multithread
command
|
|
|
news
command
|
|
|
redirect
command
|
|
|
reuse
command
|
|
|
robots
command
|
|
|
rss
command
|
|
|
rules
command
|
|
|
simple
command
|
|
|
sitemaps
command
|
|
|
strict
command
|
|
|
text
command
|
|
|
tolerant
command
|
|
|
video
command
|