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

Collect public Medium articles by keyword, tag, author, publication, or article URL. Get titles, authors, dates, tags, previews, engagement counts, access status, and optional full text in structured dataset rows.

- **URL**: https://apify.com/maximedupre/medium-articles.md
- **Developed by:** [Maxime Dupré](https://apify.com/maximedupre) (community)
- **Categories:** Social media, Marketing, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.85 / 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.
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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

### 📝 Turn public Medium reading into structured data

Content researchers, writers, SEO teams, and developers can use this Actor to find public Medium articles and save structured rows. Each row can include the title, URL, author, publication details, tags, publication date, preview, reading time, clap and response counts, access status, and optional full text. That gives you a searchable source for topic research, author or publication review, content planning, and other work that starts with public Medium data.

**Use cases**

- Collect titles, authors, and dates from public pages with **[Medium Article Scraper](https://apify.com/maximedupre/medium-articles/examples/medium-article-scraper)**.
- Find posts that match a phrase with **[Medium Blog Search](https://apify.com/maximedupre/medium-articles/examples/medium-blog-search)**.
- Look up matching pages with **[Medium Article Search](https://apify.com/maximedupre/medium-articles/examples/medium-article-search)**.
- Check member-only status with **[Is Medium Free?](https://apify.com/maximedupre/medium-articles/examples/is-medium-free)**.
- Review a topic collection with **[Medium Blog](https://apify.com/maximedupre/medium-articles/examples/medium-blog)**.

#### 📚 Public Medium article records

**What you get**

Each saved row is one public Medium article. The Actor saves the first eligible occurrence when the same article appears again through another keyword, URL, handle, ID, or similar submitted value. Later matches are ignored. `matchedInput` shows the one value that first found the row.

**Public access**

It works with supported public Medium sources without a Medium account or authentication. It does not access private data or bypass paywalls. Member-only and paywall status is reported when the source exposes it.

**Use the data**

Use article metadata to compare topics, authors, publications, tags, dates, reading time, and public engagement. Request full text when you need public article text and it is available.

#### ▶️ Choose a Medium target and run

**Choose a target**

Select exactly one target for a run. Keyword search accepts a list of same-kind keywords. Tag, author, publication, and article URL use their matching field. Fields for another target are ignored.

**Keep a run focused**

Date and preview filters apply to keyword, tag, author, and publication targets. Article URL ignores them.

**Set the amount**

Use `maxItems` to stop after a chosen number of article records. Leave it empty to return all available results until the source is exhausted. The public source may expose fewer matches than requested.

**Reduce review work**

For a small check, use a small `maxItems` value and keep `includeFullText` off. Use `previewKeyword` or date filters when you need a narrower set.

#### ⚙️ Input

**Input fields**

| Field | Type | What it does |
| --- | --- | --- |
| `target` | string | Choose `keyword`, `tag`, `author`, `publication`, or `articleUrl`. The default is `keyword`. |
| `keywords` | array of strings | For `keyword`, search one or more words or phrases. Add each one as a separate item. The default is `programming`. |
| `tag` | string | For `tag`, enter one public Medium tag, such as `programming`. |
| `authorUrl` | string | For `author`, enter one public Medium author profile URL, such as `https://medium.com/@username`. |
| `publicationUrl` | string | For `publication`, enter one public Medium publication URL, such as `https://medium.com/publication-name`. |
| `articleUrl` | string | For `articleUrl`, enter one public Medium article URL, such as `https://medium.com/@author/article-title-1234567890`. |
| `includeFullText` | boolean | When true, add public full text when it is available. This does not bypass member-only access. The default is false. |
| `includeMemberOnly` | boolean | When true, keep member-only articles in the metadata results. Set it to false to remove them. The default is true. |
| `maxItems` | integer | Stop after this many article records. Leave it empty to return all available results until the source is exhausted. The default is 1. |
| `publishedAfter` | date string | For `keyword`, `tag`, `author`, and `publication`, keep articles published on or after this UTC date in `YYYY-MM-DD` form. `articleUrl` ignores this field. |
| `publishedBefore` | date string | For `keyword`, `tag`, `author`, and `publication`, keep articles published on or before this UTC date in `YYYY-MM-DD` form. `articleUrl` ignores this field. |
| `previewKeyword` | string | After discovery, keep articles with this word or phrase in the title or public preview. `articleUrl` ignores this field. |

**Successful default input**

This example is copied from the public input of a successful current-beta run.

```json
{
  "target": "keyword",
  "keywords": [
    "programming"
  ],
  "includeFullText": false,
  "includeMemberOnly": true,
  "maxItems": 1
}
```

#### 🧾 Output

**Run output**

| Field | Type | What it does |
| --- | --- | --- |
| `dataset` | URL | Links to the default dataset view with the article rows from this run. |

**Article row fields**

There is one article-row shape. Optional fields are omitted when they are not available or were not requested. Every row requires `title`, `url`, `author.name`, `tags`, and `publishedAt`.

| Field | Type | What it does |
| --- | --- | --- |
| `title` | string | Title of the Medium article. |
| `url` | URL | Public URL of the article. |
| `author` | object | Public author details. |
| `author.name` | string | Public display name of the author. |
| `author.profileUrl` | URL | Public Medium profile URL when available. |
| `author.bio` | string | Public author bio when available. |
| `author.title` | string | Public author profile title when available. |
| `publication` | object, optional | Public publication details when the article is linked to one. |
| `publication.name` | string | Public publication name. |
| `publication.url` | URL | Public Medium URL for the publication. |
| `publication.description` | string | Public publication description when available. |
| `publication.followerCount` | integer | Public follower count when available. |
| `tags` | array of strings | Public Medium tags attached to the article. |
| `publishedAt` | date-time string | Date and time when the article was published. |
| `preview` | string, optional | Public preview or subtitle when available. |
| `readingTimeMinutes` | integer, optional | Estimated reading time in minutes when available. |
| `clapCount` | integer, optional | Public clap count when available. |
| `responseCount` | integer, optional | Public response count when available. |
| `access` | object, optional | Publication, visibility, and member-only access details exposed by Medium. |
| `access.isPublished` | boolean | Whether Medium marks the article as published. |
| `access.visibility` | string | Visibility status exposed by Medium. |
| `access.isMemberOnly` | boolean | Whether Medium marks the article as member-only. |
| `access.isPaywalled` | boolean | Whether Medium marks the article as paywalled. |
| `fullText` | string, optional | Public article text when requested and available. |
| `matchedInput` | string, optional | The keyword, URL, or other submitted value that first found the article. |

**Genuine publication row**

This complete row came from a successful current-beta publication run. It is not shortened.

```json
{
  "title": "Support Vector Machines: A Progression of Algorithms",
  "url": "https://medium.com/data-science/support-vector-machines-a-progression-of-algorithms-841d63574825",
  "author": {
    "name": "Jimin Kang",
    "profileUrl": "https://medium.com/@jimin.kang821",
    "bio": "Lifelong student"
  },
  "tags": [
    "Machine Learning",
    "Support Vector Machine",
    "Support Vector Classifier",
    "Classification Algorithms",
    "Statistical Learning",
    "machine-learning",
    "data-science"
  ],
  "publishedAt": "2024-06-11T01:33:09.176Z",
  "access": {
    "isPublished": true,
    "visibility": "PUBLIC",
    "isMemberOnly": false,
    "isPaywalled": false
  },
  "publication": {
    "name": "TDS Archive",
    "url": "https://medium.com/data-science",
    "description": "An archive of data science, data analytics, data engineering, machine learning, and artificial intelligence writing from the former Towards Data Science Medium publication.",
    "followerCount": 828220
  },
  "preview": "MMC, SVC, SVM: What’s the difference?",
  "readingTimeMinutes": 11,
  "clapCount": 69,
  "responseCount": 0,
  "matchedInput": "https://medium.com/towards-data-science"
}
```

**Genuine full-text row**

This complete row came from a successful current-beta article URL run with full text requested. It is not shortened.

```json
{
  "title": "The Freedom of Pet Projects",
  "url": "https://medium.com/@asvid/the-freedom-of-pet-projects-eadfe9cde099",
  "author": {
    "name": "Adam Świderski",
    "profileUrl": "https://medium.com/@asvid",
    "bio": "Software Engineer"
  },
  "tags": [
    "Software Engineering",
    "Career Development",
    "Programming",
    "Side Project",
    "Kotlin"
  ],
  "publishedAt": "2026-08-27T19:53:23.580Z",
  "access": {
    "isPublished": true,
    "visibility": "PUBLIC",
    "isMemberOnly": false,
    "isPaywalled": false
  },
  "preview": "In June, I learned how to change diapers and convinced myself that 5 hours of sleep is a luxury. Somewhere in between, I also shipped iOS…",
  "readingTimeMinutes": 4,
  "clapCount": 0,
  "responseCount": 0,
  "fullText": "The Freedom of Pet Projects\nIn June, I learned how to change diapers and convinced myself that 5 hours of sleep is a luxury. Somewhere in between, I also shipped iOS for Kombine — which was absolutely not on the roadmap.\nThe roadmap said: Android → Backend → Webapp → iOS sometime later. It was a good plan. Reasonable order. And that’s exactly the kind of plan a pet project doesn’t have to follow.\nI got bored, so I stopped\nThe web app worked. Golang backend, HTMX, a bit of Alpine.js — clean stack, no JS framework nonsense, exactly what I wanted to build.\nAnd I couldn’t make myself care.\nIt took me a while to realize why, but I get it now. While I created a core scaffold for the mobile app and oversaw the backend build, I completely delegated the web app to AI. No, it’s not slop. Not entirely. It’s just… I don’t know what is going on there. I get the principle-server-side HTML generation based on templates, plus some JS for handling real-time events. It doesn’t feel like mine. And when I was reviewing the web app after it was feature-ready, I had no idea how to debug it, or even how to guide the LLM to fix things effectively. I became the vibe-coder meme — “make me a website” kind of guy — and I didn’t like it.\nI’m a proud engineer. I enjoy my craft. I’ll take any new tool that makes me faster, as long as I’m still in control. Here I wasn’t. And it struck me after weeks of “developing” this service.\nWhat initially felt like boredom was actually hidden procrastination, under an “I have no idea what I’m doing here” sign.\nSo I stopped. No sprint was at risk. No stakeholder was waiting. No performance review depended on it.\nKombine started two years ago as a rough Android scaffold with no deadline — the kind of project that sits when work eats your energy. What’s changed since then is Claude Code: rebuilding a module went from a week to an afternoon, so nothing I built ever got too precious to abandon. That’s why boredom-driven pivots are affordable now. I shipped Android, built the webapp far enough to test pomodoro sync on a real second client, then lost interest — same as always. I went back to Android. The iOS migration looked too complex to touch. A month later I tried it anyway, and went from quick spike to App Store release without really planning to. And I was really curious whether Kotlin Multiplatform with Compose would actually work on iOS. It does.\nBoredom is a signal, not a problem\nThe best decision I made wasn’t the KMP migration. It was listening to the feeling that told me the web app wasn’t worth my Saturday.\nPet projects have one job: bring joy. That’s the whole deal — you don’t owe them a timeline, an order of operations, or a roadmap. The moment you’re working through something that stopped being interesting, you’ve lost the one thing that made it worth doing at all.\nPivot again?\nThis project began its life because I had a thought: how hard can it be to sync a pomodoro timer perfectly across multiple devices? I got that sorted. I added a task manager, started using it daily, moved from Todoist, and haven’t looked back.\nRecently I implemented a simple container for horizon goals: weekly, monthly, and so on. Something I used to do on paper. Sometimes. And I like it — it actually helps me track what I want to achieve and keeps me focused. Paper is great for writing and planning, but not for tracking. So instead of the automatic activity tracker I had on the roadmap, I’m exploring this direction instead.\nObviously.\nStay foolish\nI’d encourage anyone to have a pet project like this — to fool around, make stupid decisions, and invest time in them, because that’s all that’s at stake: your time-exchanged for learning, experience, and the joy of building. I had projects before this one that all died in a sad and lonely place when I got bored — too complex, too simple, stuck on something trivial, momentum gone. Every software engineer has a graveyard like that.\nI would never randomly pivot the roadmap in an organization with multiple teams and other devs depending on it. Don’t try it at work. Do try it at home. I went where my curiosity dragged me, and the worst case was wasting some time learning KMP isn’t what I want on iOS — and since I am not a fan of React, I’d rewrite it natively. OK, sure, Claude Code would.\nI didn’t optimize for time or money. I didn’t follow the roadmap. I got carried away by curiosity.\nThat’s a pretty good compass.\nDon’t ever hire me as a project manager. Just leave me with the technology.\nOriginally published at https://swiderski.tech.",
  "matchedInput": "https://medium.com/@asvid/the-freedom-of-pet-projects-eadfe9cde099"
}
```

#### 💳 Pricing

**How charging works**

Pricing is pay per event. The `Article` event charges once for each article saved to the dataset. Prices vary by Apify tier and are shown on the Store page. There is no separate run-start charge listed for this Actor.

#### 🔌 Integrations

**Dataset access**

Read the dataset in Apify Console or through its API. Export the rows in the formats supported by Apify, or send the dataset to your own workflow.

https://www.youtube.com/watch?v=bNACk1\_S\_6w\&list=PLObrtcm1Kw6MUrlLNDbK9QRg8VDJg0gOW\&index=4

#### ❓ FAQ

##### What happens when the same article appears more than once?

The first eligible occurrence is saved. Later matches for that article are ignored, and `matchedInput` keeps the value that first found it.

##### Can I search more than one keyword in a run?

Yes. Choose the `keyword` target and add each word or phrase to `keywords`. The other target types use their matching single value, and fields for another target are ignored.

##### Can I retrieve the article text?

Set `includeFullText` to true. The Actor returns public full text when it is available. It does not bypass member-only access or paywalls.

##### What happens to member-only articles?

Set `includeMemberOnly` to true to keep their metadata rows, or set it to false to remove them. The `access` fields show the status exposed by Medium.

##### Can I use date or preview filters with an article URL?

No. `publishedAfter`, `publishedBefore`, and `previewKeyword` apply to keyword, tag, author, and publication targets. The `articleUrl` target ignores them.

##### What does leaving `maxItems` empty do?

It returns all available results until the source is exhausted. The public source can still provide fewer rows than expected.

##### Why did the run return fewer articles than my limit?

The public Medium source may expose fewer matching articles, or the date and preview filters may remove matches. The Actor does not promise an exact result count or unlimited historical coverage.

##### Do I need a Medium account?

No. The Actor uses supported public Medium sources and does not require a Medium account or authentication.

##### Can I combine a tag, author, and publication search in one run?

No. Choose one target per run. Start another run for a different target or filter set.

### 📝 Changelog

**0.0: Initial release**

### 🆘 Support

For issues, questions, or feature requests, [file a ticket](https://console.apify.com/actors/maximedupre~medium-articles/issues) and I'll fix or implement it in less than 24h 🫡

### 🔗 Related Actors

- [Webpage Text Extractor](https://apify.com/maximedupre/webpage-text-extractor) extracts readable article text and metadata from public web pages when you already have page URLs.
- [RSS Feed Reader](https://apify.com/maximedupre/rss-feed-reader) turns public RSS and Atom feeds into rows with titles, links, dates, authors, and content.
- [Naver Blog Review Scraper](https://apify.com/maximedupre/naver-blog-review-scraper) collects public Naver Blog posts with content, author details, and keyword context.
- [MarketWatch Scraper](https://apify.com/maximedupre/marketwatch) collects public financial articles with summaries, text, authors, keywords, and tickers.
- [arXiv Papers Scraper](https://apify.com/maximedupre/arxiv-papers-scraper) finds public research papers with titles, authors, abstracts, categories, dates, and source URLs.

**Made with ❤️ by Maxime Dupré**

# Actor input Schema

## `target` (type: `string`):

Choose what to find in this run.

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

For Keyword search, enter one or more words or phrases. Add each one as a separate item.

## `tag` (type: `string`):

For Tag or topic, enter one public Medium tag, such as `programming`.

## `authorUrl` (type: `string`):

For Author profile, enter one public Medium author profile URL, such as `https://medium.com/@username`.

## `publicationUrl` (type: `string`):

For Publication, enter one public Medium publication URL, such as `https://medium.com/publication-name`.

## `articleUrl` (type: `string`):

For Article URL, enter one public Medium article URL, such as `https://medium.com/@author/article-title-1234567890`.

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

Also get public full text when it is available. This does not bypass member-only access.

## `includeMemberOnly` (type: `boolean`):

Keep member-only articles in the metadata results. Set this to false to remove them.

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

Stop after this many article records. Leave it empty to return all available results until the source is exhausted.

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

For Keyword search, Tag or topic, Author profile, and Publication targets, include articles published on or after this UTC date. Article URL ignores this field.

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

For Keyword search, Tag or topic, Author profile, and Publication targets, include articles published on or before this UTC date. Article URL ignores this field.

## `previewKeyword` (type: `string`):

After discovery, keep articles with this word or phrase in the title or public preview. Article URL ignores this field.

## Actor input object example

```json
{
  "target": "keyword",
  "keywords": [
    "programming"
  ],
  "includeFullText": false,
  "includeMemberOnly": true,
  "maxItems": 1
}
```

# Actor output Schema

## `dataset` (type: `string`):

A link to the dataset with the Medium article records found in this run.

# 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 = {
    "target": "keyword"
};

// Run the Actor and wait for it to finish
const run = await client.actor("maximedupre/medium-articles").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 = { "target": "keyword" }

# Run the Actor and wait for it to finish
run = client.actor("maximedupre/medium-articles").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 '{
  "target": "keyword"
}' |
apify call maximedupre/medium-articles --silent --output-dataset

```

## MCP server setup

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

```

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/AwSzA86Dt3Q0M0XkC/builds/stDWfG8lIJW12h6JZ/openapi.json
