Article
How to scrape LinkedIn jobs without login or cookies
If you have ever tried to automate a LinkedIn job search, you have probably hit the wall: the moment you script a logged-in session, LinkedIn's anti-automation defenses start throwing checkpoints, and you are one CAPTCHA away from a locked account. The good news is that for job postings specifically, you almost never need to log in at all.
This guide explains the approach that actually holds up: reading LinkedIn's public guest job-search endpoint — the same data an anonymous visitor sees before any sign-in prompt — and turning it into clean, structured JSON.
Why avoid login and cookies?
Scraping behind a login is fragile and risky for three reasons:
- Account risk. Automated activity on an authenticated session is exactly what LinkedIn's systems flag. A scraped account can get restricted or banned.
- Session rot. Cookies expire, sessions get invalidated, and 2FA challenges appear at the worst time. Any pipeline built on a logged-in cookie jar breaks constantly.
- You do not need it. Job listings are public. Anyone can browse
linkedin.com/jobswithout an account, and there is a lightweight guest endpoint behind that page that returns postings as HTML fragments.
The guest endpoint is the key. It is designed to serve the public, un-authenticated jobs page, so hitting it needs no cookies, no session token, and no headless browser.
How the public guest endpoint works
When you open LinkedIn's jobs search while logged out, the page loads results from a paginated guest API. Each request takes a keyword, a location, an optional remote filter, a time window, and an offset for pagination, and it returns a batch of job cards as HTML.
From each card you can parse the fields that matter:
| Field | What it is |
|---|---|
id |
Numeric posting id, extracted from the job URL |
title |
Job title as posted |
company |
Hiring company or organisation |
location |
Location text (often includes a remote hint like Berlin, Germany (Remote)) |
postedAt |
Posting date, read from the card's machine-readable <time datetime> attribute |
url |
Direct link to the posting |
For the full description you follow the posting URL to LinkedIn's guest posting API — again, no login — and pull the plain-text body.
One thing the guest cards never carry is salary. If a scraper hands you a salary field for LinkedIn, it either fabricated it or scraped it from somewhere else. Honest LinkedIn data has no salary column.
The one real limitation: the ~200-result cap
Here is the single most important thing to know, and the thing most tutorials leave out:
LinkedIn's guest search endpoint only paginates to roughly 200 postings per query. Past that offset, it simply stops returning new results — it does not matter whether you ask for 500 or set "no limit." The ceiling is on LinkedIn's side, not on your scraper's.
This is not a bug you can code around by hammering the offset. Instead, you narrow your queries so each one stays under the cap and covers a slice you care about:
- Split by keyword. Run
react developerandvue developeras separate searches instead of one broadfrontend. - Split by location.
Berlin,Munich,Remote Germanyas three runs rather than one country-wide sweep. - Split by time window. Search "past 24 hours" on a schedule (e.g. every day) so you continuously harvest new postings before they age out, rather than trying to backfill a month at once.
The last one is the real trick: a narrow query run often beats a broad query run once. Fresh postings are exactly what you want for a job search anyway.
Doing it without writing a scraper
You can implement all of the above yourself — parse the guest HTML, handle pagination, follow through for descriptions, and apply filters. But if you would rather skip the maintenance, we maintain a ready-to-run Actor that does exactly this on the Apify Store.
It reads only the public guest endpoint (no login, no cookies, no proxies to configure), applies the filters below, and returns one flat JSON record per posting:
keywordandlocationto target the searchremoteto restrict to remote-eligible postingstimeFilter— past hour / 24 hours / week / monthmaxItems(remember the ~200 hard ceiling per run)includeDescriptionto fetch the full plain-text bodytitleExclude/companyExcludeto drop noise by keyword
Filter by keyword, location, remote and posting age. Clean JSON out, no account required. Pay per run on the Apify Store.
Calling it from your own code is a single HTTP request that runs the Actor and returns the items in one response:
curl -X POST \
"https://api.apify.com/v2/acts/nomad-agent~linkedin-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
-H "Content-Type: application/json" \
-d '{"keyword": "frontend developer typescript", "location": "Germany", "maxItems": 100}'
Each record comes back like this:
{
"id": "4429472960",
"title": "Senior Frontend Engineer (React)",
"company": "Acme Software",
"location": "Berlin, Germany (Remote)",
"postedAt": "2026-06-30",
"url": "https://www.linkedin.com/jobs/view/4429472960",
"source": "linkedin",
"description": "We are hiring a Senior Frontend Engineer..."
}
Is scraping public LinkedIn jobs legal?
This approach reads only publicly available job postings — data any visitor can see without logging in — and touches no personal data behind authentication. That is a materially different posture from scraping member profiles behind a login. Still, "public" is not a blanket legal clearance: review LinkedIn's terms of service and the regulations that apply to your specific use case and jurisdiction before you build on it.
Where this fits in a job-search pipeline
Raw postings are only step one. In practice you also want to:
- Deduplicate across runs (the
idfield makes this easy). - Score each posting against what you actually want, instead of eyeballing hundreds of cards.
- Verify the listing is still open before you spend time applying.
That scoring-and-alerting layer is the hard part, and it is exactly what we built Oink to do.
Oink runs this kind of search for you around the clock — upload a resume, describe what you want, and an AI pipeline scores fresh postings against your profile and pings you on the web or Telegram.
Happy hunting — and remember, keep each query narrow and let the freshness do the work.