• Terms of Service

Learn Internet Grow

Tech Skills

  • Learn
  • Books
  • Search
  • Create
  • Measure
  • Secure
  • Share
  • Terms of Service

Never Parse a Feed With a Regex

Filed Under: Create

Never Parse a Feed With a Regex — a painterly illustration conveying this idea from "Never Parse a Feed With a Regex"

The first version usually works. Point a script at an RSS feed, split on <item>, grab the text between <title> tags, and it runs clean against the one blog you tested against. Then a second feed breaks it — an unescaped ampersand in a URL, an &nbsp; some CMS forgot to re-encode, an undeclared namespace prefix, a <description> that turns out to hold a full <div> of unescaped HTML. Parsing RSS feeds in code looks solved because the spec is short. It isn’t, because almost nobody who publishes one has read it, and the format lets them get away with that.

Why Feeds Resist Naive Parsing

A feed is XML, so the instinct is to reach for string splitting or a regular expression instead of a real parser, on the theory that a feed is simple enough not to need one. That instinct never survives real data. Regexes can’t reliably handle nested or malformed markup, and feeds in the wild routinely ship exactly the malformations that break them: unescaped ampersands in query-string URLs, undeclared HTML entities, mismatched tags, stray byte-order marks, a Content-Type header that contradicts the XML declaration inside the document itself. The W3C Feed Validator has a named error for the single commonest case, Undefined Named Entity: a feed using &nbsp; when only entities predefined in the XML spec (&amp;, &lt;, &gt;, &apos;, &quot;) are legal there. &nbsp; is an HTML entity, not an XML one — any CMS piping rendered HTML into a feed without re-encoding it produces a document that renders fine in a browser and fails XML parsing outright.

Why Feeds Resist Naive Parsing — a painterly illustration conveying this idea from "Never Parse a Feed With a Regex"

Layered on top: namespace sprawl. A real feed usually isn’t bare RSS or bare Atom — it’s RSS plus Dublin Core (dc:creator), the iTunes podcast namespace, Media RSS, a custom content:encoded element, sometimes several vendor extensions at once. A parser that only knows the base spec silently drops the rest. Add CDATA sections smuggling raw HTML past escaping, encoding declarations that lie about the actual byte encoding, and dates in two mutually unintelligible grammars, and “just parse the XML” stops being adequate advice fast.

Three Formats, One Job

Any real consumer handles three related-but-different documents. RSS 2.0 is the common case: an <rss> root wrapping one <channel>, with exactly three required channel elements — title, link, description — and no required item elements. The spec’s own text: “all elements of an item are optional, however at least one of title or description must be present.” Everything a consumer actually relies on — link, pubDate, guid — is optional in the spec and merely conventional in practice, which is exactly why so many feeds omit one.

Three Formats, One Job — a painterly illustration conveying this idea from "Never Parse a Feed With a Regex"

RSS 1.0 is the one people don’t expect. Despite the name, it predates RSS 2.0 by two years and isn’t the same lineage — Harvard’s own version history calls it “a brand-new format, not related to any previous format.” It’s RDF/XML: items are RDF resources identified by a rdf:about URL and referenced from an <rdf:Seq> list, not flat child elements. A parser written only against RSS 2.0’s shape won’t make sense of it.

Atom (RFC 4287, Dec 2005) is the newest and strictest, and it is not “RSS 3” — a claim that shows up even in respectable tooling but isn’t true; it’s an independent IETF standard with its own registered MIME type, application/atom+xml, versus RSS’s merely conventional application/rss+xml. Atom makes mandatory precisely what RSS leaves optional: exactly one atom:id per entry (permanent, distinct from any link), one atom:title, one atom:updated. RSS is easy to write and ambiguous to read; Atom is more verbose to write and unambiguous to consume — opposite bets about who absorbs the complexity.

The Fields That Don’t Line Up

Consuming “a feed” as one abstraction means reconciling vocabularies that don’t map one-to-one:

  • Identity. RSS’s <guid> is optional, and its isPermaLink attribute defaults to true — omitting it silently asserts the guid is a working URL. Atom’s atom:id is mandatory and explicitly separate from any link. RSS 1.0 identifies an item by its rdf:about URL. Key “have I seen this” on the wrong field, or trust a guid that turns out to be a session-tagged URL, and a site redesign shows readers every old post as new.
  • Timestamps. RSS’s pubDate is optional; Atom’s atom:updated is mandatory, with atom:published optional and extra.
  • Body. RSS’s <description> might be a two-line summary or the whole post — the spec doesn’t say which. Full-text RSS more correctly uses content:encoded, borrowed from the RSS 1.0 Content module. Atom sidesteps the guesswork with typed <content type="html">, alongside an optional <summary>.

A sane consumer picks one precedence order per concern, everywhere: identity from id → guid → link; content from content:encoded → atom:content → description → atom:summary; timestamp from published/pubDate → updated. That quietly-maintained table is most of what “supporting RSS and Atom” means in a real codebase.

Dates Are a Silent Failure Surface

This trap costs the most debugging time because it never throws. RSS 2.0 dates follow RFC 822: Sat, 16 Aug 2026 09:30:00 +0000. Atom and JSON Feed follow RFC 3339 — a profile of ISO 8601, not a synonym for it — written 2026-08-16T09:30:00Z. The two share no syntax. Feed either format to a parser expecting the other and the document is still well-formed XML; nothing errors. The date field just fails to parse, the consumer falls back to “now,” and an entire archive silently lands at the top of the timeline as if freshly published.

RFC 822 hides three more edges: two-digit years are legal but ambiguous (always emit four); single-letter military time zones are specified incorrectly and should never be used, except Z; day and month names are English abbreviations only, so a non-English locale can emit a date that quietly breaks every strict downstream parser. Apple’s podcast requirements specify “RFC 2822” formatting, the modernized successor to RFC 822 and a stricter subset of it — worth citing correctly rather than picking one RFC number and hoping.

Let a Library Take the Punishment

None of this is an argument for hand-rolling a parser — it’s the argument against it. A format anyone can emit will be emitted badly by a meaningful share of publishers, and at consumer scale that permissiveness becomes your problem. The right move is handing malformed input to a library that has already absorbed years of real-world garbage.

Let a Library Take the Punishment — a painterly illustration conveying this idea from "Never Parse a Feed With a Regex"

In Python that’s feedparser — current release 6.0.14 (30 July 2026, BSD-2-Clause, Python 3.10+). Its defining feature is the bozo bit: instead of raising on malformed XML, it sets d.bozo = 1, stashes the parse error in d.bozo_exception, and still returns everything it recovered — a better contract for input from thousands of producers you don’t control than “throw and stop”:

import feedparser d = feedparser.parse(feed_url, etag=saved_etag, modified=saved_modified)if d.bozo:    log.warning("not well-formed: %s", d.bozo_exception) for entry in d.entries:    uid = entry.get("id") or entry.get("guid") or entry.get("link")    when = entry.get("published_parsed") or entry.get("updated_parsed")    content = entry.get("content")    body = content[0].value if content else entry.get("summary", "") saved_etag, saved_modified = d.get("etag"), d.get("modified")

That one call already does most of what this article describes: detects the format, normalises two incompatible date grammars into one struct_time, and — by default — runs body content through an allow-list HTML sanitizer, because feed bodies are untrusted third-party HTML and handing raw XML straight to a WebView is a stored-XSS bug with a subscription mechanism attached.

The same shape exists outside Python. Go has gofeed (v1.4.2, 20 August 2026, MIT), normalising RSS, Atom and JSON Feed into one model and describing its own handling of broken input as “best-effort.” Java has ROME; Node has rss-parser — both real and widely used, though their exact current versions weren’t independently verifiable at time of writing. The specific library matters less than the shared property: someone else already fought the encoding ladder and the two date grammars, and shipped the fight as a dependency.

Poll Politely — Conditional Requests, Not a Small DDoS

Consuming a feed is usually a recurring fetch, which turns parsing into an HTTP-manners problem. A poller that re-downloads the full document on every check adds up: one client pulling a 100 KB feed every sixty seconds, unconditionally, moves roughly 4 GB a month from a single origin for no reason — and a reader with ten thousand subscribers doing the same multiplies that against sites that never asked for it.

The fix is HTTP, not RSS: conditional requests, defined in RFC 9110. A server sending an ETag or Last-Modified header lets the next request echo it back as If-None-Match or If-Modified-Since. If nothing changed, the server replies 304 Not Modified — a response that, by spec, carries no body — instead of resending the whole feed. feedparser exposes this directly: pass the etag/modified values saved from the prior call, and a 304 shows up as d.status == 304 with no entries. RSS’s own throttling hints, <ttl> and <skipDays>, never got real adoption among aggregators; conditional HTTP requests, honoring 429 Too Many Requests with backoff, and identifying your poller with a real User-Agent are what a well-behaved consumer actually does.

WebSub: Skip Polling Entirely

Conditional GETs make polling cheap; they don’t make it instant, and any polling interval leaves a window where new content exists but hasn’t been noticed. WebSub — a W3C Recommendation first published 23 January 2018 and revised 2 June 2026, formerly PubSubHubbub — is the alternative: push, layered on top of ordinary feeds rather than replacing them.

WebSub: Skip Polling Entirely — a painterly illustration conveying this idea from "Never Parse a Feed With a Regex"

Three roles: a publisher pings a hub on new content; a subscriber registers a callback URL with that hub in advance. Discovery happens via HTTP Link headers (rel="hub", rel="self") on the feed response, with <link> elements as a fallback. Subscribing is a short handshake: the subscriber POSTs hub.callback/hub.topic/hub.mode=subscribe; the hub replies 202 Accepted, then GETs the callback with a random challenge the subscriber must echo back to prove control of the endpoint. After that, the hub POSTs new content to the callback as it’s published — no polling loop required.

The honest cost: WebSub needs a publicly reachable HTTPS endpoint that can receive that verification GET and later POSTs, plus a hub willing to relay the feed — reintroducing exactly the intermediary a pull-based, no-account format was meant to avoid. Worth it for an aggregator watching thousands of feeds; usually not worth building for a weekend script checking three sources, where conditional polling on a sane interval is simpler and good enough.

Frequently Asked Questions

Do I need to support RSS 1.0 too? Rarely by choice, but a general-purpose parser handles it (and older RSS 0.9x) for free, so there’s little reason to special-case it out. Real-world feeds are overwhelmingly RSS 2.0 or Atom.

Is it safe to render <description> or <content> directly? No — feed content is third-party HTML, and rendering it unsanitized in a browser or WebView is a stored-XSS vector. feedparser sanitizes by default; a hand-rolled parse does not, and needs its own sanitization step first.

What about JSON Feed? A newer, smaller-adoption alternative (v1.0 in 2017, v1.1 in 2020) using plain JSON, RFC 3339 dates, and an id-stability rule as plain as Atom’s. Worth supporting if a source you follow emits it; not yet a reason to drop RSS or Atom.

How often should I poll? Whatever interval fits, as long as every request after the first is conditional — there’s no real bandwidth cost to polling every few minutes if 304s are doing their job. The argument is against polling unconditionally, not against polling often.

My parser throws on a feed that renders fine in a browser — why? Almost always an HTML entity like &nbsp; that isn’t legal XML, a Content-Type header disagreeing with the document’s own encoding, or an undeclared namespace prefix. Browsers forgive malformed markup far more than an XML parser is required to — that gap is exactly why feeds “work” for publishers and break for consumers.


The mechanics above — formats, field mismatches, date grammar, polling manners — are the developer-facing half of this subject. The broader story, of how RSS went from a consumer product to invisible infrastructure and why “RSS is dead” doesn’t hold up, is covered in this site’s companion piece on what RSS actually is. This one is the version for whoever has to make a feed work in code, not in a reader.


📚 Recommended Resources

Disclosure: some links below are Amazon affiliate links. If you buy through them, I earn a small commission — at no extra cost to you — which helps keep this site running.

The best references for the mechanics in this article are free: the RSS 2.0 Specification, the RSS Best Practices Profile, RFC 4287 (Atom), and the feedparser documentation are all short, readable, and cost nothing. The books below are worth owning as a desk reference once you’re building something bigger than a script — with one honest caveat: no post-2006 RSS-specific book exists, because the format stopped changing enough to justify one. That’s not a gap in this list; it’s a fact about the subject.


RSS and Atom in Action

Harold, Manning

The best of the format-mechanics books and the closest match to this article’s own territory — publishing and consuming both formats, written by someone who clearly hit the same <guid> and date-format traps covered above. Dated in its web-framework specifics, but the parsing chapters hold up because the underlying formats haven’t moved.


Developing Feeds with RSS and Atom

O’Reilly

A tighter, more spec-focused companion to the article above — useful specifically for the RSS-vs-Atom element-by-element differences this piece leans on, if you want them laid out in more depth than a blog post allows.


Hacking RSS and Atom

Wrox

The “build something with it” book rather than the “understand the spec” book — aimed at people writing consumers and aggregators rather than just publishing a feed. The natural next step after the code in this article.

Newsletter

Tags

Agentic AI Agentic Development AI AI Agents AI Coding Agents Artificial intelligence Backlinks Blockchain Bounded Context Browser ChatGPT Plugins Claude Code CLPR Context Engineering Cross-Chain Cryptocurrency Developer Tools Distributed Systems DLT Domain-Driven Design Email Google Search HBAR Hedera Hedera Hashgraph Internet LLM Machine Learning MCP Microservices Model Context Protocol News NLP Orchestration Photoshop Productivity Saga Pattern SEO Smart Contracts Software Architecture software engineering Twitter Typescript Web3 Writing

Recent Articles

  • QUIC and HTTP/3: The Protocol That Won Is Being Routed Around October 11, 2026
  • The Mark I Perceptron: The Machine That Learned to See October 11, 2026
  • GraphRAG vs Vector RAG: Knowledge Graphs for AI, Explained October 11, 2026
  • Cardano Tokenomics: How ADA’s Supply Actually Works October 10, 2026
  • Transformer Encoder vs Decoder: How to Read the Diagram October 10, 2026
  • Nobody Checks the Verified Badge: The Real State of Git Commit Signing October 10, 2026

Copyright © 2026 · News Pro Theme On Genesis Framework · WordPress · Log in

Content on this site is produced with the assistance of Large Language Models and edited by a human. It is intended for educational purposes — facts may occasionally be incorrect. If you spot an error, please email Larionovdmitri@learninternetgrow.com.

Affiliate Disclosure: This site contains affiliate links to Amazon and other partners. If you purchase through these links, LearnInternetGrow.com may earn a commission at no additional cost to you. We only recommend products we genuinely believe in.

Privacy Policy · Terms of Service