Blog

Pull LinkedIn job postings into a spreadsheet, on a schedule

Updated

This post shows how to pull public LinkedIn job postings for a search and location, export the rows to a spreadsheet, and repeat the run on a schedule. You get one flat row per posting with the core listing fields and, when full detail mode is on, descriptions, salary data, skills, and company details.

If you are tracking a hiring market, the job you actually want is boring: get postings for a role and location into a sortable file, then keep it fresh. Writing it yourself leaves you responsible for collection code and repairs whenever public markup shifts.

The LinkedIn Jobs Scraper handles the collection. You give it the same search terms and filters you would use on LinkedIn, then read the resulting Apify dataset as JSON, CSV, Excel, or XML.

Which fields do you get back?

The Actor returns one flat record for each public job posting it finds. The main output fields documented by the Actor are:

Missing source data stays missing. A posting with no stated salary does not acquire an estimated one in the dataset. Basic mode deliberately returns fewer fields, while full detail mode makes another request for the job page and can collect the description, salary, skills, and company details.

The call

The scraper takes a search query and a location, which are the same two things you would type into the site:

curl -X POST "https://api.apify.com/v2/acts/parseforge~linkedin-jobs-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "searchQuery": "software engineer",
    "location": "United States",
    "datePosted": "anyTime",
    "maxItems": 50
  }'

The response is the dataset: a JSON array, one object per posting. There is no pagination for your client to handle and nothing to parse out of LinkedIn markup.

Which input options control the run?

The field names matter when you save input or call the API. Defaults below refer to declared schema defaults. A form prefill is called out separately when the schema does not declare one.

The non-obvious option is includeBlockedJobs. Some public listings block access to the detail page. Turning this on keeps their basic title, company, location, and link in the dataset and marks the row with isBlocked=true, instead of losing the listing because its full description could not be fetched.

datePosted is the filter to set deliberately for recurring collection. Use pastDay or pastWeek to limit results by when the jobs were posted.

Getting it into a spreadsheet

Every run writes to a dataset that exports in other formats. Swap the endpoint and you get a CSV instead of JSON:

https://api.apify.com/v2/datasets/<DATASET_ID>/items?format=csv

That URL is stable, so a spreadsheet can pull it directly. In Google Sheets, IMPORTDATA against it gives you a sheet that refreshes from the dataset. You can also download CSV or Excel from the Dataset tab.

Running it nightly

Apify’s scheduler can run the Actor hourly or daily and keep each run’s dataset. This gives you recurring collection without hosting a server.

For a market tracker, keep the saved input fixed long enough to compare runs. Changing a location, workplace filter, or salary floor changes the population you are measuring. Create a separate saved task when you need a second market rather than editing the first one back and forth.

How much does it cost?

Result rows start at $0.70 per 1,000 results, based on pricing.perResultUsd of $0.000699. A run also has an Actor-start charge event, which currently starts at $0.099 and varies with the Actor memory used. Apify’s free monthly credit covers a trial, so start with the form prefill of 10 rows before scheduling a larger collection.

The final cost depends on rows written, plus the start event. Basic mode is 5 to 10 times faster when its four fields are enough.

What breaks and what we do about it

No results usually means the same search and location produce no public results on LinkedIn, or the filters are too narrow. Test the combination on LinkedIn, then broaden the location or remove Easy Apply and high salary filters.

Missing full details are different from missing search results. Some job pages block detail access. Set includeBlockedJobs to retain the basic row with isBlocked=true, or run in basic mode when detail fields are not required.

Slow runs usually come from full detail collection. Disable scrapeJobDetails for the documented 5 to 10 times speed increase. Raising maxConcurrency can speed up requests, but it also increases the chance of blocks.

Blocks or empty results after many runs call for less concurrency. The README recommends lowering maxConcurrency to a value such as 5. We maintain the Actor when LinkedIn changes its public markup, while your integration continues to call the same Actor endpoint and consume dataset rows.

The scope remains public job pages. The Actor needs no LinkedIn account or cookies. It does not use LinkedIn API credentials or fetch content behind a login.

FAQ

Do I need a LinkedIn account or API key?

No. The Actor reads public LinkedIn job search pages without a login or cookies. It does not use LinkedIn API credentials. You only need an Apify account and token to call the Apify API.

Can I scrape jobs from a specific company?

Yes. Put the company name in searchQuery, then add location if you want to narrow the company’s public postings to a region.

What is the difference between basic and full detail mode?

Basic mode returns title, company, location, and link and runs 5 to 10 times faster. Full detail mode also fetches descriptions, salary data, skills, and company details when they are available.

Can I schedule this scraper to run daily?

Yes. Use Apify’s scheduler to run the Actor hourly or daily and keep each run’s dataset.

Open the Apify listing, run the 10-row prefilled input, and inspect those rows before choosing your filters and schedule.

Browse the library