Skip to content

About

Medium user articles scraper for JavaScript and Node.js: fetch recent posts from Medium accounts and publication feeds as JSON objects with title, link, tags, publish time and HTML body for Slack alerts, webhooks or a content backend. JavaScript example for the Devil Scrapes Apify Actor.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Medium Author RSS Scraper — JavaScript example

Medium User Articles Scraper for JavaScript and Node.js: recent Medium posts as JSON for your app

Run on Apify JavaScript MIT license CI

Use this repository to call the Medium Author RSS Scraper from Node.js. It is one ES module built on the official apify-client package: hand it Medium handles or publication feed URLs, and it returns each source's latest posts as plain objects with title, URL, writer, tags, publish timestamp and an optional HTML body. Apify fetches and parses the feeds in the cloud, so your service, serverless handler or bot never parses XML itself. The objects are written to results.json and results.csv, or you can import the call and keep them in memory.

Actor: Medium Author RSS Scraper · Publisher: Devil Scrapes · Also in Python: medium-user-articles-scraper-python

🔥 What you get

  • Accounts and publications in one list: a handle such as @netflixtechblog (the @ is optional) is turned into its Medium feed address, and a full feed URL like https://medium.com/feed/netflix-techblog is fetched as given.
  • Rows that carry the essentials: headline, article URL, the feed's guid as article_id, the byline, the tags the writer picked and the publication time converted to ISO-8601 in UTC.
  • Article HTML on request: with includeContent on, content_html holds the body from the feed, or its summary when that is all the feed provides; switch it off and the column is null, which keeps rows small.
  • A per-account cap: maxArticlesPerUser (1 to 50) trims each feed, although Medium itself rarely lists more than the ten newest posts.
  • One dead handle does not stop the batch: a feed that answers with an error is logged and skipped, and the remaining accounts are still delivered.
  • Billed per article written: a start event plus a fee per row, so a handle whose feed is empty or unreachable costs nothing extra.

🎯 Use cases

Post new articles to Slack or Discord

Run node index.js on a schedule, remember which article_id values you have already announced and send the rest to an incoming webhook. A team channel then hears about every new post from the engineering blogs it follows, without anyone subscribing to anything.

Feed a developer newsletter or portal

A Next.js or Express backend can pull fresh posts from a curated list of company accounts and render them as cards with title, tags and date. With includeContent enabled you can show an excerpt cut from content_html server-side.

Mirror your company's own Medium posts

If your team publishes on Medium, a serverless function can copy each new article's HTML into your own CMS or static site so the content also lives under your domain. Keep the original link for a canonical tag.

Trigger downstream jobs from a webhook

Configure an Apify webhook on run success and let it call your endpoint, which reads the dataset and queues translation, summarisation or tagging work for each article it has not processed before.

⚡ Quick start

You need an Apify account and an API token from Console → Settings → Integrations. Apify's free plan includes monthly usage credit; this Actor's event prices are listed under Pricing below.

git clone https://github.com/devilscrapes/medium-user-articles-scraper-javascript.git
cd medium-user-articles-scraper-javascript
npm install
export APIFY_TOKEN=your_token_here
node index.js                       # uses input.json
node index.js my-input.json --out data --max-charge 2

The script starts the Medium Author RSS Scraper, waits for it to finish, prints a link to the run in Apify Console and saves every result to results.json and results.csv. It exits with code 1 and the run's status message if the run fails.

🧩 The core call

Remove the CLI flags and file output from index.js and this is what remains. call() waits for the run to finish before resolving, then listItems() fetches the articles from the run's default dataset in one request.

import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('DevilScrapes/medium-user-articles-scraper').call({"usernames": ["@netflixtechblog", "@AirbnbEng"], "maxArticlesPerUser": 5, "includeContent": false, "proxyConfiguration": {"useApifyProxy": true}});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);

📥 Input

index.js passes input.json through unchanged. The bundled example follows the official Netflix Technology Blog and Airbnb Engineering accounts, takes five articles from each and skips the HTML body, which caps a trial at ten lightweight objects. Extend usernames with more handles or https://medium.com/feed/<publication> URLs as your list grows.

{
  "usernames": [
    "@netflixtechblog",
    "@AirbnbEng"
  ],
  "maxArticlesPerUser": 5,
  "includeContent": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
Field Type Required Default Allowed values
usernames array yes ["@medium"] —
maxArticlesPerUser integer no 10 —
includeContent boolean no true —
proxyConfiguration object no {"useApifyProxy": true} —

📤 Output

You get one object per article. Feed links carry a tracking query string, so split on ? when you use link as an ID or show it to readers; article_id, the feed's guid, is the steadier key. author holds the byline of that article, and published may be null when an entry has no parseable date.

Field Type Always present
username string yes
article_id string or null no
title string yes
link string yes
author string no
content_html string or null no
categories array no
published string or null no
scraped_at string no

💰 Pricing

Event Price (USD)
Actor start $0.2
Result emitted $0.002

Prices as configured on 2026-08-20. The Actor page always shows the current price. Runs are billed to the Apify account that owns the token. Use --max-charge to cap what a single run can cost.

🛠️ Working with the data in JavaScript

This Node-only snippet compares the saved articles with a small posted.json state file, formats the new ones as a Slack message with up to three tags each and POSTs it when SLACK_WEBHOOK_URL is set. Without the variable it prints the message instead.

import { readFile, writeFile } from 'node:fs/promises';

const articles = JSON.parse(await readFile('results.json', 'utf8'));
const statePath = 'posted.json';
const posted = new Set(JSON.parse(await readFile(statePath, 'utf8').catch(() => '[]')));

const idOf = (a) => a.article_id ?? a.link.split('?')[0];
const fresh = articles
    .filter((a) => !posted.has(idOf(a)))
    .sort((a, b) => (a.published ?? '').localeCompare(b.published ?? ''));

const lines = fresh.map((a) => {
    const tags = a.categories?.length ? ` [${a.categories.slice(0, 3).join(', ')}]` : '';
    return `• ${a.username}: <${a.link.split('?')[0]}|${a.title}>${tags}`;
});

const webhook = process.env.SLACK_WEBHOOK_URL;
if (webhook && lines.length > 0) {
    const res = await fetch(webhook, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ text: `New Medium articles:\n${lines.join('\n')}` }),
    });
    if (!res.ok) throw new Error(`Webhook answered HTTP ${res.status}`);
} else {
    console.log(lines.join('\n') || 'Nothing new.');
}

for (const a of fresh) posted.add(idOf(a));
await writeFile(statePath, JSON.stringify([...posted]));

❓ FAQ

Is there an official Medium API I can use instead?

Medium has not offered third-party developers a supported reading API for years. Its RSS feeds are the remaining public way to read posts from code, and this Actor turns them into JSON objects for you.

What happens when one handle is misspelled?

Medium answers that feed with an error, the Actor logs it and moves on, and the run still succeeds with the other accounts' articles. Check the run log through the Console link if a source is missing from the results.

Can I call it from a serverless function?

Yes, as long as the platform lets a function wait for the run, which for a few feeds is usually well within common timeouts. Otherwise start the run with start() instead of call() and collect the dataset later from an Apify webhook.

How much does it cost?

A $0.20 start event per run and $0.002 for each article written to the dataset. Following ten accounts at ten posts each comes to $0.40 per run, and --max-charge caps any run at the amount you set.

🧯 Troubleshooting

  • Set APIFY_TOKEN first: export the token in the same shell, or put it in a local .env file your shell loads. Never commit it.
  • Run finished with FAILED or ABORTED: open the printed Console link; the run log says why. Input validation errors name the field.
  • Fewer rows than expected: a --max-charge cap also limits how many results can be charged. Raise the cap or lower the limits in your input.
  • Something looks wrong in the data: open an issue on the Actor page so the maintainers see it.

🔗 More from Devil Scrapes

Related Actors:


MIT licensed example code. The Actor itself runs on Apify and is maintained by Devil Scrapes. Not affiliated with Medium or A Medium Corporation.

About

Medium user articles scraper for JavaScript and Node.js: fetch recent posts from Medium accounts and publication feeds as JSON objects with title, link, tags, publish time and HTML body for Slack alerts, webhooks or a content backend. JavaScript example for the Devil Scrapes Apify Actor.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages