# Medium Articles Scraper: Tags, Authors, Publications (`tidyharvest/medium-articles-scraper`) Actor

Latest Medium articles by tag, author, publication (custom domains too) or article URL, with full text and HTML, word count, reading time, tags, image and date, plus date/keyword filters. Official RSS feeds, no login or proxy. $2.00 per 1,000 articles.

- **URL**: https://apify.com/tidyharvest/medium-articles-scraper.md
- **Developed by:** [TidyHarvest](https://apify.com/tidyharvest) (community)
- **Categories:** News, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 article scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Medium Articles Scraper: Tags, Authors, Publications

Get the latest **Medium articles** by **tag**, **author**, **publication** (including custom-domain publications like
`pub.towardsai.net`) or **article URL**, with **full article text and HTML**, **word count**, **reading time**, tags,
cover image and publish date. It reads Medium's official **RSS feeds** only: no login, no cookies, no proxy, no
browser. That makes it fast (a few seconds per run) and cheap: **$2.00 per 1,000 articles**, and less on higher Apify plans.

### What you can do with it

- **Monitor topics:** pull the newest posts tagged `artificial-intelligence`, `startup` or `python` every hour and
  send them to Slack, a spreadsheet or an LLM pipeline.
- **Track writers and publications:** collect every new story from a list of authors or publications, with full
  text for archiving, newsletters or content research.
- **Feed AI and RAG pipelines:** clean plain text plus HTML, word count and reading time, ready to chunk and embed.
- **Content and SEO research:** see which tags and titles competitors publish, and how long their posts are.

### Why this one

- **Full text for tag results.** Medium's tag feeds carry only a snippet. This actor looks each tag result up in its
  author's or publication's feed and fills in the full text when it is there (other RSS-based Medium actors return
  the snippet only).
- **Article URL lookup** for recent posts, **date and keyword filters**, a **member-only flag**
  (`isFullContent`), and a "full text only" switch. Filters run before you are charged.
- **De-duplicated** across your inputs: the same story found via a tag and its author is charged once.
- Fails loudly on bad input or empty results instead of returning an empty "successful" run.

### Input

| Field | What it does |
|---|---|
| `tags` | Medium tags (`python`, `machine-learning`) or tag URLs. |
| `usernames` | Authors: `@ev`, `ev`, `https://medium.com/@ev`, `https://ev.medium.com`. |
| `publications` | `better-programming`, `https://medium.com/towards-data-science`, or a custom domain (`https://aiadvances.org`). |
| `articleUrls` | Full article URLs; each is found in its author's/publication's feed (recent posts only). |
| `maxItems` | Hard cap on results (and cost) for the whole run. Default 100. |
| `maxItemsPerFeed` | Optional cap per tag/author/publication. |
| `publishedAfter` / `publishedBefore` | `2026-10-01`, an ISO timestamp, or relative (`7 days`, `24 hours`). |
| `keywords` | Keep only articles whose title, text or tags contain one of these. |
| `fullContentOnly` | Skip member-only stories (feeds give only a snippet for those). |
| `enrichTagResults` | Fill in full text for tag results (default on). |
| `includeHtml` | Include `contentHtml` (default on). |

Example:

```json
{
  "tags": ["artificial-intelligence", "python"],
  "usernames": ["@ev"],
  "publications": ["better-programming", "https://pub.towardsai.net"],
  "publishedAfter": "7 days",
  "maxItems": 50
}
```

### Output

One item per article:

```json
{
  "articleId": "0126fa5c6ce8",
  "title": "Making \"Social\" Social Again",
  "url": "https://ev.medium.com/making-social-social-again-0126fa5c6ce8",
  "author": "Ev Williams",
  "authorUsername": "ev",
  "publication": null,
  "publishedAt": "2024-12-12T17:11:33Z",
  "updatedAt": "2025-05-19T19:09:35.029Z",
  "tags": ["social", "relationships"],
  "isFullContent": true,
  "snippet": "Mozi, our new (actually!) social app Announcing Mozi ...",
  "contentText": "Mozi, our new (actually!) social app\nAnnouncing Mozi\n...",
  "contentHtml": "<figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max/1024/1*NMXy....jpeg\" />...",
  "wordCount": 1849,
  "readingTimeMinutes": 8,
  "imageUrl": "https://cdn-images-1.medium.com/max/1024/1*NMXyOoeQu3L1ZAXqf8QIYw.jpeg",
  "imageCount": 4,
  "guid": "https://medium.com/p/0126fa5c6ce8",
  "feedType": "user",
  "feedUrl": "https://medium.com/feed/@ev",
  "feedTitle": "Stories by Ev Williams on Medium",
  "source": "user:@ev",
  "scrapedAt": "2026-10-15T23:55:35Z"
}
```

`url` is the canonical article URL with Medium's `?source=rss` tracking removed. For member-only stories
`isFullContent` is `false`, `contentText`/`wordCount`/`readingTimeMinutes` are `null`, and `snippet` holds Medium's
teaser. Reading time uses Medium's own formula (265 words per minute plus time for images), so it can differ from
Medium's display by about a minute. A `SUMMARY` record in the key-value store lists each feed, how many posts it held,
how many were returned, and any input that failed.

### Pricing

Pay per result: **$2.00 per 1,000 articles** (`article-scraped`) on the Free plan, down to **$0.50 per 1,000** on
Diamond. No start fee. Filters, de-duplication and caps apply before you are charged. A run that finds nothing fails
instead of returning an empty dataset.

### Limits and notes

- **Feeds hold only the latest ~10 posts** per tag, author or publication. This is not a full-archive or search
  scraper: to build history, schedule it (hourly or daily) and keep the results. There is no Medium keyword search,
  because Medium's search pages are behind a bot check and disallowed in robots.txt.
- **Member-only (paywalled) stories** come with a snippet only; Medium does not put their text in feeds. Use
  `fullContentOnly` to skip them.
- **Tag results** get full text only if the story is still among its author's/publication's latest ~10 posts.
- **Article URLs** work only for recent posts (in the author's/publication's feed). Short `medium.com/p/<id>` links
  can't be resolved without opening Medium's article page, which sits behind a Cloudflare challenge that this actor does
  not try to bypass; use the full URL.
- No claps, responses or follower counts: feeds don't include them.
- Public data only, read from the RSS feeds Medium publishes for exactly this purpose (`/feed`, allowed by robots.txt).
  Respect the authors' copyright when you reuse the text.

# Actor input Schema

## `tags` (type: `array`):

Medium tags, one per line: 'python', 'machine-learning', or a tag URL like https://medium.com/tag/startup. Tag feeds list the ~10 newest posts; full text is filled in from each post's author or publication feed when it is still there (see 'Fill in full text for tag results').

## `usernames` (type: `array`):

Medium authors, one per line: '@ev', 'ev', https://medium.com/@ev or https://ev.medium.com. Returns the author's ~10 latest stories.

## `publications` (type: `array`):

Medium publications, one per line: 'better-programming', https://medium.com/towards-data-science, or a custom-domain publication like https://aiadvances.org or https://pub.towardsai.net. Returns the publication's ~10 latest stories.

## `articleUrls` (type: `array`):

Full Medium article URLs (https://medium.com/@user/title-1a2b3c4d5e6f, https://medium.com/<publication>/title-..., https://<user>.medium.com/title-..., or a custom-domain publication URL). Each is looked up in its author's or publication's feed, so it works for recent posts only (roughly the author's/publication's last 10). Short medium.com/p/<id> links are not supported.

## `maxItems` (type: `integer`):

Hard cap on results for the whole run (also caps your cost). Shared fairly across your inputs.

## `maxItemsPerFeed` (type: `integer`):

Optional cap per tag/author/publication. 0 or empty = no per-feed cap (feeds hold ~10 posts anyway).

## `publishedAfter` (type: `string`):

Keep only articles published on/after this date: 2026-10-01, 2026-10-01T12:00:00Z, or relative like '7 days' / '24 hours'.

## `publishedBefore` (type: `string`):

Keep only articles published on/before this date (same formats).

## `keywords` (type: `array`):

Keep an article only if its title, text or tags contain one of these (case-insensitive). Empty = all.

## `fullContentOnly` (type: `boolean`):

Skip member-only (paywalled) stories, for which Medium's feeds give only a snippet. You are not charged for skipped articles.

## `enrichTagResults` (type: `boolean`):

Tag feeds carry only a snippet. When on, each tag result is looked up in its author's or publication's feed to add full text, word count and reading time (free stories that are still in that feed).

## `includeHtml` (type: `boolean`):

Include the article body as HTML (contentHtml) next to plain text. Turn off for smaller datasets.

## Actor input object example

```json
{
  "tags": [
    "artificial-intelligence"
  ],
  "maxItems": 100,
  "fullContentOnly": false,
  "enrichTagResults": true,
  "includeHtml": true
}
```

# Actor output Schema

## `articles` (type: `string`):

Medium articles with title, URL, author, publication, date, tags, full text/HTML (free stories), word count, reading time and image, one item per article.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "tags": [
        "artificial-intelligence"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("tidyharvest/medium-articles-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "tags": ["artificial-intelligence"] }

# Run the Actor and wait for it to finish
run = client.actor("tidyharvest/medium-articles-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "tags": [
    "artificial-intelligence"
  ]
}' |
apify call tidyharvest/medium-articles-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tidyharvest/medium-articles-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/axmD4SgNsqxcSpW8d/builds/Ppw4O3C5jbV4ChjWF/openapi.json
