LinkedIn Jobs Scraper Pro avatar

LinkedIn Jobs Scraper Pro

Pricing

from $25.00 / 1,000 results

Go to Apify Store
LinkedIn Jobs Scraper Pro

LinkedIn Jobs Scraper Pro

Paste any LinkedIn job-search URL (with filters) and get every listing with full job details, deep company data, hiring-team profiles, salary, skills, applicant insights, and official-source contact enrichment. Deep pagination captures 100% of results.

Pricing

from $25.00 / 1,000 results

Rating

0.0

(0)

Developer

YESHWANT SINGH

YESHWANT SINGH

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Share

LinkedIn Jobs Scraper Pro πŸš€

Paste any LinkedIn job-search URL β†’ get every listing, with full job + company + hiring-team data and official-source contact enrichment. Nothing missed, built to be reliable.

This Actor was built from scratch to out-feature every LinkedIn job scraper in the Apify Store. It is HTTP-first against LinkedIn's public guest endpoints (fast, cheap, hard to block), adds an optional logged-in layer for data only visible when authenticated, and finishes with official-source contact enrichment for recruiter email and company phone.


✨ Why this one is different

CapabilityMost scrapersThis Actor
Paste a search URL with all your filtersPartialβœ… Full β€” search/, search-results/, collections/
Capture 100% of results past LinkedIn's ~1,000 cap❌ silently truncatesβœ… Deep pagination auto-splits by facet
Company profile (website, industry, size, HQ, followers)Sometimesβœ… Full, cached per company
Hiring team / job poster + their LinkedIn profileRareβœ… With logged-in session
Applicant insights & skillsRareβœ… With logged-in session
Recruiter email + company phone❌ or fabricatedβœ… Official sources only, never guessed
Salary parsed into min/max/currency/periodRareβœ… + raw text
Reliability (residential proxy, session pool, retries)Variesβœ… Tuned for LinkedIn

🧭 What it scrapes

Job: id Β· title (role) Β· canonical job URL Β· company apply URL (offsite/ATS) Β· careers-portal URL Β· ATS name (Greenhouse, Lever, Workday…) Β· Easy-Apply flag Β· posted date + "time ago" Β· expiry Β· location Β· workplace type (on-site/remote/hybrid) Β· seniority / job level (Entry, Associate, Mid-Senior, Director, Executive…) Β· employment type Β· job function Β· industries Β· full description (text, HTML, optional Markdown) Β· skills Β· applicant insights Β· salary (raw + min/max/location-aware currency/period).

Company: name Β· LinkedIn URL Β· official website Β· tagline Β· description Β· industry Β· size Β· employee count Β· headquarters Β· followers Β· type Β· specialties Β· logo Β· phone Β· public inbox.

Contacts & hiring team: recruiter email + phone for the role (from the description, or provider enrichment) Β· descriptionEmails / descriptionPhones Β· and, when logged in, the hiring team's name Β· headline Β· LinkedIn profile URL plus the derived recruiterName / recruiterProfileUrl.

See the full field list in Output.


🏁 Quick start

  1. Filter jobs on LinkedIn exactly how you want them.
  2. Copy the browser URL (it contains all your filters).
  3. Paste it into searchUrls and run.
{
"searchUrls": [
"https://www.linkedin.com/jobs/search-results/?keywords=Data%20Analyst&geoId=101452733&f_TPR=r86400"
],
"maxItems": 0,
"deepPagination": true,
"scrapeCompanyDetails": true,
"proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}

Prefer not to build a URL? Use the structured fields instead:

{
"keywords": "Data Analyst",
"country": "au",
"datePosted": "past24h",
"experienceLevels": ["entry", "associate"],
"workplaceTypes": ["remote", "hybrid"]
}

country: "au" targets all of Australia by its LinkedIn geoId β€” more reliable than free text. Ten countries are built in; use country: "custom" + geoId for any other region.


πŸŽ›οΈ Input reference (highlights)

FieldTypeNotes
searchUrlsstring[]One or more LinkedIn search URLs. Recommended.
keywords, location, country, geoIdstringBuild a search without a URL.
datePostedenumany / past24h / pastWeek / pastMonth.
experienceLevels, jobTypes, workplaceTypesstring[]Facet filters (used when building from keywords).
maxItemsint0 = scrape everything.
deepPaginationboolAuto-split to break the ~1,000 cap. Default on.
scrapeCompanyDetailsboolFetch each company page (cached). Default on.
useLoginSessionboolUnlock hiring team + applicant insights. See below.
liAtCookie, jsessionidCookiesecretYour LinkedIn session cookies.
enrichContactsboolFill recruiter email + company phone.
contactProviderenumnone / hunter / apollo.
hunterApiKey, apolloApiKeysecretProvider keys (bring your own).
proxyConfigurationproxyRESIDENTIAL strongly recommended.
maxConcurrency, minDelayMs, maxRequestRetriesintReliability tuning.

🧩 Deep pagination β€” capturing everything

LinkedIn's guest search returns at most ~1,000 results per query (40 pages Γ— 25) and gives no total count. NaΓ―ve scrapers just stop there and silently miss the rest.

When deepPagination is on and a search hits the cap, this Actor automatically partitions the query by the next unused discrete facet β€” first by the six experience levels, then by job type, then by workplace type. Every job belongs to exactly one bucket, results are deduplicated by job id, and the union is complete. If a fully-faceted slice still exceeds 1,000, the Actor logs a clear warning telling you to add one more filter (e.g. a tighter date window or a sub-region).


πŸ” Logged-in session (optional) β€” hiring team, applicants, skills

Some fields are only visible to a signed-in LinkedIn user: the "Meet the hiring team" poster and their profile, applicant insights, and skill data. Provide session cookies to unlock them:

  1. Log in to LinkedIn in Chrome (use a dedicated account β€” see the warning below).
  2. Open DevTools β†’ Application β†’ Cookies β†’ https://www.linkedin.com.
  3. Copy the value of li_at into liAtCookie.
  4. Copy the value of JSESSIONID (looks like "ajax:1234567890") into jsessionidCookie β€” keep the quotes.
  5. Set useLoginSession: true.

Both are stored as encrypted secrets. On startup the Actor launches a real headless Chrome, applies your cookies, and logs πŸ”“ LinkedIn login OK β€” signed in as <you> or a clear πŸ”’ login FAILED β€” so you instantly know whether the session works (stale cookies are the #1 cause of empty hiring-team fields). For each job it then opens the logged-in job page and reads the "Meet the hiring team" card (name + LinkedIn profile URL) and the true external apply URL that the public page hides behind a login wall.

Because logged-in mode drives a browser it's slower and needs more memory β€” run it with 2 GB+ RAM and a Residential proxy for best results. (linkedinHiringTeamQueryId is an optional advanced fallback for the API path and can be left blank.)

πŸ’‘ You often don't even need login for the recruiter's contact. Many postings β€” especially recruitment-agency listings β€” put the recruiter's email and phone in the description text. The Actor extracts those automatically into recruiterEmail / recruiterPhone (source job-description). See Contact enrichment below.

⚠️ Account risk. Scraping while authenticated can violate LinkedIn's User Agreement and may get the account restricted. Use a throwaway account you can afford to lose, and keep concurrency/minDelayMs gentle. Leave useLoginSession off for the safe, public-only crawl.


πŸ“§ Contact enrichment β€” email & phone, official sources only

Recruiter email and company phone are not published in LinkedIn's structured data, so this Actor sources them the legitimate way, in priority order, and leaves the field empty if nothing genuine is found (no guessed patterns, no grey-market lists):

  1. The job description itself (first-party, always legitimate, no key) β€” recruiters frequently write their email/phone into the posting ("send your resume to …", "call me on …"). The Actor pulls these into recruiterEmail / recruiterPhone (source job-description) plus full descriptionEmails / descriptionPhones arrays. This is the most reliable source of a role-specific contact.
  2. The company's own website (first-party, always legitimate) β€” reads tel: / mailto: links and schema.org Organization data (telephone, email, contactPoint) that the company itself published. No key required; enabled by scrapeCompanyWebsiteForContacts.
  3. A reputable provider you opt into (bring your own key):
    • Hunter.io β€” Email Finder returns a recruiter's professional email + confidence score from their name and the company domain. Docs.
    • Apollo.io β€” organization enrichment for a verified business phone, and people-match for email. Docs.

Set enrichContacts: true, pick contactProvider, and paste the matching API key. Enriched fields carry a recruiterEmailSource / confidence so you always know where a value came from.


πŸ“€ Output

One dataset item per job (undefined fields are omitted):

{
"jobId": "4436678938",
"title": "Senior Data Analyst",
"jobUrl": "https://www.linkedin.com/jobs/view/4436678938/",
"applyUrl": "https://boards.greenhouse.io/acme/jobs/12345",
"externalApplyUrl": "https://boards.greenhouse.io/acme/jobs/12345",
"companyApplyUrl": "https://boards.greenhouse.io/acme/jobs/12345",
"companyCareersUrl": "https://boards.greenhouse.io/",
"atsName": "Greenhouse",
"applyIsOffsite": true,
"easyApply": false,
"postedDate": "2026-07-12",
"postedTimeAgo": "1 day ago",
"expiresAt": "2026-08-12",
"location": "Sydney, New South Wales, Australia",
"workplaceType": "Hybrid",
"seniorityLevel": "Mid-Senior level",
"employmentType": "Full-time",
"jobFunction": "Analyst",
"industries": "IT Services and IT Consulting",
"descriptionText": "…",
"descriptionHtml": "…",
"skills": ["SQL", "Python", "Power BI"],
"applicantsCount": 200,
"applicantInsights": "Over 200 applicants",
"salaryText": "A$90,000 – A$110,000/yr",
"salaryMin": 90000, "salaryMax": 110000, "salaryCurrency": "AUD", "salaryPeriod": "year",
"companyName": "Acme Corp",
"companyLinkedinUrl": "https://www.linkedin.com/company/acme-corp/",
"companyWebsite": "https://www.acme-corp.com",
"companyIndustry": "Information Technology",
"companySize": "1,001-5,000 employees",
"companyHeadquarters": "Sydney, NSW",
"companyFollowers": 48213,
"companyPhone": "+61255501234",
"hiringTeam": [
{ "name": "Jane Recruiter", "headline": "Talent Acquisition @ Acme", "profileUrl": "https://www.linkedin.com/in/jane-recruiter/" }
],
"recruiterName": "Jane Recruiter",
"recruiterProfileUrl": "https://www.linkedin.com/in/jane-recruiter/",
"recruiterEmail": "jane@acme-corp.com",
"recruiterEmailSource": "hunter.io",
"recruiterEmailConfidence": 94,
"recruiterPhone": "61283195529",
"recruiterPhoneSource": "job-description",
"descriptionEmails": ["jane@acme-corp.com"],
"descriptionPhones": ["61283195529"],
"searchUrl": "https://www.linkedin.com/jobs/search-results/?keywords=Data%20Analyst&f_TPR=r86400",
"dataSource": "guest+loggedIn",
"scrapedAt": "2026-07-13T02:20:00.000Z"
}

A SUMMARY record (searches started, jobs enqueued/pushed, companies resolved) is written to the default key-value store at the end of every run.


πŸ› οΈ Deploy it (you push it yourself)

You need the Apify CLI and a free Apify account.

npm install -g apify-cli # 1. install the CLI
apify login # 2. authenticate (paste your API token)
cd linkedin-jobs-scraper # 3. from this project folder
apify push # 4. build & upload to your Apify account

apify push builds the Docker image on the platform and creates the Actor under your account. Open it in the Apify Console, run it from the auto-generated input form, and check the Dataset tab for results.

Run it locally first if you like:

npm install
npm run build
apify run -p # runs with your input.json against Apify proxy

Publish to the Store (to make it famous 🌟): in the Console open your Actor β†’ Publication β†’ set a title, description, categories, and pricing β†’ Publish. Full guide: https://docs.apify.com/platform/actors/publishing.

Memory for logged-in mode. The Actor ships on the Playwright + Chromium base image, so the logged-in browser layer works out of the box β€” no Dockerfile changes needed. When you enable the logged-in session, run it with 2 GB+ memory (choose it in the Console before Start). The public-only crawl runs fine on 512 MB–1 GB.


πŸ“ Project layout

.actor/
actor.json # Actor metadata
input_schema.json # 30 typed inputs (4 secrets)
dataset_schema.json # dataset views (overview + contacts)
Dockerfile # HTTP-first build (Playwright note inside)
src/
main.ts # entrypoint: input β†’ config β†’ crawler
router.ts # SEARCH + JOB handlers, pagination, deep-split
constants.ts # endpoints, filter maps, geoIds
types.ts # typed input / records
parsers/ # urlParser, guestList, guestDetail, companyParser
linkedin/voyager.ts # logged-in enrichment (best-effort)
enrichment/ # companySite, hunter, apollo, orchestrator
utils/text.ts # cleaning, salary parsing, helpers
smoke-test.mjs # offline test of the parsing logic (npm run build first)

Run the offline tests any time: npm run build && node smoke-test.mjs.


🧯 Reliability & troubleshooting

  • Blocking / few results β†’ use RESIDENTIAL proxy, lower maxConcurrency, raise minDelayMs. Datacenter IPs get blocked fast.
  • Missing hiring team / applicants β†’ those need useLoginSession with valid, fresh cookies.
  • Empty email/phone β†’ nothing legitimate was published or matched; that is by design.
  • Stopped at ~1,000 β†’ make sure deepPagination is on, or add a tighter filter.
  • The crawler auto-retires blocked sessions and retries with a new proxy up to maxRequestRetries.

βš–οΈ Compliance

This Actor is intended for lawful use on publicly available data. Scraping LinkedIn β€” especially while logged in β€” may conflict with LinkedIn's Terms of Service. You are responsible for how you use it, for respecting rate limits and robots directives, and for handling any personal data (recruiter names, profiles, enriched emails/phones) in line with applicable laws such as GDPR/CCPA. Enrichment uses official, opt-in provider APIs and first-party company data only.