How to Scrape LinkedIn Posts (2026)
- LinkedIn shows a member's public posts on their activity feed at
/in/<handle>/recent-activity/all/, but a logged-out fetch returns a sign-in wall, so DIY post scraping needs a real logged-in browser session. - The working DIY route is Selenium + BeautifulSoup driving your own login (the
li_atandJSESSIONIDcookies), scrolling the lazy-loaded feed, and parsing post text plus reaction, comment, and repost counts. It works small and puts your account at risk. - Reactor names, full comment threads, and repost authors each load in a separate panel, so engagement detail is a second pass, not one request.
- To skip the login and the blocks, a scraper API returns post data as JSON from a URL. I used ChocoData's LinkedIn post endpoint and got parsed posts back with no cookies on my side. Tested July 2026.
I tried to scrape LinkedIn posts the quick way first: one requests.get on a member’s activity feed, no login, no browser. LinkedIn handed back a sign-in wall before a single post loaded. That wall is the whole problem with scraping LinkedIn posts, and it is why every working route solves the same three things: the login, the lazy-loaded feed, and the block.
This guide is what I ran in July 2026 to scrape LinkedIn posts, the responses LinkedIn actually returned, and the code for each route: a logged-in Selenium session, the reactions and comments behind each post, and a scraper API that returns post JSON without a login. Every code sample is Python I executed against live LinkedIn targets.
Can you scrape LinkedIn posts?
You can scrape publicly visible LinkedIn posts, but not from the logged-out page, because LinkedIn gates the post feed behind a sign-in wall. A member’s posts render on their activity feed at https://www.linkedin.com/in/<handle>/recent-activity/all/, with a shares-only variant at /recent-activity/shares/. Company posts sit at /company/<name>/posts/. When I fetched an activity feed logged out, LinkedIn returned an HTTP 200 shell dominated by “sign in to continue” prompts, with the post content held back.
There is also no official shortcut. LinkedIn’s developer APIs are gated behind the Partner Program and member OAuth, and none of them return an arbitrary member’s post feed by profile or keyword. So the only DIY surface for posts is the authenticated HTML, which means a real logged-in browser session. Before writing the code, it helps to know exactly which fields that session can pull.
What data can you pull from a LinkedIn post?
A LinkedIn post carries a small set of fields worth scraping: the post text, the author, the publish date, the post URL, any attached media, and the engagement counts for reactions, comments, and reposts. Some of those ride on the post itself and come cheap. Others sit in separate panels and cost extra requests.
- Post text and metadata: the body text, the author name and headline, the post URL, and the publish date. This is the core object and the easiest part to parse.
- Media: image and video URLs, which LinkedIn serves from the
media.licdn.comCDN, plus any link-preview card attached to the post. - Engagement counts: the reaction total, the comment count, and the repost (share) count, all shown in the social-counts bar under the post.
- Reactors and commenters: the actual people who reacted and the full comment threads, the highest-value and hardest-to-reach data, each loaded in its own panel.
- Reposts: the accounts that re-shared the post, along with any commentary they added on top.
The first three come back in one pass over the feed. The last two need a click into a modal or an expanded thread per post, which is the split that decides how heavy your scrape gets. The Python route handles the cheap fields first.
How do you scrape LinkedIn posts with Python?
You scrape LinkedIn posts with Python by driving a logged-in Chrome session with Selenium, loading the member’s activity feed, scrolling to trigger the lazy-loaded posts, then parsing each card with BeautifulSoup. A plain requests call will not work here: logged out it returns the sign-in wall, and with no browser fingerprint it draws LinkedIn’s HTTP 999 denial code. A genuine browser session is what clears both.
Install the stack first. Selenium 4.6 and later ship Selenium Manager, so you no longer need a separate chromedriver download:
pip install selenium beautifulsoup4 pandas
The flow is: start Chrome, log into your own account, open the activity feed, scroll to load posts, then parse. The most-used open-source wrapper for the login and selector work is linkedin_scraper by joeyism, but the raw pattern is short enough to run directly:
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from bs4 import BeautifulSoup
driver = webdriver.Chrome() # Selenium Manager fetches chromedriver
driver.get("https://www.linkedin.com/login")
# Log into your own account. A real browser session is what clears the authwall.
driver.find_element(By.ID, "username").send_keys("you@example.com")
driver.find_element(By.ID, "password").send_keys("your_password")
driver.find_element(By.XPATH, "//button[@type='submit']").click()
time.sleep(3)
# A member's public posts live on their activity feed
driver.get("https://www.linkedin.com/in/williamhgates/recent-activity/all/")
# Posts load lazily, so scroll to pull more of them into the DOM
for _ in range(5):
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
time.sleep(2)
soup = BeautifulSoup(driver.page_source, "html.parser")
cards = soup.select("div.feed-shared-update-v2")
for card in cards:
text = card.select_one("span.break-words")
counts = card.select_one("span.social-details-social-counts__reactions-count")
print(text.get_text(strip=True)[:120] if text else "", "|",
counts.get_text(strip=True) if counts else "0")
driver.quit()
Two things break this in practice. The first is timing: parse before the feed finishes rendering and the cards come back empty, so the scroll-and-sleep loop is load-bearing, not decoration. The second is selectors. LinkedIn obfuscates and rotates its class names, so div.feed-shared-update-v2 and span.break-words survive longer than deep nested paths but still rot, and a broken selector returns blanks with no error. Check your field hit rate on every run.
If you would rather not type a password into a script, the common alternative is to export your li_at and JSESSIONID cookies from Chrome DevTools and load them into the driver, which reuses an existing session instead of a fresh login. Either way you are automating your own logged-in account against a site whose terms forbid it, so this scales only to small, careful jobs. For the broader Python build across profiles and jobs too, I go deeper in how to scrape LinkedIn with Python. Reactions, comments, and reposts are the next problem, and they do not live on the card you just parsed.
How do you scrape reactions, comments, and reposts from a post?
You scrape the reactions, comments, and reposts on a LinkedIn post by opening each one’s own panel, because LinkedIn loads reactor names, comment threads, and repost authors separately from the post body. The social-counts bar gives you the totals in the feed pass. The people behind those totals need a click and a second scroll loop each.
- Reactions: clicking the reaction count opens a modal that lists each reactor and their reaction type (like, celebrate, support, and so on). The modal paginates, so you scroll inside it to load the full list rather than the page.
- Comments: comments lazy-load beneath the post and hide older ones behind a “load more comments” control. Each comment carries its own author, text, timestamp, and reaction count, so a full thread is its own nested scrape.
- Reposts: the repost count opens a list of the accounts that re-shared the post, including any commentary they added, which is useful for tracing how a post spread.
The practical warning: reactor and commenter extraction multiplies the number of interactions per post, and interaction volume is exactly what LinkedIn’s behavioral layer watches. Pulling every reactor on a viral post can mean hundreds of scroll and click actions, which trips a restriction far faster than reading plain post text. That risk is the reason to treat engagement detail as a deliberate, throttled pass, and it leads straight into how to avoid the block.
How do you avoid getting your LinkedIn account restricted?
You avoid getting your LinkedIn account restricted while scraping posts by changing the IP reputation, the browser fingerprint, and the request pace together, since LinkedIn scores all three in real time. Fixing one alone does not clear the others. These are the levers that move the outcome, in rough order of impact:
- Use residential or mobile IPs. Datacenter ranges are pre-scored as low quality and draw the 999 fast. Residential addresses present as ordinary home connections, which is the single biggest factor in getting real post content back.
- Drive a real browser. A headless Chrome or Playwright session produces a browser-shaped TLS handshake and header order, so it survives the fingerprint check that kills raw HTTP requests regardless of the User-Agent you set.
- Slow down and randomize. Practitioners widely keep a logged-in account under roughly 100 to 200 feed or profile views per day with 3 to 5 second gaps and randomized timing. Sequential, machine-paced access is the clearest bot signal.
- Keep one session warm. Reuse a single authenticated session with stable cookies instead of logging in repeatedly, because fresh logins from new fingerprints look hostile.
- Respect the gated paths. LinkedIn’s robots.txt opens with “The use of robots or other automated means to access LinkedIn without the express permission of LinkedIn is strictly prohibited” and disallows the profile, activity, and search paths that post scraping touches.
The honest tradeoff is maintenance. Doing all of this yourself means buying a residential proxy pool, rotating headless browsers, refreshing sessions, and repairing selectors every time the feed markup shifts. That becomes a standing project once you pass a few hundred posts, which is why many teams move the blocking problem off their own machines.
How do you scrape LinkedIn posts at scale without a login?
You scrape LinkedIn posts at scale without a login by sending the post or profile URL to a scraper API that returns parsed post JSON, with the residential proxies, the session handling, and the retries done server-side. You send one authenticated request and get structured post data back, with no 999 to debug and no account of yours on the line. In my testing, ChocoData returned a feed of posts as clean JSON from a single call.
The request is a plain GET with the LinkedIn URL and your API key as query parameters:
curl "https://chocodata.com/api/v1/linkedin/post?url=https://www.linkedin.com/in/williamhgates&api_key=$CHOCO_API_KEY"
The Python version is the same shape, and it returns the post fields you would otherwise scroll and parse out of the rendered feed:
import requests, os
resp = requests.get(
"https://chocodata.com/api/v1/linkedin/post",
params={
"url": "https://www.linkedin.com/in/williamhgates",
"api_key": os.environ["CHOCO_API_KEY"],
},
timeout=60,
)
data = resp.json()
for post in data["posts"]:
print(post["text"][:80], post["date"], post["url"])
print("reactions:", post.get("reactions"), "comments:", post.get("comments"))
Each post object came back with the text, author, publish date, post URL, and media URLs from the licdn.com CDN intact, with the reaction and comment counts on the object. Responses were quick, a median around 2.6 seconds end to end including proxy routing, anti-bot handling, retries, and parsing. Because the fields arrive structured, the export is a one-liner:
import pandas as pd
pd.json_normalize(data["posts"]).to_csv("linkedin_posts.csv", index=False)
The free tier covers 1,000 requests with no card, and Pro works out to about $0.60 per 1,000 posts. The object carries engagement counts, not every individual reactor, so reactor-level and full comment detail still need a separate call, the same panel-by-panel work the DIY route does. The same URL-in shape swaps to profiles, companies, jobs, and search by changing the resource path, so one request pattern covers what would otherwise be several awkward Selenium scripts. If you want the managed and DIY tools compared head to head, I keep a ranked list in the best LinkedIn scrapers of 2026.
Which method should you choose?
The right method depends on the target, the volume, and how much risk you want to put on a personal account. Here is the summary I give people who ask.
| If you need… | Use | Why |
|---|---|---|
| A few post feeds, can risk a login | Selenium + your own account | Loads the authenticated activity feed, but your account carries the risk |
| Reactors, comments, reposts in depth | Selenium panels, or a managed API | Each engagement list is a separate lazy-loaded pass |
| Posts at volume, no account risk | Scraper API (ChocoData) | Proxies, session, retries, and parsing handled server-side, JSON out |
| Lowest block risk | Residential IP + slow pace, or a scraper API | A clean IP and human pacing avoid the authwall and the 999 |
For a one-off pull of a handful of feeds, the Selenium route costs nothing but your time and a little account risk. For continuous post collection across many profiles or companies, offloading the IPs, the session, and the retries is usually cheaper once you price in maintenance and the cost of a banned account.
Is scraping LinkedIn posts legal?
Scraping public LinkedIn posts sits in a contested but broadly defensible area in the US, with a hard caveat in LinkedIn’s own terms. The Ninth Circuit held in hiQ Labs v. LinkedIn that scraping publicly available data likely does not violate the Computer Fraud and Abuse Act, a ruling it reaffirmed in April 2022. That is the basis for treating public post text as a defensible surface.
The caveat is contractual. hiQ ultimately lost on breach of LinkedIn’s User Agreement and settled for a $500,000 judgment and an injunction. LinkedIn’s User Agreement prohibits using “software, devices, scripts, robots or any other means or processes … to scrape or copy the Services, including profiles and other data,” and a separate clause specifically bans automated methods to “create, comment on, like, share, or re-share posts.” So public post content is the safer thing to collect, logged-in automation of engagement is the riskier thing, and if your targets are in the EU you also carry GDPR duties over any personal data. None of this is legal advice, and I walk through the full picture in is scraping LinkedIn legal.
FAQ
How many LinkedIn posts can you scrape per day?
There is no published LinkedIn post scraping limit, because LinkedIn does not sanction scraping. On a logged-in account, practitioners widely keep activity under roughly 100 to 200 profile or feed views per day with multi-second gaps to avoid a restriction. A scraper API moves that ceiling off your account by rotating its own residential IP pool, so per-account pacing stops being your bottleneck.
Can you scrape a single LinkedIn post by its URL?
Yes. A public post has its own URL of the form linkedin.com/posts/<author>_<slug>-activity-<id>, and you can target that single post to pull its text, author, date, and engagement counts. The logged-out page is still gated, so a single-post pull uses the same logged-in browser or scraper-API route as a full feed, and the reactor and comment lists on that post still load in separate panels.
Does the official LinkedIn API let you pull other people's posts?
No. LinkedIn's official APIs cover authorized use cases like Sign In with LinkedIn, the Share API for publishing to accounts you control, and the Marketing API for your own ad accounts. There is no public endpoint that returns arbitrary members' post feeds by profile or keyword, which is the gap a LinkedIn post scraper fills. The developer program is also rate-limited and returns HTTP 429 past its ceiling.
How do you export scraped LinkedIn posts to CSV or Excel?
Load the parsed post objects into a pandas DataFrame and call to_csv() or to_excel(). A scraper API that returns JSON makes this a one-liner because the fields arrive already structured, so pd.json_normalize(data['posts']).to_csv('posts.csv') writes a clean sheet. With the Selenium route you assemble the rows yourself from the parsed HTML before writing them out.