Build an RSS feed from markdown blog posts in Python

Scans a folder of markdown files, extracts titles, dates, and excerpts, and generates a valid RSS 2.0 XML feed.

Medium Python 3.9+ Aug 9, 2026 Automation & scripting 12 views 0 copies

Python code

48 lines
Python 3.9+
import re
from pathlib import Path
from xml.etree.ElementTree import Element, SubElement, tostring
from datetime import datetime, timezone
from xml.dom import minidom

def build_rss(blog_dir, site_url="https://example.com"):
    feed = Element("rss", version="2.0")
    channel = SubElement(feed, "channel")
    SubElement(channel, "title").text = "My Blog"
    SubElement(channel, "link").text = site_url
    SubElement(channel, "description").text = "Latest posts"

    for md_file in sorted(Path(blog_dir).glob("*.md")):
        content = md_file.read_text()
        title = md_file.stem.replace("-", " ").title()
        pub_date_match = re.search(r"date:\s*(\d{4}-\d{2}-\d{2})", content)
        pub_date = pub_date_match.group(1) if pub_date_match else md_file.stat().st_mtime
            
        item = SubElement(channel, "item")
        SubElement(item, "title").text = title
        SubElement(item, "link").text = f"{site_url}/{md_file.stem}"
        SubElement(item, "pubDate").text = _format_date(pub_date)
        SubElement(item, "description").text = _get_excerpt(content)

    return minidom.parseString(tostring(feed)).toprettyxml(indent="  ")

def _format_date(date):
    if isinstance(date, str):
        return datetime.strptime(date, "%Y-%m-%d").strftime("%a, %d %b %Y 00:00:00 +0000")
    return datetime.fromtimestamp(date, timezone.utc).strftime("%a, %d %b %Y %H:%M:%S +0000")

def _get_excerpt(content):
    text = re.sub(r"[#>*`\[\]]", "", content)
    return " ".join(text.split()[:50]) + "..."

if __name__ == "__main__":
    from tempfile import TemporaryDirectory
    import os

    with TemporaryDirectory() as tmpdir:
        Path(os.path.join(tmpdir, "hello-world.md")).write_text(
            "date: 2024-01-15\n\n# Hello World\n\nThis is my first post about Python."
        )
        Path(os.path.join(tmpdir, "second-post.md")).write_text(
            "date: 2024-02-20\n\n# Second Post\n\nLearning async programming with asyncio."
        )
        print(build_rss(tmpdir))

Output

stdout
<?xml version="1.0" ?>
<rss version="2.0">
  <channel>
    <title>My Blog</title>
    <link>https://example.com</link>
    <description>Latest posts</description>
    <item>
      <title>Hello World</title>
      <link>https://example.com/hello-world</link>
      <pubDate>Mon, 15 Jan 2024 00:00:00 +0000</pubDate>
      <description>date: 2024-01-15 Hello World This is my first post about Python....</description>
    </item>
    <item>
      <title>Second Post</title>
      <link>https://example.com/second-post</link>
      <pubDate>Tue, 20 Feb 2024 00:00:00 +0000</pubDate>
      <description>date: 2024-02-20 Second Post Learning async programming with asyncio....</description>
    </item>
  </channel>
</rss>

How it works

The script uses Path.glob to find all .md files in the folder, then sorts them alphabetically for deterministic output. It parses each file's front-matter date field with a regex, falling back to the file's modification time if missing. The excerpt strips common markdown symbols and takes the first 50 words. xml.etree.ElementTree builds the feed structure, and minidom pretty-prints it for readability. RSS requires a fixed date format like RFC 822, which _format_date handles for both string dates and timestamps.

Common mistakes

  • Forgetting to encode the XML with `encoding='utf-8'` before `tostring` might cause issues with non-ASCII characters.
  • Assuming all markdown files have a `date:` line; the fallback to `st_mtime` is essential but can produce unpredictable ordering.
  • Using `md_file.stem` for the link without URL-encoding spaces or special characters in filenames.

Variations

  1. Use `email.utils.format_datetime` from stdlib to format dates instead of manual strftime.
  2. Add a `<guid>` element with `isPermaLink="true"` for better feed reader compatibility.

Real-world use cases

  • Automatically generating a site feed from a static site generator's `content/` folder after each deployment.
  • Creating a custom RSS for a newsletters folder that mixes markdown drafts with published posts.
  • Building a lightweight feed aggregator for internal team blogs that use markdown for documentation.

Sponsored

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from Automation & scripting

Related tutorials and quizzes for this topic.