Skip to main content

RSS feed retrieval and storage system with content extraction and image downloading capabilities

Project description

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rss_retriever-0.1.0.tar.gz (89.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

rss_retriever-0.1.0-py3-none-any.whl (21.2 kB view details)

Uploaded Python 3

File details

Details for the file rss_retriever-0.1.0.tar.gz.

File metadata

  • Download URL: rss_retriever-0.1.0.tar.gz
  • Upload date:
  • Size: 89.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for rss_retriever-0.1.0.tar.gz
Algorithm Hash digest
SHA256 685b2fedf7ca8ddbda3096e47a595bc3c90003a7ab39a666a0d131b693c3db78
MD5 5b67cf9678d8de894917567fc833a714
BLAKE2b-256 761d1e010ab8f8120de2a742056390a1fd145675f936a3df11d9a88911007c1d

See more details on using hashes here.

File details

Details for the file rss_retriever-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: rss_retriever-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 21.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for rss_retriever-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7cf646eba6b0a954395f3f9eeea2d40e08d91e28cf27019a6d82d8265007c1dd
MD5 bdf3c5ee135d533ce84cdd2be0d0d525
BLAKE2b-256 477b0dc0fe78c122b0b584b02284c113e6aa1bcd6edc455f805dd686e610c992

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page