LinkedIn Jobs Scraper + Recruiter & Hiring Signals avatar

LinkedIn Jobs Scraper + Recruiter & Hiring Signals

Pricing

from $1.00 / 1,000 job results

Go to Apify Store
LinkedIn Jobs Scraper + Recruiter & Hiring Signals

LinkedIn Jobs Scraper + Recruiter & Hiring Signals

Scrape public LinkedIn job listings by keyword, location, or URL into clean, CSV-ready rows with hiring-signal fields and visible recruiter indicators - no login, cookies, or Apify residential proxy.

Pricing

from $1.00 / 1,000 job results

Rating

0.0

(0)

Developer

Delowar Munna

Delowar Munna

Maintained by Community

Actor stats

0

Bookmarked

18

Total users

7

Monthly active users

12 days ago

Last modified

Share

LinkedIn Jobs Scraper + Recruiter & Hiring Signals

Turn public LinkedIn job listings into a ranked list of hiring companies. Search by keyword, location or company, or paste LinkedIn search, job or company URLs. You get one flat, deduplicated, spreadsheet-ready row per job, with:

  • the full description;
  • structured salary (min, max, currency and period);
  • the posting date and applicant count;
  • Easy Apply status;
  • the hiring company profile (website, size, employee count, industry, headquarters, type, founding year, followers);
  • the recruiter's name, title and profile link whenever LinkedIn shows them publicly;
  • a transparent 0–100 hiring-signal score with reason tags.

Every run also produces a company hiring summary: one line per hiring company, with jobs found, roles, locations, salary range, visible recruiters and signal strength. That turns a job list into an account list.

You can filter by work arrangement, employment type, experience level, Easy Apply, applicant count, visible recruiter or visible salary. You pay nothing for the rows you filter out, and the actor keeps searching until your requested number of matching jobs is saved. The actor needs no LinkedIn login or cookies and never visits anyone's profile.

Built for B2B sales and lead-gen teams, staffing agencies, recruiters and talent-market analysts.


✨ Why this actor

This actorA typical LinkedIn jobs scraper
Transparent 0–100 hiring-signal score + reason tags✅❌
Company hiring summary (one line per hiring company)✅❌
Tech-stack tags detected from each description✅❌
Only jobs with a visible recruiter filter✅❌
Only jobs with a visible salary filter✅❌
Explicit recruiter_visibility state (visible / not_visible)✅❌
Keyword × location lists in one run, plus pasted search and single-job URLs✅Partial
Structured salary (min / max / currency / period)✅Varies
Company profile on every row, included in the price✅Varies
Rows saved as they are found: a timeout never loses finished work✅Varies
Honest filters: checked on each job page, not only requested from LinkedIn✅Varies
Fully flat, snake_case rows that import straight into Sheets or Excel✅Often nested
Duplicates and filtered-out rows are never charged✅Varies
No login, no cookies, no profile visits✅Varies

🚀 Quick start

Provide at least one of keywords (with optional locations), companyUrls, or startUrls. Every other field has a sensible default.

Recipe 1: Daily recruiter-lead feed

New jobs from the last 24 hours that name a recruiter publicly. Schedule it daily.

{
"keywords": ["account executive", "sales development representative"],
"locations": ["United States"],
"datePosted": "past_24h",
"includeRecruiterVisibleOnly": true,
"maxResults": 200
}

Recipe 2: Salary-transparent roles for pay benchmarking

{
"keywords": ["data engineer"],
"locations": ["New York, NY", "San Francisco, CA"],
"requireSalaryVisible": true,
"maxResults": 300
}

Recipe 3: Remote tech roles with the tech stack tagged

{
"keywords": ["python developer", "devops engineer"],
"locations": ["United States"],
"workplaceTypes": ["remote"],
"datePosted": "past_week",
"maxResults": 250
}

Recipe 4: Competitor hiring watch

Every senior engineering role two companies posted this week, with their company profiles:

{
"companyUrls": ["https://www.linkedin.com/company/stripe", "https://www.linkedin.com/company/google"],
"keywords": ["engineer"],
"locations": ["United States"],
"experienceLevels": ["mid_senior", "director"],
"datePosted": "past_week",
"maxResults": 300
}

Recipe 5: Daily feed of only new jobs (scheduled)

Pass the job IDs you already have in skipJobIds (for example, the job_id column of yesterday's dataset). They're skipped before any page is fetched and never charged.

{
"keywords": ["registered nurse"],
"locations": ["Texas"],
"datePosted": "past_24h",
"employmentTypes": ["full_time"],
"skipJobIds": ["4445059134", "4453093350"],
"maxResults": 500
}

Recipe 6: Specific jobs, a company, or a search you built on LinkedIn

{
"startUrls": [
"https://www.linkedin.com/jobs/view/4445059134/",
"https://www.linkedin.com/company/openai/",
"https://www.linkedin.com/jobs/search/?keywords=nurse&location=Texas&f_TPR=r604800"
],
"includeDescriptionHtml": true
}

📦 What data you get

One flat row per job, 50 fields. The availability column shows what to expect. "Detail" means the field is read from the job's public detail page, which is visited when Visit job detail pages (includeDescription) is on (the default).

The percentages come from our own sample runs on US and Australian searches (September 2026). They vary by market and role.

Job & posting

FieldDescriptionAvailability
job_id, job_urlLinkedIn job ID and canonical job URLAlways
title, locationJob title and location textAlways
workplace_type, remote_signalremote / hybrid / onsite / unknown, inferred from the title, location and descriptionAlways (often unknown)
employment_typefull_time, part_time, contract, temporary, internship, volunteer, otherDetail · ~100%
seniority_levelentry, associate, mid, senior, director, executive. From LinkedIn's criteria, otherwise inferred from the title~60–70%
job_function, industriesLinkedIn job function and industries (text)Detail · ~100%
posted_atPosting date, YYYY-MM-DD. Exact from the search card; estimated from "2 weeks ago" text for single-job URLs~100%
posted_date_textPosting date as LinkedIn shows it ("18 hours ago")~100%
applicants_countInteger. "Over 200 applicants" → 200 (a lower bound). null for "Be among the first 25 applicants", where the real count is unknownDetail · ~55–85%
applicants_textApplicant caption as shownDetail · ~100%
easy_applytrue = LinkedIn Easy Apply, false = applies on the company siteDetail · ~100%
apply_urlApply link. The LinkedIn job URL for Easy Apply jobs. LinkedIn hides external company apply URLs from logged-out visitors, so it is usually null for easy_apply: false jobsDetail · Easy Apply jobs only
description_textFull plain-text descriptionDetail
description_htmlSanitized HTML description. Opt-in via includeDescriptionHtmlDetail · opt-in

Salary

FieldDescriptionAvailability
salary_textSalary exactly as found: LinkedIn's pay block, or a pay range stated in the description~50% of US / UK / Canada roles (~75% of US tech roles)
salary_visibletrue only when explicit pay text was found. Never estimatedAlways
salary_min, salary_maxNumbers. A single amount sets bothSame rows as salary_text
salary_currencyISO code (USD, AUD, CAD, GBP, EUR, …). A bare $ is resolved from the job's country. null when that's ambiguous~95% of salary rows
salary_periodyear, month, week, day or hour, when stated~40–60% of salary rows

Company

FieldDescriptionAvailability
company_name, company_linkedin_urlHiring company and its LinkedIn pageAlways
company_logo_urlCompany logo image URL~100%
company_idLinkedIn's numeric company IDCompany details · ~70–85%
company_websiteThe company's own websiteCompany details · ~70–85%
company_sizeSelf-reported size bracket, e.g. 1,001-5,000 employeesCompany details · ~70–85%
company_employee_countEmployees on LinkedIn (members who list the company)Company details · ~70–85%
company_industryCompany industryCompany details · ~70–85%
company_headquartersHeadquarters, e.g. Houston, TexasCompany details · ~70–85%
company_typePublic Company, Privately Held, Nonprofit, …Company details · ~70–85%
company_founded_yearFounding yearCompany details · ~65%
company_descriptionThe company's "About" textCompany details · ~70–85%
company_followersLinkedIn followersCompany details · ~70–85%

"Company details" fields come from the company's public LinkedIn page when Include company details (includeCompanyDetails) is on (the default). Each company is fetched once per run and reused across its jobs. The fields are null when the option is off or the company has no public page.

Recruiter (public job page only)

FieldDescriptionAvailability
recruiter_nameName of the job poster / hiring-team member shown on the public job pageDetail · ~10% of jobs (higher for staffing agencies and senior roles)
recruiter_titleThat person's public headline as shown on the job pageSame rows as recruiter_name
recruiter_profile_urlTheir LinkedIn profile linkSame rows as recruiter_name
recruiter_visibilityvisible or not_visibleAlways

Signals & provenance

FieldDescription
tech_keywordsTechnologies detected in the title + description (; -separated), e.g. python; aws; kafka
hiring_signal_score, hiring_signal_label, reason_tagsSee Hiring-signal score below
source_url, input_typeThe search or job URL that produced the row, and generated_search / search_url / job_url
search_keyword, search_locationThe keyword and location that produced the row (for keyword runs and pasted search URLs)
scraped_atISO timestamp of extraction

Sample row

Real output with long text shortened. The recruiter's name and profile are anonymized here:

{
"job_id": "4459421211",
"job_url": "https://www.linkedin.com/jobs/view/4459421211",
"apply_url": null,
"easy_apply": false,
"source_url": "https://www.linkedin.com/jobs/search/?keywords=data+analyst&location=United+States",
"input_type": "generated_search",
"search_keyword": "data analyst",
"search_location": "United States",
"title": "Digital - Senior Data Analyst / Data Analyst",
"company_name": "Aritzia",
"company_linkedin_url": "https://www.linkedin.com/company/aritzia",
"company_logo_url": "https://media.licdn.com/dms/image/v2/C560BAQGNrxsJOe6Buw/company-logo_...",
"company_id": "23840",
"company_website": "http://www.aritzia.com",
"company_size": "5,001-10,000 employees",
"company_employee_count": 8741,
"company_industry": "Retail",
"company_headquarters": "Vancouver, BC",
"company_type": "Public Company",
"company_founded_year": 1984,
"company_description": "ABOUT ARITZIA Aritzia is a design house with an innovative global plat...",
"company_followers": 341240,
"location": "Seattle, WA",
"workplace_type": "unknown",
"remote_signal": "unknown",
"employment_type": "full_time",
"seniority_level": "senior",
"job_function": "Information Technology",
"industries": "Retail Apparel and Fashion",
"posted_at": "2026-08-28",
"posted_date_text": "3 weeks ago",
"applicants_count": 200,
"applicants_text": "Over 200 applicants",
"description_text": "THE BUSINESS We are Everyday Luxury. It's what we do and how we do it...",
"description_html": null,
"salary_text": "$100,000-$300,000 per year",
"salary_visible": true,
"salary_min": 100000,
"salary_max": 300000,
"salary_currency": "USD",
"salary_period": "year",
"recruiter_name": "Jane Doe",
"recruiter_title": "Senior Technical Recruiter at Aritzia",
"recruiter_profile_url": "https://www.linkedin.com/in/jane-doe-example",
"recruiter_visibility": "visible",
"tech_keywords": "tableau; power bi; looker",
"hiring_signal_score": 65,
"hiring_signal_label": "medium",
"reason_tags": "high_competition; recruiter_visible; salary_visible; complete_job_detail; company_profile; external_apply; tech_role",
"scraped_at": "2026-09-25T15:20:57.216Z"
}

🎯 Hiring-signal score

The score is a transparent, rule-based 0–100 value computed from each job's own fields: no AI and no external lookups. It rewards what makes a job worth acting on: fresh, low-competition, reachable (named recruiter) and concrete (pay, full detail). It's included when includeHiringSignals is on (the default). When it's off, the three signal fields are null.

SignalPoints
Freshness: posted ≤ 1 day / ≤ 3 days / ≤ 7 days / ≤ 30 days20 / 15 / 10 / 5
Competition: under 25 applicants / ≤ 100 / ≤ 200 / over 20015 / 10 / 5 / 0
Recruiter visible (+5 more when their title is shown)15 (+5)
Salary visible (+5 more when parsed into min/max)10 (+5)
Full description (over 300 characters)10
Employment type or seniority known5
Company profile attached5
Director or executive role5
Apply path known (Easy Apply or external)5

Labels: high (70–100) · medium (40–69) · low (0–39). In our sample runs, about 13% of jobs scored high, 76% medium and 11% low (54 jobs). Sort by hiring_signal_score to rank.

reason_tags (; -separated) explains each row:

TagMeaning
fresh_postingPosted within 3 days
recent_postingPosted within 7 days
low_competitionUnder 25 applicants
high_competitionOver 200 applicants
recruiter_visibleA recruiter is named on the job page
salary_visibleThe pay is published
complete_job_detailA full description is present
company_profileThe company profile is attached
senior_roleDirector or executive role
easy_apply / external_applyApplies on LinkedIn / on the company site
remote_role / hybrid_roleWork arrangement
tech_roleTechnologies detected

📊 Outputs: views & summaries

OutputWhat it is
All job fields (dataset view overview)Every saved job, all 50 columns
Recruiter leads (view recruiters)Recruiter name, title and profile first, then company and job. Filter recruiter_visibility = visible (or run with includeRecruiterVisibleOnly) for recruiter-named jobs only
Hiring signals (view signals)Score, label and reason tags next to the job, applicants, salary and recruiter
Company profiles (view companies)The company profile columns for each job
COMPANY_HIRING_SUMMARY (key-value store)One entry per hiring company in this run (see below)
RUN_SUMMARY (key-value store)Run counters (see API & AI agents)

Company hiring summary

COMPANY_HIRING_SUMMARY is a JSON array with one flat entry per company. It's sorted by jobs found, then by the highest hiring score, and it always matches the saved dataset.

FieldDescription
company_name, company_linkedin_url, company_id, company_website, company_industry, company_size, company_employee_count, company_headquartersCompany identity and profile
jobs_in_runSaved jobs from this company
role_categoriese.g. engineering; sales
locationsJob locations (up to 25)
remote_or_hybrid_jobs, recruiter_visible_jobs, salary_visible_jobsCounts
recruiter_namesRecruiters named on this company's job pages
salary_min_lowest, salary_max_highest, salary_currency, salary_periodPay range across its jobs. Only given when all of them use the same currency and pay period
max_hiring_signal_score, avg_hiring_signal_scoreSignal strength
latest_posted_atMost recent posting date
job_idsThe saved job IDs (up to 25)

It's written every 50 saved jobs, at the end of the run, and if the run is aborted.


⚙️ Inputs & filters

InputWhat it does
keywords, locationsEvery keyword is searched in every location (up to 50 of each)
companyUrlsOnly jobs from these companies (LinkedIn company page URLs). Combined with keywords × locations; without keywords, every job at those companies
startUrlsLinkedIn job search URLs (their filters are kept), single job URLs (/jobs/view/{id}), and company pages (/company/{name}, meaning every open job there)
maxResultsMaximum unique jobs saved (after filters) for the whole run (1–5,000)
datePostedany / past_24h / past_week / past_month
workplaceTypesAny of remote, hybrid, onsite
employmentTypesAny of full_time, part_time, contract, temporary, internship, volunteer, other
experienceLevelsAny of internship, entry, associate, mid_senior, director, executive
easyApplyOnlyOnly LinkedIn Easy Apply jobs
maxApplicantsOnly jobs with at most this many applicants (0 = off)
includeRecruiterVisibleOnlyOnly jobs with a public recruiter name or profile link
requireSalaryVisibleOnly jobs with explicit salary text
includeDescriptionVisit each job's detail page (description, criteria, applicants, salary, apply info, recruiter). On by default
includeDescriptionHtmlAlso return description_html. Off by default
includeCompanyDetailsAdd the company profile columns. On by default
Advanced: includeHiringSignals, deduplicate, skipJobIds, proxyConfigurationSignal fields on/off; cross-input deduplication (recommended on); job IDs/URLs to skip (never fetched or charged); proxy

How each filter is applied

LinkedIn's public (logged-out) search honours only some filters. We tested each one against real results (September 2026), and every filter is re-checked on the extracted job. You never get a job that merely "should" match.

FilterNarrowed by LinkedIn's searchChecked by the actor on each job
datePosted✅✅ (posting date)
companyUrls / company startUrls✅ (one search per company)—
easyApplyOnly✅✅
workplaceTypes❌ ignored by LinkedIn for guests✅ workplace wording in the title, location and description
employmentTypes❌ ignored✅ LinkedIn's "Employment type"
experienceLevels❌ ignored✅ LinkedIn's "Seniority level". When LinkedIn says "Not Applicable", the level implied by the job title is used
maxApplicants, recruiter-only, salary-only❌ not a LinkedIn filter✅

For filters LinkedIn ignores, the actor reads more job pages than it saves. It keeps going until maxResults matching jobs are saved, the searches run out, or its safety budget of 15 job pages per requested result is used up. The budget is recorded as detail_budget_exhausted in RUN_SUMMARY. Filtered-out jobs are never saved or charged.


💰 Pricing

Pay per result. You pay for each unique job saved to your dataset, and nothing else.

One price on every Apify plan, with no tier differences:

ResultsPrice
100$0.10
1,000$1.00
10,000$10.00
  • Event: job-result, charged once per unique job that passed your filters and was saved.
  • Start fee: Apify's minimal actor-start event ($0.00005 per GB of memory), effectively zero.
  • Never charged: duplicates, rows removed by your filters, jobs in skipJobIds, rows missing a job ID or title, jobs whose detail page could not be loaded, and failed or blocked requests.
  • Company details: included in the result price.
  • Spending cap: the actor respects your per-run spending limit. It stops collecting once the limit can't pay for more results.

🔌 API & AI agents

Run it from any language through the Apify API, and get results in one call:

curl -X POST "https://api.apify.com/v2/acts/coregent~linkedin-advanced-jobs-recruiter-details-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"keywords":["data engineer"],"locations":["United States"],"maxResults":50}'
  • MCP: the actor is available through Apify's MCP server (https://mcp.apify.com). Claude, ChatGPT, Cursor and other MCP clients can call it as a tool, with the input schema discovered automatically.
  • Integrations: schedule runs and push results to Google Sheets, Slack, Zapier, Make, n8n, webhooks and more from the Apify Console.
  • Exports: JSON, CSV, Excel, XML, HTML. Every field is flat, so CSV columns map one-to-one.

Rows are written to the dataset as they are found, so if a run times out or is aborted you keep, and pay only for, everything saved up to that point. The RUN_SUMMARY and COMPANY_HIRING_SUMMARY records are refreshed every 50 saved jobs as well.

After each run a RUN_SUMMARY record is written to the key-value store, including results_saved, charged_events, duplicates_removed, filtered_out, skipped_by_skip_ids, detail_failed, detail_budget_exhausted, companies_enriched, blocked_requests, retry_count and runtime_seconds.


❓ FAQ

Do I need a LinkedIn account or cookies? No. The actor uses only LinkedIn's public, logged-out job pages.

Why are recruiter fields usually empty? LinkedIn shows the job poster on the public page for only about 5–10% of jobs, more often for staffing agencies and senior roles. The actor never logs in or visits profiles, so when the poster isn't public the fields stay null and recruiter_visibility is not_visible.

Why is apply_url often empty? For jobs that apply on the company's own site, LinkedIn hides the external URL from logged-out visitors. easy_apply: false still tells you the job applies off LinkedIn.

Why did I get fewer rows than maxResults? The actor keeps searching until maxResults matching jobs are saved. It stops earlier only when:

  • the searches run out (each LinkedIn search returns at most about 1,000 jobs); or
  • very strict filters use up the safety budget of 15 job pages per requested result (detail_budget_exhausted: true in RUN_SUMMARY).

Broaden the keywords, locations or filters to get more.

How do I get only new jobs on a scheduled run? Use datePosted: "past_24h" and pass the IDs you already have in skipJobIds. Skipped jobs are never fetched in detail, saved or charged.

Can I sort by newest? LinkedIn's public search ignores the sort option for logged-out visitors (we tested it). Use datePosted (past_24h / past_week) together with skipJobIds for fresh-only feeds, and sort by posted_at in your spreadsheet.

Why do experience-level filters drop so many jobs? Many employers leave LinkedIn's seniority as "Not Applicable". The actor then falls back to the level implied by the job title ("Senior …", "Director of …"). Jobs where neither gives a level are dropped rather than guessed.

Is salary_min/salary_max ever estimated? Never. Salary fields are filled only from pay text the employer published.

Can I export to CSV / Excel? Yes. Every field is flat, so use the Apify export or call the dataset API with format=csv.


🚧 Limitations

  • Public, logged-out data only. Member-only content is never accessed.
  • LinkedIn returns at most about 1,000 results per search. Split broad searches into narrower keyword × location pairs for more coverage.
  • maxResults caps saved unique rows across the whole run (1–5,000).
  • Work arrangement, employment type and experience level are not applied by LinkedIn's public search, so they're checked on each job page. That costs more page visits, not money: filtered-out jobs are free.
  • Sort by newest isn't available (LinkedIn ignores it for logged-out visitors).
  • Recruiter data is sparse by nature (see FAQ). External apply URLs are usually hidden from logged-out visitors.
  • Detail-only fields need a detail-page visit, so they stay null when includeDescription is off and no detail-dependent option is on.

⚖️ Responsible use

  • The actor collects only job postings that employers published publicly.
  • What we don't do: no login, no cookies, no visiting personal profiles, no guessed or enriched emails or phone numbers, and no profile photos.
  • Rows can still contain personal data, such as a recruiter's name, headline and profile link as shown on a public job page. That data is protected by the GDPR and similar laws. Only collect and use it with a legitimate purpose, and follow LinkedIn's terms and the laws that apply to you.

🚦 Proxy

Apify Proxy (Datacenter) is used by default and works for typical runs. Apify Residential proxy is not supported, and the run stops at startup if it's selected. If you need residential routing, add your own provider's URLs under Custom proxy URLs (e.g. http://user:pass@proxy.example.com:8000).


📝 Changelog

1.1

  • New fields:
    • posted_at (ISO date) and applicants_count (integer);
    • structured salary: salary_min, salary_max, salary_currency, salary_period;
    • easy_apply, company_logo_url, recruiter_title;
    • provenance: search_keyword, search_location;
    • opt-in description_html.
  • Salary detection now ignores company figures that aren't pay (funding rounds, perks such as "$6 dinners") and handles A$, CA$, S$ and similar prefixes.
  • Removed is_filtered_out: it was always false, because filtered rows are never saved.
  • Columns reordered into groups: identity, company, job, posting, description, salary, recruiter, signals.
  • RUN_SUMMARY now correctly counts requests that failed after all retries.
  • Company details (includeCompanyDetails, on by default): company_id, company_website, company_size, company_employee_count, company_industry, company_headquarters, company_type, company_founded_year, company_description, company_followers.
  • New inputs: companyUrls; company pages in startUrls; multi-select workplaceTypes, employmentTypes and experienceLevels; easyApplyOnly; maxApplicants; skipJobIds. remoteMode still works as an alias of workplaceTypes.
  • Rows are saved as they are found, so a timeout or abort keeps everything already saved.
  • Filters no longer shrink results: the actor keeps searching until maxResults matching jobs are saved, within a detail-page budget.
  • Hiring-signal score v2: freshness, applicant competition, recruiter reachability, pay transparency and detail completeness replace v1's mostly "field exists" points. New tags: fresh_posting, low_competition, high_competition, company_profile, senior_role, easy_apply/external_apply, remote_role/hybrid_role. external_apply_url is removed (it wrongly fired for Easy Apply jobs). v2 scores are not comparable with 1.0 scores.
  • New outputs: the COMPANY_HIRING_SUMMARY record, and the dataset views Recruiter leads, Hiring signals and Company profiles.
  • Accuracy fixes:
    • Jobs whose detail page fails to load are no longer saved half-empty or charged.
    • "Remote/hybrid/on-site" is no longer assumed from the search filter (LinkedIn ignores it); it's always read from the job itself.

1.0

  • Initial release.