# LinkedIn Newsletter & Article Scraper (`fanndev/linkedin-newsletter-article-scraper`) Actor

Read LinkedIn long-form articles in full: body text, author, publish and edit dates, reactions and comments, with reading time and engagement-per-thousand-followers computed. Discover an author's articles from their profile. No login required.

- **URL**: https://apify.com/fanndev/linkedin-newsletter-article-scraper.md
- **Developed by:** [Faisal Ahdan naufal](https://apify.com/fanndev) (community)
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## LinkedIn Newsletter & Article Scraper

Read LinkedIn long-form articles in full — body text, author, publish and edit dates, reactions and comments — and get reading time and a fair cross-author engagement metric computed for you.

**No login required.** LinkedIn's `/pulse/` article pages are open to anonymous visitors.

### What you get

```json
{
  "articleTitle": "How do we build a frontier intelligence ecosystem?",
  "authorName": "Satya Nadella",
  "authorFollowers": 12177036,
  "publishedAt": "2026-06-02T19:41:31.000+00:00",
  "modifiedAt": "2026-06-02T19:42:00.000+00:00",
  "wordCount": 215,
  "readingTimeMinutes": 1.0,
  "reactions": 2535,
  "comments": 140,
  "engagementPerThousand": 0.21,
  "articleText": "Great to be back at Microsoft Build today..."
}
```

**`engagementPerThousand` is the field to sort on.** Raw reaction counts just rank authors by audience size. Reactions per thousand followers tells you whether a piece actually landed — and lets you compare a 200-reaction article from a niche expert against a 2,500-reaction one from a CEO with twelve million followers.

`modifiedAt` next to `publishedAt` is quietly useful too: it shows which pieces the author went back and revised.

### Newsletters: read this before you buy

**LinkedIn newsletter *series* pages are closed to logged-out visitors.** Every request to `/newsletters/{id}` returns HTTP 500 with the same error body — tested against several ids, including real-looking ones. There is no public surface for **subscriber counts**, and no cookie option in this actor changes that.

What *does* work: **individual newsletter issues are `/pulse/` articles.** Paste those links into `articleUrls` and you get the full text and engagement for every issue, exactly like any other article.

If you pass a `/newsletters/` URL anyway, the run does not fail — it emits a `NOTICE` row telling you this. An empty dataset always comes with a reason attached.

### Discovering articles instead of listing them

Give `authorProfiles` a member handle and the actor reads the articles linked from their public profile:

```json
{
  "authorProfiles": ["satyanadella"],
  "maxArticlesPerAuthor": 20,
  "minReactions": 100
}
```

`minReactions` is the useful knob here — it turns "everything this person wrote" into "the pieces that actually worked", which is usually the question you were asking.

### Comments

`includeComments` attaches the commenters LinkedIn renders on the public page — name, profile link and comment text — at **no extra request cost**, since they arrive in the same page load.

The honest limit: LinkedIn publishes roughly the **first 8 comments** to logged-out visitors. `commentSampleSize` tells you how many you got and `comments` tells you the true total, so the gap is always visible rather than assumed away.

### Who this is for

- **Content strategists** — what topics and lengths earn engagement in your field, measured rather than guessed.
- **SEO and PR specialists** — who is publishing about your category, how often, and who they link to (`externalLinks`).
- **Industry analysts** — the `SUMMARY` row gives median reactions, median length and top hashtags across a whole run, which is a fast read on where executive attention is going.

### A parsing note

LinkedIn's article JSON-LD stores the **title** in `name` and the **opening excerpt** in `headline` — the opposite of what those field names suggest — and `articleBody` is always empty. Scrapers that trust the field names ship excerpts labelled as titles. This actor reads `name` for the title and lifts the real body from the rendered page, trimming LinkedIn's byline and "Report this article" chrome off the top.

### Rate gating

LinkedIn answers request bursts with **HTTP 999** and a ~1.5 KB stub page. It is per-IP and per-burst, not a TLS fingerprint gate. The actor sleeps a random 3–8 seconds between page loads, backs off exponentially when gated, and rotates among five browser fingerprints. Raise both delay bounds if the log shows rate-gate warnings.

Deleted articles redirect to LinkedIn's top-content hub, which answers 200 with no article on it — those produce a `not_an_article` notice rather than a silently empty row.

### Related actors

- **LinkedIn Top Voice & Competitor Activity Monitor** — posting cadence and format mix for any account.
- **LinkedIn Post Engagers Scraper** — who engaged with a specific post, with their comments.

# Actor input Schema

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

LinkedIn long-form articles to read - linkedin.com/pulse/... links. Individual newsletter issues are /pulse/ links too, so paste those here.

## `authorProfiles` (type: `array`):

Discover articles instead of listing them: give a member profile (a /in/ URL or a bare handle) and the actor reads the articles linked from their public profile page.

## `newsletterUrls` (type: `array`):

Accepted so a run does not fail on them, but LinkedIn answers HTTP 500 to logged-out visitors on every /newsletters/ series page - subscriber counts have no public surface at all. Anything passed here produces a NOTICE row explaining that. Pass the individual issue /pulse/ links in Article URLs instead; those are fully readable.

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

The same article links in the request-list format, for callers that already keep one.

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

Keep the complete body on each row. Turn off for a much smaller dataset when you only want titles, dates and engagement.

## `includeComments` (type: `boolean`):

Attach the commenters LinkedIn renders on the public page - roughly the first 8 - with their names, profile links and comment text. No extra requests: they arrive in the same page load.

## `minReactions` (type: `integer`):

Skip low performers, which is useful when discovering from an author profile and you only want what actually landed.

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

Cap on articles discovered from each author profile.

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

Overall ceiling after de-duplication. Each article costs one request.

## `emitSummary` (type: `boolean`):

Append a SUMMARY row with median reactions, median length and the most-used hashtags across everything read - a quick read on what topics and formats are working.

## `minDelaySeconds` (type: `integer`):

LinkedIn answers request bursts with HTTP 999 and a near-empty page. A randomised gap is what keeps a run under that gate.

## `maxDelaySeconds` (type: `integer`):

The ceiling of the randomised gap. Raise both bounds if the log shows rate-gate warnings.

## `exportFormats` (type: `array`):

Besides the dataset, write ready-made files into this run's key-value store.

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

Optional. Article pages carry no WAF and Apify's datacenter range reaches them; a proxy mainly helps on long runs where LinkedIn's per-IP rate gate becomes the bottleneck.

## Actor input object example

```json
{
  "articleUrls": [
    "https://www.linkedin.com/pulse/how-do-we-build-frontier-intelligence-ecosystem-satya-nadella-73jhc"
  ],
  "includeFullText": true,
  "includeComments": false,
  "minReactions": 0,
  "maxArticlesPerAuthor": 20,
  "maxArticles": 50,
  "emitSummary": true,
  "minDelaySeconds": 3,
  "maxDelaySeconds": 8,
  "exportFormats": [],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One ARTICLE row per article, a SUMMARY row for the run, plus NOTICE and ERROR rows.

# 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 = {
    "articleUrls": [
        "https://www.linkedin.com/pulse/how-do-we-build-frontier-intelligence-ecosystem-satya-nadella-73jhc"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fanndev/linkedin-newsletter-article-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 = { "articleUrls": ["https://www.linkedin.com/pulse/how-do-we-build-frontier-intelligence-ecosystem-satya-nadella-73jhc"] }

# Run the Actor and wait for it to finish
run = client.actor("fanndev/linkedin-newsletter-article-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 '{
  "articleUrls": [
    "https://www.linkedin.com/pulse/how-do-we-build-frontier-intelligence-ecosystem-satya-nadella-73jhc"
  ]
}' |
apify call fanndev/linkedin-newsletter-article-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fanndev/linkedin-newsletter-article-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/y669BeIyHmld5jVnQ/builds/AApNSok14flnPqXP6/openapi.json
