LinkedIn Post Comments Scraper
Pricing
from $4.99 / 1,000 results
LinkedIn Post Comments Scraper
Extract comments from any LinkedIn post quickly and reliably. This Apify actor captures comment text, usernames, timestamps, and engagement data. Perfect for sentiment analysis, audience insights, and tracking discussions with clean, structured output ready for use.
Pricing
from $4.99 / 1,000 results
Rating
0.0
(0)
Developer
Scraper Engine
Maintained by CommunityActor stats
0
Bookmarked
29
Total users
4
Monthly active users
9 days ago
Last modified
Categories
Share
LinkedIn Post Comments Scraper — Text, Authors and Reactions
LinkedIn Post Comments Scraper extracts publicly visible comments on a LinkedIn post as structured JSON: comment text, the author's name and profile URL, an exact UTC timestamp, the per-comment reaction count, and LinkedIn's own total comment count for the post. It runs logged-out by default — no account, no cookie. Supplying an li_at cookie switches it to the authenticated engine for full depth, nested replies and per-reaction-type counts. Paste a post URL and press Start.
⚠️ A logged-out run sees a slice, not the whole thread. LinkedIn serves an anonymous visitor roughly the first 9–10 top-level comments per post. That ceiling is LinkedIn's, not the Actor's — and it is why every row reports commentsAvailable against commentsTotal, so the coverage you actually got is visible per post instead of guessed at. Nested replies, per-reaction-type breakdowns and the edited/pinned markers are not rendered to guests at all. Supplying your own li_at cookie in the liAt field lifts all four.
What is LinkedIn Post Comments Scraper?
LinkedIn Post Comments Scraper is an Apify Actor that reads the comment thread under a public LinkedIn post and returns one dataset row per comment — text, author identity, exact UTC timestamp, reaction counts, and the post's true comment total — as typed JSON with no HTML parsing on your side.
No LinkedIn account, login or cookie is required. The default path is LinkedIn's public post page, read anonymously. A cookie is optional and only changes depth and detail, never whether the Actor runs.
It is built for community and social teams measuring engagement, sales teams working the commenters on relevant posts, and developers or AI engineers piping comment threads into their own pipelines.
What LinkedIn post comment data is publicly available to scrape?
LinkedIn's public post page renders real comments to signed-out visitors: comment text, author name, author profile URL, author avatar, a per-comment reaction count, and the post's own total comment count. What it withholds from a guest is depth and structure, not the comment body.
| Data Category | Public to a signed-out visitor | Needs your li_at cookie |
|---|---|---|
| Comment text, author name, author profile URL | ✅ | — |
| Author avatar image URL | ✅ | — |
| Exact comment timestamp in UTC | ✅ decoded from the comment ID | — |
| Total reaction count per comment | ✅ | — |
| Post's true total comment count | ✅ | — |
| Comments beyond LinkedIn's guest slice | ❌ | ✅ |
| Nested replies under a comment | ❌ | ✅ |
| Per-reaction-type breakdown (like, empathy, praise…) | ❌ | ✅ |
| Edited and pinned markers, commenter headline | ❌ | ✅ |
| Commenter email, phone or connection list | ❌ | ❌ Never returned |
LinkedIn Post Comments Scraper only returns publicly visible data — what any visitor sees on the post page. Nothing behind a login wall. When you supply your own cookie, it reads what that logged-in session is already entitled to see.
What data can I extract with LinkedIn Post Comments Scraper?
Every row carries the comment itself, the commenter's public identity, engagement counts, post-level coverage figures and run accounting. Twenty top-level keys, written on every row of every run.
| Field Name | Description |
|---|---|
comment_id | LinkedIn's numeric comment ID — stable, use it to deduplicate across runs |
text | The comment body. Newlines and tabs are collapsed to single spaces on both engines |
commentCreatedAt | Exact comment time as an ISO-8601 UTC string, e.g. 2026-07-24T08:12:41Z |
posted_at | Object: timestamp (epoch milliseconds), date (same ISO UTC string), relative (short form such as 2d) |
is_edited | true/false on the cookie engine. null logged-out — LinkedIn's guest markup carries no edit marker, so it is left null rather than defaulted to false |
is_pinned | Same treatment as is_edited |
comment_url | Permalink to the individual comment, built from the post URL plus the comment URN |
author | Object with the commenter's public identity — see below |
stats | Object with reaction and reply counts — see below |
replies | Array of nested reply objects. Always [] logged-out; populated on the cookie engine when scrapeReplies is on |
post_input | Numeric post ID the comment belongs to |
postUrl | Canonical LinkedIn post URL the row was collected from |
commentsAvailable | How many comments this run actually read for the post, before the date filter |
commentsTotal | LinkedIn's own total comment count for the post |
totalComments | The same value as commentsTotal, kept as a second key for backward compatibility |
sourceEngine | Which engine produced the row: public_page (logged-out) or voyager (cookie) |
scrapedAt | ISO-8601 UTC run timestamp — identical on every row of one run |
isError | false on comment rows, true on accounting rows |
errorReason | null on comment rows; a short machine-readable code on accounting rows |
errorMessage | null on comment rows; a human-readable explanation on accounting rows |
Commenter identity — the author object
| Field Name | Description |
|---|---|
author.name | The commenter's display name as LinkedIn renders it |
author.profile_url | Link to their LinkedIn profile |
author.headline | Their professional headline. Present only when profileScraperMode is full, and populated only on the cookie engine — logged-out it is null, because LinkedIn does not render a commenter headline to guests |
author.profile_picture | Avatar image URL. Present only when profileScraperMode is full. LinkedIn's grey placeholder asset is normalised to null, so a value here always means a real photo |
Set profileScraperMode to short and the last two keys are omitted from the row entirely rather than shipped as permanently empty columns.
Engagement counts — the stats object and replies
| Field Name | Description |
|---|---|
stats.total_reactions | Total reactions on that single comment |
stats.reactions | Per-type map — like, appreciation, empathy, interest, praise, plus any additional type LinkedIn returns. Empty object {} logged-out, because the guest page publishes only the total |
stats.comments | Number of replies to that comment. null logged-out |
replies[] | Each reply repeats the comment shape: comment_id, text, posted_at, is_edited, is_pinned, comment_url, author, stats, replies, post_input. Nesting goes up to two levels, and second-level replies additionally carry a totalComments key |
Two naming notes, stated plainly so your field mapping matches what actually arrives. The per-comment fields use snake_case (comment_id, comment_url, posted_at, is_edited, is_pinned, post_input) while the run-level and post-level fields use camelCase (postUrl, commentCreatedAt, commentsAvailable, commentsTotal, totalComments, sourceEngine, scrapedAt, isError, errorReason, errorMessage). And commentCreatedAt and posted_at.date hold the same string — the flat key exists so CSV and spreadsheet exports get a usable date column without expanding an object.
🤖 Add-on: Need additional LinkedIn data?
LinkedIn Post Scraper covers the other half of the same thread — the post body, its author and its own engagement counts — which joins to these rows on post_input. If your interest is the commenters rather than the comments, LinkedIn Profile Company Enrichment Scraper takes the author.profile_url values from this output and resolves them into full profile and company records. Post Comments Engagements Scraper Linkedin By Comment Language covers the same entity with language filtering when you only want comments written in a specific language.
Why not build this yourself?
LinkedIn publishes no public API that returns the comments on an arbitrary third-party post. Its developer programs are partner-gated and scoped to content you own or are authorised for, so reading a competitor's or an influencer's comment thread means reading the public surface — and that is the part worth understanding before you write it yourself.
The specific costs this Actor absorbs:
- Two engines, not one. The public post page and LinkedIn's authenticated Voyager GraphQL endpoint return completely different payloads. Supporting both means two parsers plus a fallback path between them.
- The cookie path needs a query ID that LinkedIn rotates. Voyager's
comments-by-social-detailquery has an opaque ID embedded in LinkedIn's front-end bundles. The Actor discovers it at runtime by scanning the feed page and then the post page — inline scripts, then page text, then every<script src>, then LinkedIn's aero CDN bundles, HEAD-filtered by content type before any body is fetched. Hardcode that ID and your scraper breaks on LinkedIn's next deploy. - HTTP 999. LinkedIn's throttle status is not a 4xx and not a ban. Clearing it needs backoff plus a genuinely new exit IP, not a retry on the same connection — and it has to be distinguished from a parse failure, or you will confidently report an empty post that actually has 400 comments.
- Guest walls arrive as HTTP 200. A login or join page is served with a success status. Keying "did this work?" on the status code produces silent garbage. The only reliable test is the presence of the comment container in the body together with the final resolved URL.
- Pagination and the reply tree. Voyager pages by
startoffset plus apaginationToken, and returns comments as URN references into a separateincludedarray. Replies are a recursive lookup through social-detail URNs, and pages overlap, so de-duplication by comment ID is mandatory. - Timestamps. LinkedIn's guest markup gives you "2d". Exact times come from decoding the comment ID as a snowflake — its top bits are epoch milliseconds — which is the only way a date filter can be precise.
- The proxy ladder. Direct works most of the time and costs nothing; residential is needed sometimes and costs money. Getting the escalation order right, and only ever ratcheting it upward, is the difference between a cheap run and an expensive one.
How to use LinkedIn Post Comments Scraper
LinkedIn Post Comments Scraper runs on Apify. Start it from the Apify Console or call it through the Apify API — your Apify token is the only credential involved.
- Open LinkedIn Post Comments Scraper on Apify and click Try for free
- Paste one or more posts into Posts (
startUrls). Full post URLs,urn:li:activity:orurn:li:ugcPost:URNs, and bare numeric activity IDs are all accepted, mixed freely in the same list - Set Max comments per post (
resultLimitPerPost). Logged-out, LinkedIn's guest slice is the effective limit, so raising this only changes the result once a cookie is supplied - Optionally narrow with Only comments newer than (
postedLimit), and switch Comment author detail toshortif you do not need avatars - Optionally paste your
li_atcookie into LinkedIn cookie for full depth, replies and per-type reactions - Click Start, then export as JSON, CSV or Excel
No field is marked required in the schema, but startUrls is required in practice: a run with an empty list logs No LinkedIn post URLs or activity IDs provided. Add at least one and re-run. and exits immediately without pushing a row or charging anything. The Console form arrives with one example post URL pre-filled, so pressing Start without editing scrapes that example post rather than doing nothing.
Leave the proxy off to begin with. The Actor starts on a direct connection and escalates on its own only when LinkedIn pushes back.
How to scrape comments from multiple LinkedIn posts in one run
startUrls is a list, so bulk collection is the default mode rather than a special case. Posts are processed strictly one at a time with a randomised 1–2 second gap between them, which is what keeps LinkedIn's throttle from engaging in the first place. resultLimitPerPost applies per post, not per run — ten posts at 10 comments each is a 100-row run.
Formats can be mixed in one list: a full https://www.linkedin.com/feed/update/urn:li:activity:.../ URL, a https://www.linkedin.com/posts/username_slug-activity-... share URL, a bare urn:li:ugcPost:123…, or just 7289521182721093633. Entries the Actor cannot parse into a post ID are written as uncharged accounting rows with errorReason of invalid_input, so a malformed line in a 500-post list stays visible instead of being silently dropped.
What can you do with LinkedIn post comment data?
- 💬 A community manager measuring which posts actually start conversations pulls
textandstats.total_reactionsacross a month of company posts, then ranks threads bycommentsTotalto find real discussion volume rather than reaction volume. - 🎯 A B2B sales team working a competitor's viral post builds a list from
author.name,author.headlineandauthor.profile_url, sorted oncommentCreatedAtso the freshest commenters are reached while the post is still in their memory. - 📊 A content strategist running a campaign post-mortem joins comment rows to posts on
post_input, then checkscommentsAvailableagainstcommentsTotalto confirm whether a thread was read in full or sampled before drawing a conclusion from it. - 🤖 An AI engineer building a social-listening agent indexes
textalongsideauthor.headlineinto a vector store, so the agent can answer "what did senior engineers say about this launch" against the actual thread instead of a summary. - 🔍 A market researcher tracking sentiment over time uses
posted_at.timestampfor exact time-series placement andpostedLimitto tile scheduled runs into non-overlapping windows.
Every one of these is callable from an agent framework over the Apify API, since the Actor is a standard HTTP-triggered run returning typed JSON.
How does LinkedIn Post Comments Scraper handle rate limits and blocking?
Three mechanisms, all automatic.
A proxy ladder that only ratchets upward. Every run starts on a direct connection with no proxy. If a request fails, the Actor climbs to an Apify datacenter proxy and then to Apify residential, and the floor stays raised for the rest of the run rather than dropping back. Enabling Apify Proxy yourself simply starts the ladder at the datacenter rung.
HTTP 999 handling. LinkedIn's throttle status is treated as throttling, not as a block and not as an empty post: the Actor requests a genuinely fresh exit IP, backs off with jitter, and retries — up to four attempts per page. 429, 500, 502, 503 and 504 get their own retry-and-escalate path.
Container-keyed success detection. A guest login wall is served with HTTP 200, so status alone is not trusted. A response counts as usable only when the comment container or the post's JSON-LD node is actually present and the final resolved URL is not a login, join or checkpoint page.
There is no CAPTCHA solving and no headless browser — requests are plain HTTP with Chrome TLS impersonation. When a post cannot be read after all retries it is written to the dataset as an uncharged accounting row with errorReason set to http_999, login_required, no_comment_container, no_comments, fetch_failed or an http_<status> code, so a missed post stays auditable instead of becoming an unexplained gap in the count. If the cookie engine fails on a post, the run falls back to the logged-out engine for that post rather than losing it, and sourceEngine records which engine the row actually came from.
⬇️ Input
Read straight from the Actor's input schema. No parameter is marked required — see the notes under the table for what that means in practice.
| Parameter | Required | Type | Description | Example Value |
|---|---|---|---|---|
startUrls | No | array | One or more LinkedIn post URLs, activity URNs (urn:li:activity:123…) or bare numeric activity IDs. Each post is scraped separately. Default []. | ["https://www.linkedin.com/feed/update/urn:li:activity:7289521182721093633/", "7301884511220895745"] |
resultLimitPerPost | No | integer | Upper bound on comments collected per post. Minimum 1, maximum 500, default 10. Logged-out, LinkedIn's guest slice caps the result well below this. | 50 |
postedLimit | No | string | Drop comments older than the chosen window: "any", "24h", "week", "month", "3months", "6months", "year". Default "any". | "week" |
scrapeReplies | No | boolean | Nests replies (up to 2 levels) inside each comment row. Requires liAt — LinkedIn renders no reply threads to logged-out visitors. Default false. | true |
profileScraperMode | No | string | "full" adds author.profile_picture, and author.headline when a cookie is supplied. "short" omits both keys entirely. Default "full". | "full" |
liAt | No | string | Your own li_at cookie. Leave empty to run logged-out. Supplying it lifts the guest comment ceiling and unlocks nested replies, per-reaction-type counts and the edited/pinned markers. Stored encrypted, and used only on the requests that need it. | "AQEDAT..." |
proxyConfiguration | No | object | Apify Proxy settings. Default is no proxy — the Actor escalates from direct to datacenter to residential on its own and rotates the exit IP on HTTP 999. | {"useApifyProxy": false} |
⚠️ Common pitfall: turning on scrapeReplies without supplying liAt. The parameter is accepted, the run succeeds, and replies comes back as [] on every row — because LinkedIn does not render reply threads to anonymous visitors at all. The same applies to stats.reactions, stats.comments, is_edited, is_pinned and author.headline: every one of them needs the cookie engine to carry a value.
Three further behaviours worth knowing, all from the Actor's own input handling:
- An empty
startUrlsproduces nothing. The run logs a warning, exits cleanly, and pushes no rows. It is not an error and it is not charged. resultLimitPerPostis coerced, not rejected. A missing, non-integer or non-positive value falls back to10; anything above 500 is clamped to 500.- Unrecognised enum values fall back quietly. A
postedLimitoutside the listed set is treated as"any", and anyprofileScraperModeother than"full"behaves as"short".
Example input
{"startUrls": ["https://www.linkedin.com/feed/update/urn:li:activity:7289521182721093633/","urn:li:activity:7301884511220895745","7295610338217426944"],"resultLimitPerPost": 50,"postedLimit": "month","scrapeReplies": true,"profileScraperMode": "full","liAt": "AQEDATxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","proxyConfiguration": { "useApifyProxy": false }}
⬆️ Output
One row per comment, twenty top-level keys, the same shape on every run and on both engines. Unavailable values arrive as null rather than a missing key — the two exceptions are author.headline and author.profile_picture, which are deliberately omitted when profileScraperMode is short so you never carry permanently empty columns. Rows are written per post while the run is still going. Export as JSON, CSV or Excel, or read the dataset through the Apify API.
Posts that could not be read are written as accounting rows: isError is true, errorReason and errorMessage are populated, and every comment field is null. These rows are never charged — the row_result event fires only on real comment rows. Filter clean results on isError being false, or on errorReason being null; note that isError is not part of the default dataset table view, so use the JSON export or the API when you want to branch on it.
One honest gap in the accounting: when postedLimit filters out every comment on a post, that post contributes no rows at all — neither comment rows nor an accounting row. Comments whose timestamp cannot be decoded are also dropped once any window other than "any" is set. Compare your input list against the distinct postUrl values in the output when the date filter is doing heavy work.
Example output
A logged-out row, sourceEngine of public_page — the default configuration:
{"comment_id": "7289600123456789012","text": "The part about onboarding friction matches what we measured internally. We cut trial drop-off by a third just by removing the second confirmation step.","posted_at": {"timestamp": 1753344761000,"date": "2026-07-24T08:12:41Z","relative": "1d"},"is_edited": null,"is_pinned": null,"comment_url": "https://www.linkedin.com/feed/update/urn:li:activity:7289521182721093633/?commentUrn=urn:li:comment:(activity:7289521182721093633,7289600123456789012)","author": {"name": "Marta Nowak","profile_url": "https://www.linkedin.com/in/marta-nowak-product","headline": null,"profile_picture": "https://media.licdn.com/dms/image/v2/D4E03AQH8k2vQ/profile-displayphoto-shrink_400_400/0/1741023881204"},"stats": {"total_reactions": 24,"reactions": {},"comments": null},"replies": [],"post_input": "7289521182721093633","postUrl": "https://www.linkedin.com/feed/update/urn:li:activity:7289521182721093633/","commentCreatedAt": "2026-07-24T08:12:41Z","commentsAvailable": 9,"commentsTotal": 412,"totalComments": 412,"sourceEngine": "public_page","scrapedAt": "2026-07-25T09:14:02Z","isError": false,"errorReason": null,"errorMessage": null}
The same post read with a cookie, sourceEngine of voyager, with scrapeReplies enabled — note the populated reaction breakdown, the real edited and pinned booleans, the commenter headline and the nested reply:
{"comment_id": "7289604487771226113","text": "Curious how this holds up for enterprise deals where procurement adds its own friction.","posted_at": {"timestamp": 1753345812000,"date": "2026-07-24T08:30:12Z","relative": "1d"},"is_edited": true,"is_pinned": false,"comment_url": "https://www.linkedin.com/feed/update/urn%3Ali%3Aactivity%3A7289521182721093633?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7289521182721093633%2C7289604487771226113%29","author": {"name": "Daniel Okafor","profile_url": "https://www.linkedin.com/in/daniel-okafor-revops","headline": "RevOps Lead | Scaling GTM systems for B2B SaaS","profile_picture": "https://media.licdn.com/dms/image/v2/C5603AQFtP0lQ/profile-displayphoto-shrink_800_800/0/1699884120411"},"stats": {"total_reactions": 18,"reactions": {"like": 14,"appreciation": 2,"empathy": 1,"interest": 1,"praise": 0},"comments": 2},"replies": [{"comment_id": "7289611203944558592","text": "Same question here. Procurement is where our cycle time actually goes.","posted_at": {"timestamp": 1753347390000,"date": "2026-07-24T08:56:30Z","relative": "1d"},"is_edited": false,"is_pinned": false,"comment_url": "https://www.linkedin.com/feed/update/urn%3Ali%3Aactivity%3A7289521182721093633?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7289521182721093633%2C7289611203944558592%29","author": {"name": "Aiko Tanaka","profile_url": "https://www.linkedin.com/in/aiko-tanaka-sales","headline": "Enterprise Account Executive","profile_picture": null},"stats": {"total_reactions": 3,"reactions": { "like": 3, "appreciation": 0, "empathy": 0, "interest": 0, "praise": 0 },"comments": 0},"replies": [],"post_input": "7289521182721093633"}],"post_input": "7289521182721093633","postUrl": "https://www.linkedin.com/feed/update/urn:li:activity:7289521182721093633/","commentCreatedAt": "2026-07-24T08:30:12Z","commentsAvailable": 50,"commentsTotal": 412,"totalComments": 412,"sourceEngine": "voyager","scrapedAt": "2026-07-25T09:14:02Z","isError": false,"errorReason": null,"errorMessage": null}
And an uncharged accounting row for a post LinkedIn would not serve comments for:
{"postUrl": "https://www.linkedin.com/feed/update/urn:li:activity:7295610338217426944/","post_input": "7295610338217426944","comment_id": null,"text": null,"commentCreatedAt": null,"posted_at": { "timestamp": null, "date": null, "relative": null },"is_edited": null,"is_pinned": null,"comment_url": null,"author": { "name": null, "profile_url": null },"stats": { "total_reactions": null, "reactions": {}, "comments": null },"replies": [],"totalComments": null,"commentsAvailable": 0,"commentsTotal": null,"sourceEngine": "public_page","scrapedAt": "2026-07-25T09:14:02Z","isError": true,"errorReason": "no_comments","errorMessage": "LinkedIn served no comments for this post."}
How does it work?
Each post you supply is normalised to a numeric LinkedIn activity ID and a canonical post URL, whatever format you pasted. By default the Actor then fetches the public post page over plain HTTP with Chrome TLS impersonation — no browser, no cookie — and reads the thread from two surfaces at once: the server-rendered comment sections, which carry the comment URN, the author's profile link and the avatar, and the page's JSON-LD posting node, which carries the exact publication date, the reaction count and LinkedIn's true total comment count. The two are matched together, so a value missing from one is filled from the other.
Exact timestamps need no extra request: a LinkedIn comment ID is a snowflake, so its top bits decode directly to epoch milliseconds. Requests begin on a direct connection and escalate through Apify datacenter and residential proxies only when LinkedIn pushes back, with a fresh exit IP on HTTP 999.
If you supply an li_at cookie, the run switches to LinkedIn's authenticated Voyager GraphQL endpoint for the same posts, which is what carries full depth, nested replies and per-reaction-type counts. Only publicly visible comment data is returned, and the output schema stays the same regardless of which engine produced a row or how LinkedIn changes its front end.
Integrations
LinkedIn Post Comments Scraper is an Apify Actor, so it works with anything that can call the Apify API or consume a dataset.
Calling LinkedIn Post Comments Scraper from Python
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("<YOUR_USERNAME>/linkedin-post-comments-scraper").call(run_input={"startUrls": ["https://www.linkedin.com/feed/update/urn:li:activity:7289521182721093633/"],"resultLimitPerPost": 50,"postedLimit": "month","profileScraperMode": "full",})comments = [c for c in client.dataset(run["defaultDatasetId"]).iterate_items()if not c.get("isError")]for c in comments:print(c["commentCreatedAt"], c["author"]["name"], c["stats"]["total_reactions"], c["text"][:80])
Works in Go, Ruby, Node.js, cURL — any language that can make an HTTP request. Filtering on isError separates real comments from the uncharged accounting rows.
No-code tools (n8n, Make, LangChain)
In n8n, use the Apify node — or an HTTP Request node pointed at the Actor's run endpoint with your token — and pass the same JSON input shown above; an IF node on isError then routes accounting rows away from your comment handling. In Make, the Apify module supports run-and-wait, so a weekly comment sweep can feed a Google Sheets, Airtable or CRM step directly. In LangChain, wrap the same run call as a tool function and hand the returned rows to your agent as context — they are already typed JSON, so no output parser is needed.
Is it legal to scrape LinkedIn post comments?
Scraping publicly accessible pages is broadly treated as permissible where no authentication is bypassed, and this Actor returns only public comments on publicly visible posts. But the output is unambiguously personal data, and it is worth treating it that way from the first run.
author.name, author.profile_url, author.headline and author.profile_picture identify a named individual, and text is that individual's own written expression, tied back to them by comment_url, comment_id and posted_at — the same applies to every object inside replies. Under GDPR, UK GDPR and the CCPA, collecting and storing this is processing, and the fact that data is publicly available is not itself a lawful basis.
In practice that means four things. Pick and document a lawful basis before you store rows — for B2B research and monitoring that is usually legitimate interest, which requires a written balancing test weighing your purpose against the commenter's reasonable expectation that a work comment would be aggregated into someone's database. Minimise: run profileScraperMode as short to drop profile_picture and headline entirely, and if you only need engagement volume, keep the counts and discard text and author.name first. Set a retention period and delete on schedule instead of accumulating indefinitely. And stay able to honour access, objection and erasure requests, which means keeping comment_id so a specific person's records can be located and removed; if you are a controller in scope of the GDPR's transparency rules, Article 14 also obliges you to inform people whose data you collected without contacting them first.
Using this data for outreach is a separate regime from collecting it. Marketing and cold-contact rules — PECR and its equivalents in the EU, CAN-SPAM in the US, plus LinkedIn's own terms if you message on-platform — apply to the outreach itself, and a valid basis for collection does not carry over to it. Consult legal counsel if your use case involves bulk storage of personal data, AI training corpora, or resale.
❓ Frequently asked questions
What LinkedIn comment fields does LinkedIn Post Comments Scraper return?
The five most used are text, author.name, author.profile_url, commentCreatedAt and stats.total_reactions. Every row also carries commentsAvailable and commentsTotal so you know what share of the thread you read, plus sourceEngine, scrapedAt and the error-accounting keys. Twenty top-level keys in total — see the data fields table above.
Does LinkedIn Post Comments Scraper require a LinkedIn account or cookie?
No. The default path reads LinkedIn's public post page anonymously — no login, no cookie, no session. Your Apify token is the only credential needed. An li_at cookie is optional and changes what you get rather than whether the Actor runs: it lifts LinkedIn's guest comment ceiling and unlocks nested replies, per-reaction-type counts, the edited and pinned markers, and the commenter headline. If the cookie path fails on a post, the run falls back to the logged-out engine for that post.
How many comments can I extract per post?
resultLimitPerPost accepts 1 to 500 and defaults to 10, but the real logged-out limit is LinkedIn's rather than the Actor's: LinkedIn serves an anonymous visitor roughly the first 9–10 top-level comments per post. For an ordinary post that is the whole thread; for a viral one it is a top slice. Every row tells you which case you are in by reporting commentsAvailable against commentsTotal. Raising resultLimitPerPost only changes the outcome once an li_at cookie is supplied.
What happens if a post has no comments, is deleted, or is not publicly visible?
You get an uncharged accounting row instead of a silent gap. A post that exists but has no comments produces errorReason of no_comments. A deleted or otherwise unreachable post produces fetch_failed or an http_<status> code. A post LinkedIn will not show a guest produces login_required when it redirects to a login or join page, or no_comment_container when the page loads without a comment thread — that post is worth retrying with an li_at cookie. A throttled post produces http_999 after retries on fresh IPs, and an unparseable input line produces invalid_input. In every case isError is true, all comment fields are null, and the row is not charged.
Can I scrape comments from multiple LinkedIn posts at once?
Yes — startUrls is a list and bulk is the normal mode. Mix full post URLs, urn:li:activity: and urn:li:ugcPost: URNs and bare numeric activity IDs in the same list. Posts are fetched one at a time with a randomised 1–2 second gap, which is deliberate: it is what keeps LinkedIn's throttle from engaging. resultLimitPerPost applies per post, so run size is your post count multiplied by that limit.
Does scrapeReplies work without a cookie?
No, and this is the most common surprise. The toggle is accepted logged-out and the run succeeds, but replies comes back as [] on every row, because LinkedIn does not render reply threads to anonymous visitors. Replies need liAt. The same is true of stats.reactions, stats.comments, is_edited, is_pinned and author.headline — all of them are cookie-engine values, and the logged-out engine returns them empty or null rather than inventing a false.
Does LinkedIn Post Comments Scraper work with Claude, ChatGPT and other AI agent tools?
Yes. It is callable as a standard HTTP endpoint through the Apify API, so LangChain, CrewAI, n8n or a hand-written tool definition can invoke it and receive typed JSON with no parsing step. There is no separate signup and no separate API key — the Apify token you already have is the only credential.
How does LinkedIn Post Comments Scraper compare to other LinkedIn post comment scrapers?
Checked on the Apify Store on 25 July 2026. harvestapi/linkedin-post-comments is the most used; its listing advertises cookie-free access, accepts post URLs and individual comment URLs, documents collecting up to 5 nested replies per comment, and documents a postedLimit set identical to this Actor's — its listing also notes that its result count differs from LinkedIn's own counter because it counts only root comments. apimaestro/linkedin-post-comments-replies-engagements-scraper-no-cookies documents the same base output shape used here (comment_id, text, posted_at, is_edited, is_pinned, author, stats, post_input) plus comment_type and parent_comment_id on replies, and adds sort-order and page-number inputs. datadoping/linkedin-post-comments-scraper documents a free-tier limit of 4 posts per run with a maximum of 100 comments per post.
What this Actor documents that those three listings do not: a per-post coverage pair, commentsAvailable versus commentsTotal, on every row; an explicit two-engine model where logged-out is the default and the cookie path is an opt-in escalation with per-post fallback; exact UTC timestamps decoded from the comment ID rather than relative text; and uncharged accounting rows for posts that could not be read. Where a competitor's listing is silent on one of these, that is not documented rather than not supported.
Does LinkedIn Post Comments Scraper return data in a format LLMs can use directly?
Yes. Typed, normalized JSON with consistent field names across runs and across both engines. No HTML parsing, no selectors, no relative-date strings to resolve. Pass a row straight into an LLM context window, index it into a vector store, or hand it to an agent tool.
What happens when LinkedIn changes its layout or anti-bot system?
The scraper is maintained, and the output schema stays stable on your side regardless of which surface changed. Two design choices reduce the exposure: the logged-out engine reads both the rendered comment sections and the page's JSON-LD node, so a markup change to one is usually covered by the other, and the cookie engine discovers LinkedIn's rotating GraphQL query ID at runtime instead of hardcoding it, so a front-end deploy does not break it. Anti-bot pushback is handled as throttling rather than failure — fresh exit IP, backoff, retry — and posts that still cannot be read become labelled uncharged rows rather than silent omissions.
Which LinkedIn comment fields work best for AI training data and RAG indexing?
For RAG indexing: text carries the entire information payload of a comment and chunks cleanly one row per chunk, with author.headline as useful metadata for filtering by role and postUrl as the citation anchor. For training data and analytics: commentCreatedAt, posted_at.timestamp, stats.total_reactions, commentsTotal and sourceEngine are the most structurally consistent fields across records, since they are populated on every non-error row of both engines. All values return as typed primitives, arrays or plain nested objects, so no normalization pass is needed before indexing.
🔗 Related scrapers
| Scraper Name | What it extracts |
|---|---|
| LinkedIn Post Scraper | Post body, author and post-level engagement counts |
| Post Comments Engagements Scraper Linkedin By Comment Language | The same comment rows, filtered by the language of the comment |
| LinkedIn Profile Company Enrichment Scraper | Full profile and company records from a LinkedIn profile URL |
| LinkedIn Jobs Scraper With Salary Range Filters | Public job postings with salary-range filtering |
| Instagram Comment Leads Scraper | Instagram comment threads with commenter identity |
| TikTok Comments Scraper By Real Comment Filter | TikTok video comments with spam filtering |
💬 Your feedback
Found a bug, or a field LinkedIn exposes on a comment that is not in the output? Open an issue on the Actor's Issues tab. Reports that include the exact input JSON and the post URL are the fastest to reproduce and fix.