# Medium Scraper (`anyxsolutions/medium-scraper`) Actor

Scrape Medium stories from any tag, writer or publication, including publications on their own domain: titles, authors, tags, dates, the full text of free stories, word count and reading time, with member-only stories flagged. Paste a Medium URL, get clean JSON. No login, no API key.

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

## Pricing

from $2.50 / 1,000 results

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

![banner](https://anyx.solutions/banners/medium-scraper.png)

## Medium Scraper

**The newest stories from any Medium tag, writer or publication — with authors, tags, dates and the full text of free stories — as clean JSON.**

Medium hosts millions of writers and thousands of publications, from Better Programming to Python
in Plain English, on `medium.com`, on writers' own `<name>.medium.com` subdomains, and on
publications' own domains. This Actor reads any of them and returns their newest stories: title,
author, publication, tags, publish and update dates, cover image, the story body as HTML and plain
text, word count, and an estimated reading time. Free stories come in full; member-only stories
come with the preview Medium shows the public and are flagged as such. Paste a tag, writer,
publication or story URL; there is no login, no API key, and no proxy to configure for small runs.
Built for content monitoring, writer and publication research, topic tracking, and AI and search
datasets.

### ⚡ Quick start

```json
{
  "startUrls": [{ "url": "https://medium.com/tag/programming" }],
  "maxItems": 5
}
```

### 🧩 Input

| Field                | Type    | Required | Default                      | Description                                                                                                                                                                                                                   |
| -------------------- | ------- | -------- | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `startUrls`          | array   | yes      | —                            | Medium tag pages (`medium.com/tag/<tag>`), writer profiles (`medium.com/@<name>` or `<name>.medium.com`), publications (`medium.com/<publication>`, the publication's own domain, or `…/tagged/<tag>`), or single story URLs. |
| `maxItems`           | integer | no       | —                            | Maximum number of stories per start URL, newest first. The input form starts at 5; Medium lists the newest 10 stories of each tag, writer or publication, so a start URL returns at most 10.                                  |
| `includeContent`     | boolean | no       | `true`                       | Include full text for tag pages: look each story on a tag page up on its writer's or publication's page, for its full text, word count and member-only flag. Writer and publication URLs always include the full text.        |
| `proxyConfiguration` | object  | no       | `{ "useApifyProxy": false }` | Proxy settings. Small runs need no proxy; use Apify Proxy (datacenter) when scraping dozens of tags, writers or publications at once.                                                                                         |

**Custom domains work.** Many publications run on their own domain (for example
`python.plainenglish.io`). Paste it as it is: the Actor checks that the site is a Medium
publication and says so in the log when it is not.

### 📤 Output

Each story is one dataset row.

#### Fields

| Field                | Type    | Description                                                                                                                                        |
| -------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                 | string  | Medium story ID (the hex code at the end of the story's URL).                                                                                      |
| `title`              | string  | Story title.                                                                                                                                       |
| `url`                | string  | The story's public page, without tracking parameters.                                                                                              |
| `author`             | string  | The writer's display name.                                                                                                                         |
| `authorUrl`          | string  | The writer's Medium profile. `null` for stories in a publication, unless you scraped the writer's own URL.                                         |
| `publication`        | string  | The publication the story appeared in, or `null` for a writer's own story.                                                                         |
| `publicationUrl`     | string  | The publication's home page.                                                                                                                       |
| `tags`               | array   | The story's tags, as Medium spells them in tag URLs.                                                                                               |
| `publishedAt`        | string  | When the story was published (ISO 8601).                                                                                                           |
| `updatedAt`          | string  | When the story was last updated (ISO 8601).                                                                                                        |
| `snippet`            | string  | The one-line preview Medium shows on tag pages and for member-only stories, when it shows one.                                                     |
| `imageUrl`           | string  | Cover image URL.                                                                                                                                   |
| `contentHtml`        | string  | The full story as HTML. `null` for member-only stories, and for tag-page stories when `includeContent` is off.                                     |
| `contentText`        | string  | The full story as plain text, one paragraph per line.                                                                                              |
| `wordCount`          | number  | Words in `contentText`.                                                                                                                            |
| `readingTimeMinutes` | number  | Estimated reading time, at 265 words per minute. An estimate from `wordCount`, not Medium's own figure.                                            |
| `isMemberOnly`       | boolean | `true` when Medium withholds the full text from the public (a member-only story), `false` when the full text is included, `null` when not checked. |

<details><summary>Example output</summary>

```json
{
  "id": "0126fa5c6ce8",
  "title": "Making “Social” Social Again",
  "url": "https://ev.medium.com/making-social-social-again-0126fa5c6ce8",
  "author": "Ev Williams",
  "authorUrl": "https://medium.com/@ev",
  "publication": null,
  "publicationUrl": null,
  "tags": ["social", "startupş", "relationships"],
  "publishedAt": "2024-12-12T17:11:33.000Z",
  "updatedAt": "2025-05-19T19:09:35.029Z",
  "snippet": null,
  "imageUrl": "https://cdn-images-1.medium.com/max/1024/1*NMXyOoeQu3L1ZAXqf8QIYw.jpeg",
  "contentHtml": "<figure><img alt=\"\" src=\"https://cdn-images-1.medium.com/max …",
  "contentText": "Mozi, our new (actually!) social app\n\nAnnouncing Mozi\nEv Williams, Twitter and Medium Foun …",
  "wordCount": 1799,
  "readingTimeMinutes": 7,
  "isMemberOnly": false
}
```

</details>

### 💡 Use cases

- **Content monitoring** — follow the tags, writers and publications in your field and catch every new story as it is published.
- **Writer and publication research** — see who publishes on a topic, how often, and how much of their work is member-only.
- **Topic tracking** — collect stories and their tags to spot rising subjects in tech, business, and culture.
- **AI and search datasets** — build clean, attributed corpora from the full text of free Medium stories.

### ❓ FAQ

- **Do I need a Medium account or an API key?** No. The Actor reads what Medium publishes to any visitor.
- **Why do I get at most 10 stories per URL?** Medium publicly lists only the newest 10 stories of each tag, writer or publication. To collect more, add more URLs — several tags, or a publication filtered by tag (`…/tagged/<tag>`) — or run the Actor on a schedule and keep the new stories each time.
- **Can I get the full text of member-only stories?** No. Member-only stories come with the preview Medium shows the public; `isMemberOnly` is `true` on those rows and the text fields are `null`.
- **Can I get claps and response counts?** No. Medium only shows them on story pages, which it reserves for browsers; this Actor returns the data Medium publishes for readers' feeds, which leaves those counts out.
- **Can I search Medium by keyword?** Not directly: Medium's search is not open to automated access. Use tag pages instead (`medium.com/tag/<keyword>`), which cover most topics.
- **Can I scrape an old story?** A single story URL works while the story is among the newest 10 of its writer or publication. Older stories are not listed publicly, and the log says so.
- **Why is `authorUrl` empty for some stories?** Stories in a publication do not name the writer's profile. Scrape the writer's own URL (`medium.com/@<name>`) to get it on every row.
- **Do I need a proxy?** Not for small runs. Medium throttles many requests from one IP; for dozens of URLs, turn on Apify Proxy (datacenter).

### 🔗 More scrapers by Anyx

- [Substack Scraper](https://apify.com/anyxsolutions/substack-scraper)
- [Hacker News Scraper](https://apify.com/anyxsolutions/hacker-news-scraper)
- [Google News Scraper](https://apify.com/anyxsolutions/google-news-scraper)
- [Web Content Scraper](https://apify.com/anyxsolutions/web-content-scraper)

### 🤝 Anyx Solutions

We build custom scrapers and data-extraction pipelines.

- Email: tantosthor@gmail.com

### 🖼 Image credit

Image credit: [medium.com](https://medium.com/)

# Actor input Schema

## `startUrls` (type: `array`):

Medium tag pages (https://medium.com/tag/programming), writer profiles (https://medium.com/@ev or https://ev.medium.com), publications (https://medium.com/better-programming, a publication's own domain such as https://python.plainenglish.io, or …/tagged/<tag>), and single story URLs from any of them.

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

Maximum number of stories to return per start URL, newest first. Medium publishes the newest 10 stories of each tag, writer or publication, so a start URL returns at most 10.

## `includeContent` (type: `boolean`):

Tag pages list story previews only. Turn this on to look each story up on its writer's or publication's page for its full text, word count and member-only flag. Adds one request per story; writer and publication URLs always include the full text.

## `proxyConfiguration` (type: `object`):

Proxy settings. Small runs need no proxy. Medium throttles many requests from one IP, so use Apify Proxy (datacenter) when scraping dozens of tags, writers or publications.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://medium.com/tag/programming"
    }
  ],
  "maxItems": 5,
  "includeContent": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

# 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 = {
    "startUrls": [
        {
            "url": "https://medium.com/tag/programming"
        }
    ],
    "maxItems": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("anyxsolutions/medium-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 = {
    "startUrls": [{ "url": "https://medium.com/tag/programming" }],
    "maxItems": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("anyxsolutions/medium-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 '{
  "startUrls": [
    {
      "url": "https://medium.com/tag/programming"
    }
  ],
  "maxItems": 5
}' |
apify call anyxsolutions/medium-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,anyxsolutions/medium-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/bFQ8ajVJ02PVo9MtD/builds/2EZXJxLOZux6AOsED/openapi.json
