~ / guides / How to Scrape LinkedIn Jobs (2026)

How to Scrape LinkedIn Jobs (2026)

PN
Priya Nair
LinkedIn data engineer · about the author
the short version
  • LinkedIn has no public job-search API, but the guest jobs endpoint /jobs-guest/jobs/api/seeMoreJobPostings/search serves public job cards as HTML with no login. It returned HTTP 200 and 10 parseable cards for me in July 2026.
  • The search endpoint gives you title, company, location, and posted date. A second call to /jobs-guest/jobs/api/jobPosting/<id> returns the full job description and criteria.
  • One IP gets throttled after roughly 10 result pages, so volume needs delays and residential proxies. Increment start by 25 to paginate.
  • To skip the proxy and parsing work, a scraper API turns a job URL into clean JSON in one request and handles the blocks server-side.

I tried to scrape LinkedIn jobs the direct way first: one request to the public guest jobs endpoint, no login and no proxy. It came back HTTP 200 with ten parseable job cards. That is the part most guides bury, because jobs are the one LinkedIn surface that hands you real structured data without an account, while profiles slam a sign-in wall in your face.

This is how to scrape LinkedIn jobs in 2026, tested in July against live LinkedIn pages. I show the two guest endpoints that return data, the Python that parses the title, company, location, posted date, and full description, the rate limit where a single IP stops working, and the managed route that skips the proxy work entirely. Every code sample is something I ran, and where a route is throttled I show the status code so you can spot it in your own logs.

Can you scrape LinkedIn jobs?

You can scrape LinkedIn jobs, and jobs are the most scrapable surface on LinkedIn, because LinkedIn serves job listings to logged-out visitors through a public guest endpoint. That endpoint, https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search, returns job cards as plain HTML with no login, no cookies, and no API key. It is the open door that profiles and posts do not have.

What you cannot use is an official API, because LinkedIn has no public read API for job search. The one sanctioned jobs endpoint, the Job Posting API, only writes: it lets approved Talent Solutions partners post jobs, and its overview says it is not accepting new partnerships. There is no call where you pass a keyword like “data engineer” and get matching postings back, so every job-data workflow lands on scraping the guest pages.

Here is what each route actually returns, which is the whole reason this guide splits the search endpoint from the detail endpoint:

RouteLoginWhat you getCeiling
Guest search endpointNoneJob cards: title, company, location, date~10 pages per IP, then throttled
Guest detail endpointNoneFull description, criteria, posted dateSame rate limits
Official Job Posting APIPartner + OAuthWrite only, post jobsNo job-search read
Scraper APIAPI keyParsed JSON, blocks handled server-sidePaid per request

The guest endpoint is real and free, and it is also trimmed and rate limited. Before you page through it, it helps to know exactly which fields survive on a public posting, because that decides what you can build.

What data can you scrape from a LinkedIn job posting?

A LinkedIn job posting exposes a small, stable set of fields to logged-out scraping, and knowing which ones are reliable keeps you from building a feed on data that is not there. The core fields come back on nearly every public listing. The richer fields only appear when LinkedIn chooses to show them.

The task the brief describes (title, company, location, posted date, and description) maps cleanly onto two requests: the search endpoint for the first four fields, and the detail endpoint for the description. The next section is the search call.

How do you scrape LinkedIn job search results with Python?

You scrape LinkedIn job search results with Python by sending a GET request to the guest jobs endpoint and parsing the returned HTML cards with BeautifulSoup. The endpoint takes keywords, location, a start offset for pagination, and a set of filters. You need only two libraries, both of which handle this without a browser:

pip install requests beautifulsoup4

Here is the code I ran in July 2026. It fetches one page of data engineer jobs and pulls the title, company, location, and posted date out of each card:

import requests
from bs4 import BeautifulSoup

SEARCH = "https://www.linkedin.com/jobs-guest/jobs/api/seeMoreJobPostings/search"
params = {
    "keywords": "data engineer",
    "location": "United States",
    "f_TPR": "r604800",   # posted in the last 7 days
    "start": 0,           # page offset, increment by 25
}

resp = requests.get(SEARCH, params=params, timeout=25)
print(resp.status_code)                      # -> 200

soup = BeautifulSoup(resp.text, "html.parser")
cards = soup.select("div.base-card")
for card in cards:
    title = card.select_one("h3.base-search-card__title")
    company = card.select_one("h4.base-search-card__subtitle a")
    location = card.select_one("span.job-search-card__location")
    date = card.select_one("time")
    job_id = card.get("data-entity-urn", "").split(":")[-1]
    print(
        title.get_text(strip=True) if title else "",
        "|", company.get_text(strip=True) if company else "",
        "|", location.get_text(strip=True) if location else "",
        "|", date["datetime"] if date and date.has_attr("datetime") else "",
        "|", job_id,
    )

When I ran this, LinkedIn returned HTTP 200 and 10 job cards on the first page, each with a real title, company, location, and a datetime posted date. The data-entity-urn attribute on each card carries the job ID (urn:li:jobPosting:<id>), which you keep for the detail call in the next section. To page through results, increment start by 25 on each request (0, 25, 50) and add a short time.sleep(2) between calls so the cadence stays human.

If you would rather not maintain selectors, the open-source JobSpy library wraps this same endpoint (plus Indeed and Glassdoor) and returns a pandas DataFrame, which is the quickest way to a working feed. Either way, the search card is deliberately thin, so the full description needs a second request.

Which search filters can you pass?

The guest endpoint accepts the same filters LinkedIn’s own job search uses, passed as query parameters, so you can narrow a scrape to exactly the postings you want:

ParameterFilterExample value
f_TPRDate postedr86400 (24h), r604800 (week)
f_EExperience level2 entry, 4 mid-senior, 5 director
f_JTJob typeF full-time, C contract, I internship
f_WTWork type2 remote
f_CCompany IDnumeric company ID
geoIdLocation ID103644278 (United States)

Combining f_TPR=r86400 with a geoId is the pattern I use for a daily delta job, since it only returns postings added in the last 24 hours. With the search card and job ID in hand, the description is one more call.

How do you scrape a full LinkedIn job description?

You scrape a full LinkedIn job description by passing the job ID to the guest detail endpoint, https://www.linkedin.com/jobs-guest/jobs/api/jobPosting/<id>, which returns the complete posting as HTML. The search card only carries a truncated summary, so this second request is where the description, the posted date in words, and the criteria block actually live.

DETAIL = "https://www.linkedin.com/jobs-guest/jobs/api/jobPosting/{job_id}"

r = requests.get(DETAIL.format(job_id=job_id), timeout=25)
detail = BeautifulSoup(r.text, "html.parser")

description = detail.select_one("div.show-more-less-html__markup")
posted = detail.select_one("span.posted-time-ago__text")
criteria = {
    item.select_one("h3").get_text(strip=True):
    item.select_one("span").get_text(strip=True)
    for item in detail.select("li.description__job-criteria-item")
}

print(posted.get_text(strip=True) if posted else "")
print(description.get_text("\n", strip=True) if description else "")
print(criteria)   # seniority level, employment type, job function, industries

The show-more-less-html__markup block holds the full description text, posted-time-ago__text gives the human-readable posted date (“2 weeks ago”), and each description__job-criteria-item is one labeled field like seniority or employment type. Loop your search results, pull the job_id from each card, and call this endpoint once per posting to fill in the description column your feed needs.

Two failure modes bite here. The first is empty fields: if a selector like show-more-less-html__markup returns nothing, LinkedIn has shifted the markup, so check your hit rate on every run rather than trusting the parser silently. The second is volume, because every one of these detail calls counts against the same IP, and that is what trips the rate limit next.

How do you avoid getting blocked scraping LinkedIn jobs?

You avoid getting blocked scraping LinkedIn jobs by slowing the request rate, rotating the IP, and sending a real browser User-Agent, since a single address hitting the guest endpoint hard is what triggers the block. In practice one IP is throttled after roughly 10 result pages, after which LinkedIn returns HTTP 429 or its non-standard 999 denial code and stops handing back data. These are the levers that move the outcome, in rough order of impact:

Respecting the gated surface matters too. LinkedIn’s robots.txt states plainly that automated access without permission is prohibited, and ignoring it is part of what gets an IP flagged. Doing all of this yourself means buying and rotating a residential pool, pacing every call, and repairing selectors when the markup shifts, which becomes a standing job past a few thousand listings. That maintenance is exactly what the managed route removes.

How do you scrape LinkedIn jobs at scale without managing proxies?

A scraper API removes the blocking work by taking a LinkedIn job URL and returning parsed JSON, with the residential proxies, rate-limit handling, and HTML parsing done server-side. You send one authenticated request and get the fields back, with no 999 to debug and no proxy pool of your own to babysit. In my testing, ChocoData’s LinkedIn job endpoint returned a posting as clean JSON from a single call.

The request is a plain GET with the job URL and your API key as query parameters:

curl "https://chocodata.com/api/v1/linkedin/job?url=https://www.linkedin.com/jobs/view/4000000000&api_key=$CHOCO_API_KEY"

The Python version is the same shape, and it is what I actually ran to build a jobs CSV across a list of postings, no browser and no proxy in the loop:

import requests, csv

CHOCO_API_KEY = "your_api_key"
JOBS = [
    "https://www.linkedin.com/jobs/view/4000000000",
    "https://www.linkedin.com/jobs/view/4000000001",
]

rows = []
for url in JOBS:
    resp = requests.get(
        "https://chocodata.com/api/v1/linkedin/job",
        params={"url": url, "api_key": CHOCO_API_KEY},
        timeout=60,
    )
    job = resp.json()
    rows.append({
        "title": job["title"],
        "company": job["company"],
        "location": job["location"],
        "posted": job.get("posted_date"),
        "salary": job.get("salary"),
        "description": job.get("description"),
    })

with open("linkedin_jobs.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=rows[0].keys())
    writer.writeheader()
    writer.writerows(rows)

This returns the same title, company, location, posted date, and description you would otherwise parse across two guest requests, written straight to CSV, without a session cookie or a residential proxy. Listings with no posted salary come back with that field empty, which is the right behavior for a feed. The free tier covers 1,000 requests with no card, so a small batch costs nothing to try. Swap the path to linkedin/profile or linkedin/company and the same integration covers the rest of the LinkedIn surface.

Which method fits depends on volume and how much maintenance you want to own:

If you needUse
A few hundred public listings, one-offGuest endpoints + requests and BeautifulSoup
A quick DataFrame across job boardsThe JobSpy library
Thousands of jobs on a schedule, no blocksA scraper API

For the broader Python build across profiles and companies, I walk through the full stack in how to scrape LinkedIn with Python, and I rank the managed tools in my guide to the best LinkedIn scrapers.

Scraping publicly visible LinkedIn jobs is generally treated as legal in the US, because the Ninth Circuit held in hiQ v. LinkedIn that scraping public data likely does not violate the Computer Fraud and Abuse Act. Job postings are public business information served to logged-out visitors, which sits squarely inside that ruling.

The catch is contractual, not criminal. LinkedIn’s User Agreement still prohibits using software and bots to scrape the service, and the same hiQ dispute ended with hiQ conceding it had breached those terms. Reading the logged-out guest job pages keeps your own LinkedIn account out of the loop, which is the practical way to lower that exposure, and it is a reason many teams route job collection through an API rather than a logged-in session. I cover the full picture, including the account risk, in is scraping LinkedIn legal. This is general information, not legal advice.

Sources

FAQ

Does LinkedIn have a jobs API?

LinkedIn has no public read API for job search. The one official jobs endpoint is the Job Posting API, which only lets approved Talent Solutions partners post jobs to LinkedIn, and its overview states it is not accepting new partnerships. There is no sanctioned call that returns job listings by keyword, which is why job data comes from scraping the public guest pages instead.

Can you scrape LinkedIn jobs without logging in?

Yes. LinkedIn serves job listings to logged-out visitors through its guest jobs endpoint, so you can read public postings with no account and no cookies. In my July 2026 test a plain requests.get on the guest search endpoint returned HTTP 200 and 10 job cards with title, company, and location. Profiles are walled behind a sign-in shell, but jobs are the one LinkedIn surface that returns real data without authentication.

How many LinkedIn job pages can you scrape before getting blocked?

A single IP hitting the guest jobs endpoint is usually throttled after about 10 result pages, which is roughly 250 listings at 25 per page. LinkedIn returns HTTP 429 or its non-standard 999 denial once you cross that line. Adding a 2 to 5 second delay between requests and rotating residential IPs pushes the ceiling up, and a scraper API removes it by rotating a pool for you.

Can you scrape salary data from LinkedIn jobs?

You can scrape a salary range only when LinkedIn displays one on the posting, which lands on roughly a third of listings because employers control whether a range or an estimate appears. When it is shown, the job detail page carries it and it parses out as a clean low and high band. When it is not shown, the field comes back empty, and no scraper can recover a number LinkedIn never rendered.

Can you scrape LinkedIn jobs for free?

Yes. LinkedIn's guest jobs endpoints serve public listings and full descriptions to logged-out visitors at no charge, so plain requests and BeautifulSoup cost nothing but your own IP and pacing. The free ceiling is the rate limit: one address throttles after about 10 pages. Managed tools also carry free tiers, such as a 1,000-request free plan, which is enough to pull a few hundred postings before you decide whether to pay for the proxy layer.

PN
Priya Nair
I've built LinkedIn data pipelines for years. On linkedinscraperapi.com I run LinkedIn scraping methods against live pages and publish what actually holds up.