LinkedIn's own job alerts live in your LinkedIn inbox, need an account, and are hard to share with a team. If you want new postings for a few searches dropped into a Slack channel (a recruiting team, a job-board newsletter, a sales team watching who hires), you need three things: the public job search, a memory of what you already saw, and a Slack webhook.
This guide sets that up with no LinkedIn login at all, first with no code, then in Python.
Disclosure: I'm Aurelix, and I built the LinkedIn Jobs Scraper used below. It is a paid tool, priced per job returned; the current price is on its tool page.
What you need
- A Slack incoming webhook URL: in Slack, create an app (or use an existing one), turn on Incoming Webhooks and add one for your channel. It looks like
https://hooks.slack.com/services/T000/B000/XXXX. - An Apify account (the free plan works) to run the scraper on a schedule.
The scraper reads LinkedIn's public job search, the pages any logged-out visitor sees. It never logs in and never uses cookies, so there is no account to get blocked.
Option 1: no code
Open the tool on the Apify Store and use this input:
{
"keywords": ["data engineer", "analytics engineer"],
"country": "CA",
"datePosted": "past24h",
"maxResultsPerSearch": 100,
"incremental": true,
"stateKey": "ca-data-jobs",
"slackWebhookUrl": "https://hooks.slack.com/services/T000/B000/XXXX"
}
What each part does:
-
countryuses LinkedIn's exact country id, and results whose location names another country are dropped. -
incrementalturns on Only new jobs since last run: the run remembers every job it returned understateKeyand returns (and charges) only jobs it has not seen before. -
slackWebhookUrlposts a short digest after each run: how many new jobs were saved and the top 10 with links.
Then add a schedule (Apify Console > Schedules, for example every weekday at 8:00) that runs this saved input. The first run returns everything that matches; later runs post only what is new.
Option 2: Python, with your own message format
If you want a different message (grouped by company, with the salary when LinkedIn shows one), run the scraper from Python and post to the webhook yourself:
pip install apify-client requests
import os
from collections import defaultdict
import requests
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("aurelix/linkedin-jobs").call(run_input={
"keywords": ["data engineer"],
"country": "CA",
"datePosted": "past24h",
"maxResultsPerSearch": 100,
"incremental": True,
"stateKey": "ca-data-jobs-python",
})
jobs = list(client.dataset(run["defaultDatasetId"]).iterate_items())
by_company = defaultdict(list)
for j in jobs:
by_company[j["company"]].append(j)
lines = [f"*{len(jobs)} new data engineer jobs in Canada*"]
for company, items in sorted(by_company.items(), key=lambda x: -len(x[1])):
for j in items:
pay = f" · {j['salary_text']}" if j.get("salary_text") else ""
lines.append(f"• <{j['job_url']}|{j['title']}> at {company}, {j['location']}{pay}")
if jobs:
requests.post(os.environ["SLACK_WEBHOOK_URL"], json={"text": "\n".join(lines[:40])}, timeout=30)
Run it from cron, GitHub Actions or an Apify schedule. Because incremental keeps its memory on Apify under the stateKey, the script itself stores nothing.
What the rows look like
Real rows from a run searching "data engineer" in Canada, trimmed to the fields used above:
| title | company | location | seniority | job type |
|---|---|---|---|---|
| Data Engineer | Samsara | Toronto, Ontario, Canada | Mid-Senior level | Full-time |
| Ingénieur principal de données | LGS, an IBM Company | Montreal, Quebec, Canada | Mid-Senior level | Full-time |
| Data Engineer (Snowflake, Azure Data Factory, Python & SQL) | Cognizant | Halifax, Nova Scotia, Canada | Mid-Senior level | Full-time |
Each row also has job_url, posted_date, company_url, applicants, industries, salary_text (where LinkedIn shows one) and the full description.
Tips
-
One search per keyword.
keywordsruns one search each; to separate alerts per team, give each schedule its ownstateKeyand webhook. -
Paste your own search. If you already filter on linkedin.com, copy the URL from the address bar into
searchUrls; its filters are kept. -
Cheaper list mode. Set
includeDetailsto false for title, company, location and link only, without opening each job page.
Limits
- LinkedIn shows at most about 1,000 jobs per search; split broad searches by keyword or location.
- The scraper uses Apify's datacenter proxy only. LinkedIn rate-limits those addresses, so very large runs can stop early; the run's status message says which searches did.
- Salary appears only when LinkedIn shows it on the posting.
To send the same jobs to a spreadsheet instead of Slack, see Export new LinkedIn jobs to Google Sheets every day.
Top comments (0)