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.0 | Atom 1.0 | JSON Feed 1.1 | RSS 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> | id | rdf: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:
| Platform | Feed URL |
|---|---|
| WordPress | /feed/ (comments: /comments/feed/) |
| Ghost, many blogs | /rss/ |
| Hugo, Jekyll, static sites | /index.xml, /feed.xml, /atom.xml, /rss.xml |
| Substack | https://NAME.substack.com/feed |
| GitHub releases | https://github.com/OWNER/REPO/releases.atom |
| YouTube channel | https://www.youtube.com/feeds/videos.xml?channel_id=CHANNEL_ID |
| GOV.UK | Add .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
- Open RSS Feed Reader & Monitor on Apify.
- 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.
- Optionally add Keywords (whole words,
launch*wildcards), Exclude keywords, Published after (24 hours,7 days,2026-10-01) or Max items per feed. - 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
| Field | What it holds |
|---|---|
title, url, itemId | Plain-text title, link, and the ID used for deduplication |
publishedAt, updatedAt | ISO 8601 UTC, whatever the feed's format |
author, authors, categories | Author names (emails removed) and tags |
summary | Plain text cut at a word boundary (1,000 characters by default) |
imageUrl | From media:thumbnail, media:content, an image enclosure or the first image |
enclosureUrl, enclosureType, enclosureLength, durationSecs | Podcast and media files |
feedTitle, feedUrl, feedType, feedLanguage | Which feed it came from and its format |
inputUrl, discoveredVia | What you entered and how the feed was found |
matchedKeywords | Which of your keywords matched |
contentHtml, contentText | Full 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 ashttp_errorwith 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).
| Field | Type | Default | What it does |
|---|---|---|---|
Feed or website URLs requiredfeeds | array | RSS, 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 | boolean | false | Remember 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 runoutputOnFirstRun | boolean | true | In 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 | string | rss-feed-monitor-state | Name 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 feedmaxItemsPerFeed | integer | 100 | Newest 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 afterpublishedAfter | string | Only 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. | |
Keywordskeywords | array | Keep 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 keywordsexcludeKeywords | array | Drop items that contain any of these words or phrases. | |
Match keywords inkeywordsIn | string | titleAndContent | Title 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 URLsdiscoverFeeds | boolean | true | When 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 websitemaxFeedsPerSite | integer | 1 | How many of the feeds a website declares to read. 1 = the main feed only (comment feeds are always tried last). |
Include full contentincludeContent | boolean | false | Add 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 | integer | 1000 | The plain-text summary is cut at a word boundary after this many characters. 0 = no limit. |
Max items in totalmaxItems | integer | 5000 | The run stops after this many items. You can also cap the cost with the run's 'Maximum cost per run' option. |
| Advanced | |||
Parallel requestsmaxConcurrency | integer | 10 | Feeds read at the same time (at most 2 at once per website). |
Request timeout (seconds)requestTimeoutSecs | integer | 30 | How long to wait for each feed or page to respond. |
Retries per requestmaxRetries | integer | 2 | Retries 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.