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 ContentPort implementation: full text and images via newspaper4k
BrowserPageFetcher PagePort implementation presenting a browser's TLS fingerprint
PacedPageFetcher PagePort decorator enforcing a minimum interval per host
AiohttpImageFetcher ImagePort implementation downloading images concurrently
FileSystemStorage StoragePort implementation writing to disk
NewsPort, ContentPort, PagePort, ImagePort, 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
RSS_RETRIEVER_IMAGE_SCOPE page page: every image on the page; article: the lead image plus those inside the article body
RSS_RETRIEVER_IMPERSONATE chrome Browser whose TLS fingerprint article fetches present (a curl_cffi name); empty leaves the download to newspaper
RSS_RETRIEVER_HOST_INTERVALS {} JSON object of host to minimum seconds between page fetches, e.g. {"thehill.com": 180}, for sites that rate-limit bursts

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.5.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.5.0
File Size Uploaded
rss_retriever-0.5.0.tar.gz 110.9 kB Details

Built distribution (wheel)

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

Total release size: 139.8 kB

Release files / rss_retriever-0.5.0.tar.gz

Download URL rss_retriever-0.5.0.tar.gz
Size 110.9 kB
Tags Source
SHA-256 checksum
How to use checksums
0f44effd4b2cfba5ab593fc9a519bead1407c1a0312c11db7afb770189c39451
BLAKE2b-256 checksum
How to use checksums
660203502ad5ef34eebab189266b5e0b307173a4d2a6055b359c9099041b3c44
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.5.0-py3-none-any.whl

Download URL rss_retriever-0.5.0-py3-none-any.whl
Size 28.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f69bb7994bd29277a08620784be69bb6a4ff037199b3bde73393a4c06293adaf
BLAKE2b-256 checksum
How to use checksums
129c5074710f334dba35a466389b1345f5ee7e5f290382f2853fd1daaae18c22
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

This release

0.5.0 This release

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

0.2.0

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