High-performance RSS/Atom/JSON Feed parser written in Rust, with Python and Node.js bindings. A drop-in replacement for Python feedparser that is 90-100x faster.
- Multi-format support -- RSS 0.9x, 1.0, 2.0 / Atom 0.3, 1.0 / JSON Feed 1.0, 1.1
- Tolerant parsing -- Handles malformed feeds gracefully with the
bozoflag pattern, propagated at both feed and entry level - HTTP fetching -- Built-in URL fetching with compression (gzip, deflate, brotli) and conditional GET (ETag/Last-Modified)
- Podcast support -- iTunes and Podcast 2.0 namespace extensions
- Security -- DoS protection via
ParserLimits, SSRF protection, input size validation - Multi-language bindings -- Native Python (PyO3) and Node.js (napi-rs) bindings
- feedparser drop-in -- Dict-style access, field aliases, same API patterns as Python feedparser
| Format | Versions | Status |
|---|---|---|
| RSS | 0.90, 0.91, 0.92, 1.0, 2.0 | Full support |
| Atom | 0.3, 1.0 | Full support |
| JSON Feed | 1.0, 1.1 | Full support |
| Namespace | Description |
|---|---|
| Dublin Core | Creator, date, rights metadata |
| Content | Encoded HTML content |
| Media RSS | Media attachments and metadata (media:content, media:thumbnail, media:title, media:description, media:credit, media:copyright, media:rating, media:keywords) |
| iTunes | Podcast metadata (author, duration, explicit) |
| Podcast 2.0 | Chapters, transcripts, funding |
| Syndication | Update schedule (period, frequency, base) |
| GeoRSS | Geographic location data (point, line, polygon, box) |
| Creative Commons | License information with rel="license" links |
| Slash | Comment count (slash:comments) |
| WFW | Comment feed URL (wfw:commentRss) |
| Atom Threading (thr:) | In-reply-to, reply count, reply datetime (RFC 4685) |
cargo add feedparser-rsOr add to your Cargo.toml:
[dependencies]
feedparser-rs = "0.5"Important
Requires Rust 1.88.0 or later (edition 2024).
pip install feedparser-rsNote
Requires Python 3.10 or later.
npm install feedparser-rs
# or
pnpm add feedparser-rsNote
Requires Node.js 18 or later.
use feedparser_rs::parse;fnmain() -> Result<(),Box<dyn std::error::Error>>{let xml = r#" <?xml version="1.0"?> <rss version="2.0"> <channel> <title>Example Feed</title> <link>https://example.com</link> <item> <title>First Post</title> <link>https://example.com/post/1</link> </item> </channel> </rss> "#;let feed = parse(xml.as_bytes())?;println!("Version: {}", feed.version.as_str());// "rss20"println!("Title: {:?}", feed.feed.title);println!("Entries: {}", feed.entries.len());for entry in&feed.entries{println!(" - {:?}", entry.title);}Ok(())}use feedparser_rs::parse_url;fnmain() -> Result<(),Box<dyn std::error::Error>>{let feed = parse_url("https://example.com/feed.xml",None,None,None)?;println!("Fetched {} entries", feed.entries.len());Ok(())}Tip
Use parse_url for URL fetching with automatic compression handling (gzip, deflate, brotli) and conditional GET via the etag/modified parameters.
importfeedparser_rsasfeedparser# Drop-in replacement# Parse from bytes, string, or URL (auto-detected)d=feedparser.parse(b'<rss>...</rss>')
d=feedparser.parse('https://example.com/feed.xml') # URL auto-detected# Attribute-style accessprint(d.version) # 'rss20'print(d.feed.title)
print(d.bozo) # True if parsing had issues# Dict-style access (feedparser-compatible)print(d['feed']['title'])
print(d['entries'][0]['link'])
# Deprecated field aliases workprint(d.feed.description) # -> d.feed.subtitleprint(d.channel.title) # -> d.feed.titleNote
Python bindings provide full feedparser compatibility: dict-style access, field aliases, and time.struct_time for date fields.
import{parse,parseUrl}from'feedparser-rs';// Parse from stringconstfeed=parse('<rss version="2.0">...</rss>');console.log(feed.version);// 'rss20'console.log(feed.feed.title);console.log(feed.entries.length);// Fetch from URL (synchronous)constremoteFeed=parseUrl('https://example.com/feed.xml');See Node.js API documentation for complete reference.
| Feature | Description | Default |
|---|---|---|
http | Enable URL fetching with reqwest (gzip/deflate/brotli support) | Yes |
To disable HTTP support and reduce dependencies:
[dependencies]
feedparser-rs = { version = "0.5", default-features = false }| Crate | Description | Package |
|---|---|---|
feedparser-rs | Core Rust parser | crates.io |
feedparser-rs-node | Node.js bindings | npm |
feedparser-rs-py | Python bindings | PyPI |
# Install cargo-make
cargo install cargo-make
# Run all checks (format, lint, test)
cargo make ci-all
# Run tests with coverage
cargo make coverage
# Run benchmarks
cargo make benchSee all available tasks:
cargo make --list-all-stepsMeasured on Apple M1 Pro, parsing real-world RSS feeds:
| Feed Size | Time | Throughput |
|---|---|---|
| Small (2 KB) | 10.7 us | 187 MB/s |
| Medium (20 KB) | 93.6 us | 214 MB/s |
| Large (200 KB) | 939 us | 213 MB/s |
Format detection: 128 ns (near-instant)
| Operation | feedparser-rs | Python feedparser | Speedup |
|---|---|---|---|
| Parse 20 KB RSS | 0.09 ms | 8.5 ms | 94x |
| Parse 200 KB RSS | 0.94 ms | 85 ms | 90x |
Tip
Run your own benchmarks with cargo bench or compare against Python with cargo make bench-compare.
Minimum Supported Rust Version: 1.88.0 (edition 2024).
MSRV increases are considered breaking changes and will result in a minor version bump.
Licensed under either of:
at your option.
Contributions are welcome! Please read our Contributing Guide before submitting a pull request.
This project follows the Rust Code of Conduct.