RSS to JSON: convert any feed, then get only the new items

Feeds are still the cleanest way to follow blogs, news sites, GitHub releases, YouTube channels, podcasts and government announcements, but they come in four formats with different field names and date styles. This guide shows how to find a feed, convert it to JSON with a short script, and what it takes to turn that into an alert that sends only new items. Then it shows RSS Feed Reader & Monitor, an Apify actor that reads thousands of feeds (or plain website URLs) into one clean dataset and remembers what it has already sent.

Run it on Apify Opens the actor's page on apify.com. A free Apify account is needed to run it.

Only need one feed right now? Try the free RSS to JSON converter: it runs on this site with no sign-up.

Four formats, one idea

RSS 2.0Atom 1.0JSON Feed 1.1RSS 1.0 (RDF)
Item<item><entry>items[]<item>
Link<link><link href>url<link>
Date<pubDate> (RFC 822)<published>, <updated> (ISO 8601)date_published<dc:date>
ID<guid> (optional)<id>idrdf:about
Text<description>, content:encoded<summary>, <content>summary, content_html<description>
Media<enclosure><link rel="enclosure">attachments[]rare

Find a site's feed URL

Most sites declare their feed in the page head. This prints the declaration:

curl -s https://blog.cloudflare.com/ | grep -o '<link[^>]*rel="alternate"[^>]*>'
<link rel="alternate" type="application/rss+xml" title="Cloudflare Blog" href="/rss/">

If nothing is declared, try the platform patterns:

PlatformFeed URL
WordPress/feed/ (comments: /comments/feed/)
Ghost, many blogs/rss/
Hugo, Jekyll, static sites/index.xml, /feed.xml, /atom.xml, /rss.xml
Substackhttps://NAME.substack.com/feed
GitHub releaseshttps://github.com/OWNER/REPO/releases.atom
YouTube channelhttps://www.youtube.com/feeds/videos.xml?channel_id=CHANNEL_ID
GOV.UKAdd .atom to a search or topic page, for example /search/news-and-communications.atom

Convert a feed to JSON with Python

The widely used feedparser library reads RSS and Atom (including broken XML) and gives every format the same field names:

import json, sys
import feedparser

feed = feedparser.parse(sys.argv[1])
items = [
    {
        "title": e.get("title"),
        "url": e.get("link"),
        "publishedAt": e.get("published") or e.get("updated"),
        "id": e.get("id") or e.get("link"),
    }
    for e in feed.entries
]
print(json.dumps({"feed": feed.feed.get("title"), "items": items}, indent=2))
uv run --with feedparser python rss_to_json.py https://blog.cloudflare.com/rss/

Output on 2026-10-12, first item:

{
  "feed": "Cloudflare Blog",
  "items": [
    {
      "title": "Introducing Clef-omni with full multimodality, plus a faster Clef and a cheaper Clef-flash",
      "url": "https://blog.cloudflare.com/clef-faster-cheaper-multimodal/",
      "publishedAt": "Fri, 09 Oct 2026 18:27:34 GMT",
      "id": "01M4GV0DTKYJTCM5GPYBCME1T4"
    }, …

Note the date is still in RSS's RFC 822 style; use published_parsed to normalise it. To make this a monitor you also need: HTML stripped from summaries, JSON Feed support (feedparser does not read it), a store of item IDs already sent per feed, conditional requests with ETag and Last-Modified so unchanged feeds cost almost nothing, retries for 429 and 5xx, and a scheduler.

Read many feeds without code

  1. Open RSS Feed Reader & Monitor on Apify.
  2. Paste feed URLs or plain website URLs into Feed or website URLs, one per line (up to 5,000). For a website it reads the declared feed links, then RSS-looking links on the page, then common paths allowed by robots.txt.
  3. Optionally add Keywords (whole words, launch* wildcards), Exclude keywords, Published after (24 hours, 7 days, 2026-10-01) or Max items per feed.
  4. Click Start and export JSON, CSV or Excel, or use the Items and Podcasts and media views.

A row from a real run on 2026-10-04 with the website URL https://blog.cloudflare.com/ as input:

{
  "title": "Streamline: custom video pipelines with Cloudflare Stream and Workers",
  "url": "https://blog.cloudflare.com/streamline/",
  "publishedAt": "2026-10-02T16:13:45Z",
  "authors": ["Willi Geiger", "Taylor Smith"],
  "categories": ["Birthday Week", "Cloudflare Stream", "Containers", "Developer Platform", "Durable Objects", "Workers"],
  "summary": "Streamline demonstrates how to build long-running, continuous video processing pipelines by pairing Cloudflare Workers and Durable Objects with a containerized media engine.",
  "feedTitle": "Cloudflare Blog",
  "feedUrl": "https://blog.cloudflare.com/rss/",
  "feedType": "RSS 2.0",
  "inputUrl": "https://blog.cloudflare.com/",
  "discoveredVia": "link-alternate"
}

Podcast feeds fill the media fields: enclosureUrl, enclosureType (audio/mpeg), enclosureLength in bytes and durationSecs. A feed that cannot be read appears only in the SUMMARY record with a readable reason, for example DNS_ERROR: the domain name does not resolve.

Get only new items on a schedule

{
  "feeds": [
    "https://blog.cloudflare.com/",
    "https://github.com/apify/apify-sdk-python/releases.atom",
    "https://www.gov.uk/search/news-and-communications.atom",
    "https://feeds.npr.org/510289/podcast.xml"
  ],
  "onlyNew": true,
  "outputOnFirstRun": false,
  "keywords": [
    "security",
    "outage",
    "launch*"
  ],
  "stateStoreName": "my-feed-alerts"
}
  • Only new items stores the IDs already seen per feed (items still in the feed plus 90 days of history, up to 5,000 per feed) in a named key-value store in your Apify account.
  • Output current items on the first run off means the first run only remembers the backlog, so your first alert is not 500 old posts.
  • Unchanged feeds are checked with a conditional request (ETag and Last-Modified) and return nothing.
  • Give each independent monitor its own Memory name.

Worked example (2026-10-04). The default input (3 feeds) returned 40 items. A first monitoring run on 6 feeds returned 95 items; running it again straight away found all 6 feeds unchanged and returned 0 items.

Why not just "published in the last hour"? A date filter misses items that arrive late with an older date and repeats items when runs overlap. Comparing IDs returns every item exactly once, whatever the schedule.

Route the run's dataset to Slack, email, Google Sheets, Zapier, Make or a webhook with Apify's integrations.

What each row contains

FieldWhat it holds
title, url, itemIdPlain-text title, link, and the ID used for deduplication
publishedAt, updatedAtISO 8601 UTC, whatever the feed's format
author, authors, categoriesAuthor names (emails removed) and tags
summaryPlain text cut at a word boundary (1,000 characters by default)
imageUrlFrom media:thumbnail, media:content, an image enclosure or the first image
enclosureUrl, enclosureType, enclosureLength, durationSecsPodcast and media files
feedTitle, feedUrl, feedType, feedLanguageWhich feed it came from and its format
inputUrl, discoveredViaWhat you entered and how the feed was found
matchedKeywordsWhich of your keywords matched
contentHtml, contentTextFull content, only with Include full content on

Honest limits

  • Only what the feed carries. Many feeds hold the newest 10 to 50 items and a summary. Older items and full article text are not fetched from the website.
  • Blocked feeds are reported, not bypassed. It identifies itself as RSSFeedReaderMonitor/1.0, sends at most 2 parallel requests per site and uses no proxy. Feeds behind bot protection come back as http_error with the status code.
  • Items without a date are dropped when Published after is set.
  • Private and internal addresses are refused, and responses over 15 MB are skipped.
  • Copyright stays with publishers. Linking back is usually fine; republishing full articles usually needs permission.

All input fields

Generated from the actor's live input schema (build 1.0.3).

FieldTypeDefaultWhat it does
Feed or website URLs required
feeds
arrayRSS, Atom, RDF or JSON Feed URLs, one per line. You can also paste a website or blog URL: the actor reads the feed links the page declares and, if there are none, tries common feed paths such as /feed and /rss.xml. Up to 5,000 per run.
Only new items (monitoring mode)
onlyNew
booleanfalseRemember which items were already output and return only items that are new since the last run. Schedule the actor (for example hourly) and connect a webhook, Slack, email or Zapier integration to get feed alerts. Unchanged feeds are checked with conditional requests (ETag / Last-Modified).
Output current items on the first run
outputOnFirstRun
booleantrueIn monitoring mode: when a feed is checked for the first time, output its current items (on) or only remember them so that later runs output just what is new (off).
Memory name (monitoring mode)
stateStoreName
stringrss-feed-monitor-stateName of the key-value store that remembers seen items. Use a different name for each independent monitor; the same feed URL in two monitors with the same name is remembered once.
Filters
Max items per feed
maxItemsPerFeed
integer100Newest items to output from each feed per run (after filters). 0 = all items in the feed. In monitoring mode, items beyond this limit are remembered as seen.
Published after
publishedAfter
stringOnly items published after this date (2026-10-01 or 2026-10-01T08:00:00Z) or within a period before the run: "24 hours", "7 days", "2 weeks". Items without a date are dropped when this is set.
Keywords
keywords
arrayKeep only items that contain ANY of these words or phrases (whole words, case-insensitive). A trailing * matches word endings: launch* matches launches and launched. Empty = no keyword filter.
Exclude keywords
excludeKeywords
arrayDrop items that contain any of these words or phrases.
Match keywords in
keywordsIn
stringtitleAndContentTitle and content = title, summary, full content and categories. Title only = stricter, fewer false matches.
Options: titleAndContent, title
Feed discovery and output
Find feeds on website URLs
discoverFeeds
booleantrueWhen a URL is a web page instead of a feed, look for its feed (declared feed links, RSS links on the page, then common feed paths allowed by robots.txt). Off = web page URLs are reported as 'no feed found'.
Max feeds per website
maxFeedsPerSite
integer1How many of the feeds a website declares to read. 1 = the main feed only (comment feeds are always tried last).
Include full content
includeContent
booleanfalseAdd the full item content as HTML and as plain text (contentHtml, contentText) when the feed provides it. Makes the dataset much larger.
Summary length (characters)
summaryMaxLength
integer1000The plain-text summary is cut at a word boundary after this many characters. 0 = no limit.
Max items in total
maxItems
integer5000The run stops after this many items. You can also cap the cost with the run's 'Maximum cost per run' option.
Advanced
Parallel requests
maxConcurrency
integer10Feeds read at the same time (at most 2 at once per website).
Request timeout (seconds)
requestTimeoutSecs
integer30How long to wait for each feed or page to respond.
Retries per request
maxRetries
integer2Retries on timeouts, 429 and 5xx responses (a Retry-After header up to 30 seconds is honoured).

Call it from code

Start a run and get the results in one HTTP request with the Apify API (your token is under Console, Settings, API & Integrations):

curl -X POST "https://api.apify.com/v2/acts/adamhaleworks~rss-feed-reader-monitor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"feeds":["https://github.blog/feed/"],"maxItemsPerFeed":10}'

The run's Output tab offers 2 ready-made views; every view exports to CSV, Excel, JSON or XML:

  • Items: publishedAt, title, feedTitle, author, summary, categories, imageUrl, url
  • Podcasts and media: publishedAt, title, feedTitle, durationSecs, enclosureType, enclosureLength, enclosureUrl, url

What it costs

Free to run right now. The actor charges no fee of its own today. Apify bills only the compute a run uses against your account's platform credit, and the free Apify plan includes $5 of usage every month. For scale: building all five of our actors and test-running each once on its default input used $0.03 of credit in total (2026-10-12).

Planned: pay-per-event pricing of $0.0002 per item in the output ($0.20 per 1,000), $0.0005 per feed checked once the account's payout setup is finished. This page and the Apify page will show the change, and Apify notifies existing users before a price change takes effect.

With the planned prices, feeds that fail and items removed by filters or already seen would not be charged. The default 3-feed run would cost $0.0095, and 10 feeds checked every hour for a month with about 300 new items about $3.66.

FAQ

How do I convert RSS to JSON?

Parse the feed with a library such as Python's feedparser and dump the entries as JSON, as in the script on this page. For many feeds, JSON Feed support, deduplication and scheduling, run the Apify actor and export its dataset as JSON.

Can I give it a website instead of a feed URL?

Yes. It reads the page's declared feed links first, then links that look like feeds, then common paths such as /feed and /rss.xml that robots.txt allows. discoveredVia says which method found it.

Can I get the full article text?

Turn on Include full content to get whatever the feed carries (content:encoded or Atom content). Many WordPress and Substack feeds include the full post; the actor does not fetch article pages.

Why did a feed return nothing?

Open SUMMARY in the run's key-value store. not_modified means unchanged since the last run, no_feed_found means no feed was declared or found at common paths, and http_error with 403 means the site blocks automated readers.

Does it read YouTube channels and podcasts?

Yes. YouTube's public channel feed works, including thumbnails, and podcast feeds return audio URLs, MIME types, sizes and durations.

Try it on your own data

Read many RSS, Atom and JSON feeds in one run, or paste website URLs and the feed is found for you. Clean items with title, link, date, summary, author, image and podcast audio. Only-new mode with conditional requests for cheap hourly feed alerts. Keyword and date filters.

Open RSS Feed Reader & Monitor (RSS, Atom, JSON Feed) Opens the actor's page on apify.com. A free Apify account is needed to run it.