LinkedIn Jobs Scraper - Full Descriptions & Job Feeds avatar

LinkedIn Jobs Scraper - Full Descriptions & Job Feeds

Pricing

from $0.90 / 1,000 jobs

Go to Apify Store
LinkedIn Jobs Scraper - Full Descriptions & Job Feeds

LinkedIn Jobs Scraper - Full Descriptions & Job Feeds

Search public LinkedIn jobs with full descriptions, company data, source filters and persistent only-new monitoring. No LinkedIn login or third-party API key.

Pricing

from $0.90 / 1,000 jobs

Rating

0.0

(0)

Developer

Ben

Ben

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

3 days ago

Last modified

Share

LinkedIn Jobs Scraper: full descriptions and new-job monitoring

Search public LinkedIn job postings and export a consistent dataset with the full public description, employer, location, posting date and employment criteria. Use keywords and a location, or provide exact job URLs. Enable onlyNew to remember exported job IDs and omit them from later runs of the same saved search.

This Actor retrieves LinkedIn's public job-search cards and public job-detail pages directly. It does not require a LinkedIn login, session cookie or separate data-provider API key. Default runs use HTTP without a browser or proxy. Optional proxy configuration is available for accounts that need a different source route.

For recurring hiring feeds: collect full public job descriptions and retain job IDs between runs with onlyNew. Result events cost $1.80 per 1,000 jobs on Free or $0.90 on paid plans, plus the start event. This Actor returns jobs, not personal profiles or candidate contact details.

Quick start

Start with a tested example for a Berlin Python job feed:

Open the example and choose Try for free to copy its input into your account. Check the input and set a maximum run charge before starting; Actor fees still follow the pricing below. When the run finishes, open the run's dataset and select the Overview view and download JSON for nested fields or CSV/Excel for a table.

Your goalInput to use
Find recent jobskeywords, location and timeRange
Read known postingsjobUrls; this bypasses search filters
Receive only new IDsonlyNew with a separate monitorId per search

The real output example shows the record shape. The Python workflow saves your own export to a local JSON file with a $0.05 maximum Actor charge.

{
"keywords": "python",
"location": "Berlin, Germany",
"maxResults": 3,
"maxPages": 2,
"timeRange": "week",
"sortBy": "date",
"strictKeywords": true,
"onlyNew": false
}

The Actor asks LinkedIn for recent Python jobs in Berlin, reads the public descriptions, checks the keywords, and returns up to three matching jobs. It examines no more than two search pages. Fewer results can be correct: the source may have fewer matches, repeat an earlier page, or return recommendations that do not satisfy the strict keyword check.

Inspect the first dataset before increasing the cap. The overview table shows title, employer, location, posting date, employment type, salary text and the source link. JSON contains the full description and additional fields. Export CSV, Excel or JSON from Apify, or consume the default dataset through its API.

Why check the keywords again?

LinkedIn's guest endpoint can return unrelated recommendations even for a nonsense keyword. A technically successful HTTP request does not establish that those jobs match your search. With strictKeywords: true, every whitespace-separated keyword word must appear in the title or public description, ignoring case and respecting word boundaries. For example, Java does not match a record solely because it mentions JavaScript.

This is a literal text check, not a relevance score. Use simple words such as python or software engineer. For LinkedIn-specific Boolean syntax or a broader source-defined search, turn strict matching off and inspect the resulting relevance yourself. The Actor does not replace missing matches with invented listings.

Example output

One record from a verified September 9, 2026 cloud run. Only the description is shortened here; the Actor exports the complete public description, including paragraph breaks. The dated vacancy may no longer be open.

{
"job_id": "4462232588",
"title": "Python Developer",
"company": "Russell Tobin",
"company_url": "https://www.linkedin.com/company/russell-tobin-&-associates-llc",
"location": "Berlin, Germany",
"posted_date": "2026-09-07",
"posted_text": "1 day ago",
"salary": null,
"url": "https://www.linkedin.com/jobs/view/4462232588",
"description": "Role: Software Engineer Employment Type: Contract – 12 Months minimum Location: Germany - Berlin, Cologne, Frankfurt, Dusseldorf, Munich, Hamburg, Stuttgart Travel & Mode of work: Travel required at client’s locations & Hybrid mode of work Experience required – 5-12 years Max NOTE – No architect, Manager, Full stack, Frontend required Required- have experience using tools like Python, Kedro, AWS, Azure, Databricks, Docker, Kubernetes, Django, Flask, and MySQL / Postgres advocate using the right tech for the right task—you’ll be up to date with the latest [description shortened in this documentation example]",
"applicants_text": "100 applicants",
"apply_url": null,
"seniority": "Mid-Senior level",
"employment_type": "Contract",
"job_function": null,
"industries": "Technology, Information and Media",
"scraped_at": "2026-09-09T17:50:26.850503+00:00",
"is_new": false,
"monitor_id": null
}

Returned fields

FieldMeaning
job_idLinkedIn's numeric job ID, returned as a string
titleTitle from the job-detail page
company, company_urlEmployer name and public LinkedIn company URL
locationLocation text supplied by LinkedIn
posted_dateCalendar date from the search card, when available
posted_textSource wording such as “4 days ago”
descriptionComplete public description as plain text, retaining paragraph and list breaks
seniority, employment_typeExplicit job criteria from the detail page
job_function, industriesSource job criteria, when exposed
salarySource salary text, without guessing a currency or annualizing a range
applicants_textSource applicant wording; it may be a range rather than an exact count
apply_urlExternal application URL only when the public page exposes it
urlStable LinkedIn job URL without search tracking parameters
scraped_atUTC collection timestamp
is_new, monitor_idOptional monitor metadata

Missing source fields are not inferred. A direct job URL may have relative posting text without a machine-readable calendar date. Salary and external application URLs are not guaranteed. Company information describes the employer named on the posting; the Actor does not identify employees or infer their contact addresses.

Source filters

location is passed to LinkedIn as entered. timeRange accepts all, day, week or month; sortBy accepts date or relevance. Available posting dates are also checked against the selected recency window at calendar-day precision. Date-only source data cannot establish an exact elapsed-hour boundary.

Optional array filters use LinkedIn's source values:

InputAccepted values
workTypes1 onsite, 2 remote, 3 hybrid
jobTypesF full time, P part time, C contract, T temporary, I internship, V volunteer, O other
experienceLevels1 internship, 2 entry, 3 associate, 4 mid senior, 5 director, 6 executive

These filters are submitted to the source. The Actor preserves the returned criteria instead of claiming that every employer has categorized its listing correctly. An empty filter array means any value. Keep the query and all filter settings stable when comparing recurring snapshots.

Exact job URLs

{
"jobUrls": ["https://www.linkedin.com/jobs/view/4462980082"],
"maxResults": 1
}

That ID came from a real September 9, 2026 local source check for a Software Engineer (Python) role. It is a dated example, not a promise that the vacancy remains open. Replace it with a current public job URL. Numeric IDs are also accepted. Direct mode bypasses search, keyword and recency filters, deduplicates repeated IDs, and verifies that each detail response belongs to the requested job.

Recurring only-new feed

{
"keywords": "python",
"location": "Berlin, Germany",
"timeRange": "week",
"maxResults": 25,
"maxPages": 5,
"onlyNew": true,
"monitorId": "berlin-python-jobs",
"firstRunMode": "seedOnly"
}

Save this input as an Apify Task and schedule it after inspecting a small run. A seedOnly first run records its snapshot without returning the existing backlog. Later runs compare their bounded snapshots against the saved IDs. Use emitAll to export the initial snapshot instead.

Monitor state lives in a named key-value store in the account running the Actor. Different customers do not share IDs. Use one explicit monitorId per search, avoid overlapping runs, and create a new ID when changing filters. resetState: true clears only the selected baseline; turn it off again before scheduling.

The monitor retains at most 5,000 IDs for 90 days. An ID that ages out can appear as new if the source returns it again. maxResults bounds the collected snapshot before only-new filtering, so an unchanged run can correctly produce zero rows. It does not continue indefinitely until it finds that many unseen jobs. Increase the snapshot/page limits when the current window is too small for your use case.

Only successfully exported IDs are marked seen after a spending-limit stop. Unexported jobs remain eligible for a later run. A source failure stops collection before the monitor baseline is updated. This is a new-job feed; it does not detect every description edit or treat a missing search result as proof that a job closed.

Pricing and limits

Free-plan pricing is $0.0018 per exported job, or $1.80 per 1,000 jobs. Bronze, Silver, Gold, Platinum and Diamond pay $0.0009 per job, or $0.90 per 1,000 jobs. Full public descriptions and monitor mode are included; there is no separate description or monitoring event.

At 512 MB, the start event is $0.00005 on Free, $0.000045 on Bronze, $0.00004 on Silver and $0.000035 on Gold, Platinum or Diamond. A ten-job export costs $0.01805 on Free or $0.009035 on Gold. A seed-only run or an unchanged repeat has no result charge, but still has the start charge. Larger memory settings use one start event per GB, with a minimum of one. These are pricing examples, not forecasts of output or future bills; inspect Apify's current pricing panel and set a maximum run charge.

The result cap is 500 and the page cap is 50. A page currently contains about ten search cards, but the source can change that. Requests run sequentially with a delay. Default memory is 512 MB and the default timeout is five minutes; large searches or slow routes can require a larger timeout. Optional proxy availability and usage depend on your account. No paid external data provider is required by the default engine.

Python API workflow

Copy the first input JSON above into input.json and save this code as export.py. In a Python virtual environment, install apify-client with python -m pip install apify-client. Set APIFY_TOKEN to your own Apify token in your environment, then run python export.py. Keep the token out of source files and shared screenshots.

import json
import os
from decimal import Decimal
from pathlib import Path
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("benthepythondev/linkedin-jobs-scraper").call(
run_input=json.loads(Path("input.json").read_text(encoding="utf-8")),
max_total_charge_usd=Decimal("0.05"),
memory_mbytes=512,
timeout_secs=300,
)
if not run or run["status"] != "SUCCEEDED":
raise RuntimeError(f"Inspect the run before retrying: {run and run['id']}")
rows = list(client.dataset(run["defaultDatasetId"]).iterate_items())
Path("berlin-python-jobs.json").write_text(
json.dumps(rows, ensure_ascii=False, indent=2), encoding="utf-8"
)
print(f"Saved {len(rows)} records from run {run['id']}")

This starts a run in your account, waits for completion and saves berlin-python-jobs.json. The $0.05 limit bounds Actor charges; it is not a promised bill or result count. Check the printed run ID and server log if the result is shorter than expected. If your client loses its connection, inspect the existing run in Console before starting another one. See the official Python client for other run options.

Use job_id as the destination key and keep the complete description. The example exports a snapshot. To create a recurring feed, first test the documented only-new input, then save it as your own Task. An unchanged monitor can return zero rows. A job absent from one bounded search is not confirmed closed.

For recurring runs without writing a scheduler, copy a tested public Task into your account and attach an Apify Schedule to that copy. Keep its maximum-charge limit and avoid overlapping runs when using monitor state. The Python example above is also suitable for an existing scheduler; run it from the directory containing input.json and provide the token through that scheduler's secret settings.

A blocked, rate-limited or malformed source page is surfaced as a failed run rather than a successful empty export. Temporary rate limits and server errors receive bounded retries. Persistent restrictions, removed direct jobs and missing descriptions still need attention; reduce the scope or inspect the failed run before retrying. No scraper can promise uninterrupted source access.

For a reproducible issue, open an Actor issue with the run URL, relevant input and expected result. Do not include credentials. An honest review after a useful export helps other users evaluate the Actor.

For other job sources, see the Remote Jobs feed and Greenhouse jobs. They have separate source coverage and pricing. This Actor covers public job advertisements; it does not scrape private LinkedIn profiles, employee lists or messages.

Keywords: LinkedIn jobs scraper, LinkedIn job search API, public job postings, full job descriptions, hiring data, job monitoring, new job alerts, recruitment data, LinkedIn jobs CSV, Berlin Python jobs.