sitemap

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 9, 2026 License: MIT Imports: 16 Imported by: 0

README

sitemap

Go Reference Build Status codecov

A fast, memory-efficient XML sitemap parser for Go. It tokenizes sitemaps and sitemap indexes incrementally, reusing buffers across entries, and streams results via channels without loading entire documents into memory.

Features

  • Automatic Recursion: Handles sitemap indexes by fetching and parsing child sitemaps concurrently.
  • Streaming & Low-Allocation: Entries are streamed via channels as they are tokenized, avoiding the need to hold the entire document in memory at once.
  • Dual Parsing Modes: Defaults to the reliable standard library encoding/xml, with an opt-in ultra-fast custom lexer for maximum throughput.
  • Selective Extraction: Extract only the fields you need (like <lastmod>, <changefreq>, or <priority>). Each field is opt-in, so you only pay for the parsing you actually use.
  • Flexible Filtering:
    • Date-based: Drop entries older than a specific cutoff.
    • Regex-based: Whitelist or blacklist specific URIs or URLs.
  • Jalali Date Support: Automatically detects and parses Jalali dates in both <lastmod> values and WithModifiedSince cutoffs.
  • Customizable HTTP Requests: Fully supports custom HTTP clients, allowing you to easily configure proxies, timeouts, and other transport settings.

Performance

The following results are from benchmarking a single sitemap containing 50,000 <loc> entries (the maximum URLs allowed in a sitemap per the sitemaps.org protocol).

Mode Time/op Urls/s Mem/Op Allocs/Op
Custom lexer (loc only) 47.01 ms 1.064M 2.52 MB 50,009
Custom lexer (full) 76.32 ms 655.1k 4.43 MB 150,012
Standard encoding/xml (loc only) 476.0 ms 105.0k 57.42 MB 2,150,021
Standard encoding/xml (full) 521.9 ms 95.80k 58.94 MB 2,250,021

(full: <loc> + <lastmod> + <changefreq> + <priority>)

For reference, here are benchmark numbers for other Go sitemap packages:

Package Time/op Urls/s Mem/Op Allocs/Op
github.com/snabb/sitemap 611.0 ms 81.84k 117.70 MB 3,500,053
github.com/yuya-matsushima/go-sitemap 611.2 ms 81.81k 128.90 MB 3,450,053
github.com/oxffaa/gopher-parse-sitemap 615.7 ms 81.21k 120.90 MB 3,600,027
github.com/aafeher/go-sitemap-parser 887.8 ms 56.32k 346.30 MB 5,250,117

Run it yourself in /benchmarks, with:

go test -bench . -benchtime=5s -benchmem -count=6

Install

go get github.com/HerneHunter/sitemap

Quick start

Fetch a sitemap (or sitemap index) over HTTP

Fetch follows sitemap indexes recursively and streams only the final page URLs:

package main

import (
	"context"
	"fmt"

	"github.com/HerneHunter/sitemap"
)

func main() {
	ctx := context.Background()

	for result := range sitemap.Fetch(ctx, "https://example.com/sitemap_index.xml") {
		fmt.Println(result.Loc)
	}
}

FetchAll does the same for several starting URLs concurrently, merging everything into one channel.

Parse from a local file
for result := range sitemap.ParseFile(ctx, "sitemap.xml") {
	fmt.Println(result.Loc)
}
Parse from any io.Reader
// reader could be an os.File, strings.Reader, etc.
for result := range sitemap.Parse(ctx, reader) {
	fmt.Println(result.Loc)
}

Options

All streaming/fetching functions take ...Option. They can be combined freely.

Option Description Default
WithBufferSize(n int) Sets the output channel buffer size 2500
WithMaxConcurrency(n int) Max concurrent sitemap fetches when recursing through indexes (Fetch, FetchAll) 20
WithCustomLexer() Uses the high-performance custom XML lexer instead of encoding/xml off (encoding/xml)
WithLastMod() Parses <lastmod> into LastMod off
WithChangeFreq() Parses <changefreq> into ChangeFreq off
WithPriority() Parses <priority> into Priority off (-1 if unset)
WithModifiedSince(t time.Time) Drops entries last modified before t (implies WithLastMod()) off
WithSitemapFilter(f *Filter) Regex whitelist/blacklist on sitemap entry URLs off
WithSitemapIndexFilter(f *Filter) Regex whitelist/blacklist on sitemap index URLs off
WithHTTPClient(c *http.Client) Overrides the HTTP client used for fetching http.DefaultClient

Every option follows the same pattern. pass it in as an argument, wherever it fits in the call:

sitemap.Fetch(ctx, url, sitemap.WithLastMod())

The rest work exactly the same way. Combine as many as you need:

cutoff := time.Now().AddDate(0, -1, 0) // last 30 days

sitemap.Fetch(ctx, url,
	sitemap.WithCustomLexer(),
	sitemap.WithChangeFreq(),
	sitemap.WithPriority(),
	sitemap.WithModifiedSince(cutoff),
	sitemap.WithMaxConcurrency(5),
)
Filters

WithSitemapFilter and WithSitemapIndexFilter both take a *Filter, which is a bit richer than the other options since it has three independent fields:

// Filter provides rules for filtering sitemap or index URLs.
type Filter struct {
	// Applied to the URI (path + query). If set, the URI must match at least one.
	Whitelist []*regexp.Regexp

	// Applied to the URI (path + query). If set, the URI must not match any.
	Blacklist []*regexp.Regexp

	// Applied to the full loc string, including the domain.
	RawMatch *regexp.Regexp
}
filter := &sitemap.Filter{
	Blacklist: []*regexp.Regexp{
		regexp.MustCompile(`^/admin`),
		regexp.MustCompile(`\?sort=`),
	},
}
sitemap.Fetch(ctx, url, sitemap.WithSitemapFilter(filter))

WithSitemapIndexFilter works the same way, but applies to URLs within a sitemap index. sitemaps filtered out here are never fetched or parsed.

Result and error handling

Every function returns/streams ParseResult values. A result carries the parsed fields (like Loc, LastMod, ChangeFreq, Priority) or an Err.

Errors you may see:

  • ErrMalformedXML: the document couldn't be tokenized.
  • *HTTPError: a fetch returned a non-2xx status code. Exposes URL and StatusCode.

If a <lastmod> value is present but fails to parse, it's not treated as an error: LastMod is simply left as its zero value (time.Time{}), same as when the field is missing entirely. So with WithModifiedSince, URLs with unparsable timestamps are not filtered out. They still appear in the output channel with a zero-value LastMod, leaving it up to your own code to decide whether to keep or drop them.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrMalformedXML = errors.New("malformed xml")
)

Functions

func Fetch

func Fetch(ctx context.Context, url string, opts ...Option) <-chan ParseResult

Fetch retrieves entries from url, recursing into child sitemaps as needed.

func FetchAll

func FetchAll(ctx context.Context, urls []string, opts ...Option) <-chan ParseResult

FetchAll fetches multiple URLs concurrently into a single combined stream.

func Parse

func Parse(ctx context.Context, reader io.Reader, opts ...Option) <-chan ParseResult

Parse parses sitemap/index entries from reader, fetching children concurrently.

func ParseFile

func ParseFile(ctx context.Context, filepath string, opts ...Option) <-chan ParseResult

ParseFile parses entries from a local sitemap/index file, closing it when done.

func ParseTime

func ParseTime(input string) (time.Time, error)

ParseTime attempts to parse a time string using multiple strategies It tries standard formats, dateparse library, and Jalali calendar parsing

Types

type ChangeFreq

type ChangeFreq string

ChangeFreq is the value of a sitemap entry's <changefreq> tag.

const (
	ChangeFreqAlways  ChangeFreq = "always"
	ChangeFreqHourly  ChangeFreq = "hourly"
	ChangeFreqDaily   ChangeFreq = "daily"
	ChangeFreqWeekly  ChangeFreq = "weekly"
	ChangeFreqMonthly ChangeFreq = "monthly"
	ChangeFreqYearly  ChangeFreq = "yearly"
	ChangeFreqNever   ChangeFreq = "never"
)

type Filter

type Filter struct {
	// Applied to the URI (path + query). If set, the URI must match at least one.
	Whitelist []*regexp.Regexp

	// Applied to the URI (path + query). If set, the URI must not match any.
	Blacklist []*regexp.Regexp

	// Applied to the full loc string, including the domain.
	RawMatch *regexp.Regexp
}

Filter provides rules for filtering sitemap or index URLs.

type HTTPError

type HTTPError struct {
	URL        string
	StatusCode int
}

func (*HTTPError) Error

func (e *HTTPError) Error() string

type Option

type Option func(*options)

Option configures the streaming functions in this package.

func WithBufferSize

func WithBufferSize(n int) Option

WithBufferSize overrides the output channel buffer size.

func WithChangeFreq

func WithChangeFreq() Option

WithChangeFreq parses <changefreq> into Entry.ChangeFreq.

func WithCustomLexer

func WithCustomLexer() Option

WithCustomLexer opts into a faster custom XML lexer instead of the standard library decoder.

func WithLastMod

func WithLastMod() Option

WithLastMod parses <lastmod> into Entry.LastMod.

func WithMaxConcurrency

func WithMaxConcurrency(n int) Option

WithMaxConcurrency overrides the max number of concurrent fetches.

func WithModifiedSince

func WithModifiedSince(t time.Time) Option

WithModifiedSince drops entries modified before t, implies WithLastMod. Jalali time is converted to Gregorian automatically.

func WithPriority

func WithPriority() Option

WithPriority parses <priority> into Entry.Priority.

func WithSitemapFilter

func WithSitemapFilter(f *Filter) Option

WithSitemapFilter applies a filter to sitemap entries.

func WithSitemapIndexFilter

func WithSitemapIndexFilter(f *Filter) Option

WithSitemapIndexFilter applies a filter to sitemap index entries.

type ParseResult

type ParseResult struct {
	Loc        string
	LastMod    time.Time
	ChangeFreq ChangeFreq
	Priority   float64
	Err        error
}

ParseResult represents a parsed sitemap entry or an error. Loc is always populated. Other fields are only populated if their corresponding With* option was provided; otherwise they remain zero-valued (or -1 for Priority).

type TimeParseError

type TimeParseError struct {
	Raw string
}

func (*TimeParseError) Error

func (e *TimeParseError) Error() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL