What the parser keeps
The example reads standard item identity, title, date and link fields plus Dublin Core contributor and language values when present. It deliberately leaves the body unrequested. Namespace-aware parsing avoids confusing a wire publisher, a contributor and an unrelated XML field.
Run a local fixture
Save this Python file and run it with Python 3. It uses only the standard library and never requests a publisher page.
from datetime import timezone
from email.utils import parsedate_to_datetime
import xml.etree.ElementTree as ET
DC = "{http://purl.org/dc/elements/1.1/}"
def parse_release_feed(xml_bytes):
if len(xml_bytes) > 2_000_000 or b"<!DOCTYPE" in xml_bytes.upper():
raise ValueError("unsupported XML payload")
channel = ET.fromstring(xml_bytes).find("channel")
if channel is None:
raise ValueError("RSS channel missing")
output = []
for item in channel.findall("item"):
raw_date = item.findtext("pubDate")
published = parsedate_to_datetime(raw_date) if raw_date else None
if published is not None and published.utcoffset() is None:
raise ValueError("publication time needs an offset")
output.append({
"id": item.findtext("guid") or item.findtext("link"),
"headline": item.findtext("title"),
"original_url": item.findtext("link"),
"issuer_name": item.findtext(DC + "contributor"),
"language": item.findtext(DC + "language"),
"published_at": published.astimezone(timezone.utc).isoformat() if published else None,
"body_status": "not_requested",
})
return output
fixture = b"""<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel><item><guid>fixture-1</guid><title>Synthetic release</title>
<link>https://example.com/release</link><pubDate>Sat, 14 Nov 2026 12:00:00 -0500</pubDate>
<dc:contributor>Example issuer</dc:contributor><dc:language>en-US</dc:language>
</item></channel></rss>"""
records = parse_release_feed(fixture)
assert records[0]["published_at"] == "2026-11-14T17:00:00+00:00"
assert records[0]["issuer_name"] == "Example issuer"
assert records[0]["body_status"] == "not_requested"
print("synthetic RSS fixture passed")Executed locally on 14 November 2026 using a synthetic RSS item. The size and DOCTYPE checks are basic input guards, not a complete hostile-XML security review.
Connect an authorized feed separately
Get the feed address from the publisher’s official directory and check the terms that apply to your intended use. PR Newswire’s directory distinguishes RSS from publisher full-text feeds. Prezly documents newsroom-scoped feeds; drafts and private stories are excluded. Neither a successful HTTP request nor a parser success grants a reuse license.
Operational checks before production
Store the last successful fetch time, HTTP status and observed source identifiers. Use conditional requests only if the server supports validators, honor throttling and do not poll faster than the approved service allows. Compare repeated snapshots by item identity, not by position in the list.
- A missing item may have aged out of the feed window.
- An unchanged channel timestamp does not establish that every item is unchanged.
- HTML in a summary needs safe rendering; this parser does not render it.
- Use the provider’s authorized archive route for gaps beyond the rolling feed.
Source notes
- PR Newswire RSS and publisher feed options ↗
Distinguishes headline/summary/link RSS from full-text XML and HTML publisher feeds.
- Prezly public feed documentation ↗
Newsroom-scoped public JSON/RSS feeds, cache behavior and exclusion of unpublished/private stories.
- PR Newswire terms of use ↗
Public-site reuse, scraping and AI restrictions; service-specific agreements may govern a licensed feed.
The Synoptic workflow
Evaluate a Synoptic release workflow
Start with the sources and fields the monitoring task requires.
Evaluate a Synoptic release workflow