Medium Articles Scraper: Tag, Publication & Author Feeds avatar

Medium Articles Scraper: Tag, Publication & Author Feeds

Pricing

from $0.56 / 1,000 article scrapeds

Go to Apify Store
Medium Articles Scraper: Tag, Publication & Author Feeds

Medium Articles Scraper: Tag, Publication & Author Feeds

Scrape Medium's public RSS feeds: title, author, publish date, tags, canonical URL and article text. Follow a tag, a publication or a specific writer.

Pricing

from $0.56 / 1,000 article scrapeds

Rating

0.0

(0)

Developer

Arman Hossain

Arman Hossain

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Share

Medium Articles Scraper: Articles by tag, publication or author, title, author, publish date, tags and full text when the feed carries it

Reads Medium's public RSS feeds by tag, by publication or by author, and returns one structured record per article. You get the title, author, publish date, tag list, canonical URL, lead image and the article body where the feed carries it.

Medium has no public API, but it does publish RSS for exactly this purpose, and that is what this Actor reads. No browser, no proxy, no login.

Read the limits before you build on it. Medium's feeds return 10 items each with no pagination, and only some of them include the article body. Both constraints are Medium's, not this Actor's, and both are reported per record so you always know what you got. See Notes.

Agent skill: SKILL.md

https://api.apify.com/v2/key-value-stores/t7YoTxpZEJOWvw4Ug/records/medium-articles-scraper.md

What you get

FieldWhat it holds
sourceType, sourceNametag, publication or author, and the tag, slug or handle it came from
feedTitle, feedUrlFeed's own title and the exact URL that was fetched
articleIdMedium's post ID, parsed from the guid. null for publications on their own domain
title, authorArticle title and the dc:creator byline
publishedAt, updatedAtPublication and last-update timestamps, ISO 8601
urlCanonical article URL with Medium's ?source=rss-. attribution parameter stripped
categoriesEvery tag on the article
contentPlainArticle text with HTML stripped and entities decoded. Full body or one-line teaser, depending on the feed
isFullContenttrue when the feed supplied a real body, false when contentPlain is only a teaser
wordCount, readingTimeMinutesBoth null unless isFullContent is true. Reading time is computed at 200 words per minute, not taken from Medium
imageUrlFirst image in the article, with Medium's view-tracking pixel filtered out
scrapedAtRun timestamp

RUN_SUMMARY in the key-value store holds the feeds that were read (feedsRequested), the filters you used, and anything that did not go to plan by name: failures for feeds that could not be fetched, entriesRejected for entries that could never address a feed, feedsSkipped for feeds left unread because maxItems was already spent, and duplicatesSkipped for articles a previous feed had already delivered.

Use cases

  • Topic monitoring. Poll machine-learning and programming hourly and diff on articleId to catch new posts.
  • Writer tracking. Follow a list of authors in your niche, since author feeds are the ones that reliably carry full text.
  • Content-gap analysis. Collect titles and tag combinations across ten tags to see what is already saturated.
  • Newsletter sourcing. A de-duplicated, tagged candidate list with lead images ready for a digest layout. An article that appears in six of your tag feeds is returned once.
  • Competitive publication watch. Track what a rival publication ships, when, and by whom.

Quick start

One tag, defaults for everything else:

{
"tags": ["programming"]
}

Tags, a publication and an author together, titles filtered:

{
"tags": ["machine-learning", "data-science"],
"publications": ["better-programming"],
"authors": ["@dhh"],
"searchTerms": ["llm", "rust"],
"maxItemsPerFeed": 10,
"includeContent": true
}

A metadata-only sweep for change detection:

{
"tags": ["programming", "javascript", "python", "devops"],
"includeContent": false,
"maxItems": 20
}

Input

FieldTypeDefaultNotes
tagsarray[]Tag slugs such as machine-learning. Reads medium.com/feed/tag/<tag>. Teaser text only.
publicationsarray[]Publication slugs such as better-programming. A publication on its own domain works too, so paste the site or /feed URL.
authorsarray[]Handles with or without @. Reads medium.com/feed/@<handle>.
maxItemsPerFeedinteger10Cap per feed, counted after de-duplication. Medium serves 10, so higher values change nothing on medium.com feeds. 0 means everything the feed returns.
maxItemsinteger0Ceiling on the articles saved across the whole run. Remaining feeds are skipped once it is reached, and named in RUN_SUMMARY.feedsSkipped. 0 means no total cap.
includeContentbooleantrueInclude contentPlain and wordCount. Turn it off for a much smaller dataset.
searchTermsarray[]Keep only titles containing one of these terms, case-insensitive. Empty means all.

None of the three source fields is individually required, but at least one of tags, publications or authors must be set. The Actor stops with that message if all three are empty.

Both caps are validated before anything is fetched. A negative, fractional or non-numeric value is refused with a message naming the field, rather than being read as "no limit" — an unset field sent as null gets the documented default, not the whole feed.

One feed is read once. Entries that resolve to the same feed collapse before any fetch, whether they repeat across the three lists or are just the same publication written several ways, so you are never charged twice for the same article.

Output example

A real record from a publication feed, with contentPlain trimmed:

{
"sourceType": "publication",
"sourceName": "better-programming",
"feedTitle": "Better Programming - Medium",
"feedUrl": "https://medium.com/feed/better-programming",
"articleId": "bf37baef8f27",
"title": "Let a thousand programming publications bloom.",
"author": "Tony Stubblebine",
"publishedAt": "2023-11-10T18:18:10.000Z",
"updatedAt": "2023-11-27T17:53:37.557Z",
"url": "https://medium.com/better-programming/let-a-thousand-programming-publications-bloom-bf37baef8f27",
"categories": [],
"contentPlain": "I’m putting Better Programming on hiatus to make room for other programming publications. …",
"isFullContent": true,
"wordCount": 1305,
"readingTimeMinutes": 7,
"imageUrl": "https://cdn-images-1.medium.com/max/1024/1*tKikPWjE4MZ5WgdfZuyvEg.png",
"scrapedAt": "2026-08-06T11:58:36.656Z"
}

And a real record from a tag feed. Same keys, teaser instead of body:

{
"sourceType": "tag",
"sourceName": "programming",
"title": "6 Best AI Tools for Software Engineers in 2026 (Compared)",
"author": "Shahid Bahadur",
"url": "https://shahidyousafxai.medium.com/6-best-ai-tools-for-software-engineers-in-2026-compared-ddb8992886d2",
"categories": ["web-development", "programming", "artificial-intelligence", "productivity", "software-engineering"],
"contentPlain": "AI has become a daily part of software development. Whether you’re debugging code, refacto",
"isFullContent": false,
"wordCount": null,
"readingTimeMinutes": null,
"imageUrl": "https://cdn-images-1.medium.com/max/1536/1*gGldf-aWBPpS11vTV4p9Pg.png"
}

Every record carries the same keys. Only the values change.

Naming a feed

You wantWriteFeed fetched
A tagprogramming in tagshttps://medium.com/feed/tag/programming
A publicationbetter-programming in publicationshttps://medium.com/feed/better-programming
A publication on its own domainhttps://towardsdatascience.com/feed in publicationshttps://towardsdatascience.com/feed
An author@dhh or dhh in authorshttps://medium.com/feed/@dhh

Tag slugs are lower-case and hyphenated: machine-learning, not Machine Learning. Get one from the URL of any tag page on Medium. Non-English tags such as programación work as written.

Publications on their own domain often left Medium and now run WordPress or Ghost. Their feeds still parse, because the Actor handles standard RSS rather than only Medium's flavour, but articleId will be null and the field mix can differ.

API example

curl -X POST "https://api.apify.com/v2/acts/arman-bd~medium-articles-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"tags": ["programming", "machine-learning"],
"authors": ["@dhh"],
"maxItemsPerFeed": 10
}'

JavaScript example

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('arman-bd/medium-articles-scraper').call({
tags: ['machine-learning'],
publications: ['better-programming'],
includeContent: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const a of items) {
console.log(`${a.title} by ${a.author} (${a.publishedAt})${a.isFullContent ? ` [${a.readingTimeMinutes} min]` : ' [teaser only]'}`);
}

Notes

  • Every feed returns 10 items with no pagination. Medium's RSS has no page, offset or limit parameter, and ?limit=25 is ignored. To build an archive, schedule the Actor and dedupe on articleId, because there is no way to reach back through history in one run.
  • Full text is not guaranteed, and isFullContent tells you which you got. Tag feeds never include <content:encoded>, so contentPlain is the one-line teaser and wordCount is null. Author feeds reliably include it. Publication feeds are inconsistent, and in testing one item in a publication feed carried the full body while the other nine did not. Nothing is fabricated to paper over the gap.
  • Reading time is computed, not scraped. Medium's own badge is not in the feed. readingTimeMinutes is ceil(wordCount / 200), and it is null whenever there is no real body to count.
  • Feed furniture is removed. The trailing "Continue reading on … »" line is stripped from teasers, and the medium.com/_/stat view-tracking pixel is never returned as imageUrl.
  • URLs are canonicalised. Medium appends ?source=rss-. to every link, and that is stripped so the same article produces the same url across feeds.
  • A bad handle is a 404, recorded in RUN_SUMMARY.failures while other feeds carry on. A tag that does not exist returns an empty feed instead, and the log line shows 0 listed.
  • An entry that cannot address a feed at all — https://, a slug with spaces in it — is rejected before the run starts, listed in RUN_SUMMARY.entriesRejected with the reason, and the remaining feeds are read normally. It never ends the run.
  • The same article is returned once per run. Medium files a post under every tag it carries, so overlapping tag feeds repeat heavily; repeats are dropped before they reach the dataset and counted in RUN_SUMMARY.duplicatesSkipped. Identity is the Medium post ID, or the canonical URL for feeds that have no post ID.
  • One publication is one feed. Scheme, www., host case, a trailing slash and a missing /feed suffix all collapse to the same feed URL, so https://towardsdatascience.com, http://www.TowardsDataScience.com/feed/ and towardsdatascience.com are read once between them. A non-default port is kept, because that is a different endpoint.
  • Requests are spaced 400 ms apart and identify themselves as apify-medium-articles-scraper/1.0.
  • Public data only. No authentication, no member-only content, no paywall bypass.

FAQ

Does it get paywalled member-only stories? No. The feed gives what Medium chooses to publish there. Member-only posts appear as metadata plus a teaser, and this Actor does not attempt to get past that.

Can I get more than 10 articles from a tag? Not in one run. Medium caps its feeds at 10 with no pagination. Run on a schedule and accumulate, since an hourly run on a busy tag captures most of what is posted.

Why is contentPlain one sentence for tag feeds? Because that is all the tag feed contains. Use author or publication feeds when you need the body, and check isFullContent.

Is there a JSON API I should use instead? No public one. Medium's internal GraphQL endpoint is undocumented, unauthenticated access is not offered, and it changes without notice. RSS is the supported public surface, which is why this Actor uses it.

Do claps or responses come through? No. Neither is in the feed, and nothing is inferred from anywhere else.

Can I plug it into something else? Yes. Apify API, the client libraries, webhooks, scheduled runs, dataset exports to JSON, CSV or Excel, or MCP. The output is structured JSON.