Skip to main content

Python package that generates SEO-friendly XML sitemaps

Project description

Sitemapy

PyPI version Python Support License: MIT

Sitemapy is a Python package for generating SEO-friendly XML sitemaps.

Features

  • 🚀 Simple and intuitive API for sitemap generation
  • 📝 Support for all standard sitemap attributes (priority, changefreq, lastmod)
  • 🌍 Full hreflang support for multilingual sites
  • 📦 Sitemap index support for large sites
  • 🗜️ Built-in compression (gzip) support
  • 🔒 Safe XML parsing with defusedxml
  • ⚡ Method chaining for cleaner code
  • 🔄 Load and modify existing sitemaps

Table of Contents

Installation

pip install sitemapy

Quick Start

from sitemapy import Sitemap

sitemap = Sitemap.from_list([
    "https://example.com/",
    "https://example.com/about/", 
])
sitemap.write_to_file("sitemap.xml")

Output (sitemap.xml):

<?xml version='1.0' encoding='utf-8'?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
   <url>
      <loc>https://example.com/</loc>
   </url>
   <url>
      <loc>https://example.com/about/</loc>
   </url>
</urlset>

Usage Examples

Adding URLs Incrementally

from sitemapy import Sitemap

sitemap = Sitemap()
sitemap.add_url("https://example.com/", lastmod="2025-01-15")
sitemap.add_url("https://example.com/blog/", priority=0.8)
sitemap.write_to_file()

Setting Dates

from sitemapy import Sitemap

sitemap = Sitemap.from_list([
    "https://example.com/",
    "https://example.com/about/"
])

# Set all URLs to a specific date
sitemap.set_all_lastmod("2025-12-01")

# Or set all URLs to today's date
sitemap.set_all_lastmod_to_today().write_to_file("today-sitemap.xml")

Using URLEntry Objects for More Control

from sitemapy import Sitemap, URLEntry

url = URLEntry(
    loc="https://example.com/important-page/",
    lastmod="2025-10-21",
    changefreq="weekly",
    priority=0.9
)

sitemap = Sitemap()
sitemap.add_url(url)
sitemap.write_to_file()

Working with Existing Sitemaps

from sitemapy import Sitemap

# Load, modify, and save
sitemap = Sitemap.from_file("existing-sitemap.xml")
sitemap.add_url("https://example.com/new-page/")
sitemap.deduplicate()
sitemap.write_to_file("updated-sitemap.xml")

Hreflang Support

Sitemapy makes creating hreflang alternates for multilingual sites easy:

from sitemapy import Sitemap, URLEntry, HreflangAlternate

# Create a URL with language alternates
url = URLEntry(loc="https://www.example.com/")

# Add alternates individually
url.add_alternate(href="https://www.example.de/", hreflang="de-de")

# Or using HreflangAlternate objects
href_alt = HreflangAlternate(href="https://www.example.es/", hreflang="es-es")
url.add_alternate(href_alt)

# Or add multiple alternates at once
url.add_alternates([
    {"href": "https://www.example.de/", "hreflang": "de-de"},
    {"href": "https://www.example.es/", "hreflang": "es-es"},
    {"href": "https://www.example.fr/", "hreflang": "fr-fr"},
])

# Add to sitemap
sitemap = Sitemap()
sitemap.add_url(url)
sitemap.write_to_file()

Sitemap Index

For large sites with multiple sitemaps, use a sitemap index:

from sitemapy import SitemapIndex

# Create an index of multiple sitemaps
index = SitemapIndex.from_list([
    "https://example.com/sitemap-posts.xml",
    "https://example.com/sitemap-pages.xml",
    "https://example.com/sitemap-products.xml",
])

# Add more sitemaps with metadata
index.add_sitemap(
    "https://example.com/sitemap-images.xml",
    lastmod="2025-10-27"
)

index.write_to_file("sitemap-index.xml")

Compression

Generate compressed sitemaps for better performance and reduced bandwidth:

from sitemapy import Sitemap

sitemap = Sitemap.from_list([
    "https://example.com/",
    "https://example.com/about/",
])

# Creates sitemap.xml.gz
sitemap.write_compressed("sitemap.xml.gz")

# Or let it use the default name
sitemap.write_compressed()  # Creates sitemap.xml.gz

Sitemap Extensions

Google recognizes a set of extensions for sitemaps. Sitemapy can intake and create News and Image elements (Video coming soon...)

URLEntry can contain an arrays of ImageEntry. URLEntry can contain a single NewsEntry.

Images

from sitemapy import URLEntry, ImageEntry
url = URLEntry(loc="https://www.example.com/")

# Create image(s) with just a URL string
url.add_image(image="https://www.example.com/cat.png/")
url.add_image(image="https://www.example.com/dog.png/")

# Create image(s) from an ImageEntry
image_entry_one = ImageEntry(image="https://www.example.com/cat.png/")
image_entry_two = ImageEntry(image="https://www.example.com/dog.png/")
url.add_image(image_entry_one)
url.add_image(image_entry_two)

News

News elements can only be added by building a NewsEntry object.

from sitemapy import URLEntry, NewsEntry
url = URLEntry(loc="https://www.example.com/")

news_entry = NewsEntry(
        publication_name="The New York Times",
        publication_language="en",
        publication_date="2025-12-01",
        title="First Contact Made"
    )

url.add_news_entry(news_entry)

Real-World Examples

Generate Sitemap from Database

from sitemapy import Sitemap, URLEntry
from datetime import datetime

sitemap = Sitemap()

# Example: Blog posts from database
for post in get_blog_posts():  # Your database query
    sitemap.add_url(
        URLEntry(
            loc=f"https://example.com/blog/{post.slug}/",
            lastmod=post.updated_at.strftime("%Y-%m-%d"),
            changefreq="monthly",
            priority=0.8
        )
    )

sitemap.deduplicate().write_to_file("blog-sitemap.xml")

Generate from Multiple Sources

from sitemapy import Sitemap, SitemapIndex

# Create separate sitemaps for different content types
posts_sitemap = Sitemap()
for post in get_posts():
    posts_sitemap.add_url(f"https://example.com/posts/{post.id}/")
posts_sitemap.write_compressed("sitemap-posts.xml.gz")

pages_sitemap = Sitemap()
for page in get_pages():
    pages_sitemap.add_url(f"https://example.com/{page.slug}/", priority=0.9)
pages_sitemap.write_compressed("sitemap-pages.xml.gz")

# Create an index
index = SitemapIndex.from_list([
    "https://example.com/sitemap-posts.xml.gz",
    "https://example.com/sitemap-pages.xml.gz",
])
index.write_to_file("sitemap-index.xml")

Filter and Update URLs

from sitemapy import Sitemap

# Load existing sitemap
sitemap = Sitemap.from_file("sitemap.xml")

# Find all blog URLs
blog_urls = sitemap.get_urls_by_pattern(r"/blog/")

# Update their lastmod date
for url in blog_urls:
    url.lastmod = "2025-10-27"
    url.changefreq = "weekly"

sitemap.write_to_file("updated-sitemap.xml")

Create Multilingual Sitemap

from sitemapy import Sitemap, URLEntry

sitemap = Sitemap()

# Add homepage with language alternates
homepage = URLEntry(loc="https://example.com/en/")
homepage.add_alternates([
    {"href": "https://example.com/en/", "hreflang": "en"},
    {"href": "https://example.com/de/", "hreflang": "de"},
    {"href": "https://example.com/es/", "hreflang": "es"},
    {"href": "https://example.com/fr/", "hreflang": "fr"},
])
sitemap.add_url(homepage)

# Add more pages...
for page in get_pages():
    url = URLEntry(loc=f"https://example.com/en/{page.slug}/")
    url.add_alternates([
        {"href": f"https://example.com/en/{page.slug}/", "hreflang": "en"},
        {"href": f"https://example.com/de/{page.slug}/", "hreflang": "de"},
        {"href": f"https://example.com/es/{page.slug}/", "hreflang": "es"},
        {"href": f"https://example.com/fr/{page.slug}/", "hreflang": "fr"},
    ])
    sitemap.add_url(url)

sitemap.write_to_file("multilingual-sitemap.xml")

API Reference

Sitemap

Main class for creating and managing sitemaps.

Class Methods:

  • from_list(urls) - Create sitemap from list of URL strings or URLEntry objects
  • from_file(path) - Load existing sitemap from XML file

Instance Methods:

  • add_url(url, **kwargs) - Add single URL (string or URLEntry)
  • remove_url(url) - Remove URL by location string
  • get_urls_by_pattern(pattern) - Filter URLs by regex pattern
  • deduplicate() - Remove duplicate URLs
  • set_all_lastmod(date) - Set lastmod for all URLs to specified date
  • set_all_lastmod_to_today() - Set lastmod for all URLs to today's date
  • write_to_file(filename) - Save as uncompressed XML (default: "sitemap.xml")
  • write_compressed(filename) - Save as compressed .xml.gz (default: "sitemap.xml.gz")

Special Methods:

  • __len__() - Returns number of URLs in sitemap
  • __iter__() - Allows iteration over URLEntry objects

URLEntry

Represents a single URL in a sitemap with optional metadata.

Constructor:

URLEntry(
    loc: str,                    # Required: URL location
    lastmod: str = None,         # Last modification date (YYYY-MM-DD)
    changefreq: str = None,      # Change frequency (always, hourly, daily, weekly, monthly, yearly, never)
    priority: float = None       # Priority 0.0-1.0
)

Methods:

  • add_alternate(href_alt=None, hreflang="", href="") - Add single hreflang alternate
  • add_alternates(alternates) - Add multiple hreflang alternates from list of dicts

HreflangAlternate

Represents a language/region alternate for a URL.

Constructor:

HreflangAlternate(
    hreflang: str,  # Language code (e.g., "en-us", "de-de")
    href: str       # Alternate URL
)

SitemapIndex

Class for creating and managing sitemap index files.

Class Methods:

  • from_list(urls) - Create index from list of sitemap URLs or IndexEntry objects
  • from_file(path) - Load existing sitemap index from XML file

Instance Methods:

  • add_sitemap(url, **kwargs) - Add sitemap URL (string or IndexEntry)
  • remove_sitemap(url) - Remove sitemap by location string
  • write_to_file(filename) - Save as XML (default: "sitemap-index.xml")

Special Methods:

  • __len__() - Returns number of sitemaps in index
  • __iter__() - Allows iteration over IndexEntry objects

IndexEntry

Represents a single sitemap reference in a sitemap index.

Constructor:

IndexEntry(
    loc: str,              # Required: Sitemap URL location
    lastmod: str = None    # Last modification date (YYYY-MM-DD)
)

Best Practices

Sitemap Limits

  • Maximum 50,000 URLs per sitemap file
  • Maximum 50MB uncompressed size per sitemap
  • Use a sitemap index for sites with more than 50,000 URLs
  • Consider splitting large sitemaps by content type or date

URL Guidelines

  • Use absolute URLs (include protocol and domain)
  • URLs should be canonical (no duplicates or redirects)
  • Update lastmod when content actually changes
  • Use priority (0.0-1.0) to indicate relative importance within your site
    • Note: Priority is relative to other URLs on your site, not the entire web

Change Frequency

Valid changefreq values:

  • always - Changes with every access
  • hourly - Changes every hour
  • daily - Changes daily
  • weekly - Changes weekly
  • monthly - Changes monthly
  • yearly - Changes yearly
  • never - Archived URLs that never change

Performance Tips

  • Use compression (write_compressed()) for large sitemaps
  • Generate sitemaps incrementally during off-peak hours
  • Submit sitemap location to search engines via robots.txt:
    Sitemap: https://example.com/sitemap.xml
    

Hreflang Best Practices

  • Each language version should reference all other versions (including itself)
  • Use correct language codes (ISO 639-1) and optional region codes (ISO 3166-1 Alpha 2)
  • Ensure all alternate URLs are accessible and return 200 status codes
  • Common format: en-US, de-DE, es-ES, fr-FR
  • See Google's documentation here.

Requirements

  • Python 3.10+
  • defusedxml 0.7.0

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

Security

Sitemapy uses defusedxml instead of Python's standard XML library when parsing external sitemap files. This protects against XML vulnerabilities like billion laughs attacks and XML external entity (XXE) attacks that could occur when loading untrusted sitemap files.

Development Setup

# Clone the repository
git clone https://github.com/Graham-Schmidt/sitemapy.git
cd sitemapy

# Install dependencies
pip install -e ".[dev]"

# Run tests
pytest

License

This project is licensed under the MIT License - see the LICENSE file for details.

Links


Made with ❤️ by Graham Schmidt

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sitemapy-0.2.0.tar.gz (9.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sitemapy-0.2.0-py3-none-any.whl (6.5 kB view details)

Uploaded Python 3

File details

Details for the file sitemapy-0.2.0.tar.gz.

File metadata

  • Download URL: sitemapy-0.2.0.tar.gz
  • Upload date:
  • Size: 9.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for sitemapy-0.2.0.tar.gz
Algorithm Hash digest
SHA256 ea42015ce7b07546ee89959fa59f0f9e04a1b96edf32d5fc628d101bfeb5bf52
MD5 3faef70a3de90415dd49bdca2237d90a
BLAKE2b-256 b23a5dfc541133185c99e75523c04c3a42448d04ba3ab7760d9cea2e53b78338

See more details on using hashes here.

File details

Details for the file sitemapy-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: sitemapy-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 6.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for sitemapy-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3ef6d50956cbd39366d85ad3e34d3df9ede56f4d5f83858ff0129742960d0eb5
MD5 7d7dede2beaadff5ff86d8c4d7c1311e
BLAKE2b-256 b7a5b52d183947ac475a37f91a23e23ebc720ce2b49eb23327e51f2823cfe0de

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page