LinkedIn Search Jobs Scraper By Multiple Keywords & Locations
Pricing
Pay per usage
LinkedIn Search Jobs Scraper By Multiple Keywords & Locations
The LinkedIn Search Jobs Scraper actor collects job listings from LinkedIn based on keywords, locations, and filters. It extracts titles, companies, locations, posting dates, and job URLs. Ideal for recruiters, analysts, and job platforms needing automated LinkedIn job market data collection.
Pricing
Pay per usage
Rating
0.0
(0)
Developer
Scraper Engine
Maintained by CommunityActor stats
1
Bookmarked
37
Total users
2
Monthly active users
3 days ago
Last modified
Categories
Share
LinkedIn Jobs Scraper — Titles, Companies and Applicants
Run a whole keyword × location matrix of LinkedIn job searches in a single job and get every listing back as structured JSON — job title, company, location, posting age, applicant counts, full description, and parsed criteria (seniority, employment type, job function, industries). No LinkedIn login is required. Every job is tagged with the exact keyword and location that surfaced it, duplicates found across different searches are collapsed automatically, and a per-run coverage summary tells you honestly how many jobs were requested versus delivered. Try a run on the Apify Console below.
What is LinkedIn Jobs Scraper By Multiple Keywords & Locations?
It's an Apify Actor that expands a keyword @ location1; location2 matrix into a full cross-product of individual LinkedIn job searches, runs them in one job, and returns deduplicated job listings as clean JSON. It reads LinkedIn's public guest job-search and job-detail pages only — no LinkedIn account or login is used anywhere in the code. It's built for recruiters and sales-intelligence teams tracking hiring activity across markets, market researchers, and developers or AI agents that need structured job data for many roles and regions at once.
What LinkedIn job data is publicly available to scrape?
LinkedIn's guest job-search and job-detail pages expose a job posting's core details — title, company, location, description, and hiring criteria — to any visitor, no sign-in required. What's gated sits behind LinkedIn's login and application flow.
| Data Category | Publicly Available (no login) | Restricted (login / apply required) |
|---|---|---|
| Job title, company, location | ✅ | — |
| Full job description | ✅ | — |
| Posting criteria (seniority, employment type, job function, industries) | ✅ | — |
| Applicant count, as LinkedIn displays it (e.g. "Over 200 applicants") | ✅ | — |
| Time since posting | ✅ | — |
| Identities of individual applicants | ❌ | Login + poster/recruiter access |
| Apply flow, hiring-manager contact details | ❌ | Login + Easy Apply |
| Salary/compensation range | ❌ (not parsed by this Actor) | — |
LinkedIn Jobs Scraper By Multiple Keywords & Locations only returns publicly visible data — what any visitor sees. Nothing behind a login wall.
What data can I extract with LinkedIn Jobs Scraper?
Every row is a single LinkedIn job listing, returned with these fields:
| Field | Description |
|---|---|
id | Numeric LinkedIn job posting ID |
title | Job title |
company | Hiring company name |
location | Job location text as shown on LinkedIn |
description | Full job description text |
criteria | Array of {title, value} pairs — seniority level, employment type, job function, industries, as listed on the posting |
companyUrl | URL to the hiring company's LinkedIn page |
jobUrl | Canonical LinkedIn URL for the job posting |
postedTimeAgo | Free-text posting age as shown on LinkedIn (e.g. "3 days ago") |
numberOfApplicants | Free-text applicant count as shown on LinkedIn (e.g. "Over 200 applicants") |
applicantsCount | The same applicant count parsed into a nullable number, or null if LinkedIn didn't display one |
scrapedAt | UTC timestamp of when this job was scraped |
sourceKeyword | The keyword (or term1|term2 OR-group) that surfaced this job |
sourceLocation | The location string that surfaced this job |
sourceGeoId | LinkedIn's resolved numeric geoId for that location, if resolution succeeded |
matchedTargets | Array of every {keyword, location} target that returned this job — how cross-target duplicates are traced |
Two legacy aliases are kept for backward compatibility with the base actor's output shape and always mirror the values above: link (= companyUrl) and job.link (= jobUrl, nested).
Job identity and description fields
id, title, company, location, description, criteria, companyUrl, jobUrl (plus deprecated link / job.link).
Metric and timing fields
postedTimeAgo, numberOfApplicants, applicantsCount, scrapedAt.
Search-provenance fields
sourceKeyword, sourceLocation, sourceGeoId, matchedTargets — these exist because this Actor runs many searches in one job and needs to trace which job came from which target.
🤖 Add-on: Need additional LinkedIn data?
If you also need data from LinkedIn profile pages rather than job postings, LinkedIn Profile Post Scraper (also in this catalog) extracts posts from LinkedIn profile pages. Pair the two when you need both hiring activity and the people behind it.
Why not build this yourself?
Scraping LinkedIn's guest job surface directly means maintaining your own HTML selectors against markup that changes without notice, handling LinkedIn's 429 and 999 rate-limit responses with backoff, and reverse-engineering two non-obvious LinkedIn quirks this Actor already handles: resolving a free-text location to LinkedIn's numeric geoId (the text-only filter is far less precise), and resolving a linkedin.com/company/<slug> URL to its real numeric organization ID before filtering by f_C — LinkedIn silently ignores the slug form and returns the entire unfiltered job firehose instead of erroring. On top of that you'd need to build cross-target dedup and an honest requested-vs-delivered report yourself. This Actor does all of it out of the box, with retries, jitter, and optional residential proxy support already wired in.
How to use LinkedIn Jobs Scraper
No field is marked required in the input schema, but the run needs at least one usable entry in targets or startUrls — with neither, it exits immediately with "No usable targets found."
- Open the Actor on its Apify Store page and click Try for free (or run it from your own Console).
- Fill in
targetswith one or morekeywords @ location1; location2lines — this is the bulk keyword × location matrix. - Optionally set
sortOrder,maxJobs,maxTargets, andproxyConfiguration. - Start the run.
- Download results as JSON, CSV, Excel, or any format the Apify dataset export supports, or stream them via the API as they're written.
How to scale to bulk job extraction
targets is the primary bulk mechanism: each line's keyword part is cross-product-expanded against every semicolon-separated location on that line, so a handful of lines can expand into dozens of searches in one run. startUrls adds legacy single-line inputs (search URLs, bare keywords, company URLs, keyword, location strings, or direct job URLs/IDs) as additional implicit targets alongside the matrix, for input lists carried over from the base actor. maxTargets caps the expanded total so a large matrix can't run away; 0 means no cap.
What can you do with LinkedIn job data?
- 📊 Market researchers tracking hiring demand use
sourceKeyword,sourceLocation, andpostedTimeAgoto compare how many roles for a title are posted across regions and how fresh they are. - 🎯 Sales and demand-gen teams use
company,companyUrl, andcriteria(employment type, seniority) to flag companies actively hiring as a buying-intent signal. - 🤖 AI agents and job-alert bots use
title,description, andapplicantsCountto filter and rank newly posted jobs before notifying a candidate — the JSON is typed and ready to pass straight into an agent's context. - 🔬 People-ops and workforce analysts use
criteriaandnumberOfApplicantsacrossmatchedTargetsto gauge how competitive postings are for a given role and region. - 👩💻 Developers building an internal job board pull the structured JSON directly, deduped via
matchedTargets, instead of re-scraping the same listing twice when it appears under two different searches.
How does LinkedIn Jobs Scraper handle rate limits and blocking?
Every request goes through a retry loop (3 attempts) with randomized delays and exponential backoff, and specifically recognizes LinkedIn's 429 (rate limit) and 999 (temporary block) responses to back off rather than fail outright. Requests use realistic browser headers, a warm-up request before the run starts, randomized delays between requests, and capped concurrency (a global connector limit and a 3-way semaphore for detail-page scraping) to avoid hammering LinkedIn. By default the Actor makes direct requests with no proxy — enabling proxyConfiguration (Apify Proxy, including residential) is optional and only recommended if you see requests getting blocked. If a job's detail page still fails to load after all retries, that single job is skipped and logged — it doesn't stop the run. If a location or company URL can't be resolved to LinkedIn's internal ID, that target is skipped rather than run unfiltered.
⬇️ Input
| Parameter | Required | Type | Description | Example Value |
|---|---|---|---|---|
targets | No | array | One line per target: 'keywords @ location1; location2'. Everything before @ is the keyword search ('term1|term2' for an OR search); everything after @ is one or more locations separated by ; — each location becomes its own search (cross-product). Each location is auto-resolved to LinkedIn's numeric geoId. | ["software engineer|backend developer @ United States; United Kingdom"] |
startUrls | No | array | Backward-compatible with the base actor's input: LinkedIn job search URLs, keywords, company URLs, structured 'keyword, location' strings, or job URLs/IDs — one item per line. Each item is treated as one additional implicit target alongside targets. | [] |
sortOrder | No | string (enum: relevant, recent) | Choose how to sort results within each target: most relevant or most recent. Default "relevant". | "relevant" |
maxJobs | No | integer (min 1, max 10000) | Maximum number of jobs to scrape for each target/search. ⚠️ LinkedIn's guest search surface has a real hard ceiling of about 1,000 jobs per target regardless of this value — the coverage summary reports maxJobsRequested vs maxJobsDelivered honestly if the ceiling is hit. Default 25. | 50 |
maxTargets | No | integer (min 0) | Safety cap on how many expanded keyword × location targets (after cross-product) will actually run in one job. 0 = no cap. Default 0. | 0 |
proxyConfiguration | No | object (proxy editor) | Optional: enable Apify Proxy if requests get blocked. By default the Actor runs with direct requests (no proxy). Proxies may consume extra credits. Default {"useApifyProxy": false}. | {"useApifyProxy": false} |
Example input
{"targets": ["software engineer|backend developer @ United States; United Kingdom","product manager @ United States"],"startUrls": [],"sortOrder": "relevant","maxJobs": 50,"maxTargets": 0,"proxyConfiguration": { "useApifyProxy": false }}
⬆️ Output
Each job is pushed to the default dataset the moment its detail page finishes scraping — typed, normalized JSON with a consistent schema across runs. Export it as JSON, CSV, Excel, or any other format the Apify platform supports, or stream it via the API.
Example output
{"id": "3891234567","title": "Senior Backend Engineer","company": "Acme Robotics","location": "London, England, United Kingdom","postedTimeAgo": "3 days ago","numberOfApplicants": "Over 200 applicants","applicantsCount": 200,"description": "We are looking for a Senior Backend Engineer to join our platform team...","criteria": [{ "title": "Seniority level", "value": "Mid-Senior level" },{ "title": "Employment type", "value": "Full-time" },{ "title": "Job function", "value": "Engineering and Information Technology" },{ "title": "Industries", "value": "Software Development" }],"companyUrl": "https://www.linkedin.com/company/acme-robotics","jobUrl": "https://www.linkedin.com/jobs/view/senior-backend-engineer-at-acme-robotics-3891234567","link": "https://www.linkedin.com/company/acme-robotics","job": { "link": "https://www.linkedin.com/jobs/view/senior-backend-engineer-at-acme-robotics-3891234567" },"sourceKeyword": "software engineer|backend developer","sourceLocation": "United Kingdom","sourceGeoId": "101165590","matchedTargets": [{ "keyword": "software engineer|backend developer", "location": "United Kingdom" }],"scrapedAt": "2026-08-04T10:15:32Z"}
Alongside the main dataset, each run also writes one coverage summary row to a separate, run-specific dataset (coverage-summary-<runId>) with: type, targetsRequested, targetsResolved, totalUniqueJobs, duplicatesCollapsed, maxJobsRequested, maxJobsDelivered, perTargetCounts (an array of {target, sourceKeyword, sourceLocation, sourceGeoId, resolved, jobsFound} per target), and scrapedAt. This is what gives you an honest requested-vs-delivered figure instead of a run that silently under-delivers.
How does it work?
Requests go to LinkedIn's public guest job-search pages over plain HTTP, with realistic browser headers, randomized delays, and optional Apify Proxy — there's no headless browser or JavaScript rendering involved. The run has two phases: first, a discovery pass across every expanded target that collects job IDs and resolves each location's geoId and each company slug's numeric organization ID; second, a detail-scrape pass that fetches each unique job exactly once, even if it matched several different keyword/location targets, and tags it with all of them via matchedTargets. Only what LinkedIn already shows a signed-out visitor is returned. The output schema is defined by this Actor, not by LinkedIn's raw HTML, so it stays stable even if LinkedIn changes its page markup.
Integrations
LinkedIn Jobs Scraper runs on the Apify platform, so it works with everything already built around Apify's API.
Calling LinkedIn Jobs Scraper programmatically
from apify_client import ApifyClientclient = ApifyClient("<YOUR_APIFY_API_TOKEN>")run_input = {"targets": ["product manager @ United States"],"sortOrder": "relevant","maxJobs": 50,}run = client.actor("linkedin-search-jobs-scraper-by-multiple-keywords-and-locations").call(run_input=run_input)for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item["title"], item["company"], item["location"])
Works in Go, Ruby, Node.js, cURL — any language that can make an HTTP request to the Apify API.
No-code tools (n8n, Make)
In n8n, use the Apify node (or an HTTP Request node against the Actor's run endpoint) to trigger a run and pull dataset items into your workflow. In Make, the Apify app module can start this Actor and pass its output straight into a downstream scenario, such as a CRM update or a Slack alert on new postings.
⚖️ Is it legal to scrape LinkedIn job listings?
Scraping publicly available job-posting data is generally permitted — LinkedIn Jobs Scraper only returns what LinkedIn's guest job-search and job-detail pages already show to a visitor without logging in. A job listing is business/product data (a company's own posting about an open role), not personal data about an individual, so the framework that applies is LinkedIn's Terms of Service and database-rights law, not GDPR/CCPA-style personal-data rules. Note that some free-text fields, like description, may incidentally contain a hiring contact's name — treat any such text with the same care you'd apply to any scraped web content. Consult legal counsel if your use case involves bulk storage of personal data.
Frequently asked questions
What LinkedIn job fields does LinkedIn Jobs Scraper return?
The top fields are title, company, location, description, and criteria (seniority, employment type, job function, industries) — see What data can I extract above for the full field list.
Does LinkedIn Jobs Scraper require a LinkedIn account or login?
No. It reads only LinkedIn's public guest job-search and job-detail pages — no login, cookies, or session is used anywhere in the Actor.
How many job listings can I extract in one run?
Up to maxJobs (max 10,000) per target, across as many targets as your targets/startUrls matrix expands to (optionally capped by maxTargets). In practice, LinkedIn's guest search surface caps discovery at roughly 1,000 jobs per individual target regardless of a higher maxJobs value — the coverage-summary row reports maxJobsRequested vs maxJobsDelivered so you can see if that ceiling was hit.
What happens if a search target returns zero results?
That target's jobsFound is recorded as 0 in the coverage summary's perTargetCounts, and the run simply moves on to the next target — no error is raised. If every target returns zero jobs, the run finishes with an empty main dataset and a coverage-summary row showing totalUniqueJobs: 0.
Can I scrape multiple LinkedIn keyword/location combinations at once?
Yes — that's the Actor's core feature. targets accepts multiple keyword @ location1; location2 lines, cross-product-expanded into individual searches, and startUrls adds further legacy inputs on top.
Does LinkedIn Jobs Scraper work with Claude, ChatGPT, and other AI agent tools?
It's callable as a standard HTTP endpoint through the Apify API by any agent framework that can make a web request — there is no dedicated MCP server for this specific Actor.
How is this different from a plain LinkedIn Jobs Scraper?
This variant is built specifically for running many keyword/location combinations in one job: it expands a bulk matrix input into a cross-product of searches, auto-resolves each location's geoId, dedupes jobs found across different targets and tags each with matchedTargets, and reports maxJobsRequested vs maxJobsDelivered honestly instead of silently under-delivering when LinkedIn's guest-surface ceiling is hit. A single-keyword, single-location LinkedIn job search doesn't need this — this Actor is for running dozens of searches together and getting one clean, deduplicated dataset back.
Does LinkedIn Jobs Scraper return data in a format LLMs can use directly?
Yes. Every job comes back as typed, normalized JSON with consistent field names across runs — no HTML parsing or CSS selectors needed. Pass it straight to an LLM, index it into a vector store, or hand it to an agent tool.
What happens when LinkedIn changes its layout or anti-bot system?
The Actor is maintained and its output schema is designed to stay stable even when LinkedIn's page markup changes. No specific update turnaround time is promised.
Can I use LinkedIn Jobs Scraper without managing proxies or browser infrastructure?
Yes — there's no browser to manage since the Actor makes plain HTTP requests, and by default it runs without any proxy at all. If you do hit blocking, turning on proxyConfiguration routes requests through Apify Proxy without any infrastructure of your own.
Which fields work best for AI training data and RAG indexing?
For RAG, index description alongside title, company, and criteria as metadata for filtering. For structured training data, title, company, location, postedTimeAgo, and applicantsCount return as consistent typed primitives (strings and a nullable number) across every record.
Related scrapers
| Scraper | What it extracts |
|---|---|
| LinkedIn Profile Post Scraper | Posts published on LinkedIn profile pages |
💬 Your feedback
Found a bug or missing a field? Use the Issues tab or the Contact developer button on this Actor's Apify Store page to let us know — we read every report.