# LinkedIn Posts Scraper (`khadinakbar/linkedin-posts-scraper`) Actor

Scrape public LinkedIn posts from keyword searches, company feeds or exact post URLs. Each post has text, author, likes, comments, media and a comments preview. Works with Apify MCP and the API.

- **URL**: https://apify.com/khadinakbar/linkedin-posts-scraper.md
- **Developed by:** [Khadin Akbar](https://apify.com/khadinakbar) (community)
- **Categories:** Social media, Lead generation, MCP servers
- **Features:** Limited permissions
- **Stats:** 154 total users, 54 monthly users, 100.0% runs succeeded, 9 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.00 / 1,000 linkedin post scrapeds

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 is LinkedIn Posts Scraper?

LinkedIn Posts Scraper collects public LinkedIn posts from keyword searches, public company feeds, and exact post URLs. It is designed for readers who need one record per post, with fields such as post text, author details, engagement counts, media URLs, comments preview, provider provenance, and scrape metadata. The result is a dataset of structured public post records plus a machine-readable run summary in key-value store output.

This Actor is available through Apify and usable through Apify MCP. It accepts public LinkedIn source inputs and returns one dataset row per scraped post, which makes each record a single post-level observation from the chosen source. Typical output fields include `postUrl`, `postId`, `publishedAt`, `text`, `authorName`, `authorUrl`, `companyName`, `reactionCount`, `commentCount`, `shareCount`, `engagementCount`, `mediaUrl`, and `provider`.

### Best fit and connected workflows

This Actor fits workflows that start from public LinkedIn discovery and end with structured records for analysis or downstream automation. It routes naturally into:

- keyword-based social listening for public post discovery
- company page monitoring for recent public company posts
- exact URL enrichment for a known public LinkedIn post, feed update, or article
- agent pipelines that need a compact post record with provenance
- B2B research and lead qualification flows built on public post activity

If your workflow begins with a search phrase, a company page URL, or a post URL, this Actor turns that input into consistent post rows that can be filtered, reviewed, or passed to another step.

### Practical scenario

A sales analyst wants to review public posts about "AI agents" from the last month. She enters one search query, sets a post cap, and enables enrichment for fuller post detail. The returned rows include `authorName`, `postUrl`, `text`, `reactionCount`, `commentCount`, `shareCount`, and `provider`. She uses those fields to shortlist posts for manual review, then opens the source post URLs to assess messaging trends and outreach relevance.

### Input

Provide at least one source: search query, company URL, or exact post URL.

| Field | Type | Purpose |
| --- | --- | --- |
| `searchQueries` | array of strings | Keywords or phrases to search in public LinkedIn posts. |
| `companyUrls` | array of strings | Public LinkedIn company page URLs whose recent posts should be scraped. |
| `postUrls` | array of strings | Exact public LinkedIn post, feed update, or article URLs to fetch. |
| `maxPosts` | integer | Maximum number of post records to save across all sources. |
| `datePosted` | string | Freshness window for keyword search (last hour, day, week, month, year). |
| `postedAfter` | string | Keep only posts published on or after this date (for example `2026-11-01`). Older posts are dropped and not billed. |
| `maxAgeDays` | integer | Keep only posts from the last N days. Older posts are dropped and not billed. |
| `minReactions` | integer | Keep only posts with at least this many reactions. Others are dropped and not billed. |
| `minComments` | integer | Keep only posts with at least this many comments. Others are dropped and not billed. |
| `maxSearchPages` | integer | Maximum search pages for keyword pagination. |
| `maxPagesPerCompany` | integer | Maximum pages to fetch per company URL. |
| `startCursor` | string | Cursor for continuing a keyword search run. |
| `providerOrder` | string | Provider order for company feeds and post-detail enrichment. |
| `enrichPostDetails` | boolean | Off by default. Fetch each post's full detail with one extra lookup; those rows use the full-detail price. |
| `includeComments` | boolean | Include up to five public comments from post detail payloads. |
| `outputMode` | string | `full` or `compact` output shape. |
| `includeRawData` | boolean | Attach raw provider payloads to each dataset item. |

Focused JSON example:

```json
{
  "searchQueries": ["ai agents", "sales automation"],
  "datePosted": "last-month",
  "maxPosts": 25,
  "maxSearchPages": 3,
  "enrichPostDetails": true,
  "includeComments": false,
  "outputMode": "compact"
}
```

### Output

Each dataset item represents one public LinkedIn post row.

| Field | Type | Purpose |
| --- | --- | --- |
| `sourceType` | string | Discovery method: `search`, `company`, or `postUrl`. |
| `sourceInput` | string | Search query, company URL, or exact post URL that produced the row. |
| `postUrl` | string or null | Public LinkedIn URL for the post, feed update, or article. |
| `postId` | string or null | LinkedIn activity, share, article, or provider identifier. |
| `publishedAt` | string or null | Provider-returned publication date or timestamp. |
| `text` | string or null | Visible post body, article text, or provider description. |
| `authorName` | string or null | Public name of the post author. |
| `authorUrl` | string or null | Public LinkedIn profile or page URL for the author. |
| `companyName` | string or null | Company name for company-sourced or enriched rows. |
| `reactionCount` | integer or null | Total reaction count when available. |
| `commentCount` | integer or null | Public comment count when available. |
| `shareCount` | integer or null | Share or repost count when available. |
| `engagementCount` | integer or null | Convenience total of available engagement counts. |
| `mediaUrl` | string or null | Primary media or image URL when available. |
| `hashtags` | array | Hashtags found in the post text (without the # sign). |
| `links` | array | Web links found in the post text (full output mode). |
| `provider` | string | Managed provider or provider chain used for the row. |
| `scrapedAt` | string | ISO timestamp when the Actor saved the row. |

Illustrative JSON record:

```json
{
  "sourceType": "search",
  "sourceInput": "ai agents",
  "postUrl": "https://www.linkedin.com/posts/example_activity-7354600338621906944",
  "postId": "7354600338621906944",
  "publishedAt": "2025-01-12T10:15:00Z",
  "text": "Public post text",
  "authorName": "Example Author",
  "authorUrl": "https://www.linkedin.com/in/example-author/",
  "companyName": "Example Company",
  "reactionCount": 42,
  "commentCount": 5,
  "shareCount": 3,
  "engagementCount": 50,
  "mediaUrl": "https://example.com/image.jpg",
  "scrapedAt": "2025-01-12T10:16:02Z"
}
```

The default output also includes a machine-readable run summary in the key-value store, with counters such as records saved, provider attempts, retries, status codes, billing counters, dedupe counts, stop reason, and provider usage.

### How it works

This Actor uses managed provider-backed collection for public LinkedIn posts. Search mode uses Google-indexed public LinkedIn results through the primary data provider. Company feeds and exact post URLs are handled through the configured provider order, with the default provider order as the default. When post enrichment is enabled, the Actor can fetch fuller text, engagement counts, media, and comments preview when the provider exposes them. Raw provider payloads are available only when `includeRawData` is enabled.

### Pricing

Pay per event. Platform usage (compute and storage) is included in the price. You pay only for posts that are saved: posts removed by your date, reactions or comments filters are not billed.

**New prices start on 2026-11-25. Until then every saved post costs the current $0.006.**

| What you get | Price from 2026-11-25 | Event |
| --- | --- | --- |
| Post from a keyword search or company feed | $0.004 per post | `post-scraped` |
| Post fetched by exact URL, or any post when `enrichPostDetails` is on | $0.013 per post | `post-detail-scraped` |
| Actor start | $0.00005 | one per run |

| Example run (from 2026-11-25) | Posts | Cost |
| --- | --- | --- |
| Keyword search, 10 posts | 10 | about $0.04 |
| Keyword search, 100 posts | 100 | about $0.40 |
| Three exact post URLs | 3 | about $0.039 |

Paid Apify plans get plan discounts: Silver 4% and Gold and above 9% off both events. Set `maxPosts` to cap spend for any run or agent call.

#### Free Apify plan

Runs started from a free Apify plan return up to 25 results per run, with every input mode and output field available. Paid Apify plans have no per-run limit, so your usual result settings apply.

### Use with AI agents (MCP)

This Actor is available through Apify MCP as a tool for retrieving structured public LinkedIn post records. The exact Actor identity is `khadinakbar/linkedin-posts-scraper`.

> Find recent public LinkedIn posts about AI agents, return the post text, author name, engagement counts, source URL, and provider for each row, and keep the result compact for downstream agent processing.

The output is suitable interpreted as post-level records. `sourceType` and `sourceInput` explain how each row was discovered, `provider` shows the managed provider or chain that supplied it, and `scrapedAt` marks when the Actor saved the row. `postUrl` and `postId` identify the public post, while counts such as `reactionCount`, `commentCount`, `shareCount`, and `engagementCount` support ranking or filtering.

For agent workflows, `compact` output keeps the record smaller, while `full` output includes richer post fields when available. `includeComments` adds up to five public comments inside the post-detail payload. Search mode uses provider pagination through `maxSearchPages`, and company mode uses `maxPagesPerCompany`. Billing follows the saved post count, so `maxPosts` is a practical cap for agent calls and test runs.

### Example using the Apify API

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

const client = new ApifyClient({
  token: process.env.APIFY_TOKEN,
});

const run = await client.actor('khadinakbar/linkedin-posts-scraper').call({
  searchQueries: ['ai agents'],
  datePosted: 'last-month',
  maxPosts: 10,
  outputMode: 'compact',
});

const datasetId = run.defaultDatasetId;
const { items } = await client.dataset(datasetId).listItems();
console.log(items);
```

Set `APIFY_TOKEN` in your environment before running the script. The dataset readback returns the saved post rows from the default dataset.

### Best results and outcome guidance

Use one source family at a time when the workflow starts with a narrow goal: keyword search for topic discovery, company URLs for page monitoring, and exact post URLs for single-post enrichment. Lower `maxPosts` for quick agent calls, then raise it when you want a broader sample. Search and company rows already include author, reactions and comments, so leave `enrichPostDetails` off unless you need the fuller text or comments preview. To find high-performing posts, set `minReactions` or `minComments` and `maxAgeDays`. If you are testing cursor behavior, adjust `startCursor` and `maxSearchPages` together for search runs.

### Which Actor for which job

| You have | Use | Why |
| --- | --- | --- |
| A keyword or topic, many posts | this Actor | Search with date, reactions and comments filters |
| A company page | this Actor | Recent company posts in the same output shape |
| One known post URL, full record | [LinkedIn Post Details Scraper](https://apify.com/khadinakbar/linkedin-post-details-scraper) | Built for single-URL lookups |
| A person's profile, all their posts | [LinkedIn Profile Posts Scraper](https://apify.com/khadinakbar/linkedin-profile-posts-scraper) | Person feed |
| Who commented on or reacted to a post | [LinkedIn Post Engagers Scraper](https://apify.com/khadinakbar/linkedin-post-engagers-scraper) | People, not posts |

### What's new

- 2026-11-10: new filters `postedAfter`, `maxAgeDays`, `minReactions`, `minComments` (filtered posts are not billed); new `hashtags` and `links` fields; full-detail lookups are now opt-in; runs stop early with a partial result instead of timing out.

### Continue the workflow

- Then use [LinkedIn Profile Comments Scraper — Engagement Leads](https://apify.com/khadinakbar/linkedin-profile-comments-scraper) to add Linkedin engagement evidence to records selected with LinkedIn Posts Scraper.
- Then use [LinkedIn Profile Details Scraper — Profile by URL](https://apify.com/khadinakbar/linkedin-profile-details-scraper) to extend LinkedIn Posts Scraper research with a complementary enrichment contract.

### Design note

I found that the dataset contract marks `sourceType`, `sourceInput`, `provider`, and `scrapedAt` as required, which makes each record traceable even when many other fields are optional or provider-dependent.

### FAQ

#### Can this Actor start from a keyword search and a company URL in the same run?

Yes. The input schema accepts `searchQueries`, `companyUrls`, and `postUrls` together, and the Actor combines the selected sources into one output set.

#### What does `sourceType` tell me?

It tells you how the post was discovered: `search`, `company`, or `postUrl`.

#### Which fields are useful for routing a post to downstream analysis?

`postUrl`, `postId`, `authorName`, `companyName`, `publishedAt`, `reactionCount`, `commentCount`, `shareCount`, and `engagementCount` are the most common routing fields.

#### When should I use `compact` output?

Use `compact` when you want a smaller record shape for MCP agents, tables, or simple pipelines.

#### When should I use `full` output?

Use `full` when you want richer post fields such as media lists, author images, and additional company context where the provider exposes them.

#### Am I charged for posts my filters remove?

No. Posts removed by `postedAfter`, `maxAgeDays`, `minReactions` or `minComments` are not saved and not billed.

#### Why are exact URLs priced higher?

An exact URL needs its own lookup for each post, while search results return ten posts per lookup.

#### How do I continue a keyword search?

Use `startCursor` with a previous the primary data provider search cursor and keep `maxSearchPages` aligned with the search depth you want.

### Responsible use

This Actor is intended for public LinkedIn post data returned by managed provider APIs. Review LinkedIn's terms, applicable laws, and your own use case before running large-scale collection. Use the output for lawful analysis, monitoring, enrichment, and agent workflows that respect privacy and platform rules.

# Changelog

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

# Actor input Schema

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

Keywords or phrases to search in public LinkedIn posts, for example ai agents or open source CRM. Uses Google-indexed public LinkedIn results via the primary managed public-data route. Defaults to an empty list. Not for private LinkedIn feeds or logged-in-only posts.

## `companyUrls` (type: `array`):

Public LinkedIn company pages whose recent posts should be scraped, for example https://www.linkedin.com/company/shopify. Accepts one URL per line. Defaults to an empty list. Not for personal profile URLs.

## `postUrls` (type: `array`):

Exact public LinkedIn post, feed update, or article URLs to fetch, for example https://www.linkedin.com/posts/example_activity-7354600338621906944. Defaults to an empty list. Invalid or non-post LinkedIn URLs are skipped.

## `maxPosts` (type: `integer`):

Maximum number of post records to save across all sources. This is the hard cap for billable post-scraped events. Default is 100. Use lower values for canaries or agent calls. Free Apify plan runs return up to 25 results per run.

## `datePosted` (type: `string`):

Freshness window for keyword search results (last-hour, last-day, last-week, last-month or last-year). Default is last-month. Does not filter company feeds or exact post URLs. For an exact cut-off use Posted after or Max post age (days) below.

## `postedAfter` (type: `string`):

Only keep posts published on or after this date, for example 2026-11-01. Applied to search, company and URL rows; posts older than this are dropped and not billed. Leave empty for no cut-off. Posts without a publish date are dropped when this is set.

## `maxAgeDays` (type: `integer`):

Only keep posts published within the last N days, for example 7. Applied to search, company and URL rows; older posts are dropped and not billed. Default is 0 (no limit). If you also set Posted after, the stricter cut-off wins.

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

Only keep posts with at least this many reactions (likes), for example 100. Posts below it are dropped and not billed, so this finds high-engagement posts at no extra cost. Default is 0 (no minimum).

## `minComments` (type: `integer`):

Only keep posts with at least this many comments, for example 10. Posts below it are dropped and not billed. Default is 0 (no minimum).

## `maxSearchPages` (type: `integer`):

Advanced cap for the primary managed public-data route search pagination. Default is 5 pages and maximum is 60. Lower it when testing cursor behavior. Not used for company URLs or exact post URLs.

## `maxPagesPerCompany` (type: `integer`):

How many provider pages to fetch for each LinkedIn company URL. Default is 2 and maximum is 7 because public company-post coverage is shallow. the managed fallback route company fallback only returns one page. Not used for search queries.

## `startCursor` (type: `string`):

Optional the primary managed public-data route cursor from a previous keyword-search run. Paste the cursor when continuing a search. Leave empty for a fresh search. Not used for company feeds or exact post URLs.

## `enrichPostDetails` (type: `boolean`):

Fetch each post's full detail (fuller text, media, comments preview) with one extra lookup per post. Default is off because search and company results already include author, likes and comments. Rows fetched this way are billed as the higher-priced full-detail post event. Exact post URLs are always full-detail.

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

Include up to five public comments returned inside each post detail payload. Default is false to keep records compact for agents. This is a preview only, not exhaustive comment scraping. Use the comments actor for full comment rows.

## `outputMode` (type: `string`):

Compact mode keeps the main fields for MCP and tables. Full mode also includes image lists, company extras, author images, and richer provenance. Default is full. Raw provider payloads still require includeRawData.

## `includeRawData` (type: `boolean`):

Attach raw provider payloads to each dataset item. Default is false because raw payloads are large and less useful for agents. Enable only for debugging or custom downstream parsing. Does not expose provider API keys.

## Actor input object example

```json
{
  "searchQueries": [
    "ai agents"
  ],
  "companyUrls": [],
  "postUrls": [],
  "maxPosts": 1,
  "datePosted": "last-month",
  "maxAgeDays": 0,
  "minReactions": 0,
  "minComments": 0,
  "maxSearchPages": 1,
  "maxPagesPerCompany": 2,
  "enrichPostDetails": false,
  "includeComments": false,
  "outputMode": "full",
  "includeRawData": false
}
```

# Actor output Schema

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

Dataset items containing post URL, text, author, company context, engagement counts, media, comments preview, source type, and scrape metadata.

## `summary` (type: `string`):

Run summary including recordsSaved, provider attempts, retries, status codes, billing counters, dedupe counts, stopReason, and provider usage.

# 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 = {
    "searchQueries": [
        "ai agents"
    ],
    "companyUrls": [],
    "postUrls": [],
    "maxPosts": 1,
    "maxSearchPages": 1,
    "enrichPostDetails": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("khadinakbar/linkedin-posts-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 = {
    "searchQueries": ["ai agents"],
    "companyUrls": [],
    "postUrls": [],
    "maxPosts": 1,
    "maxSearchPages": 1,
    "enrichPostDetails": False,
}

# Run the Actor and wait for it to finish
run = client.actor("khadinakbar/linkedin-posts-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 '{
  "searchQueries": [
    "ai agents"
  ],
  "companyUrls": [],
  "postUrls": [],
  "maxPosts": 1,
  "maxSearchPages": 1,
  "enrichPostDetails": false
}' |
apify call khadinakbar/linkedin-posts-scraper --silent --output-dataset

```

## MCP server setup

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