I run a visa agency in Bali. The rest of my week goes into Apify Actors, and one of them reads the latest public posts of any LinkedIn company or person without a login: text, date, reactions, comment count, video, document, mentions, repost details — and, if you ask for it, the comments visible on each post's own page.
That last option is the one people write to me about. The request is always some version of the same plan: pull a few hundred competitor posts, feed the comments to a model, get a sentiment readout. So before promising anything, I measured what the comment field actually contains.
The run
Eight sources, ten posts each, comments on, 20 October 2026:
{
"urls": [
"https://www.linkedin.com/company/hubspot",
"https://www.linkedin.com/company/stripe",
"https://www.linkedin.com/company/notionhq",
"https://www.linkedin.com/company/figma",
"https://www.linkedin.com/company/microsoft",
"https://www.linkedin.com/in/justinwelsh",
"https://www.linkedin.com/in/williamhgates",
"https://www.linkedin.com/in/satyanadella"
],
"includeComments": true,
"includeReposts": true,
"includeArticles": true,
"maxPostsPerSource": 10,
"concurrency": 3
}
76 seconds, 80 rows, $0.128 billed. All eight pages hit the cap of ten, so nothing in what follows is a scraping shortfall — these are the rows LinkedIn served.
75 of the 80 rows are posts. Those 75 posts report 16,552 comments between them in commentsCount. The topComments arrays on the same 75 rows hold 669 comments. That is 4.04% of the conversation, and the shape of the loss is worth looking at before you plan anything on top of it.
The ceiling is ten, and it does not move
Lengths of topComments across the 75 posts:
10 comments → 48 posts
9 comments → 13 posts
8 comments → 2 posts
7 comments → 3 posts
6 comments → 4 posts
5 comments → 1 post
2 comments → 3 posts
0 comments → 1 post
Nothing above ten. 64 posts had more than ten comments, and 48 of those returned exactly ten. Here are the loudest ones:
| Source | commentsCount |
comments returned |
|---|---|---|
| Justin Welsh | 1,174 | 10 |
| Justin Welsh | 1,110 | 10 |
| Justin Welsh | 1,098 | 10 |
| Justin Welsh | 1,020 | 10 |
| Microsoft | 761 | 10 |
| Satya Nadella | 760 | 10 |
| Bill Gates | 279 | 10 |
A post with 1,174 comments and a post with 12 comments hand you the same ten rows. The ratio you lose is a function of how popular the post is, which means the busiest conversations — the ones you actually wanted — are the ones you see least of. That is not a bug in my code: a logged-out visitor on the post page sees the same ten. Replies are not shown at all, which I can state flatly because across all 669 comments, isReply was false 669 times.
The ten are not the top ten
This is the part that quietly breaks the sentiment plan. Take the biggest post in the run, Justin Welsh's, 4,937 reactions and 1,174 comments. The ten comments that came back, with their like counts in the order they arrived:
Drew Carrell 10
Russ Khaimov 11
AJ Acosta 10
Lara Acosta 17
Adam Landrum 5
Bryce Seto 8
Anne Jeline Locsin 10
Dairius K. 8
Shreya Londhe 6
LespriCore 16
Five to seventeen likes, on a post with 1,174 comments. Those are not the most-liked replies on a thread that size, and the sequence isn't sorted either. I checked the ordering across every post with at least three comments: 5 of 71 came back in descending like order, 66 did not. Whatever ranking LinkedIn applies for logged-out visitors, it is not reaction count, and I'm not going to guess at what it is.
I did check that the selection is LinkedIn's and not my page-reading path. I re-pulled that single post through the dedicated single-post route, which opens the post URL directly instead of finding it on a profile:
{ "postUrls": ["https://www.linkedin.com/posts/justinwelsh_were-starting-to-see-the-decline-of-the-activity-7516102602078576641-33y_"] }
Same ten commenters, same order, same like counts. Stable and reproducible — and still arbitrary with respect to engagement.
So topComments is neither a top-N nor a random sample. It is a fixed, LinkedIn-chosen slice of ten. You can quote it, show it, read it for texture. You cannot average it and call the result sentiment.
Even small posts come back incomplete
I assumed the ceiling only mattered above ten. It doesn't. The eleven posts in the run with ten or fewer comments:
commentsCount returned
1 0
2 2
3 2
3 2
5 5
6 6
7 6
8 6
9 7
10 6
10 7
Three of eleven were complete. A post that reports one comment returned zero. So there is no post size at which topComments is guaranteed whole, and len(topComments) is never a usable comment count. commentsCount is — it comes off the post's own counter and matched every page I spot-checked.
What to use instead
commentsCount is a complete number on every post row, and it is more informative than the reaction total because the two diverge. Per source, from the same 80 rows:
| Source | reactions | comments | comments per 100 reactions |
|---|---|---|---|
| Justin Welsh | 37,499 | 8,257 | 22.0 |
| Bill Gates | 17,995 | 1,278 | 7.1 |
| Notion | 2,781 | 175 | 6.3 |
| HubSpot | 6,063 | 366 | 6.0 |
| Stripe | 4,504 | 211 | 4.7 |
| Satya Nadella | 65,625 | 3,033 | 4.6 |
| Microsoft | 67,636 | 3,001 | 4.4 |
| Figma | 5,743 | 231 | 4.0 |
Seven of the eight sit between 4.0 and 7.1. One sits at 22.0. Justin Welsh has 886,873 followers against Microsoft's 29.3 million and pulled 2.75× Microsoft's comment volume on ten posts. Whatever you want to call that — community, prompt-the-reply writing — it is visible in a field that arrives complete, on every row, for free. The 669 comment texts never would have told you: they are the same ten per post either way.
Two other things in these rows that are not what they look like
18 of the 80 rows were reposts, and authorName is the original author. Per source: Microsoft 8, Stripe 5, Notion 4, HubSpot 1. Microsoft's ten-row page contained two posts Microsoft wrote — five reposts of Satya Nadella, one of Microsoft Surface, two of employees. Group your rows by authorName to build a posting-cadence chart and you will credit Microsoft's output to Satya Nadella and conclude Microsoft posts twice a fortnight. The page you asked for is in sourceUrl/sourceName; the amplifier is in repostedBy. Filter on isRepost before counting anything.
A smaller trap inside that one: for 12 of the 18 reposts, activityId equalled originalActivityId and publishedAt equalled repostedAt. Only the other 6 carried a separate repost id and a later repost timestamp. So you cannot reliably ask when a company amplified something — on two thirds of reposts the only date available is the original's.
5 of the 80 rows are long-form articles, and three fields on them are null. activityId, text and commentsCount are all empty on article rows (headline, publishedAt, likes and postUrl are filled). LinkedIn articles aren't feed activities, so there is no activity id to carry. If your dedupe key is activityId — the sane choice for posts — article rows will collide on None and all but one will vanish. Key on postUrl for articles, or set includeArticles: false if you only want feed posts. shareUrn has the same problem for a different reason: it was null on 25 of 75 posts.
Working code
import requests
rows = requests.post(
"https://api.apify.com/v2/acts/lergassy~linkedin-posts-scraper/run-sync-get-dataset-items",
params={"token": TOKEN},
json={"urls": PAGES, "includeComments": True, "maxPostsPerSource": 10},
).json()
seen, own = set(), []
for r in rows:
key = r["activityId"] or r["postUrl"] # articles have no activityId
if key in seen:
continue
seen.add(key)
if r.get("isRepost"): # authorName is someone else
continue
own.append(r)
for r in sorted(own, key=lambda x: -(x.get("commentsCount") or 0))[:10]:
cc = r.get("commentsCount") or 0
got = len(r.get("topComments") or [])
print(f"{r['sourceName']:<16}{cc:>6} comments, {got:>2} readable {r['postUrl']}")
Three lines of defence, each for a measured failure: a dedupe key that survives null, a repost filter, and printing the readable count next to the real one so nobody downstream mistakes ten quotes for a thread.
When this is the wrong tool
- You need whole comment threads. Not available to a logged-out visitor at any price. Ten per post, replies never. If your work depends on all 1,174, you need LinkedIn's own partner access, not a scraper.
-
You need post history. Without login LinkedIn shows about 7–10 recent items per page. My oldest Microsoft row was 14 days old. This is a monitoring tool — schedule it daily with
postedAfterset to yesterday and you miss nothing; run it once and ask for a year, and you get a fortnight. -
You need reaction breakdowns or repost counts.
topReactionTypesgives the frequent types, nothing gives per-type totals, and repost counts are logged-in-only.
The Actor is LinkedIn Posts Scraper — no login, no cookies, $0.0016 per post, filtered and refused pages free. The 80-row run in this post took 76 seconds and cost 12.8 cents.
If you keep one number from this: 4.04%. That is how much of 16,552 comments I could read with the comment option switched fully on, by a tool working exactly as designed. Print it next to any LinkedIn comment analysis before you believe the analysis.
Top comments (0)