You can scrape LinkedIn job postings without logging in. LinkedIn serves a public version of its job search and of every individual job page to visitors who have no account. That public version is what search engines index, and it is what you get when you open a job link in a private browser window. It contains the title, the company name, the location, the posting date, the full description text and a few labels such as seniority level and employment type. It does not contain applicant counts, the recruiter's name, salary insights or anything that LinkedIn shows only to members.
The catch is the search itself. A public search on LinkedIn never returns more than 1,000 results, no matter how many jobs match. If you search for "software engineer" in the United States you will see a count in the thousands, but you can only page through the first 1,000. The way around this is not a trick. You split one broad search into many narrow searches that each return fewer than 1,000 results, and you deduplicate by job ID afterwards. The rest of this article explains what the public pages return, how the limit behaves, how to split a search well, and when you should not scrape at all.
What a visitor without an account can see
LinkedIn has two faces. The logged-in site is a web application that loads data through internal APIs, and that data is tied to your account. The logged-out site is a set of plain HTML pages built for search engines and for people who click a shared link. The second face is the one you can read without credentials.
The public job search lives at a URL like https://www.linkedin.com/jobs/search?keywords=data+engineer&location=Berlin. It renders a list of job cards. Each card carries a job ID, a title, a company name, a location, a relative posting date and a link to the job page. The list is paginated with a start parameter and each page holds a small batch of cards. LinkedIn also serves the same list through a lightweight guest endpoint that returns only the card fragment, which is what most scrapers call because it is smaller and faster than the full page.
The public job page lives at https://www.linkedin.com/jobs/view/<job id>. It contains the full description, the posting date, the company name with a link to the company page, and a small block of criteria. That block usually lists seniority level, employment type, job function and industries. Many job pages also embed a JobPosting block in JSON-LD. This is the structured data format described at schema.org/JobPosting, and it exists so that Google and other search engines can show jobs in their own listings. When it is present it is the cleanest source on the page, because it gives you a clean date, a structured location and sometimes a salary range, all without parsing HTML.
Now the limits. As a logged-out visitor you will not see the number of applicants, the "Easy Apply" state, the hiring manager or poster, skills matching, salary estimates that LinkedIn computes itself, or any job that the poster has restricted. You will see salary only when the employer typed it into the posting or when it appears in the JSON-LD. Posting dates are approximate. The card shows a relative value such as "2 days ago" and the JSON-LD shows a date, but reposted jobs get a fresh date, so you cannot trust it as the original publication date. Some job pages redirect to a login wall after a few requests from the same address, and the description can be truncated behind a "Show more" button in the HTML even though the full text is in the source.
The company field is also weaker than it looks. The public page gives you the display name and the company slug, not a stable company ID. Two companies with the same name are hard to tell apart. If you need company matching you have to pair the slug with the company page or with an external register.
Why a search stops at 1,000 results
The public search is paginated with a start offset. You request offset zero, then the next batch, and so on. At some point the endpoint stops returning cards even though the count on the page says there are more. That ceiling sits at 1,000 results per search. It is a server side limit on how deep LinkedIn lets anyone page, and it applies equally to the full HTML page and to the guest endpoint.
This is not a bug and it is not rate limiting. Rate limiting shows up as HTTP 429 responses, redirects to the login page or empty responses that come back after a pause. The 1,000 limit shows up as a consistent empty response once your offset passes that point, every time, regardless of how slowly you go. The two problems need different fixes. Rate limiting needs slower requests and more IP addresses. The 1,000 limit needs a different query.
One more thing to know. The order of results inside a search is not stable. LinkedIn mixes recency and relevance, and two runs of the same search minutes apart can return cards in a different order with slight differences in the set. So the first 1,000 results of a broad search are not a clean sample of anything. If you want completeness you must get under the ceiling.
How to split a search
The goal is simple. Every individual search you run must return fewer than 1,000 results, and the union of all your searches must cover the broad search you actually wanted. Then you deduplicate by the numeric job ID, because the same job will appear in several of the narrow searches.
These are the dimensions that work, roughly in order of usefulness.
Time window. The search has a time filter that limits results to jobs posted in the last day, week or month. Under the hood it is a parameter expressed in seconds, so you can set custom windows such as the last six hours. For a feed that you refresh every day, the last 24 hours is almost always under 1,000 for a single keyword and location. This is the first filter to reach for, because it also matches how most people use the data. They want new jobs, not the whole archive.
Location. Replace one country with its cities or regions. "United States" is far over the limit for almost any job title. "Austin, Texas" is usually under it. LinkedIn resolves free text locations through its own geocoder, and it also accepts a geoId parameter that pins a specific area. Use the geoId when you can, because free text can resolve to a different place than you expected.
Keywords. Split a broad title into its variants. "Engineer" becomes "software engineer", "data engineer", "platform engineer" and so on. Keep in mind that keyword search matches the description as well as the title, so narrowing the keyword also changes what matches. Quoted phrases help.
Job type, experience level and remote. These filters exist as URL parameters on the public search. They cut the result set along lines that most users care about anyway, so they are cheap to apply. Combining experience level with location is often enough on its own.
Company. The search accepts a company filter by ID. If you track a fixed list of employers, a per-company search is the most reliable split of all, since very few companies have more than 1,000 open roles in one place.
A practical pattern looks like this. Start with the broadest search you want. Read the result count on the first page. If it is under 1,000, run it and stop. If it is over, split along the time window first, then location, then keyword. Recurse until every leaf is under the limit. Store the job ID of every card, fetch the detail page once per unique ID, and never fetch the same ID twice in a run. For a daily refresh you only need to fetch detail pages for IDs you have not seen before.
Be careful with overlap. Splitting by city within a country will double count jobs that are listed as remote or that use a region name. Splitting by keyword will double count any job whose description matches more than one term. Deduplication by ID handles both, but your request budget does not. Expect to fetch a large share more cards than unique jobs.
Rules, robots and the terms of use
LinkedIn's User Agreement prohibits scraping of the service by anyone who has agreed to it, and the list of things members must not do includes using software or bots to scrape or copy data from LinkedIn. That agreement binds account holders. The legal position of a visitor who has never logged in and reads public pages has been litigated and is not settled in every jurisdiction, and LinkedIn has argued in court that the public pages are still covered by its rules. You should read the agreement yourself and get your own advice. What is clear is that doing any of this while logged in with your own account puts that account at risk and is a plain breach of the terms.
LinkedIn's robots.txt is also worth reading. It allows a short list of named crawlers and disallows everyone else by default, and it points to a page where LinkedIn explains that crawling requires explicit permission. A scraper that ignores robots.txt is doing so knowingly. That does not make it illegal, but it should inform how you behave. Keep request rates low, cache aggressively, identify yourself and never collect personal data from profile pages under the cover of a jobs project.
Personal data deserves one more sentence. A job posting is mostly company data, and most people treat it as public information. The moment your scraper starts storing the name of the recruiter attached to a posting you are processing personal data, and in the European Union that pulls you into the GDPR. Keep the scope to the posting itself.
Free ways to get the same data
Before you build or buy a scraper, check whether one of these covers your need. They are often the better choice.
The LinkedIn job search itself. If you want jobs for yourself, LinkedIn's own search with job alerts is free, complete and faster than any scraper. The 1,000 limit does not matter when you are reading twenty results a day.
The official LinkedIn APIs. LinkedIn publishes its developer documentation on Microsoft Learn. The Talent Solutions APIs let approved partners post jobs and sync applicants, not pull the job market. There is no public API that returns other companies' postings. If you are an employer or an applicant tracking system, the partner program is the route LinkedIn wants you to take.
The employer's own site. Nearly every job on LinkedIn is also on the employer's careers page, and most careers pages run on a handful of applicant tracking systems with open JSON feeds. Greenhouse, Lever, Ashby and Workday all expose public listing endpoints. If your target is a fixed list of companies, their ATS feeds are free, allowed and more detailed than LinkedIn's public page, and they do not have a 1,000 ceiling.
Structured data in search engines. Because LinkedIn and most employers publish JobPosting markup, Google indexes the same jobs in its jobs experience. For small research questions, a search engine with a site filter is a free way to get a sample without writing code.
The honest summary is that scraping LinkedIn's public job pages is the right tool when you need jobs across many employers you do not know in advance, you need them as structured rows, and you need them refreshed on a schedule. For a single employer, use their ATS feed. For your own job search, use LinkedIn. For a one time sample, use a search engine.
What a working pipeline needs
If you still decide to scrape, these are the pieces that separate a script that works once from one that works every morning.
- A query planner that splits searches along the dimensions above and stops recursing as soon as a leaf is under 1,000.
- A deduplication store keyed on the numeric job ID, so that you fetch each detail page once and can detect removed jobs by their absence.
- A parser that prefers JSON-LD and falls back to the HTML, because either one can be missing on a given page.
- Rate limiting and retries that distinguish the login wall, HTTP 429 and the 1,000 ceiling, since each needs a different response.
- A check on the posting date that compares against your last seen date, so reposted jobs do not look new.
Building and maintaining this is a few days of work and then a recurring chore, because the page markup changes without notice. If you would rather pay per result and skip the maintenance, the LinkedIn jobs scraper on this site runs the search splitting, deduplication and parsing described here, returns clean rows with the public fields listed above, and charges only for the postings it actually delivers.
Want the signal instead of the raw filings? Get a free report preview. Prefer the tool to the write-up? Browse all data feeds or connect the free MCP server.