# Medium Articles Scraper (`hebaabdalaziz/medium-articles-scraper`) Actor

Scrape Medium articles by tag, author, publication, search query or post URL. Get title, author, publication, claps, responses, reading time, tags and publish dates, plus optional full text of free posts. No login or cookies.

- **URL**: https://apify.com/hebaabdalaziz/medium-articles-scraper.md
- **Developed by:** [Heba Abdalaziz](https://apify.com/hebaabdalaziz) (community)
- **Categories:** Social media, News, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $2.00 / 1,000 articles

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

### What does it do?

**Medium Articles Scraper** collects articles from [Medium](https://medium.com) by **tag**, **author**, **publication**, **search query** or **single article URL**. For every article you get the title, subtitle, author, publication, claps, responses, reading time, tags, publish and update dates, and whether it is member-only. Optionally it also downloads the **full text of free articles**.

It does **not** bypass Medium's paywall. Member-only articles are returned with their metadata and public preview (subtitle) only, never with full text.

### Why use it?

- **Content research**: find the most-clapped posts on a topic this week, this year or of all time.
- **Author and publication monitoring**: track what a writer or a publication publishes, and how it performs.
- **Datasets for NLP and AI**: collect clean full text of free articles with word counts.
- **Market and trend analysis**: see which tags and topics get the most engagement.
- No login, no browser, no Medium account needed. Fast: about 600 articles in 25 seconds.

### How to use it

1. Open the Actor and go to the **Input** tab.
2. Add what you want to scrape: paste Medium links into **Medium URLs**, or type tags, author usernames, publication slugs or search keywords into the matching lists.
3. Choose the **Tag feed sort** (newest, top this week/year/all time, or all of them merged).
4. Set **Max articles per source** and switch on **Include full text** if you need article bodies.
5. Click **Start**, then download the results as JSON, CSV, Excel or HTML from the **Output** tab, or read them through the Apify API.

### Input

| Field | Type | Default | Description |
| --- | --- | --- | --- |
| `startUrls` | array of URLs | `[]` | Any Medium links: `medium.com/tag/python` (also `/archive`, `/top/week`, `/top/year`, `/top/all-time`), `medium.com/@name`, `name.medium.com`, `medium.com/<publication>`, custom publication domains such as `levelup.gitconnected.com`, `medium.com/search?q=...`, and single article URLs. Use full links starting with `https://`. |
| `tags` | array of strings | `[]` (prefill `["python"]`) | Tag slugs or names, e.g. `python`, `machine-learning`, `Data Science` (spaces, `_` and `.` become `-`). Tag links also work, with or without `https://`. |
| `authors` | array of strings | `[]` | Usernames, with or without `@` (e.g. `karpathy`, `@sumit.ai`). Profile links also work here, with or without `https://`. |
| `publications` | array of strings | `[]` | Publication slugs (`data-science`), `<name>.medium.com` subdomains or custom domains (`levelup.gitconnected.com`), with or without `https://`. |
| `searchQueries` | array of strings | `[]` | Keywords, like Medium's search box. |
| `tagFeedMode` | string | `NEW` | `NEW`, `TOP_WEEK`, `TOP_YEAR`, `TOP_ALL_TIME`, or `ALL` (top week, top year, top all time, then newest; duplicates removed). A mode in a pasted tag URL wins. |
| `maxArticlesPerSource` | integer | `100` (prefill `10`) | Maximum articles saved per tag, author, publication, search or URL. |
| `includeFullText` | boolean | `false` | Add `fullText` and `wordCount` for free (non-member-only) articles. |

At least one source (URL, tag, author, publication or search query) is required.

```json
{
    "startUrls": [
        { "url": "https://medium.com/tag/machine-learning/top/year" },
        { "url": "https://karpathy.medium.com/" }
    ],
    "tags": ["python"],
    "publications": ["levelup.gitconnected.com"],
    "searchQueries": ["rust async"],
    "tagFeedMode": "NEW",
    "maxArticlesPerSource": 100,
    "includeFullText": true
}
```

### Output

One row per article. Real rows from a run (full text shortened here):

```json
[
  {
    "id": "5b515c9e4cca",
    "url": "https://www.towardsdeeplearning.com/a-single-claude-md-file-went-viral-the-reason-is-embarrassingly-simple-5b515c9e4cca",
    "title": "A Single CLAUDE.md File Went Viral. The Reason Is Embarrassingly Simple.",
    "subtitle": "91,000 stars on GitHub. No code. Four rules from Andrej Karpathy that every coding agent should have been following from day one.",
    "authorName": "Sumit Pandey",
    "authorUsername": "sumit.ai",
    "authorId": "73d7984df4b2",
    "publicationName": "Towards Deep Learning",
    "publicationSlug": "towards-deep-learning",
    "claps": 7285,
    "responses": 120,
    "readingTimeMinutes": 4.35,
    "tags": ["artificial-intelligence", "deep-learning", "ai", "machine-learning", "claude-code"],
    "publishedAt": "2026-05-08T08:32:29Z",
    "updatedAt": "2026-05-08T08:32:29Z",
    "isMemberOnly": true,
    "source": "https://medium.com/tag/machine-learning/top/year",
    "sourceType": "tag",
    "scrapedAt": "2026-10-10T11:46:06Z"
  },
  {
    "id": "a64152b37c35",
    "url": "https://karpathy.medium.com/software-2-0-a64152b37c35",
    "title": "Software 2.0",
    "subtitle": "I sometimes see people refer to neural networks as just “another tool in your machine learning toolbox”. They have some pros and cons, they…",
    "authorName": "Andrej Karpathy",
    "authorUsername": "karpathy",
    "authorId": "ac9d9a35533e",
    "publicationName": null,
    "publicationSlug": null,
    "claps": 61029,
    "responses": 194,
    "readingTimeMinutes": 8.8,
    "tags": ["machine-learning", "artificial-intelligence", "programming", "software-development", "future"],
    "publishedAt": "2017-11-11T22:18:53Z",
    "updatedAt": "2021-03-13T18:18:43Z",
    "isMemberOnly": false,
    "source": "https://karpathy.medium.com/",
    "sourceType": "author",
    "scrapedAt": "2026-10-10T11:46:05Z",
    "fullText": "I sometimes see people refer to neural networks as just “another tool in your machine learning toolbox”. They have some pros and cons, they work here or there, …",
    "wordCount": 2127
  }
]
```

Inputs that returned nothing (unknown tag, author or publication, empty search, unsupported URL) or failed are listed with a reason in the `FAILED_INPUTS` record of the run's key-value store.

### Data fields

| Field | Type | Description |
| --- | --- | --- |
| `id` | string | Medium post id |
| `url` | string | Article URL |
| `title` | string | Title |
| `subtitle` | string | Subtitle or public preview text |
| `authorName` | string | Author display name |
| `authorUsername` | string | Author username |
| `authorId` | string | Author user id |
| `publicationName` | string or null | Publication name (null for self-published posts) |
| `publicationSlug` | string or null | Publication slug |
| `claps` | integer | Total claps |
| `responses` | integer | Number of responses (comments) |
| `readingTimeMinutes` | number | Medium's reading time estimate |
| `tags` | array of strings | Tag slugs |
| `publishedAt` | string | First publish time, ISO 8601 UTC |
| `updatedAt` | string | Latest publish/update time, ISO 8601 UTC |
| `isMemberOnly` | boolean | `true` for member-only (paywalled) articles |
| `source` | string | The input this row came from |
| `sourceType` | string | `tag`, `author`, `publication`, `search` or `post` |
| `fullText` | string | Article body, paragraphs separated by blank lines. Only with `includeFullText`, only for free articles |
| `wordCount` | integer | Number of words in `fullText` (present with `fullText`) |
| `scrapedAt` | string | Scrape time, ISO 8601 UTC |

### How much does it cost?

This Actor is **pay-per-result**:

| Event | Planned price |
| --- | --- |
| Article saved to the dataset | $0.002 per article ($2 per 1,000) |
| Full text downloaded (only with `includeFullText`, only free articles) | $0.002 per article ($2 per 1,000) |

Examples: 1,000 articles without full text cost about $2. 1,000 articles with full text, of which 400 are free, cost about $2 + $0.80 = $2.80. Member-only articles never incur the full-text charge. Nothing is charged for inputs that return no articles, and duplicate articles (for example the same post found by two tags) are saved and charged only once.

You can cap your spend with the **Maximum cost per run** run option; the Actor stops cleanly when the limit is reached.

### Tips

- **Top lists are short.** Medium shows about 100 articles per top list (week, year, all time). `ALL` reads the three top lists first and then the newest feed, removing duplicates, until `maxArticlesPerSource` articles are saved for the tag.
- **The newest feed goes deep.** `NEW` keeps paging back in time, so set `maxArticlesPerSource` to the number you need.
- **Custom domains work.** Paste the publication's own domain (e.g. `levelup.gitconnected.com`) or any article URL on it.
- **Subdomains** like `name.medium.com` are tried as an author first, then as a publication.
- **Keep full text off** when you only need titles and engagement numbers: runs are faster and cheaper.

### FAQ

**Can it get the full text of member-only articles?**
No. The Actor never bypasses Medium's paywall. Member-only articles return metadata plus the public preview (subtitle) and `isMemberOnly: true`.

**Why did my author or tag return nothing?**
Check the spelling. Unknown authors, tags and publications are listed in `FAILED_INPUTS` with the reason, and the run still succeeds. The run only fails when Medium could not be reached for any input.

**Does it need a Medium account or cookies?**
No. It reads public data only.

**Is it legal to scrape Medium?**
The Actor collects only publicly available article data. Scraping public data is generally lawful, but you are responsible for how you use it: respect Medium's Terms of Service, copyright on article texts (do not republish full texts without permission) and applicable laws.

**Does the output contain personal data?**
Rows include authors' public names and usernames as published on Medium. If you process this data, make sure you have a legitimate purpose and comply with privacy laws such as the GDPR.

### Support

Found a bug or need another field or input type? Open an issue in the **Issues** tab of this Actor and we will look into it.

# Actor input Schema

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

Paste any Medium links: tag pages (medium.com/tag/python, also /archive or /top/year), authors (medium.com/@name or name.medium.com), publications (medium.com/publication or a custom domain like levelup.gitconnected.com), searches (medium.com/search?q=...) and single articles.

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

Medium tag slugs such as `python` or `machine-learning`.

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

Medium usernames, with or without `@` (e.g. `karpathy`).

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

Publication slugs (e.g. `data-science`) or custom domains (e.g. `levelup.gitconnected.com`).

## `searchQueries` (type: `array`):

Keywords to search Medium articles for, like on medium.com/search.

## `tagFeedMode` (type: `string`):

Which tag feed to read. Newest = latest posts. Top lists = most popular in that period (Medium shows about 100 posts per top list). All feeds merged = the three top lists first, then the newest feed, without duplicates. A mode in a pasted tag URL (/archive, /top/year) takes precedence.

## `maxArticlesPerSource` (type: `integer`):

Maximum number of articles to save for each tag, author, publication, search or URL.

## `includeFullText` (type: `boolean`):

Also download the article body for free posts (adds `fullText` and `wordCount`, charged as an extra event). Member-only posts never get full text: they keep metadata and the public preview only.

## Actor input object example

```json
{
  "startUrls": [],
  "tags": [
    "python"
  ],
  "authors": [],
  "publications": [],
  "searchQueries": [],
  "tagFeedMode": "NEW",
  "maxArticlesPerSource": 10,
  "includeFullText": false
}
```

# Actor output Schema

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

No description

## `failedInputs` (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"
    ],
    "maxArticlesPerSource": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("hebaabdalaziz/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": ["python"],
    "maxArticlesPerSource": 10,
}

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

```

## MCP server setup

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