
A checked row is a source row you accept after you read its status and its keys. Keep every comment that has no match. A left join keeps each starting row even when no match exists. Count comments at the comment row. Count posts at the post row. Finish those counts before you combine totals.
An Actor is a hosted data collection program. A Task is a saved Actor input example. A dataset is a collection of output rows. A join combines rows with matching values. The Actors collect source rows. You make the joins and the tables. No Actor returns a joined dataset.
The main restriction is compatibility. A key is a value used to match rows. Public sources must expose compatible keys. A missing key stays unresolved. A conflicting key stays unresolved. Unresolved means the row stays and gets no match. A public count is not a full list of comments or people.
Use one known public post URL. A URL is the web address of a page. Collect public top-level comments and their parent keys. A top-level comment is a comment on the post, not a reply. Collect a snapshot of that same post. A snapshot records public data at one observation time. If a person author still needs profile evidence, collect one checked public person profile.
Null means a missing value, not zero. Do not match null. Do not match a blank cell. Do not match a name, a photo, or a headline.
Choose the row grain first
Row grain means what one row represents. Choose the grain before you collect rows.
The evidence grain is one selected comment observation. One comment observation is one saved top-level comment from one snapshot. The count grain is one selected post. A comment count and a post count are different measures. Write both meanings in the file before you add them together.
Write the known source URLs. Write the report period. The report cutoff is the last observation time you include. A row after the cutoff stays in the original dataset. It does not enter this report.
Use this method only for known public post URLs, their public top-level comments, and checked person profiles. Do not use it for account search, every post from one account, reaction lists, replies, message sending, job labels, or text scores.
Terms
A URN is a typed text identifier. In `urn:li:activity:1001`, the URN type is `activity`. The type is part of the key. `ugcPost` is a different post type. `member` is a person-account type in these Actors. `organization` is not a person-account type.
The LinkedIn URN guide explains URN types for the official LinkedIn API. That API uses a `person` URN. The Actor profile key uses `member`. Those two schemas are not the same. Do not copy an identifier from one LinkedIn API into the other and call it equal.
JSON is a text data format. You will use JSON, or text columns, so long identifiers do not lose digits.
A boolean field is true or false. Default means the value used when you leave the field unchanged. The input schema is the published list of fields an Actor accepts. A parent key is the post key stored on a comment row. Fallback means you try the next key only when the earlier key is missing. Canonical means the one source URL you have confirmed, not a second form of that URL.
Compatible keys have the same URN type and the same value. A conflict means one key points at two targets, or two keys point at two different targets. Different URN types alone are not a conflict when the same-type keys name one post.
What the three Actors return
Three public Actors supply the source rows. You still build the join.
The LinkedIn Post Scraper is build 1.0.60. The LinkedIn Comments Scraper is build 1.0.47. The LinkedIn Profile Scraper is build 1.0.28. Read each Actor page before a run. A later build can change a field.
Each Actor reads public pages. None of them uses your LinkedIn login. None of them returns every post from an account.
| Actor | Input you must supply | What one output row is |
|---|---|---|
| Profile | `startUrls`, a list of strings, at least one public `/in/` URL. The schema states no maximum. | One current attempt for each unique input string. `status` is `ok` or `failed`. A failed attempt stays as a row. |
| Post | `startUrls`, a list of strings, at least one public `/posts/` URL. The schema states no maximum. The input is the URL, not a post ID. | One current post attempt for each unique input string. `status` is `ok`, `failed`, or `invalid_url`. Two URL forms can make two attempts. |
| Comments | `startUrls`, 1 to 100 public `/posts/` URLs or typed `/feed/update/` URLs. | One saved public top-level comment. A failed input does not create a fake comment row. |
The comments input also has `maxComments`, `includeAuthorDetails`, and `collectAllAvailable`. `maxComments` is a whole number from 1 to 1000. Its default is 100. `maxComments` is the maximum saved comments per post/input. It is not a cap across a multi-input run. `includeAuthorDetails` is a boolean. Its default is false. `collectAllAvailable` is a boolean. Its default is false. `collectAllAvailable` removes only the numeric save cap. It does not remove a public restriction. It does not collect replies.
Comment counts and input status are in `OUTPUT`. `OUTPUT` is the run summary. It is not a comment row. Inside it, `inputResults` carries `status`, `statusReason`, `pageCommentCount`, `publicRowsOnPage`, `savedRows`, `isPublicPageComplete`, `isComplete`, and `stopReason`.
`pageCommentCount` is the count reported for the page. `publicRowsOnPage` is the number of public rows seen on that page. `savedRows` is the number stored. Those three numbers can differ. `isPublicPageComplete` says whether the Actor marked the public page complete. `isComplete` says whether the saved collection is complete. `stopReason` says why saving stopped. A partial result can come from the save cap, a public preview, or a billing limit. An unknown count stays null. Do not write zero in its place.
The comments Actor can accept a typed feed-update URL. The post Actor requires a `/posts/` URL. If you know only the feed URL, keep the comment parent keys. Get a verified matching `/posts/` URL before you collect the post snapshot. Do not invent a `/posts/` slug.
Use these Tasks, then change the sample input. An unchanged sample cannot be joined. The post Task sample is a different post from the comments Task sample.
| Task | Use | Change before the first run |
|---|---|---|
| LinkedIn Post and Comment IDs for Safe Data Joins | Public top-level comments and parent keys. | Replace the sample post. Change `maxComments` from 100 to 3. Keep `collectAllAvailable` false. Author details stay off. |
| LinkedIn Comment Authors with Public Profile Details | Optional alternative when you need author keys or extra public author facts. | Replace the sample post. Use three comments. Keep unlimited off. Set `includeAuthorDetails` true. |
| LinkedIn Post IDs and Author Keys for Data Joins | The same source post snapshot. | Replace the sample with the same known `/posts/` URL. |
| LinkedIn Profile IDs and URNs for Data Joins | One checked public person profile for each unique supported comment author that still needs profile evidence. | Replace the sample with a collected `/in/` author link. Remove repeated URLs. Exclude organization URLs. |
Do not append the two comments Tasks as two comment sets. Choose one comments Task for that post. The details Task can still return a comment with no author ID. A useful detail request can cause a separate billing event. The request does not guarantee an ID. It does not guarantee a charge.
Default author details are off. Stable comment author keys are often null in that default output. Public identifiers and public URLs can change later. A profile snapshot shows the profile at its own time. It does not prove a job at the earlier comment time. This method does not assign job roles.
The run documentation describes run caps and timeout. Read it before a large run. Do not put access tokens in the sheet.
How to match a comment to a post
Match in the stated order. Keep the same-type rules. The matching key names one post. Check all available compatible keys for conflicts first. Do not skip a contradictory lower key. Only after that check, choose the first supported matching basis in the stated order. Record that basis. The basis column is reader-made. The Actors do not return it.
- Equal typed `postUrn`.
- Equal `activityId`, only inside the activity type.
- Equal `ugcPostId`, only inside the ugcPost type.
- A checked canonical source URL.
The comment fields are `postUrn`, `activityId`, `ugcPostId`, and `postUrl`. The post fields are `postUrn`, `activityId`, `ugcPostId`, and `url`. Any of them can be null.
Use a typed URN before a bare ID. An activity value and a ugcPost value can both exist on one post and still differ. That is normal. Do not compare an activity value with a ugcPost value. Equal digits across types are not evidence.
Post A in the fictional example has activity `1001` and ugcPost `2001`. Those are two keys for one post, not a conflict. A conflict exists when one key points at two posts, or when the available keys point at two different posts. The fallback order does not let a weaker key override that conflict. Leave the comment unresolved.
Use the URL only when the typed keys are missing. Both URLs must be the same canonical source URL that you checked. If you cannot check that, leave the row unresolved. A URL basis is weaker than a typed key. Write the basis and both observation times.
Reduce duplicate post snapshots to one selected lookup row before this join. If the lookup still has two successful rows for one post, the join will copy comments. Fix the lookup. Do not delete the copied comment rows and leave the lookup unchanged.
A missing post snapshot is a missing match. It is not an empty post. A successful post with no saved comments is a different result. A failed post attempt is a different result. An input you did not run is a different result.
How to match a person profile
Match a profile to a comment author only after the author means a person account. The comment field is `authorType`. It can be `person`, `organization`, or null. The post Actor can write the post author type as `Person`. Keep each source value exactly as it arrived. For this meaning check, `person` and `Person` both mean a person account. Do not require the same capital letters. Do not edit the stored text so the letters match.
An organization author stays outside the person profile join. An organization URL cannot become an `/in/` profile row. `urn:li:member:11` and `urn:li:organization:11` are different keys. Do not strip the URN type to force a match.
After the person check, match in this order:
- Equal member ID: profile `profileId` to comment `authorId`.
- Equal member URN: profile `profileUrn` to comment `authorUrn`.
- A weaker fallback: `publicIdentifier` to `authorPublicIdentifier`, or the canonical `/in/` URL from profile `url` and comment `authorUrl`.
Do not match null. Do not match a blank cell. If the member ID points at profile X and the URL points at profile Y, the row is a conflict. Do not use the weaker URL to pick a winner.
A URL or public identifier can change between snapshots. If you use that weaker basis, write both `observedAt` values and the basis. A null URL stays unresolved. Names, photos, and headlines are not keys. Do not search by name to fill a missing link.
The same profile fields can be compared with the post author fields `authorId`, `authorUrn`, `authorPublicIdentifier`, `authorUrl`, and `authorType`. That comparison checks the post author role only. It is optional. It is not the comment author. Never replace a comment author with the post author. Never join every post by one account to every comment by that account.
one post snapshot
-> many comment observations
one person profile
-> many comments by that person
optional post-author match
-> the post row only
-> not the comment authorOne person can write many comments. One post can have many comments. Those two arrows are separate.
Comment identity uses `commentId`, `commentUrl`, the parent key, and `observedAt`. An ID or a link can be null. Keep that observation. Do not invent a stable ID. Do not treat equal text as the same comment. Do not treat a sheet row number as an ID. Across runs, select one observation only when the parent key and `commentId` are both stable and equal. Rows with no stable `commentId` stay separate observations.
Profile and post status use `status`, `statusReason`, and `observedAt`. Keep a failed profile row in the source file. Do not treat it as a successful profile match.
Select one snapshot before the join
A lookup row is the one snapshot you allow the join to use. Select at most one successful post row for each checked post. Select at most one successful profile row for each checked person. Do this before the join.
Keep the original datasets. Do not blend two snapshots into a new row. Write the selection rule in the file. One clear rule is the latest successful snapshot before the report cutoff.
If one person has two successful profile snapshots, choose one. If both stay in the lookup, each comment by that person is copied once for each snapshot. The extra joined rows are a lookup error. Fix the lookup. Do not delete the copies and call the sheet correct.
Then build a left join from the selected comment observations. First join each comment to one source post. Then join its person author to at most one profile. A comment with no post match stays. A comment with no profile match stays.
The joined evidence table must have the same number of rows as the selected comment observations. More rows mean a lookup key still points at more than one row.
These reader-made fields belong on the evidence row: match basis, match outcome, selected snapshot rule, and the source times you compared. Label them reader-made. Do not present them as Actor fields.
Keep long IDs as text. A spreadsheet can change a long number and drop digits. Export JSON, or set every ID column to text before import. JSON is defined above. The dataset documentation gives the export path: select the dataset, choose Export, choose the format, and choose Download. Save `OUTPUT` separately. A dataset export does not contain `OUTPUT`.
Steps
- Write the grain, the known URLs, the report cutoff, and the meaning of each count. The evidence file will have one row per selected comment observation. The count file will have one row per selected post. A comment total and a displayed post count stay separate until that writing is done.
- Open LinkedIn Post and Comment IDs for Safe Data Joins. Replace the sample URL with one known public post URL. Set `maxComments` to 3. Keep `collectAllAvailable` false. Leave author details off. Start the run. Open `OUTPUT` before you explain a small set or an empty set. Few rows can mean the save cap, a failed input, a public restriction, or a short public comment set. The row count alone does not say which.
- If you need author keys that the default comments rows do not contain, use LinkedIn Comment Authors with Public Profile Details instead of the first comments Task. Replace the sample post. Use three comments. Keep unlimited off. Set `includeAuthorDetails` true. Do not paste both outputs together. Details can still be missing. The extra detail request can be billed separately, and it still may not return an ID.
- Open LinkedIn Post IDs and Author Keys for Data Joins. Replace its sample with the same `/posts/` URL. Do not pass a post ID. Do not pass a feed-update URL. If you have only a feed-update URL, keep the comment parent keys and stop the post collect until you have a verified `/posts/` URL. Do not invent the slug. Keep `ok`, `failed`, and `invalid_url` rows apart. Keep `observedAt`. A missing post snapshot stays a missing match.
- Read the comment author keys. If the comments rows already have every author fact this report needs, skip the profile Actor. If they do not, collect unique public `/in/` URLs with LinkedIn Profile IDs and URNs for Data Joins. Remove repeated URLs. Exclude organization URLs. Do not search by name, photo, or headline. Keep failed profile attempts in the source file. A failed attempt is not a matched person.
- Select the lookup. Keep one successful post snapshot per checked post. Keep one successful profile snapshot per checked person. For repeated comment runs, keep one observation per stable parent key plus `commentId`. Keep rows with no `commentId` as separate observations, even when the text is equal. Keep every original dataset. Write the cutoff rule. Resolve duplicate keys and conflicts in the lookup before you join.
- Left join each selected comment to one post. Use equal typed `postUrn`, then equal activity ID, then equal ugcPost ID, then a checked canonical URL. Do not cross URN types. Then join the person author to at most one profile. Use member ID, then member URN, then a dated public identifier or canonical `/in/` URL. Write the reader-made basis, outcome, and both source times. Do not fill a comment author from the post author. Do not join all posts by one account to all comments by that account.
- Count the evidence rows. The number must equal the selected comment observations. If it is larger, the lookup is still many rows for one key. Fix that lookup and join again. Do not delete repeated evidence rows as the only repair. Separately count unmatched parents, missing person keys, failed profiles, organization authors, and conflicts.
- Group the comment observations by the resolved post key. Put `commentsCount` on one selected post row only. Put that run's `OUTPUT` coverage beside the post. Keep a post that has no saved comments. Keep a successful empty collect, a failed attempt, and an input you did not run as different facts. Count author keys as observed accounts. Do not call them unique people. Do not call them the full set of commenters or viewers.
- Export the evidence table, the unresolved table, the per-post count table, and a source summary. Use the dataset export path above. Save `OUTPUT` beside it. Keep IDs as text. On every result row, show the source keys, the reader-made basis, and the missing evidence. Fill a gap only when you have a supported source URL for that exact gap. Otherwise leave it unknown.
The evidence count is step 8. The post count is step 9. Do not use the post count as a comment count.
What one public check proved
One public check saved three comments from one known post. The source showed four comments and exposed four rows. The cap saved three. Each of the three saved parent keys matched that same source post. One comment author matched one separate person-profile row. All of those checked matches succeeded.
The comments `OUTPUT` for that run had `isPublicPageComplete` true, `isComplete` false, and `stopReason` `max_comments_reached`. Read together, the Actor marked the public page complete, and the save was still incomplete because the save cap was reached.
That check proves only those rows and that summary. It does not prove every later post. It is not a fresh live test of ugcPost matching, organization authors, null keys, or conflicts. Those cases use the field rules above and the fictional counts below.
Fictional posts, comments, and counts
Every value in this section is fictional. C1 through C6 are teaching labels. The short IDs are teaching IDs. They are not collected LinkedIn values. Do not treat any URN below as a real profile, post, or comment.
The report cutoff is after `time-2`. The selected member12 snapshot is the later successful one. Post author type is stored as `Person` on A and B. Comment author type is stored as `person` on C1 through C4. Those meanings match. The stored letters stay different. Post C is stored as an organization author.
| Post | Typed post URN | Activity ID | UGC ID | commentsCount |
|---|---|---|---|---|
| A | `urn:li:activity:1001` | `1001` | `2001` | 10 |
| B | `urn:li:activity:1002` | `1002` | null | 20 |
| C | `urn:li:ugcPost:1001` | null | `1001` | null |
Member11 has one successful selected profile row. Member12 has two successful snapshots. Select one before the join. Member13 has a failed profile attempt. A and B were written by member11. C was written by organization11, not by member11.
| Snapshot | Identity | status | observedAt | Selected for lookup |
|---|---|---|---|---|
| Early | member12 | `ok` | `time-1` | No |
| Later | member12 | `ok` | `time-2` | Yes |
| Comment | Parent evidence | Matched source post and basis | Author evidence | Profile outcome |
|---|---|---|---|---|
| C1 | `urn:li:activity:1001` | A, equal typed URN | `urn:li:member:11` | member11 matched |
| C2 | `urn:li:ugcPost:2001` plus activity ID `1001` | A, activity ID; equal UGC ID confirms | `urn:li:member:12` | member12 matched |
| C3 | `urn:li:activity:1002` | B, equal typed URN | `urn:li:member:12` | member12 matched |
| C4 | `urn:li:activity:1002` | B, equal typed URN | `urn:li:member:13` | unresolved: failed profile |
| C5 | `urn:li:ugcPost:1001` | C, typed URN; not A's activity digits | `urn:li:organization:11` | outside person profile scope |
| C6 | `urn:li:activity:9999` | unresolved: parent absent from the selected posts | null | unresolved: missing author key |
C2 does not match on a typed activity URN, because that parent URN is a ugcPost URN. The activity ID `1001` matches post A. The ugcPost ID `2001` also matches post A. Both same-type keys agree, so this is confirmation, not a conflict. The basis you write is the activity ID.
C5 matches post C because both sides use ugcPost `1001`. Post A also contains the digits `1001`, but those digits are an activity ID. Equal digits do not move C5 onto A.
The evidence table keeps six rows. The match table and the unresolved table are views. They can repeat a comment when one side matches and the other side does not. Do not add those views together and call the sum a comment total.
| Comment | Match side | Target | Basis (reader-made) | Times kept |
|---|---|---|---|---|
| C1 | post | A | equal typed URN | comment time and post A time |
| C1 | profile | member11 | equal member URN after person check | comment time and selected profile time |
| C2 | post | A | activity ID; UGC ID confirms | comment time and post A time |
| C2 | profile | member12 | equal member URN after person check | comment time and `time-2` |
| C3 | post | B | equal typed URN | comment time and post B time |
| C3 | profile | member12 | equal member URN after person check | comment time and `time-2` |
| C4 | post | B | equal typed URN | comment time and post B time |
| C5 | post | C | equal typed ugcPost URN | comment time and post C time |
The match view has 8 lines. C1, C2, and C3 each have two successful sides. C4 and C5 have only the post side. Eight is not the comment total. Six is the comment total.
| Comment | Unresolved part | Reason | Keys kept |
|---|---|---|---|
| C4 | person profile | failed profile attempt | `urn:li:member:13` |
| C5 | person profile | organization is outside person profile scope | `urn:li:organization:11` |
| C6 | source post | parent is not in the selected posts | `urn:li:activity:9999` |
| C6 | person profile | missing author key | null |
C6 has two unresolved parts and one evidence row. Count it once as a comment.
| Post | Collected comment rows | commentsCount, once | Post author role, separate |
|---|---|---|---|
| A | 2 | 10 | member11 |
| B | 2 | 20 | member11 |
| C | 1 | null | organization11 |
| unmatched parent | 1 | no selected post, so no displayed count | none |
Check each total:
- Selected comment observations: 6.
- Evidence rows after the left join: 6.
- Parent-post matches: 5, which are C1, C2, C3, C4, and C5.
- Unmatched parent: 1, which is C6.
- Comment rows matched to a successful person profile: 3, which are C1, C2, and C3.
- Successful person profiles used: 2, which are member11 and member12.
- Unresolved person outcomes: 2, which are the failed profile on C4 and the missing author key on C6.
- Organization author outside person profile scope: 1, which is C5.
- Identified member keys: 3, which are member11, member12, and member13.
- Those 3 member keys are not 2 profiles, and they are not 3 proven people.
- Collected rows by parent: A has 2, B has 2, C has 1, and the unmatched parent has 1.
- 2 plus 2 plus 1 plus 1 equals 6.
`commentsCount` stays on the post row. If you copy it onto every collected comment and then add the copies, A contributes 10 two times and B contributes 20 two times. That is 20 plus 40, which is 60. The correct known displayed sum uses each selected post once: 10 plus 20 equals 30. Post C is null. Do not change null to 0. Do not call 30 complete. The collected observation measure is different: 5 matched parents plus 1 unmatched parent equals 6.
This fictional sheet has no `OUTPUT`. Real coverage still needs `pageCommentCount`, `publicRowsOnPage`, `savedRows`, `isPublicPageComplete`, `isComplete`, `stopReason`, `status`, and `statusReason` from that run. A displayed count of 10 with 2 collected rows is not a coverage rate by itself.
If both member12 snapshots stay in the lookup, C2 is copied and C3 is copied. The evidence table grows from 6 rows to 8 rows. Select the `time-2` snapshot first. The evidence table returns to 6 rows. Deleting the two extra evidence rows, while both snapshots remain in the lookup, does not repair the next join.
Member11 wrote two selected posts, A and B, and one selected comment, C1. If you join those two authored posts directly to the comment member11 wrote, C1 appears twice. Count each branch before you speak about both. The post-author branch has 2 posts. The comment-author branch has 1 comment. Parent matching leaves C1 on post A only. C1 matches member11 because the comment author key is member11, not because member11 wrote post A.
Member12 shows the allowed direction. One selected profile matches two comments. C2 stays on A. C3 stays on B. The shared profile does not move either comment onto the other post.
A successful post snapshot alone does not prove an empty comments collection. Only a checked successful comments input with zero saved rows is an observed empty result. A cap still bounds that result. A source restriction still bounds that result. A failed input stays unknown. An unattempted input stays unknown. An observed empty result is not a null displayed `commentsCount`. The six-row example totals do not change. C6 is an unmatched parent key. It is not a failed post attempt.
Three more fictional rules sit outside the six rows. Do not add them to the totals above.
A person with null member keys can match one checked profile when the current `/in/` URL is exactly the same. Write the weaker URL basis and both observation times. A null URL stays unresolved. If the member ID points at X and the URL points at Y, keep a conflict and do not use the URL. A comment with null `commentId` stays its own observation. Equal text does not repair the missing ID. A sheet row number does not repair it.
The finished file shows what matched, why it matched, and what remains unknown. It does not rank people. It does not judge the success of a post.
Sources & further reading
- LinkedIn Post Scraper
- LinkedIn Post IDs and Author Keys for Data Joins
- LinkedIn Comments Scraper
- LinkedIn Post and Comment IDs for Safe Data Joins
- LinkedIn Comment Authors with Public Profile Details
- LinkedIn Profile Scraper
- LinkedIn Profile IDs and URNs for Data Joins
- LinkedIn URNs and IDs
- Apify dataset documentation
- Run Actor documentation
- Commenter profiles
- profiles from one post
- commenter spreadsheet
By Chronometrica · Updated
Back to top ↑

