LinkedIn Job Scraper + Qualifier using Apify
A few weeks ago I shared a small weekend project I built for a friend who lost his job β a workflow that pulls LinkedIn listings, filters them with AI, and emails only the jobs that truly match his skills.
Shared 11/24/2025
44 views
Visual Workflow
JSON Code
{
"id": "PnbrcV8S3ZTpI9Lm",
"meta": {
"instanceId": "9fb634bb746cbe3fe9f7fa86fdd635510d2f0c24c4ba919fbba1ca6aec3c4e57",
"templateCredsSetupCompleted": true
},
"name": "LinkedIn Job Scraper",
"tags": [],
"nodes": [
{
"id": "d313e3d4-98db-4c55-ae91-106d7a7412e3",
"name": "OpenRouter Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"position": [
256,
544
],
"parameters": {
"options": {}
},
"credentials": {
"openRouterApi": {
"id": "6z6zYFNsqyqFRUEB",
"name": "OpenRouter account"
}
},
"typeVersion": 1
},
{
"id": "1f77b72e-41cc-40a3-b534-116777772830",
"name": "AI Screening",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
256,
304
],
"parameters": {
"text": "=You are a career assistant. \nABOUT ME: {{ $('Global Vars').item.json.ABOUT_ME }}\nMY SKILLS: {{ $('Global Vars').item.json.MY_SKILL }}\n\nAnalyze this job posting and respond strictly in JSON format as follows:\n\n{\n \"job_title\": \"Title of the job\",\n \"match\": \"Yes or No\",\n \"reason\": \"A short explanation why this job is a strong match for my profile\"\n}\n\nHere is the job details text to analyze:\nTitle: {{ $json.job_title }}\nDescription: {{ $json.job_description }}\nlocation: {{ $json.location }}\nemployment_type: {{ $json.employment_type }}\njob_function: {{ $json.job_function }}\nindustries: {{ $json.industries }}\n",
"options": {
"systemMessage": "=System Prompt (Formatted for Use):\n\nYou are a career assistant helping users evaluate job postings against their profile. The user will provide:\n\nTheir About Me description.\n\nTheir Skills list.\n\nA job posting text.\n\nYour task is to analyze the job posting and respond strictly in JSON array format with the following structure:\n {\n \"job_title\": \"Title of the job\",\n \"match\": \"Yes or No\",\n \"reason\": \"A short explanation why this job is a strong match for me\"\n}\nInstructions for completion:\n\nRead the user's About Me and Skills carefully.\n\nCompare them to the job posting text.\n\nDetermine if the user is a strong match for the role.\n\nFill in all JSON fields with accurate or best-estimate information.\n\nRespond only in valid JSON array format, no extra text or explanations.\n\nIf any information is not available in the posting, use \"Not specified\"."
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 2
},
{
"id": "f2420f7c-9b8d-4f68-be41-25a6facc764a",
"name": "OpenRouter Chat Model1",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"position": [
1200,
544
],
"parameters": {
"options": {}
},
"credentials": {
"openRouterApi": {
"id": "6z6zYFNsqyqFRUEB",
"name": "OpenRouter account"
}
},
"typeVersion": 1
},
{
"id": "e8cd2762-623b-4190-9643-0d807f950197",
"name": "AI Compose Email Body",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
1200,
304
],
"parameters": {
"text": "=Job Listings:\n{{ $json.data }}",
"options": {
"systemMessage": "=You are an assistant that summarizes LinkedIn job search data from JSON input. \n\nYour responsibilities:\n1. Accept JSON data containing any number of job postings. Each posting may have keys like: job_title, reason, url, job_location, job_seniority_level, job_posted_time, job_num_applicants, match, job_function, job_employment_type, company_url, or others.\n2. Dynamically handle any keys: include them in the summary table if present, skip them if missing.\n3. Present a **clean, professional HTML summary** of new jobs.\n4. Include at least these details if available: Job Title (bold), Location, Seniority Level, Posted Time, Number of Applicants, Match Status, Employment Type, Company URL (clickable).\n5. Calculate \"days since posted\" from `job_posted_time` if possible, and highlight matches if `match` is \"Yes\".\n6. Structure HTML with a table for easy reading, with headers and alternating row colors.\n7. Include a total count of jobs and today's date at the top.\n8. Only return **direct HTML**, no extra text or explanations.\n9. Ensure the HTML works for any number of jobs and variable JSON keys.\n\nExample HTML structure:\n<div>\n <h2>LinkedIn Job Search Daily Summary</h2>\n <p>Total New Jobs: X</p>\n <p>Start From: TODAY'S DATE</p>\n <table>\n <thead>...</thead>\n <tbody>\n <tr>...</tr>\n </tbody>\n </table>\n</div>\n\nToday is {{ $now.format('cccc') }} the {{ $now.format('yyyy-MM-dd HH:mm') }}."
},
"promptType": "define"
},
"typeVersion": 2
},
{
"id": "7953c534-af08-4f22-a4c0-67ea520bb392",
"name": "Trigger Daily",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
256,
-80
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 7
}
]
}
},
"typeVersion": 1.2
},
{
"id": "fd35bf62-a4df-446c-9180-083f3eebc7a3",
"name": "Send Mail",
"type": "n8n-nodes-base.httpRequest",
"position": [
1520,
304
],
"parameters": {
"url": "https://api.resend.com/emails",
"method": "POST",
"options": {},
"sendBody": true,
"authentication": "genericCredentialType",
"bodyParameters": {
"parameters": [
{
"name": "from",
"value": "=Jobs Daily <onboarding@resend.dev>"
},
{
"name": "to",
"value": "=thesohailjafri@gmail.com"
},
{
"name": "subject",
"value": "=LinkedIn Jobs Daily | {{ new Date().toLocaleDateString() }}"
},
{
"name": "html",
"value": "={{ $json.output }}"
}
]
},
"genericAuthType": "httpBearerAuth"
},
"credentials": {
"httpBearerAuth": {
"id": "9yxG8hHGCrFmQQ1K",
"name": "Resend Bearer Token"
}
},
"typeVersion": 4.2
},
{
"id": "236ba680-99ff-47fb-a417-2a72600ff22c",
"name": "Set Listings as String",
"type": "n8n-nodes-base.code",
"position": [
992,
304
],
"parameters": {
"jsCode": "\nreturn {\n json:{\n data: JSON.stringify(items)\n }\n}"
},
"typeVersion": 2
},
{
"id": "ed2792bc-7255-42a7-a5b2-85c04089f5f8",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
208,
-208
],
"parameters": {
"color": 5,
"width": 1520,
"height": 352,
"content": "## Part 1: Job Data Collection"
},
"typeVersion": 1
},
{
"id": "59982874-0dd0-470e-b661-401c634e31e9",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
208,
176
],
"parameters": {
"color": 5,
"width": 688,
"height": 560,
"content": "## Part 2: AI Screening & Filtering\n"
},
"typeVersion": 1
},
{
"id": "085f7438-09b2-470f-9bea-2f7a085158e1",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
928,
176
],
"parameters": {
"color": 5,
"width": 800,
"height": 560,
"content": "## Part 3: Delivery & Notification"
},
"typeVersion": 1
},
{
"id": "464db3be-890b-4220-950a-1f7909a822c0",
"name": "Sticky Note12",
"type": "n8n-nodes-base.stickyNote",
"position": [
-352,
-208
],
"parameters": {
"width": 528,
"height": 1040,
"content": "# π οΈ Setup Guide \n\n**Author: [Sohail Jafri](https://link.thesohailjafri.me/web_ai) | [Youtube](https://link.thesohailjafri.me/yt_ai) | [Instagram](https://link.thesohailjafri.me/ig_ai) | [Join Skool](https://link.thesohailjafri.me/join-skool)**\n\nFollow the steps below to get your LinkedIn Job Scraper running:\n\n### β
Step 1: Copy the Google Sheet template \nMake a copy of the sheet and connect it to the **[Append row in sheet](https://docs.google.com/spreadsheets/d/1awPFjM8r-fimvwiYznWLMF1KxRx2MRDrqs-fYWnaOs4/copy)** node. \nTemplate: \n\n### β
Step 2: Add [Apify Data](https://console.apify.com/) credentials \nAdd your Apify Data token to the Apify nodes. \n\n### β
Step 3: Connect your AI models \nAdd your OpenRouter (or preferred LLM) API key to both LM nodes used for screening and email composition. \n- [OpenRouter](https://openrouter.ai/)\n- [OpenAI](https://platform.openai.com/)\n\n### β
Step 4: Add [Resend](https://resend.com/) API key \nConfigure the Resend bearer token in the **Send Mail** node to enable daily emails. \n\n\n### β
Step 5: Populate Edit Fields \nFill these values in the **Edit Fields** node: \n- **ABOUT_ME** β short resume summary \n- **MY_SKILL** β comma-separated skill list \n- **JOB_INPUT** β array of search queries (location, keyword, time_range, job_type, experience_level, remote)\n\n### β
Step 6: Test the flow \nRun the workflow manually with a small JOB_INPUT to confirm end-to-end success.\n\n### π‘ Quick config tips\n- **Poll interval** = 2 minutes (adjust in **Wait - Polling Apify Data** if needed). \n- Ensure the Google Sheet columns match the node schema. \n- Verify the email **to** address in **Send Mail** before enabling production.\n\n### β
Final step: Enable workflow \nSwitch the workflow to active after confirming credentials and a successful test run.\n "
},
"typeVersion": 1
},
{
"id": "5cba6501-e8d7-4bf8-92f0-c92ffc16283b",
"name": "Global Vars",
"type": "n8n-nodes-base.set",
"position": [
432,
-80
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "1ec8fcc8-51b1-4542-a39c-f1341d3165c7",
"name": "ABOUT_ME",
"type": "string",
"value": "=AI-driven automation and full-stack software developer with experience creating scalable systems, backend APIs, and workflow automation pipelines. Skilled in integrating LLMs, building RAG-based knowledge systems, and deploying production-grade cloud infrastructure. Strong end-to-end understanding of web application architecture with a long-term aim to evolve into a Software Architect. Passionate about system design, automation, psychology, and personal development."
},
{
"id": "df199bdc-e748-4db0-8f41-f064651a5be8",
"name": "MY_SKILL",
"type": "string",
"value": "n8n Workflow Automation LLM-based Applications RAG Pipelines & Knowledge Retrieval API/CRM Integrations Chatbots & Conversational AI Systems"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "cf2107d6-6c3a-4867-a9ea-11eaa13b8e1a",
"name": "Structured Output Parser",
"type": "@n8n/n8n-nodes-langchain.outputParserStructured",
"position": [
432,
544
],
"parameters": {
"jsonSchemaExample": " {\n \"job_title\": \"Title of the job\",\n \"match\": \"Yes or No\",\n \"reason\": \"A short explanation why this job is a strong match for me\"\n}"
},
"typeVersion": 1.3
},
{
"id": "79265c43-f864-48a2-8897-2b54dcfafe68",
"name": "Filter Matched Jobs",
"type": "n8n-nodes-base.filter",
"position": [
560,
304
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "c5e5a5e5-4d52-4dd8-a4ea-9a4a5a4cb940",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.output.match }}",
"rightValue": "=Yes"
}
]
}
},
"typeVersion": 2.2
},
{
"id": "9c39af5d-7850-40b5-906b-d48d481c72e9",
"name": "Array Of Job Inputs",
"type": "n8n-nodes-base.code",
"position": [
608,
-80
],
"parameters": {
"jsCode": "const inputs = [\n {\n \"experience_level\": \"4\",\n \"job_post_time\": \"r86400\",\n \"job_title\": \"automation engineer\",\n \"job_type\": \"F\",\n \"jobs_entries\": 5,\n \"location\": \"United States\",\n \"start_jobs\": 0,\n \"work_schedule\": \"2\"\n },\n {\n \"experience_level\": \"4\",\n \"job_post_time\": \"r86400\",\n \"job_title\": \"ai engineer\",\n \"job_type\": \"F\",\n \"jobs_entries\": 5,\n \"location\": \"United States\",\n \"start_jobs\": 0,\n \"work_schedule\": \"2\"\n }\n]\n\nreturn inputs.map(i=> ({ json: i }))\n"
},
"typeVersion": 2
},
{
"id": "2e36e694-ab52-4038-bc31-2b211a647241",
"name": "Format Input",
"type": "n8n-nodes-base.code",
"position": [
1072,
-64
],
"parameters": {
"jsCode": "let input = JSON.stringify($input.first().json)\nreturn {\n json: {\n input\n }\n}"
},
"typeVersion": 2
},
{
"id": "7e025919-29cd-435e-8e09-9fb4012ace78",
"name": "Remove Duplicate Jobs",
"type": "n8n-nodes-base.code",
"position": [
1568,
-96
],
"parameters": {
"jsCode": "const jobs = $input.all().map(i=>i.json)\nconst seen = new Set();\nconst uniqueJobs = [];\n\nfor (const job of jobs) {\n if (!seen.has(job.job_id)) {\n seen.add(job.job_id);\n uniqueJobs.push({ json:job });\n }\n}\n\nreturn uniqueJobs\n"
},
"typeVersion": 2
},
{
"id": "7dfc5b1a-55a9-4799-a5c1-b7fbed9df618",
"name": "Loop Over Jobs",
"type": "n8n-nodes-base.splitInBatches",
"position": [
816,
-80
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "3bbf5af7-c1fd-452e-bd83-51489c75e2ee",
"name": "Run an Actor and get dataset",
"type": "@apify/n8n-nodes-apify.apify",
"position": [
1312,
-64
],
"parameters": {
"actorId": {
"__rl": true,
"mode": "list",
"value": "JkfTWxtpgfvcRQn3p",
"cachedResultUrl": "https://console.apify.com/actors/JkfTWxtpgfvcRQn3p/input",
"cachedResultName": "β‘οΈRapid Linkedin Jobs Scraper (No Cookies) | Free Jobs Scraper (worldunboxer/rapid-linkedin-scraper)"
},
"operation": "Run actor and get dataset",
"customBody": "={{ $json.input }}"
},
"credentials": {
"apifyApi": {
"id": "QYF5ZYibqkW9a0UB",
"name": "Apify account"
}
},
"typeVersion": 1
},
{
"id": "5e130dd1-3652-4c06-9972-05e79d7226d9",
"name": "Append or update row in sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
992,
496
],
"parameters": {
"columns": {
"value": {},
"schema": [
{
"id": "job_id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "job_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "job_url",
"type": "string",
"display": true,
"required": false,
"displayName": "job_url",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "job_title",
"type": "string",
"display": true,
"required": false,
"displayName": "job_title",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "employment_type",
"type": "string",
"display": true,
"required": false,
"displayName": "employment_type",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "salary_range",
"type": "string",
"display": true,
"required": false,
"displayName": "salary_range",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "job_description",
"type": "string",
"display": true,
"required": false,
"displayName": "job_description",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "company_name",
"type": "string",
"display": true,
"required": false,
"displayName": "company_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "company_url",
"type": "string",
"display": true,
"required": false,
"displayName": "company_url",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "location",
"type": "string",
"display": true,
"required": false,
"displayName": "location",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "seniority_level",
"type": "string",
"display": true,
"required": false,
"displayName": "seniority_level",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "job_function",
"type": "string",
"display": true,
"required": false,
"displayName": "job_function",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "industries",
"type": "string",
"display": true,
"required": false,
"displayName": "industries",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "autoMapInputData",
"matchingColumns": [
"job_id"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1XPo7y3arrCgADgr-h-2aGBeML63mN48Jyx-tVgdae-w/edit#gid=0",
"cachedResultName": "Auto Jobs"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1XPo7y3arrCgADgr-h-2aGBeML63mN48Jyx-tVgdae-w",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1XPo7y3arrCgADgr-h-2aGBeML63mN48Jyx-tVgdae-w/edit?usp=drivesdk",
"cachedResultName": "n8n - Linkedin Jobs Using Apify"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"id": "TB2tCNqF1GBm2ag8",
"name": "Google Sheets account"
}
},
"typeVersion": 4.7
},
{
"id": "195217c2-8c60-4ef4-8683-7bbb8beaea17",
"name": "Set Job",
"type": "n8n-nodes-base.set",
"position": [
736,
304
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "2ab0f45a-66e5-4a76-a65c-929551c4b011",
"name": "job_id",
"type": "string",
"value": "={{ $('Remove Duplicate Jobs').item.json.job_id }}"
},
{
"id": "a7838663-0228-4f75-960b-e68e415f0ffd",
"name": "job_url",
"type": "string",
"value": "={{ $('Remove Duplicate Jobs').item.json.job_url }}"
},
{
"id": "4f03786a-82b7-417a-b665-c20e0744001a",
"name": "job_title",
"type": "string",
"value": "={{ $('Remove Duplicate Jobs').item.json.job_title }}"
},
{
"id": "1c753037-f87b-454d-9e54-8bd2c81e000c",
"name": "employment_type",
"type": "string",
"value": "={{ $('Remove Duplicate Jobs').item.json.employment_type }}"
},
{
"id": "8bc54249-be68-48a0-a1bd-cec3f3091e73",
"name": "salary_range",
"type": "string",
"value": "={{ $('Remove Duplicate Jobs').item.json.salary_range }}"
},
{
"id": "b082e44a-fa8d-4e74-b4a4-ecd56f34427e",
"name": "company_name",
"type": "string",
"value": "={{ $('Remove Duplicate Jobs').item.json.company_name }}"
},
{
"id": "e747d572-0ef6-405c-9a6a-14107eb71892",
"name": "company_url",
"type": "string",
"value": "={{ $('Remove Duplicate Jobs').item.json.company_url }}"
},
{
"id": "33a764b1-d7d6-4869-b8c2-8be3f901c59b",
"name": "location",
"type": "string",
"value": "={{ $('Remove Duplicate Jobs').item.json.location }}"
},
{
"id": "a51be77d-0094-44d5-8d47-f89401595cbb",
"name": "seniority_level",
"type": "string",
"value": "={{ $('Remove Duplicate Jobs').item.json.seniority_level }}"
},
{
"id": "ebef586f-fc1f-4f71-a68e-0b57c886b6d4",
"name": "job_function",
"type": "string",
"value": "={{ $('Remove Duplicate Jobs').item.json.job_function }}"
},
{
"id": "5d8bc0a3-77b9-4585-b5aa-dc02d11c23f4",
"name": "=industries",
"type": "string",
"value": "={{ $('Remove Duplicate Jobs').item.json.industries }}"
}
]
}
},
"typeVersion": 3.4
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "655baf8b-ad62-4f87-9e53-5abbda4d83a4",
"connections": {
"Set Job": {
"main": [
[
{
"node": "Set Listings as String",
"type": "main",
"index": 0
},
{
"node": "Append or update row in sheet",
"type": "main",
"index": 0
}
]
]
},
"Global Vars": {
"main": [
[
{
"node": "Array Of Job Inputs",
"type": "main",
"index": 0
}
]
]
},
"AI Screening": {
"main": [
[
{
"node": "Filter Matched Jobs",
"type": "main",
"index": 0
}
]
]
},
"Format Input": {
"main": [
[
{
"node": "Run an Actor and get dataset",
"type": "main",
"index": 0
}
]
]
},
"Trigger Daily": {
"main": [
[
{
"node": "Global Vars",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Jobs": {
"main": [
[
{
"node": "Remove Duplicate Jobs",
"type": "main",
"index": 0
}
],
[
{
"node": "Format Input",
"type": "main",
"index": 0
}
]
]
},
"Array Of Job Inputs": {
"main": [
[
{
"node": "Loop Over Jobs",
"type": "main",
"index": 0
}
]
]
},
"Filter Matched Jobs": {
"main": [
[
{
"node": "Set Job",
"type": "main",
"index": 0
}
]
]
},
"AI Compose Email Body": {
"main": [
[
{
"node": "Send Mail",
"type": "main",
"index": 0
}
]
]
},
"OpenRouter Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Screening",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Remove Duplicate Jobs": {
"main": [
[
{
"node": "AI Screening",
"type": "main",
"index": 0
}
]
]
},
"OpenRouter Chat Model1": {
"ai_languageModel": [
[
{
"node": "AI Compose Email Body",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Set Listings as String": {
"main": [
[
{
"node": "AI Compose Email Body",
"type": "main",
"index": 0
}
]
]
},
"Structured Output Parser": {
"ai_outputParser": [
[
{
"node": "AI Screening",
"type": "ai_outputParser",
"index": 0
}
]
]
},
"Run an Actor and get dataset": {
"main": [
[
{
"node": "Loop Over Jobs",
"type": "main",
"index": 0
}
]
]
}
}
}