# Medium Scraper - articles, authors, tags, search, no login (`datagleaner/medium-scraper`) Actor

Scrape public Medium articles by URL, author, tag, publication or search: full text and Markdown, claps, tags, author followers, paywall flag, responses. No login, no browser, $3 per 1,000 articles.

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

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 Scraper: Medium articles by author, tag, publication or search

Medium scraper for public Medium articles: give it article URLs, authors, tags, publications or a search query and get full text, Markdown, claps, tags and author followers as JSON. It also flags member-only stories and can collect the responses (comments) under each article. No Medium account, no cookies, no browser.

**It costs $2 per 1,000 articles ($0.002 each).** The leading Medium scraper on the Apify Store reads RSS feeds only, so it returns at most 10 items per feed and has no claps, responses, reading time or paywall flag. This Actor pages through Medium's own article data, so you get all of those fields and can go as deep as the cap you set.

### What you can do with it

- **Content and competitor research.** Pull every article a publication or author has posted, ranked by claps, with tags and reading time.
- **Trend tracking.** Run a tag such as `machine-learning` every day on the newest order and see what is being written about.
- **Lead and creator lists.** Find the authors who write about a topic, with their follower counts and profile links.
- **AI and RAG datasets.** Article text is delivered both as plain text and as Markdown with headings, links, lists, code blocks and image links preserved.
- **Reader sentiment.** Turn on responses to collect the comments under an article.

### Input

Nothing is required. A run started with empty input scrapes a 5-article example of top articles tagged `python`.

| Field | Type | What it does |
|---|---|---|
| `startUrls` | list of text | Any Medium URL: article, `@profile`, publication, tag page, or a custom domain. Each is scraped by what it points at, using the per-source caps below. |
| `articleUrls` | list of text | Article URLs (medium.com, publication subdomains and custom domains all work). Profile, publication and tag URLs placed here are routed to the right source too. |
| `authors` | list of text | `@handle`, bare handle or profile URL. Newest articles first. |
| `maxArticlesPerAuthor` | number | Cap per author. Default 20. |
| `tags` | list of text | Tags such as `python` or `startup`. |
| `maxArticlesPerTag` | number | Cap per tag. Default 20. |
| `tagSort` | choice | `newest` (default) or `top`. |
| `publications` | list of text | Publication slug, URL or domain, e.g. `better-programming` or `netflixtechblog.com`. |
| `maxArticlesPerPublication` | number | Cap per publication. Default 20. |
| `searchQuery` | text | Keywords to search stories for. |
| `maxArticlesPerSearch` | number | Cap for the search. Default 20. |
| `maxArticles` | number | Optional cap on articles across the whole run, so one number bounds the cost of a mixed input. Empty means no total cap. |
| `includeContent` | true / false | Add `contentText` and `contentMarkdown`. Default on. |
| `includeResponses` | true / false | Also return responses (comments) as separate items. Default off. |
| `maxResponsesPerArticle` | number | Cap per article. Default 10. |
| `requestDelaySeconds` | number | Pause between requests, default 0.6 s (randomised up to 1.5x). |
| `proxyConfiguration` | object | Optional Apify Proxy. Default is no proxy. |

```json
{
  "authors": ["@netflixtechblog", "https://medium.com/@kentcdodds"],
  "maxArticlesPerAuthor": 15,
  "tags": ["python", "startup"],
  "maxArticlesPerTag": 20,
  "tagSort": "top",
  "includeContent": true,
  "includeResponses": false
}
```

Articles found more than once in a run (for example in two tags) are returned once. Entries that are not Medium URLs, handles or publications are skipped and named in the run's status message, as are articles that are not found or not public.

### Output

One dataset item per article, plus one per response when responses are on. Items are told apart by `type`. Export as JSON, CSV, Excel or via the API.

| Field | Meaning |
|---|---|
| `id`, `url`, `title`, `subtitle` | Article ID, link, title and the preview subtitle. |
| `author` | `id`, `name`, `handle`, `url` and `followers` of the writer. |
| `publication`, `publicationSlug`, `publicationUrl` | Name, slug and URL of the publication, or `null` when the article is not in one. |
| `publishedAt`, `updatedAt` | ISO 8601 UTC. `updatedAt` is the last publish of the article. |
| `readingTimeMinutes`, `claps`, `responsesCount` | Numbers. |
| `tags` | Tag names. |
| `isPaywalled` | `true` for member-only stories. |
| `previewImage` | Preview image link. |
| `contentText`, `contentMarkdown` | Full text (with `includeContent`). The leading title, subtitle and any kicker line above the title are removed, so the body does not repeat them. |
| `contentIsPartial`, `contentNote` | For member-only stories: Medium only shows a preview without a subscription. We return that public preview and set `contentIsPartial: true`. |
| `sourceType`, `source` | Which input produced the item. |
| `scrapedAt` | UTC time of collection. |

**Response** items (`type: "response"`): `id`, `url`, `articleId`, `articleUrl`, `articleTitle`, `author`, `publishedAt`, `claps`, `repliesCount`, `contentText`, `contentMarkdown`, `sourceType`, `source`, `scrapedAt`.

```json
{
  "type": "article",
  "id": "516d5a29b252",
  "url": "https://netflixtechblog.com/trading-a-cloud-identity-for-your-own-workload-attestation-on-managed-compute-516d5a29b252",
  "title": "Trading a Cloud Identity for Your Own: Workload Attestation on Managed Compute",
  "subtitle": "By Dhruv Pratap",
  "author": {"name": "Netflix Technology Blog", "handle": "netflixtechblog", "url": "https://medium.com/@netflixtechblog", "followers": 484146},
  "publication": "Netflix TechBlog",
  "publishedAt": "2026-09-25T16:01:02+00:00",
  "updatedAt": "2026-09-25T16:01:02+00:00",
  "readingTimeMinutes": 8.1,
  "claps": 127,
  "responsesCount": 2,
  "tags": ["Apache Spark", "Aws Emr", "Security", "Identity", "Cloud"],
  "isPaywalled": false,
  "previewImage": "https://miro.medium.com/v2/resize:fit:1400/1*eIWpHw7J_6qZCuaffD0lQQ.png",
  "contentText": "Introduction\n\nOrganizations that have been around for a while ...",
  "contentMarkdown": "### Introduction\n\nOrganizations that have been around for a while ...",
  "contentIsPartial": false,
  "sourceType": "tag",
  "source": "python",
  "scrapedAt": "2026-10-08T16:22:21+00:00"
}
```

### Pricing

Pay per event: **$0.002 per article** ($2 per 1,000) and **$0.0005 per response** ($0.50 per 1,000). You pay only for items you receive, nothing for failed requests or retries, and the run stops by itself when your **maximum total charge** is reached. Platform usage is included.

Worked example: 20 authors at 50 articles each is 1,000 articles, so $2.00. Adding the first 10 responses under each of those articles gives up to 10,000 responses, another $5.00, so $7.00 in total.

### Limits

- **Member-only stories** return the public preview only, flagged by `contentIsPartial`. The Actor does not log in and cannot read paywalled text.
- **Depth of feeds.** Author, publication and tag feeds are paged by Medium; very old articles may not be reachable through a tag. For a full archive, use the author or publication feed.
- **Search** returns Medium's relevance order.
- **Fallback.** If Medium refuses the main endpoint, the Actor switches to the public RSS feed for authors, tags and medium.com publications (not publications on their own domain). That returns only the 10 newest stories, without claps, response counts or follower counts, and the log says so.
- Medium can change or throttle its public endpoints at any time. The Actor slows down and retries, but a run may return less than requested if Medium limits it. Raise `requestDelaySeconds` or enable a proxy if that happens.

### Use with Python

```python
## pip install apify-client
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("datagleaner/medium-scraper").call(
    run_input={"tags": ["machine-learning"], "tagSort": "top", "maxArticlesPerTag": 10}
)
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item["title"], item["claps"], item["url"])
```

Ten articles cost $0.02 at $0.002 each.

### Use with JavaScript / Node.js

```js
// npm install apify-client
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('datagleaner/medium-scraper').call({
    tags: ['machine-learning'],
    tagSort: 'top',
    maxArticlesPerTag: 10,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const item of items) console.log(item.title, item.claps, item.url);
```

### Use it from n8n, Make, Zapier or an AI agent

Actor ID: `datagleaner/medium-scraper`

Minimal input:

```
{"tags": ["machine-learning"], "tagSort": "top", "maxArticlesPerTag": 10}
```

Each tool below runs this Actor with your own Apify API token.

- **n8n:** add the **Apify** node (`@apify/n8n-nodes-apify`). On n8n Cloud you install it from the community node registry. Choose **Run an Actor and get dataset**, set Actor to `datagleaner/medium-scraper` and paste the input above.
- **Make:** use the Apify app's **Run an Actor** module, then **Get Dataset Items** to read the results. **Watch Actor Runs** can trigger a scenario when a run finishes.
- **Zapier:** use the Apify action **Run Actor**, then the search **Fetch dataset items**. The trigger **Finished Actor run** starts a Zap when a run ends.
- **AI agents (MCP):** connect to `https://mcp.apify.com?tools=datagleaner/medium-scraper`. Without the `tools` parameter, the agent can still find the Actor with the `search-actors` tool and run it with `call-actor`. In Claude Code:

  ```
    claude mcp add --transport http apify "https://mcp.apify.com?tools=datagleaner/medium-scraper"
  ```

  Then run `/mcp` to sign in to Apify in your browser. Then ask in plain words, for example: "Get the 30 top Medium articles tagged llm and the 20 newest from the netflixtechblog.com publication, then list the authors with the most followers and summarise what the five most-clapped articles are about."
- **LangChain (Python):**

```python
## pip install langchain-apify, then set APIFY_TOKEN in your environment
import json
from langchain_apify import ApifyActorsTool
tool = ApifyActorsTool("datagleaner/medium-scraper")
result = tool.invoke({"run_input": json.loads('{"tags": ["machine-learning"], "tagSort": "top", "maxArticlesPerTag": 10}')})
```

### FAQ

**How do I scrape Medium articles?** Add article URLs, author handles, tags, publications or a search query to the input and start the Actor. Each article comes back as one JSON item, which you can export as JSON, CSV or Excel, or read through the API.

**Is there a Medium API for reading articles?** This Actor works as one: call it from the Apify API, the Python or JavaScript client or an MCP client, and get articles with full text, claps, tags and author data as JSON.

**Can I scrape Medium articles with Python?** Yes. See "Use with Python" above: install `apify-client`, pass the input, and iterate the dataset.

**Does it scrape member-only stories behind the Medium paywall?** Only what Medium shows to a logged-out visitor. The Actor does not log in; member-only stories return the public preview with `isPaywalled: true` and `contentIsPartial: true`.

**Can I get the comments under an article?** Yes: turn on `includeResponses`. Responses arrive as items with `type: "response"`, linked to their article by `articleId`, at $0.0005 each.

### Responsible use

This Actor collects only publicly visible data. Respect Medium's terms and keep the request rate low (the default pacing is deliberately polite). Articles are the authors' copyrighted work: the data is for analysis and research, not a licence to republish it. Author names and handles are personal data: **you** are responsible for having a lawful basis and meeting your obligations under GDPR, CCPA and any other law that applies to your use, including retention and deletion requests. This is not legal advice.

# Actor input Schema

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

Paste any Medium links: articles, author profiles (medium.com/@name or name.medium.com), publications (medium.com/better-programming or netflixtechblog.com) or tag pages (medium.com/tag/python). Each is scraped by what it points at, using the per-source caps below.

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

Medium article URLs, including ones on custom domains or publication subdomains. Returns each article with full text. Profile, publication and tag URLs placed here are routed to the right source too.

## `authors` (type: `array`):

Author handles (@netflixtechblog) or profile URLs (https://medium.com/@netflixtechblog). Returns their newest articles.

## `maxArticlesPerAuthor` (type: `integer`):

Upper bound on articles for each author.

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

Medium tags such as python, startup or machine-learning (a medium.com/tag/... URL also works).

## `maxArticlesPerTag` (type: `integer`):

Upper bound on articles for each tag.

## `tagSort` (type: `string`):

Newest articles first, or the tag's top (hot) articles.

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

Publication slugs, URLs or domains, for example better-programming, https://medium.com/netflix-techblog or netflixtechblog.com.

## `maxArticlesPerPublication` (type: `integer`):

Upper bound on articles for each publication.

## `searchQuery` (type: `string`):

Keywords to search Medium stories for.

## `maxArticlesPerSearch` (type: `integer`):

Upper bound on articles returned for the search query.

## `maxArticles` (type: `integer`):

Optional cap on articles across every source in the run, so one number bounds the cost of a mixed input. Leave empty for no total cap.

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

Add contentText and contentMarkdown. Member-only stories return the public preview Medium shows without a subscription, flagged with contentIsPartial. Switching this off makes runs faster; the price per article stays the same.

## `includeResponses` (type: `boolean`):

Also return the responses under each article as separate items of type response, charged per response.

## `maxResponsesPerArticle` (type: `integer`):

Upper bound on responses for each article when responses are on.

## `requestDelaySeconds` (type: `number`):

Base pause between requests; the actual pause is randomised up to 1.5x. Raise it if you see throttling.

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

Optional. Medium works without a proxy; enable Apify Proxy if your runs start getting blocked.

## Actor input object example

```json
{
  "maxArticlesPerAuthor": 20,
  "tags": [
    "python"
  ],
  "maxArticlesPerTag": 20,
  "tagSort": "newest",
  "maxArticlesPerPublication": 20,
  "maxArticlesPerSearch": 20,
  "includeContent": true,
  "includeResponses": false,
  "maxResponsesPerArticle": 10,
  "requestDelaySeconds": 0.6,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

No description

## `responses` (type: `string`):

No description

## `results` (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 = {
    "tags": [
        "python"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("datagleaner/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 = {
    "tags": ["python"],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("datagleaner/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 '{
  "tags": [
    "python"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call datagleaner/medium-scraper --silent --output-dataset

```

## MCP server setup

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