# Substack Scraper (`dacoder/substack-scraper`) Actor

Download every post from any Substack newsletter: full article text, likes, comments, shares, author and subscriber count. Whole archive, any newsletter (including custom web addresses), date and keyword filters. Export to Excel, CSV or Google Sheets.

- **URL**: https://apify.com/dacoder/substack-scraper.md
- **Developed by:** [Da Coder](https://apify.com/dacoder) (community)
- **Categories:** News, Social media, Automation
- **Features:** Limited permissions
- **Stats:** 40 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: 1.00 out of 5 stars

## Pricing

from $1.20 / 1,000 posts

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

**Download every post from any Substack newsletter in a few clicks.** Paste a newsletter's link, press Start, and get a spreadsheet of its posts: the full article, likes, comments, shares, the author, and how many subscribers the newsletter has.

No Substack account needed. No copying and pasting posts one by one.

### Why people use Substack Scraper

📚 **Every post, not just the latest ones.** Many tools only grab the newest 12 or 20 posts. This one goes all the way back to the first post, whether a newsletter has 50 posts or 5,000.

✍️ **The whole article, ready to use.** You get each post's full text, cleaned up with buttons and clutter removed. Read it in a spreadsheet, or paste it straight into ChatGPT, Claude or Notion. It also comes in Markdown, a simple text format that AI tools read best.

🌐 **Works with any Substack newsletter.** Including the ones with their own web address, like lennysnewsletter.com. Paste the newsletter's link, a single post's link, or the writer's @name, whatever you have.

🔒 **Honest about paid posts.** For subscriber-only posts, you get the part anyone can read for free, and each one is clearly marked so you know when you don't have the whole article.

🎯 **Only the posts you want.** Choose a date range (like "the last 30 days"), free or paid posts only, written posts or podcasts, or posts mentioning a keyword. You only pay for the posts you get.

📈 **See what's popular.** Likes, comments and shares (Substack calls them "restacks") for every post, plus the newsletter's subscriber count and owner.

⚡ **Quick and reliable.** Most runs finish in seconds to a few minutes. If a link doesn't work, you're told why instead of getting an empty result.

### What can you use Substack data for?

- **See what works in your niche.** Sort a newsletter's posts by likes and comments to find the topics its readers love most.
- **Teach an AI a writer's work.** Give ChatGPT or another AI tool a newsletter's entire archive, then ask it questions about everything they've written.
- **Never miss a post.** Run it every day on the newsletters you follow and get new posts sent to Google Sheets, Slack or your email.
- **Find partners.** Collect newsletter owners and subscriber counts for sponsorships, guest posts or outreach.
- **Back up your own newsletter.** Save a copy of everything you've published, with the stats.
- **Research.** Follow how writers have covered a topic over months or years.

### How to scrape Substack in 3 steps

1. **Paste the newsletters you want**, e.g. `https://www.lennysnewsletter.com` or just `astralcodexten`.
2. **Choose how many posts** you want from each, and add a date range if you like.
3. **Press Start.** Your posts appear in the **Output** tab within seconds. Download them as an Excel or CSV file, or send them to Google Sheets.

That's it. No setup and no coding.

### What you get

Every post is one row in your spreadsheet:

| What | Example |
|---|---|
| Title and link | "Open Thread 453", astralcodexten.com/p/open-thread-453 |
| Published | 28 Sep 2026 |
| Author | Scott Alexander |
| Likes / comments / shares | 52 / 616 / 2 |
| The full article | as plain text, plus Markdown and HTML versions for AI tools and websites |
| Length | 299 words |
| Paid post? Only the free part? | No / No |
| Newsletter | Astral Codex Ten, offers paid subscriptions, plus the subscriber count when the newsletter shows it (e.g. Lenny's Newsletter: 1.2M+) |

The **Output** tab shows your results three ways: a **Posts** table with likes and comments, a **Full text** view for reading the articles, and a **Publications** list of the newsletters.

<details>
<summary>For developers: example input and output (JSON)</summary>

**Input**

```json
{
    "publications": ["https://www.lennysnewsletter.com", "astralcodexten", "@noahpinion"],
    "maxPostsPerPublication": 50,
    "includeContent": true,
    "startDate": "6 months"
}
```

**Output (one row per post)**

```json
{
    "type": "post",
    "id": 217850009,
    "url": "https://www.astralcodexten.com/p/open-thread-453",
    "title": "Open Thread 453",
    "postType": "newsletter",
    "audience": "everyone",
    "isPaywalled": false,
    "publishedAt": "2026-09-28T16:02:37.132Z",
    "wordCount": 299,
    "reactionCount": 52,
    "commentCount": 616,
    "restackCount": 2,
    "authorName": "Scott Alexander",
    "authorHandle": "astralcodexten",
    "bodyHtml": "<p>This is the weekly visible open thread…</p>",
    "bodyText": "This is the weekly visible open thread…",
    "bodyMarkdown": "This is the weekly visible open thread…",
    "contentWordCount": 296,
    "isTruncated": false,
    "publicationName": "Astral Codex Ten",
    "publicationUrl": "https://www.astralcodexten.com",
    "publicationCustomDomain": "www.astralcodexten.com",
    "publicationHasPaidPlans": true,
    "input": "astralcodexten",
    "scrapedAt": "2026-10-01T13:31:52.926Z"
}
```

If a link can't be scraped (for example, the newsletter has moved off Substack), you get a row with `"type": "error"` saying why. Version 1.0 changed the output to one row per post; see the changelog.

</details>

### How much does it cost to scrape Substack?

You pay only for the posts you receive. No subscription, no monthly fee.

| What you get | Price per post | Per 1,000 posts |
|---|---|---|
| Post details: title, link, date, likes, comments, author and newsletter info | $0.0015 | $1.50 |
| Post details **plus the full article** | $0.004 | $4.00 |

**Examples**

| You want | Cost |
|---|---|
| The latest 20 posts of one newsletter, with the articles | $0.08 |
| 500 posts with the articles | $2.00 |
| Details of 1,000 posts, without the articles | $1.50 |
| A whole 5,000-post newsletter, with the articles | $20.00 |

You're never charged for links that don't work or for posts your filters leave out. You can also set a spending limit for each run, and the scraper stops when it reaches it.

### Use it with AI assistants, apps and code

- **AI assistants:** Claude, ChatGPT, Cursor and other AI assistants can use Substack Scraper as a tool through the [Apify MCP server](https://mcp.apify.com). Just ask: *"Get the last 20 posts from Lenny's Newsletter."*
- **Other apps:** connect it to Google Sheets, Slack, Zapier, Make or n8n in a few clicks, no coding needed.
- **Developers:** call it from the Apify API or client libraries:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('dacoder/substack-scraper').call({
    publications: ['https://www.lennysnewsletter.com'],
    maxPostsPerPublication: 20,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

### FAQ

**Can it get the full text of paid posts?**
No. You get what anyone can read without paying: the free opening of a paid post, up to where the writer put the paywall. Every paid post is clearly marked, and you can see how many words you got compared with the full article.

**Does it work with newsletters that have their own web address?**
Yes. Paste their address (like `www.lennysnewsletter.com`) or their `.substack.com` address; both work.

**How many posts can I get?**
All of them. Set **Max posts per publication** as high as you like. Very big newsletters take a little longer, especially ones with lots of paid posts.

**Can I get only new posts?**
Yes. Set **Posts published after** to "1 day" or "7 days" and schedule it to run daily or weekly. You'll get just the new posts each time.

**Do I need a Substack account?**
No. Everything works without logging in.

### Need help or a new feature?

Tell us on the **Issues** tab. Substack changes its website from time to time, and fixes come quickly. Ideas for new features are welcome too. To catch problems early, errors are automatically reported to our private error tracker (with the links you scraped, never your account details).

**Also by this author:** [Threads Search Post Scraper](https://apify.com/dacoder/threads-search-post-scraper), which finds posts on Threads by keyword.

# Changelog

This Actor's version history is a separate document: https://apify.com/dacoder/substack-scraper/changelog.md

# Actor input Schema

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

What to scrape. Each entry can be a publication URL (https://www.lennysnewsletter.com or https://astralcodexten.substack.com), a custom domain (www.noahpinion.blog), a subdomain name (astralcodexten), a single post URL (https://www.lennysnewsletter.com/p/some-post), or an author (@lenny or https://substack.com/@lenny, which scrapes their main publication).

## `maxPostsPerPublication` (type: `integer`):

Stop after this many posts from each publication (after filters). Posts are fetched newest first unless you change the sort order. Set a large number (e.g. 100000) to get the whole archive.

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

On by default. Fetches each post's full body as HTML, plain text and Markdown (charged as a small add-on per post). For paid posts you get the free part before the paywall, and isTruncated tells you if it was cut. Turn off to get only titles, dates, stats and previews: faster and cheaper.

## `startDate` (type: `string`):

Only posts published on or after this date. Use a date (2025-01-31) or a relative period (30 days, 6 months, 1 year).

## `endDate` (type: `string`):

Only posts published on or before this date. Use a date (2025-12-31) or a relative period (7 days).

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

Only posts matching this keyword search inside each publication (Substack's own archive search). Results are ordered by relevance.

## `audience` (type: `string`):

Return all posts, only free posts, or only paid (subscriber-only) posts.

## `postType` (type: `string`):

Return all posts or only one type: newsletters (written posts), podcasts or threads.

## `sortBy` (type: `string`):

Newest first, or the publication's most popular posts first.

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

Usually not needed: Substack's API works without a proxy. Use one only if you see repeated 403/429 errors.

## `substacks` (type: `array`):

Old input field, kept so existing tasks keep working. Use "Substack publications, posts or authors" instead.

## Actor input object example

```json
{
  "publications": [
    "https://www.lennysnewsletter.com",
    "astralcodexten"
  ],
  "maxPostsPerPublication": 10,
  "includeContent": true,
  "audience": "all",
  "postType": "all",
  "sortBy": "new",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

One item per post (plus error rows for inputs that could not be scraped). Opens on the Posts overview; switch to the Full text or Publications views, or use every field including bodyHtml, bodyText and bodyMarkdown.

# 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 = {
    "publications": [
        "https://www.lennysnewsletter.com",
        "astralcodexten"
    ],
    "maxPostsPerPublication": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("dacoder/substack-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 = {
    "publications": [
        "https://www.lennysnewsletter.com",
        "astralcodexten",
    ],
    "maxPostsPerPublication": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("dacoder/substack-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 '{
  "publications": [
    "https://www.lennysnewsletter.com",
    "astralcodexten"
  ],
  "maxPostsPerPublication": 10
}' |
apify call dacoder/substack-scraper --silent --output-dataset

```

## MCP server setup

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