Post Comments Engagements Scraper Linkedin By Comment Language
Pricing
$19.99/month + usage
Post Comments Engagements Scraper Linkedin By Comment Language
Extract engagement data from LinkedIn post comments using this scraper. Collect commenter names, comment text, likes, replies, timestamps, and profile URLs. Ideal for lead generation, audience insights, brand monitoring, and analyzing discussions on LinkedIn posts.
Pricing
$19.99/month + usage
Rating
0.0
(0)
Developer
Scraper Engine
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
11 days ago
Last modified
Categories
Share
LinkedIn Comment Scraper by Language — Text, Authors and Reactions
Post Comments Engagements Scraper Linkedin By Comment Language extracts publicly visible LinkedIn post comments as structured JSON and labels each one by the language it was written in: the comment text, an inferred commentLanguage with a languageConfidence score, a Unicode-measured commentScript, the author's name and profile URL, and the per-comment reaction count. Keep or drop languages with include and exclude lists. It runs logged out — no account, no cookie. Paste a post URL and press Start.
⚠️ Language detection is keyless and offline — translation is not
Two things are easy to conflate, so they are separated here before anything else.
Language detection needs no key and no model. commentLanguage, languageConfidence, commentScript and the whole per-post language mix are produced inside the Actor by an offline statistical detector (py3langid) and by counting Unicode code points. Nothing is sent anywhere, nothing is billed for it, and it works on every run including a completely empty configuration.
translateToEnglish is the only feature that calls an external model, it is off by default, and Apify does not bill it. When you switch it on, the Actor sends comment text to the provider implied by aiModel — Anthropic for claude-*, OpenAI for gpt-*, Google for gemini-*, xAI for grok-*, DeepSeek for deepseek-*, Perplexity for sonar*, Mistral for mistral-*. Those calls are charged to your account with that provider, on their pricing, entirely separately from your Apify usage. Comments are sent in batches of 20, so token spend scales with how many comments you keep, not with how many posts you queue.
Comment text leaves Apify when translation is on. A LinkedIn comment is written by an identifiable person, and turning this on sends that text to a third-party model provider. Under GDPR that provider becomes a processor in your processing chain: it belongs in your record of processing activities, needs to be covered by your controller-processor terms, and should appear where your privacy notice lists recipients and international transfers. With translateToEnglish off, no comment text goes anywhere except your own Apify dataset.
If translation is on and no key is found, the run does not fail — and the rows are still charged. The Actor checks aiApiKey first, then the provider's environment variable (ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY / GOOGLE_API_KEY, XAI_API_KEY / GROK_API_KEY, DEEPSEEK_API_KEY, PERPLEXITY_API_KEY / PPLX_API_KEY, MISTRAL_API_KEY). If both are empty it logs a warning and carries on: every comment is still scraped, still written, and still charged as a row_result, with textEnglish set to null. Unlike a silent no-op, the reason is written into the row — translationStatus reads no_api_key, so you can detect it in the dataset instead of only in the log.
One field is model output. textEnglish is written by the model you selected in aiModel. Every other field in the output is either scraped from LinkedIn or computed offline inside the Actor. The tables below label it explicitly, and no accuracy figure is claimed anywhere in this README — not for the translation, and not for the language detection.
What is Post Comments Engagements Scraper Linkedin By Comment Language?
It is an Apify Actor that reads the comment thread under a public LinkedIn post, returns one dataset row per comment, and adds two language layers to each row: a writing script measured from Unicode code points, and a detected ISO 639-1 language with a confidence score. Include and exclude lists then keep only the languages you asked for, and excluded comments are neither written nor charged.
No LinkedIn account, login or cookie is required. The default path is LinkedIn's public post page, read anonymously. An li_at cookie is optional and only changes depth and detail, never whether the Actor runs. An LLM provider key is needed only if you switch English translation on.
It is built for regional social and community teams who can only action the comments they can read, market researchers segmenting a multilingual thread, and developers or AI engineers routing comment text by language 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 |
| The language a comment is written in | ❌ LinkedIn publishes no language tag | ❌ Computed inside the Actor |
Post Comments Engagements Scraper Linkedin By Comment Language only returns publicly visible data — what any visitor sees on the post page. Nothing behind a login wall. The language and script fields are not fetched from LinkedIn at all; they are derived locally from the comment text the page already showed you.
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. Supplying your own li_at cookie in the liAt field lifts it, along with replies, per-type reactions and the edited/pinned markers.
What data can I extract with Post Comments Engagements Scraper Linkedin By Comment Language?
Every comment row carries the comment itself, the commenter's public identity, engagement counts, the language and script verdict for that comment, the language mix of the whole post, per-post coverage figures and run accounting. Forty-two top-level keys, written on every comment row of every run, on both engines.
The comment and its author — scraped from LinkedIn
| Field Name | Description |
|---|---|
comment_id | LinkedIn's numeric comment ID — stable, use it to deduplicate across runs |
text | The comment body, verbatim. Newlines and tabs are collapsed to single spaces. Never overwritten by translation |
commentCreatedAt | Exact comment time as an ISO-8601 UTC string, e.g. 2026-07-24T08:12:41Z |
posted_at | Object: timestamp (epoch milliseconds), date (YYYY-MM-DD HH:MM:SS in UTC), 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: name, profile_url, profile_picture, and headline which is populated only on the cookie engine |
authorName | Flat copy of author.name, so CSV exports get a usable column without unpacking |
authorProfileUrl | Flat copy of author.profile_url |
stats | Object: total_reactions, reactions (per-type map, {} logged-out), comments (reply count, null logged-out) |
reactionCount | Flat copy of stats.total_reactions |
replies | Array of nested reply objects. Always [] logged-out; populated up to two levels on the cookie engine |
post_input | Numeric post ID the comment belongs to |
postUrl | Canonical LinkedIn post URL the row was collected from |
Language and script — measured and inferred, computed offline
The distinction between these two claims runs through the whole output. commentScript is a measurement taken from Unicode code points and is populated on every row. commentLanguage is a statistical inference and is populated only when the comment clears both guard floors.
| Field Name | Description |
|---|---|
commentScript | Writing system measured from the letters in the comment: latin, greek, cyrillic, hebrew, arabic, devanagari, cjk, other, mixed (no single script holds 90% of the letters) or none (emoji or digits only) |
scriptLetterCount | How many letters were left to work with after URLs, @mentions, #hashtags and emoji were stripped out |
isNonLatinScript | true only for a positive non-Latin read. mixed, other and none return false rather than being claimed either way |
commentLanguage | ISO 639-1 code inferred by the offline detector, e.g. en, pt, ar, ja. null whenever a guard floor stopped it |
commentLanguageName | Human-readable name for that code, e.g. Portuguese. null alongside a null commentLanguage |
languageConfidence | Detector confidence, 0–1, rounded to 4 decimals. Present even when the label was rejected for being below minLanguageConfidence |
languageDetector | py3langid when the offline detector loaded, null if it did not |
languageGuardReason | Why no language was claimed: too_short, low_confidence, not_detected or detector_unavailable. null on a labelled comment |
The post's language mix — computed across every comment read
These nine keys are identical on every comment row from the same post. They are calculated over all comments read from that post, before the include/exclude filter runs, so the mix describes the thread rather than your filter.
| Field Name | Description |
|---|---|
languageBreakdown | Object of language code to comment count, e.g. {"en": 5, "pt": 3, "es": 1} |
scriptBreakdown | Object of script label to comment count, e.g. {"latin": 8, "arabic": 1} |
dominantLanguage | The most frequent labelled language on the post, or null until at least 3 comments were actually labelled |
dominantLanguageBasis | labelled_comments when a dominant language was claimed, otherwise insufficient_sample or no_language_labelled |
languagesDetected | How many distinct languages were labelled on the post |
languageLabelledCount | How many comments got a language label |
languageUndeterminedCount | How many comments were left unlabelled by the guards |
nonLatinScriptCount | How many comments were written in a non-Latin script |
commentSampleSize | How many comments the mix was computed from — read this next to dominantLanguage before trusting it |
Coverage, translation status and run accounting
| Field Name | Description |
|---|---|
commentsAvailable | How many comments this run actually read for the post, before the language filter. Equal to commentSampleSize |
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 |
translationStatus | disabled, no_api_key, translated or failed — see the AI table below |
isError | false on comment rows, true on uncharged 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 |
AI-derived field — model output, not LinkedIn data
One key in the whole output is written by a language model. It is always present; it is null unless a translation actually landed.
| Field Name | Description |
|---|---|
textEnglish | English translation of text, produced by the model set in aiModel — AI-derived, not published by LinkedIn. null when translation is off, unkeyed, failed, or returned nothing for that comment |
translationStatus records which of those it was: disabled when translateToEnglish is off, no_api_key when it was on but no key was found, translated when a translation landed, and failed when the call was made but nothing usable came back for that comment. Note that failed also covers the deliberate case where the model returns null for a comment carrying no translatable words — it means "no translation on this row", not necessarily an error.
Two naming notes, so your field mapping matches what actually arrives. The per-comment fields inherited from the base output shape use snake_case (comment_id, comment_url, posted_at, is_edited, is_pinned, post_input) while everything added by this Actor uses camelCase (postUrl, commentCreatedAt, commentLanguage, commentsAvailable, sourceEngine, isError). And commentCreatedAt holds an ISO-8601 UTC string while posted_at.date holds the same instant in YYYY-MM-DD HH:MM:SS form — the flat key exists so CSV and spreadsheet exports get a sortable date column.
🤖 Add-on: Need additional LinkedIn data?
LinkedIn Post Comments Scraper is the same comment engine without the language layer, and adds a date-window filter and a replies toggle instead. LinkedIn Post Scraper covers the other half of the 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.
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:
- Short text is where language detectors lie. Run raw,
"👍"scores Japanese and"Congrats!"scores English at 0.17. A detector with no guard in front of it will label every emoji reply as a confident language and quietly poison your segmentation. This Actor strips URLs, mentions, hashtags and emoji first, then demands both a letter floor and a confidence floor before it claims anything. - Classical Latin eats short English comments. The stock detector model carries 97 classes including dead and vanishingly rare languages, and
"Hello, it's a pleasure to join your network"came back aslaat confidence 1.0 — which no confidence floor can catch. The candidate set is restricted to the 57 language codes this Actor is willing to name, which is the only fix that works. - Script and language are different claims.
Obrigado,DankeandThanksare all Latin script and three different languages. Collapsing the two into one column loses the claim that is always correct in order to keep the one that sometimes is not. - 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 validates baked IDs against the live API and falls back to scanning LinkedIn's script bundles at runtime. Hardcode it 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, 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. The only reliable test is the presence of the comment container in the body together with the final resolved URL.
- 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 Post Comments Engagements Scraper Linkedin By Comment Language
The Actor 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 the Actor on Apify and click Try for free
- Paste one or more posts into LinkedIn post URLs or activity IDs (
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 Comments to read per post (
commentsPerPost). Logged-out, LinkedIn's guest slice is the effective limit, so raising this only changes the result once a cookie is supplied - Add ISO 639-1 codes to Keep only these languages (
includeLanguages) or Drop these languages (excludeLanguages). Leave both empty to keep everything and still get the language columns - Optionally tighten
minLanguageConfidenceandminCharsForLanguage, or setundetectedLanguageHandlingtoexcludeto drop the comments the guards would not label - Optionally paste your
li_atcookie into LinkedIn li_at cookie for full depth, replies and per-type reactions, and switch on Translate comments to English with your own provider key - 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 two example post URLs pre-filled, so pressing Start without editing anything scrapes those two posts with default settings — every language kept, every language column populated, no translation and no key needed.
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. commentsPerPost applies per post, not per run — ten posts at 100 comments each is a 1,000-row budget before the language filter takes its cut.
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 long list stays visible instead of being silently dropped. There is no deduplication of the input list — the same post listed twice is fetched and written twice.
What can you do with LinkedIn comment language data?
- 🌍 A regional community manager covering LATAM sets
includeLanguagesto["pt", "es"]on a global brand post and gets only the comments their team can actually answer, withauthorNameandauthorProfileUrlready for the reply queue. - 📊 A market researcher sizing where a launch landed reads
languageBreakdown,dominantLanguageandcommentSampleSizeoff any single row per post, turning "it went viral" into a countable split by audience language. - 🔤 A localisation lead deciding which markets need native support filters on
isNonLatinScriptandcommentScriptto find the threads their Latin-only tooling has been mangling, then checkscommentsAvailableagainstcommentsTotalbefore drawing a conclusion. - 🌐 A global social team with no in-house Japanese sets
excludeLanguagesto["en"]and switches ontranslateToEnglish, reading the non-English half of a thread throughtextEnglishwhile keeping the originaltextbeside it for verification. - 🤖 An AI engineer building a multilingual social-listening agent indexes
textwithcommentLanguage,languageConfidenceandpostUrlas retrieval metadata, so the agent can answer "what did the Arabic-speaking commenters say" against the actual thread rather than a blended summary.
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 Post Comments Engagements Scraper Linkedin By Comment Language 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, and the cookie engine backs off 2, 4 then 8 seconds on a 429 of its own.
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. On the cookie engine the equivalent check is that a soft-blocked datacenter IP gets HTTP 200 with no social node and no included array at all.
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, empty_body, no_comments, fetch_failed, invalid_input, network_error:<type> or an http_<status> code, so a missed post stays auditable instead of becoming an unexplained gap. 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
Every parameter below is read directly from the Actor's input schema. Nothing 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, urn:li:activity: / urn:li:ugcPost: URNs, or bare numeric activity IDs. Each post is scraped separately. Pre-filled with two example post URLs. | ["https://www.linkedin.com/feed/update/urn:li:activity:7461916737781964800/", "7476267243664887809"] |
commentsPerPost | No | integer | Upper bound on comments read from each post. Minimum 1, maximum 500, default 100. Logged-out, LinkedIn's guest slice caps the result well below this. | 100 |
includeLanguages | No | array | ISO 639-1 codes to keep, one per line. Empty keeps every language. | ["en", "pt"] |
excludeLanguages | No | array | ISO 639-1 codes to remove, one per line. Applied after the keep list. | ["en"] |
undetectedLanguageHandling | No | string | What to do with comments the guards would not label: "keep" or "exclude". Default "keep". | "keep" |
minLanguageConfidence | No | number | Confidence floor, 0–1, default 0.65. Below it the language is null with languageGuardReason of low_confidence. | 0.65 |
minCharsForLanguage | No | integer | Letter floor, counted after URLs, mentions, hashtags and emoji are stripped. Minimum 1, maximum 200, default 12. Below it the reason is too_short. | 12 |
translateToEnglish | No | boolean | Adds textEnglish per kept comment using your own provider key. text is never overwritten. Default false. | false |
aiModel | No | string | Model and, by prefix, provider: claude-haiku-4-5, claude-sonnet-5, claude-opus-4-8, gpt-4o-mini, gpt-4o, gpt-4.1-mini, gpt-4.1, gemini-2.0-flash-lite, gemini-2.0-flash, gemini-2.5-flash, gemini-2.5-pro, grok-3-mini, grok-3, deepseek-chat, sonar, sonar-pro, mistral-small-latest, mistral-large-latest. Default "claude-haiku-4-5". | "gpt-4o-mini" |
aiApiKey | No | string | Your LLM provider API key, stored encrypted. Used only when translateToEnglish is on. Falls back to the provider's environment variable. | "sk-…" |
liAt | No | string | Your own li_at cookie, stored encrypted and sent only to LinkedIn. Leave empty to run logged out. Supplying it lifts the guest comment ceiling and unlocks nested replies, per-reaction-type counts, the edited/pinned markers and the commenter headline. | "AQEDAT…" |
sortOrder | No | string | "REVERSE_CHRONOLOGICAL" (most recent first) or "RELEVANCE". Default "REVERSE_CHRONOLOGICAL". | "REVERSE_CHRONOLOGICAL" |
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: setting undetectedLanguageHandling to exclude on its own does nothing. It only takes effect when includeLanguages or excludeLanguages has at least one entry — with both lists empty, no language filter runs at all and unlabelled comments are kept regardless. Likewise, aiModel and aiApiKey are ignored entirely unless translateToEnglish is true, and sortOrder is only sent to LinkedIn on the cookie path; logged out, LinkedIn ignores comment sort parameters and always serves the same top slice.
Further behaviours worth knowing, all read 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. - A run with no configuration at all still gives you the language layer. Every language parameter has a working default, so the two pre-filled example posts return fully labelled rows — script, language, confidence, guard reasons and the post's language mix — with no key, no cookie and no filter.
commentsPerPostis coerced, not rejected. A missing, non-integer or non-positive value falls back to100; anything above 500 is clamped to 500.minCharsForLanguagemust be a whole number. A non-integer value —12.0included — silently falls back to12.minLanguageConfidenceaccepts any number and falls back to0.65when it is not one.- Language codes are normalised.
EN,en-GBandpt_BRreduce toen,enandpt; duplicates are collapsed and order is preserved. A comma- or space-separated string works as well as a list. - Two undocumented input aliases exist, for compatibility with the base Actor's input shape.
postUrlsis accepted in place ofstartUrls, andresultLimitPerPostis accepted in place ofcommentsPerPost— and because Apify pre-fills schema defaults,resultLimitPerPostwins overcommentsPerPostwhen both are present. Pass only one. startUrlsaccepts objects as well as strings.{"url": "…"}entries are normalised, and a single bare string works in place of a list.- A custom proxy block is only partly honoured. The Actor reads
useApifyProxyfromproxyConfiguration; setting it starts the ladder at the datacenter rung instead of direct. On the cookie path, requests always go through residential or datacenter regardless.
Example input
{"startUrls": ["https://www.linkedin.com/feed/update/urn:li:activity:7461916737781964800/","urn:li:activity:7476267243664887809","7289521182721093633"],"commentsPerPost": 100,"includeLanguages": ["en", "pt", "es"],"excludeLanguages": [],"undetectedLanguageHandling": "keep","minLanguageConfidence": 0.65,"minCharsForLanguage": 12,"translateToEnglish": true,"aiModel": "claude-haiku-4-5","aiApiKey": "<YOUR_PROVIDER_API_KEY>","liAt": "","sortOrder": "REVERSE_CHRONOLOGICAL","proxyConfiguration": { "useApifyProxy": false }}
⬆️ Output
One row per comment, forty-two top-level keys, the same shape on every run and on both engines. Unavailable values arrive as null rather than a missing key — including textEnglish, which is present and null when translation is off. 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.
Charging, stated plainly: one row_result is charged per comment row written. Accounting rows are pushed without a charged event and are never billed. The marker is isError — filter clean results on isError being false, or equivalently on errorReason being null. Both keys are in the default dataset table view, so the split is visible in the Console as well as in code.
Four things produce an uncharged accounting row: a post that could not be read (http_999, login_required, no_comment_container, empty_body, fetch_failed, network_error:<type>, http_<status>), a post that has no comments (no_comments), an unparseable input line (invalid_input), and a comment LinkedIn returned with neither text nor an author (empty_comment_row). All comment fields are null on those rows.
The language filter costs you nothing you would otherwise have been charged for. Comments excluded by includeLanguages, excludeLanguages or undetectedLanguageHandling are dropped before anything is pushed, so they are never written and never charged — and they are dropped before the translation step, so an excluded comment is never sent to your LLM provider either. What the filter cannot save is the fetch: one page request is made per post whether you keep ten comments or one, since LinkedIn serves the whole guest slice in a single response. The exclusions themselves are summarised in one uncharged accounting row per post with errorReason of language_filtered, carrying excludedCommentCount, an excludedByReason map and the post's full language mix — so a filtered-out comment is accounted for rather than silently missing.
Three limits to plan around, stated rather than glossed:
- Replies are not language-analysed. On the cookie engine,
repliesis populated up to two levels, but the language and script fields apply to the top-level comment only. Reply text is not classified, not filtered and not translated, and replies are nested inside the parent row rather than charged as rows of their own. - Deduplication is per post, not per run. The cookie engine drops repeated comment IDs across overlapping pages within one post. The logged-out engine reads a single page, so it has nothing to deduplicate. Neither engine deduplicates across posts, across runs, or across a
startUrlslist containing the same post twice. commentsAvailablecounts what was read, not what you kept. On a filtered run the number of comment rows for a post is lower than itscommentsAvailable; the difference is in thelanguage_filteredaccounting row.
Example output
A logged-out comment row, sourceEngine of public_page, with translation on:
{"comment_id": "7461916812345678901","text": "Excelente iniciativa. Aqui no Brasil a equipe de suporte já está usando isso todos os dias e a diferença no tempo de resposta é enorme.","textEnglish": "Excellent initiative. Here in Brazil the support team is already using this every day and the difference in response time is enormous.","translationStatus": "translated","posted_at": {"timestamp": 1753344761000,"date": "2026-07-24 08:12:41","relative": "1d"},"commentCreatedAt": "2026-07-24T08:12:41Z","is_edited": null,"is_pinned": null,"comment_url": "https://www.linkedin.com/feed/update/urn:li:activity:7461916737781964800/?commentUrn=urn:li:comment:(activity:7461916737781964800,7461916812345678901)","author": {"name": "Rafael Moreira","headline": null,"profile_url": "https://br.linkedin.com/in/rafael-moreira-cx","profile_picture": "https://media.licdn.com/dms/image/v2/D4D03AQH8k2vQ/profile-displayphoto-shrink_400_400/0/1741023881204"},"authorName": "Rafael Moreira","authorProfileUrl": "https://br.linkedin.com/in/rafael-moreira-cx","stats": {"total_reactions": 24,"reactions": {},"comments": null},"reactionCount": 24,"replies": [],"commentScript": "latin","scriptLetterCount": 108,"isNonLatinScript": false,"commentLanguage": "pt","commentLanguageName": "Portuguese","languageConfidence": 0.9981,"languageDetector": "py3langid","languageGuardReason": null,"languageBreakdown": { "en": 4, "pt": 3, "es": 1 },"scriptBreakdown": { "latin": 8, "none": 1 },"dominantLanguage": "en","dominantLanguageBasis": "labelled_comments","languagesDetected": 3,"languageLabelledCount": 8,"languageUndeterminedCount": 1,"nonLatinScriptCount": 0,"commentSampleSize": 9,"post_input": "7461916737781964800","postUrl": "https://www.linkedin.com/feed/update/urn:li:activity:7461916737781964800/","commentsAvailable": 9,"commentsTotal": 412,"totalComments": 412,"sourceEngine": "public_page","scrapedAt": "2026-07-25T09:14:02Z","isError": false,"errorReason": null,"errorMessage": null}
A comment the guards refused to label — the fields that stay exact are the ones that survive on any length of text:
{"comment_id": "7461916899887766554","text": "Congrats! 🎉","textEnglish": null,"translationStatus": "failed","commentCreatedAt": "2026-07-24T09:41:03Z","commentScript": "latin","scriptLetterCount": 8,"isNonLatinScript": false,"commentLanguage": null,"commentLanguageName": null,"languageConfidence": null,"languageDetector": "py3langid","languageGuardReason": "too_short","authorName": "Elena Vasquez","authorProfileUrl": "https://www.linkedin.com/in/elena-vasquez-ops","reactionCount": 3,"commentsAvailable": 9,"commentsTotal": 412,"sourceEngine": "public_page","scrapedAt": "2026-07-25T09:14:02Z","isError": false,"errorReason": null,"errorMessage": null}
And the uncharged accounting row that reports what the language filter removed from a post:
{"postUrl": "https://www.linkedin.com/feed/update/urn:li:activity:7461916737781964800/","post_input": "7461916737781964800","comment_id": null,"text": null,"textEnglish": null,"translationStatus": null,"commentCreatedAt": null,"posted_at": { "timestamp": null, "date": null, "relative": null },"author": { "name": null, "headline": null, "profile_url": null, "profile_picture": null },"stats": { "total_reactions": null, "reactions": {}, "comments": null },"replies": [],"excludedCommentCount": 4,"excludedByReason": { "language_not_included:en": 4 },"languageBreakdown": { "en": 4, "pt": 3, "es": 1 },"scriptBreakdown": { "latin": 8, "none": 1 },"dominantLanguage": "en","dominantLanguageBasis": "labelled_comments","commentSampleSize": 9,"commentsAvailable": 9,"commentsTotal": 412,"sourceEngine": "public_page","scrapedAt": "2026-07-25T09:14:02Z","isError": true,"errorReason": "language_filtered","errorMessage": "4 of 9 comment(s) on this post were excluded by the language filter and were not charged."}
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. Exact timestamps need no extra request: a LinkedIn comment ID is a snowflake, so its top bits decode directly to epoch milliseconds.
Each comment is then stripped of URLs, mentions, hashtags and emoji, and what remains is measured for writing script and classified by an offline n-gram detector that runs inside the container with no network call and no key. Only comments clearing both the letter and confidence floors get a language label. Include and exclude lists are applied next, then optional translation on the kept comments only.
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. Supplying an li_at cookie switches the fetch to LinkedIn's authenticated Voyager GraphQL endpoint for full depth. 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
Post Comments Engagements Scraper Linkedin By Comment Language is an Apify Actor, so it works with anything that can call the Apify API or consume a dataset.
Calling the Actor from Python
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_TOKEN>")run = client.actor("<YOUR_USERNAME>/post-comments-engagements-scraper-linkedin-by-comment-language").call(run_input={"startUrls": ["https://www.linkedin.com/feed/update/urn:li:activity:7461916737781964800/"],"commentsPerPost": 100,"includeLanguages": ["pt", "es"],"minLanguageConfidence": 0.8,})comments = [c for c in client.dataset(run["defaultDatasetId"]).iterate_items()if not c.get("isError")]for c in comments:print(c["commentLanguage"], c["languageConfidence"], c["authorName"], 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; a Switch node on commentLanguage then routes each comment to the right regional channel, with an IF node on isError keeping accounting rows out of the flow. In Make, the Apify module supports run-and-wait, so a weekly multilingual comment sweep can feed a Google Sheets, Airtable or Slack step directly. In LangChain, build documents from text with commentLanguage, commentScript and postUrl as metadata — every value is already a typed primitive or a plain object, 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.
authorName, author.name, authorProfileUrl, author.profile_url, author.profile_picture and author.headline identify a named individual, and text — plus textEnglish when translation is on — 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 five things:
- Lawful basis. Decide and document yours 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.
- Minimisation. If you only need the language split and engagement volume, keep
commentLanguage,commentScript,reactionCountand the per-post mix, and discardtext,authorNameandauthorProfileUrlfirst.includeLanguagesalso narrows what enters the dataset in the first place, which is a stronger position than collecting everything and filtering later. - Transparency and retention. Set a retention period and delete on schedule. If you are a controller in scope of the GDPR's transparency rules, Article 14 obliges you to inform people whose data you collected without contacting them first.
- Subject requests. Keep
comment_idso a specific person's records can be located and removed on an access, objection or erasure request. - Translation adds a processor to your chain. With
translateToEnglishon, comment text — personal data written by an identifiable person — is sent to Anthropic, OpenAI, Google, xAI, DeepSeek, Perplexity or Mistral depending onaiModel. That provider processes it on your behalf under your own account, so it belongs in your record of processing activities, needs to be covered by your controller-processor terms with them, and should appear where your privacy notice lists recipients or international transfers.
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, resale, or sending comment text to a model provider at scale.
❓ Frequently asked questions
What LinkedIn comment fields does this Actor return?
The five most used are text, commentLanguage, authorName, commentCreatedAt and reactionCount. Every row also carries commentScript, languageConfidence, languageGuardReason, the post's full languageBreakdown and dominantLanguage, and the coverage pair commentsAvailable / commentsTotal. Forty-two top-level keys in total — see the data fields section above.
Does it require a LinkedIn account or cookie?
No. The default path reads LinkedIn's public post page anonymously — no login, no cookie, no session, and no cookie of any kind is stored inside the Actor. 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 is the language of a comment detected — is that the AI?
No, and this is the most common assumption. Language detection runs offline inside the Actor using the py3langid statistical detector, with no API key, no network call and no cost. The AI option is only translation. Before detection runs, URLs, @mentions, #hashtags and emoji are stripped so they cannot skew the result, and the detector's candidate set is restricted to the 57 ISO 639-1 codes this Actor is willing to name — without that restriction, short English comments come back as Classical Latin at full confidence. commentScript is not a detector output at all: it is counted directly from the comment's Unicode code points.
How accurate is the language detection?
No accuracy figure is claimed for it, and you should not assume one. commentLanguage is a statistical inference and can be wrong, especially on short text. That is why two guards sit in front of it: a comment is labelled only when it has at least minCharsForLanguage letters (default 12) and the detector's confidence is at least minLanguageConfidence (default 0.65). Below either floor, commentLanguage is null with a languageGuardReason of too_short or low_confidence rather than a guess. commentScript is a different kind of claim — it is measured from Unicode code points, so it is deterministic and is populated on every row, including emoji-only comments, which return none. Raise minLanguageConfidence to 0.9 for fewer but safer labels; lowering it to 0.3 will label far more comments and mislabel some of them.
What happens if a post has no comments in the language I selected?
You get an uncharged accounting row instead of a silent gap. If every comment on a post is removed by includeLanguages, excludeLanguages or undetectedLanguageHandling, that post contributes no comment rows and no charges — but it does contribute one row with isError: true, errorReason: "language_filtered", an excludedCommentCount, an excludedByReason map naming exactly which rule dropped what, and the post's full languageBreakdown. So you can always tell "this post had no Portuguese" apart from "this post could not be read". A post that has no comments at all produces no_comments instead, and a post LinkedIn would not serve produces login_required, no_comment_container or http_999.
How many comments can I extract per post?
commentsPerPost accepts 1 to 500 and defaults to 100, 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 commentsPerPost only changes the outcome once an li_at cookie is supplied, and 500 is a hard ceiling enforced in code regardless of what you pass.
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. commentsPerPost applies per post, so your budget is post count multiplied by that limit, minus whatever the language filter removes.
Does the English translation cost extra, and what happens if I forget the key?
Yes, it costs extra, and no, a missing key does not fail the run. Translation calls go to your chosen provider on your own account and are billed by them, separately from Apify. If translateToEnglish is on and neither aiApiKey nor the provider's environment variable is set, the Actor logs a warning and carries on: comments are still scraped, still written and still charged, with textEnglish as null and translationStatus reading no_api_key on every row. Because that reason is written into the dataset rather than only into the log, a run with empty translation columns tells you why in the data itself. Only comments that survive the language filter are ever sent to the model, so your token spend follows what you keep, not what LinkedIn served.
How does this Actor compare to other LinkedIn post comment scrapers?
Checked on the Apify Store on 25 July 2026. apimaestro/linkedin-post-comments-replies-engagements-scraper-no-cookies is the most used of the three; its listing advertises cookie-free access, documents comments and nested replies with per-type reaction counts, sort options, page-number pagination described as up to 100 items per page, and an output shape carrying comment_id, text, posted_at, is_edited, is_pinned, author, stats and post_input, plus comment_type and parent_comment_id on replies. api-empire/post-comments-engagements-scraper-linkedin and simpleapi/post-comments-engagements-scraper-linkedin publish the same listing text as each other: batch input of up to 500 posts per run, an optional li_at cookie, a documented direct → datacenter → residential proxy fallback, and a dataset shape of one item per post with a nested comments array. None of the three listings mentions comment language, script, confidence, uncharged accounting rows or translation — where a listing is silent, that is not documented rather than not supported.
Observable differences here: one row per comment rather than one item per post; a language and script verdict on every row with explicit guard reasons instead of a bare label; include and exclude language lists that also keep the excluded comments off the bill; a per-post coverage pair, commentsAvailable versus commentsTotal, on every row; uncharged accounting rows carrying a stated errorReason; and optional translation on your own model key across seven providers.
Does it work with Claude, ChatGPT and other AI agent tools, and is the output LLM-ready?
Yes to both. 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, normalized JSON with consistent field names across runs and both engines — no HTML parsing, no selectors, no relative-date strings to resolve. That is separate from translateToEnglish, which is about the Actor calling a model, not a model calling the Actor. There is no separate signup and no separate API key for the run itself: the Apify token you already have is the only credential.
What happens when LinkedIn changes its layout or anti-bot system?
The Actor 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 validates LinkedIn's rotating GraphQL query ID against the live API before use, falling back to scanning LinkedIn's script bundles at runtime rather than trusting a hardcoded value. 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. The language layer is unaffected by LinkedIn entirely, since it runs on text the Actor already has.
Which 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 commentLanguage, commentLanguageName and commentScript as filter metadata for language-aware retrieval and postUrl as the citation anchor. Add textEnglish as a parallel English document when translation is on, keeping it flagged as model output rather than source text. For training data and analytics: commentCreatedAt, posted_at.timestamp, reactionCount, languageConfidence, scriptLetterCount, 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 normalisation pass is needed before indexing.
🔗 Related scrapers
| Scraper Name | What it extracts |
|---|---|
| LinkedIn Post Comments Scraper | The same comment rows without the language layer, plus a date-window filter and a replies toggle |
| LinkedIn Post Scraper | Post body, author and post-level engagement counts |
| 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, a language the detector keeps getting wrong, 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, the post URL and the comment text are the fastest to reproduce and fix.