Skip to main content

rss-retriever

Status: Active Run Unit Tests PyPI

Fetch articles from RSS feeds, extract their full text and images, and store them on disk.

Built around ports and adapters, so the pieces are usable independently: take the feed adapter without the storage layer, swap in your own storage backend, or drive the whole pipeline from the bundled CLI.

Install

pip install rss-retriever

Optional tracing support (OpenTelemetry SDK, OTLP exporters, and Arize Phoenix):

pip install "rss-retriever[otel]"

The base install deliberately stays light. opentelemetry-api is included because it is a no-op without an SDK, so instrumented code paths cost nothing when tracing is not configured.

Requires Python 3.12+.

Library usage

from rss_retriever import ContentExtractor, FileSystemStorage, NewsService, RSSFeedAdapter

service = NewsService(
    RSSFeedAdapter({"Phys.org": "https://phys.org/rss-feed/"}),
    FileSystemStorage("article_storage"),
    ContentExtractor(),
)

articles = service.fetch_and_store_articles(limit_per_source=5)
for article in service.get_recent_articles(limit=10):
    print(article.source_name, article.title)

Articles already present in storage are skipped before the expensive extraction step, so re-running against the same feeds is cheap.

Bulk imports

save_article rewrites both index files on every call so a crash cannot orphan an article. That is the right default for a nightly run but makes a large backfill quadratic in corpus size. Wrap bulk ingestion in batch_writes() to pay the index cost once:

with storage.batch_writes():
    for article in many_articles:
        storage.save_article(article)

Article payloads are still written immediately; only the indexes are deferred, and they are flushed even if the block raises. On a 5,000-article import this is roughly 12x faster.

Public API

Object Role
NewsService Orchestrates fetch, extract, and store
RSSFeedAdapter NewsPort implementation backed by feedparser
ContentExtractor Full-text and image extraction via newspaper4k
FileSystemStorage StoragePort implementation writing to disk
NewsPort, StoragePort Abstract ports for custom implementations
Article, ArticleImage Domain models, with to_dict() / from_dict()

Custom backends

Implement StoragePort to store somewhere other than the filesystem:

from rss_retriever import StoragePort

class S3Storage(StoragePort):
    def save_article(self, article): ...
    def get_article(self, article_id): ...
    def get_recent_articles(self, limit=50): ...
    def get_unread_articles(self, limit=50): ...
    def article_exists(self, url) -> bool: ...

CLI

export RSS_RETRIEVER_RSS_FEEDS='{"Phys.org": "https://phys.org/rss-feed/"}'
export RSS_RETRIEVER_STORAGE_DIR=./article_storage
rss-retriever

Configuration

Read from the environment by Config.from_env(). Construct Config directly to bypass the environment entirely.

Variable Default Meaning
RSS_RETRIEVER_RSS_FEEDS {} JSON object mapping source name to feed URL
RSS_RETRIEVER_STORAGE_DIR article_storage Where articles are written
RSS_RETRIEVER_ARTICLES_PER_SOURCE 5 Max articles fetched per feed per run
RSS_RETRIEVER_RECENT_ARTICLES_LIMIT 10 Max articles returned by recent-article queries
RSS_RETRIEVER_PREVIEW_IMAGE_COUNT 3 Images summarised per article in CLI output
RSS_RETRIEVER_LOG_LEVEL INFO Root log level
RSS_RETRIEVER_REQUEST_TIMEOUT 10 Per-request timeout in seconds
RSS_RETRIEVER_CHUNK_SIZE 8192 Download chunk size in bytes

There are no default feeds: a library should not fetch anything the caller did not ask for. rss_retriever.config.EXAMPLE_FEEDS holds a starting set if you want one.

Storage layout

article_storage/
├── index.json                  # article_id -> metadata, for recency queries
├── url_index.json              # url -> article_id, for deduplication
└── <source>_<url_hash>/
    ├── content.txt
    ├── content.html
    ├── metadata.json
    └── images/

Article IDs are <source_slug>_<first 10 hex of md5(url)> and are stable across runs.

Tracing

from rss_retriever.telemetry import setup_telemetry

setup_telemetry(service_name="my-service")

Configured through the standard OTEL_* environment variables. Requires the otel extra. Note that this installs a global tracer provider and sets OTLP defaults, so call it from an application entry point rather than from library code.

Development

uv sync --extra dev
uv run pytest -m "not network"   # unit tests, no network
uv run pytest                    # includes live-network tests
uv run ruff check .
uv run mypy rss_retriever/

License

MIT

Metadata

Release files for rss-retriever 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rss-retriever 0.2.0
File Size Uploaded
rss_retriever-0.2.0.tar.gz 91.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for rss-retriever 0.2.0
File Interpreter ABI Platform
rss_retriever-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 114.3 kB

Release files / rss_retriever-0.2.0.tar.gz

Download URL rss_retriever-0.2.0.tar.gz
Size 91.6 kB
Tags Source
SHA-256 checksum
How to use checksums
051292bd402a34736b8db09639648b4c3d0514acf4a58561a8d4344412e5b6cd
BLAKE2b-256 checksum
How to use checksums
f46030e81758cbfdc73529be94c4ce7b6c8f279ae47cc57c0e9dac1897a5d8fc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / rss_retriever-0.2.0-py3-none-any.whl

Download URL rss_retriever-0.2.0-py3-none-any.whl
Size 22.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2673a899a93afca8f921f6730159deaad606348199c58e05c00c1599d754cd3e
BLAKE2b-256 checksum
How to use checksums
66d5038a7e2fe1d309b3ff9c840240d3703f592a37709e3f6be05533fbdf4b36
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.2

2 release files

0.2.1

2 release files

This release

0.2.0 This release

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page