An automated n8n workflow that scrapes LinkedIn for Product Manager job listings in Bangalore, extracts structured data using AI, deduplicates with vector search, and surfaces similar job recommendations. All hands-free, runs every 12 hours.
I built this because I was tired of manually checking LinkedIn every day during my job search. Now it just runs in the background, picks up new PM listings, pulls out the important stuff (skills needed, experience, urgency), finds similar roles I might have missed, and dumps everything into a Google Sheet I can check whenever I want.
If you are a PM (or anyone really) looking for jobs and want to automate the grunt work, this is for you. No software background needed. I will walk you through every step.
Here is the simple version. Every 12 hours, this workflow:
- Scrapes LinkedIn for PM job listings in Bangalore using Apify (no LinkedIn login needed)
- Filters out junk (project managers, supply chain roles, etc.) using keyword matching + Gemini AI
- Checks if the job was already processed before (dedup using Qdrant vector database)
- Sends new jobs to Gemini AI to extract 21 structured fields (skills, experience, salary, urgency score, etc.)
- Creates a skill embedding (a numerical fingerprint of the job's required skills)
- Saves everything to Qdrant and finds similar jobs already in the database
- Logs it all to Google Sheets with two tabs: Job Listings and Similar Jobs
You end up with a clean, structured spreadsheet of PM jobs that updates itself.
| Tool | What It Does Here |
|---|---|
| n8n (self-hosted on Railway) | Runs the whole workflow. Think of it as your automation engine |
| Apify | Scrapes LinkedIn job listings without needing your LinkedIn login |
| Gemini 2.5 Flash | Reads job descriptions and extracts structured fields using AI |
| Gemini embedding-001 | Converts skills into numerical vectors for finding similar jobs |
| Qdrant Cloud | Vector database that handles deduplication and similarity search |
| Google Sheets | Your output dashboard. Two tabs: Job Listings and Similar Jobs |
MCP & Skills Setup for Claude Code (You'll need to install CLaude on Desktop for this)
Paste these repo links in Claude Code and ask it to install the MCP on your computer while you sit back and relax! (That easy)
This is the part most tutorials skip or gloss over. I am going to walk you through every single thing you need to set up, including the parts that took me hours to figure out.
You need 4 services. All of them have free tiers that are more than enough.
- Go to apify.com and sign up (free tier gives you $5/month in credits, plenty for this)
- Once logged in, click on your profile icon (top right) and go to Settings
- In the left sidebar, click Integrations
- You will see your Personal API Token. Copy it and save it somewhere safe
- That is it. The workflow uses the
harvestapi/linkedin-job-searchactor which costs about $0.10-0.20 per run
- Go to Google AI Studio
- Sign in with your Google account
- Click Create API Key
- Select any Google Cloud project (or let it create one for you)
- Copy the API key. This one key works for both Gemini 2.5 Flash (text extraction) and Gemini embedding-001 (skill vectors)
- Free tier gives you 15 requests per minute which is enough for this workflow
- Go to cloud.qdrant.io and sign up
- Click Create Cluster. Pick the free tier (1GB storage, more than enough)
- Choose a region close to you (I used EU Central)
- Once the cluster is created, go to Data Access Control (in the cluster dashboard)
- Create an API Key. Copy both the key and your cluster URL (looks like
xxxx-xxxx.eu-central-1-0.aws.cloud.qdrant.io) - Now you need to create the collection. Go to the Dashboard tab of your cluster, or use the API:
curl -X PUT "https://YOUR_CLUSTER_URL:6333/collections/job_listings" \
-H "api-key: YOUR_QDRANT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"vectors": {
"size": 768,
"distance": "Cosine"
}
}'
Replace YOUR_CLUSTER_URL and YOUR_QDRANT_API_KEY with your actual values. This creates the collection where all your job data will be stored.
This is where most people get stuck. n8n needs OAuth2 credentials to write to Google Sheets on your behalf. Here is the exact process:
Part A: Create the Google Sheet
- Go to sheets.google.com and create a new spreadsheet
- Name it whatever you want (e.g., "LinkedIn Job Radar")
- Create two tabs at the bottom:
- Job Listings (with these column headers in row 1):
post_id,role,company_name,location,primary_skills,secondary_skills,must_have_skills,years_of_experience,looking_for_college_students,intern,salary_package,email,phone,hiring_intent,author_name,author_linkedin_url,post_url,date_posted,date_processed,keyword_matched,hiring_urgency_score - Similar Jobs (with these headers):
job_title,job_company,job_skills,job_url,similar_title,similar_company,similar_skills,similar_url,similarity_score,date_found
- Job Listings (with these column headers in row 1):
- Copy the Sheet ID from the URL. It is the long string between
/d/and/editin the URL bar. You will need this later.
Part B: Set Up Google Cloud Console (OAuth2 Credentials)
This is the painful part. Buckle up.
- Go to console.cloud.google.com
- If you do not have a project, create one. Name it anything (e.g., "n8n Automations")
- In the left sidebar, go to APIs & Services then Library
- Search for "Google Sheets API" and click Enable
- Now go to APIs & Services then OAuth consent screen
- Choose External as user type and click Create
- Fill in the required fields:
- App name: anything (e.g., "n8n LinkedIn Radar")
- User support email: your email
- Developer contact email: your email
- Leave everything else as default
- Click Save and Continue
- On the Scopes page, click Add or Remove Scopes
- Search for
https://www.googleapis.com/auth/spreadsheetsand check the box - Also add
https://www.googleapis.com/auth/drive.file - Click Update, then Save and Continue
- On the Test Users page, click Add Users and add your own Google email
- Click Save and Continue, then Back to Dashboard
Now create the actual credentials:
- Go to APIs & Services then Credentials
- Click Create Credentials then OAuth client ID
- Application type: Web application
- Name: anything (e.g., "n8n Google Sheets")
- Under Authorized redirect URIs, add your n8n OAuth callback URL. This is your n8n URL +
/rest/oauth2-credential/callback. For example:https://your-n8n-instance.up.railway.app/rest/oauth2-credential/callback - Click Create
- You will see your Client ID and Client Secret. Copy both. You will need these in n8n.
Part C: Connect Google Sheets in n8n
- In n8n, open any Google Sheets node in the workflow
- Click on the Credential dropdown and select Create New
- Choose Google Sheets OAuth2 API
- Paste your Client ID and Client Secret from the Google Cloud Console
- Click Sign in with Google. A popup will appear asking you to authorize
- If you see a "This app isn't verified" warning, click Advanced then Go to [your app name] (unsafe). This is fine since it is your own app
- Grant the permissions and you should see a green "Connected" status
- This credential will now be available for all Google Sheets nodes in the workflow
You could run n8n locally, but self-hosting on Railway means your workflows run 24/7 without keeping your laptop open. Railway gives you a $5 free trial, and the n8n instance costs about $5/month after that.
- Go to railway.app and sign up (GitHub login works)
- From the dashboard, click New Project
- Click Deploy a Template
- Search for "n8n" and select the n8n (w/ Postgres) template. This gives you n8n + a Postgres database for storing your workflows
- Railway will ask you to configure some variables. The important ones:
N8N_ENCRYPTION_KEY: Set this to any random string (this encrypts your credentials). Write it down. If you lose this, all your saved credentials breakWEBHOOK_URL: Set tohttps://${RAILWAY_PUBLIC_DOMAIN}(Railway fills this in automatically)
- Click Deploy. Wait a few minutes for it to build
- Once deployed, click on the n8n service and you will see a public URL like
n8n-production-xxxx.up.railway.app. That is your n8n instance
Important Railway Environment Variables
After your first deploy, add these in the n8n service's Variables tab. These fix common issues:
| Variable | Value | Why |
|---|---|---|
N8N_TRUST_PROXY |
true |
Railway uses a reverse proxy. Without this, n8n's rate limiter breaks and your workflows get stuck |
N8N_RATE_LIMIT_ENABLED |
false |
Prevents the rate limiter from blocking your own workflow executions |
N8N_RUNNERS_ENABLED |
true |
Enables task runners for Code nodes |
EXECUTIONS_DATA_PRUNE |
true |
Auto-cleans old execution data so your Postgres does not fill up |
EXECUTIONS_DATA_MAX_AGE |
168 |
Keeps execution data for 7 days (168 hours) then prunes |
After adding these, click Redeploy on the n8n service.
If your workflows get stuck and nothing runs:
This happened to me. Check these things:
- Go to the n8n Executions tab and delete any stuck/crashed executions
- Check Railway Deploy Logs for errors (look for
ERR_ERL_UNEXPECTED_X_FORWARDED_FOR, that means you need theN8N_TRUST_PROXYvariable) - Restart the n8n service from Railway (three-dot menu on the service, click Redeploy)
- Download the
linkedin_job_radar_workflow_public.jsonfile from this repo - Open your n8n instance
- Go to Workflows and click the Import button (or the "..." menu, then Import from File)
- Select the JSON file
- Now you need to replace the placeholder values. Open each of these nodes and update:
In the Apify node:
- Replace
YOUR_APIFY_API_TOKENwith your Apify token in the query parameters
In all Gemini nodes (Extract Job Fields, Embed Job Skills, and inside the Filter: PM Relevance Check code):
- Replace
YOUR_GEMINI_API_KEYwith your Gemini API key
In all Qdrant nodes (Check Job Exists, Save Job, Find Similar Jobs):
- Replace
YOUR_QDRANT_CLUSTER_URLwith your Qdrant cluster URL (include the port:6333) - Replace
YOUR_QDRANT_API_KEYwith your Qdrant API key in the headers
In all Google Sheets nodes:
- Replace
YOUR_GOOGLE_SHEET_IDwith your Sheet ID (from the URL) - Connect your Google Sheets OAuth2 credential (the one you set up in Step 1)
- Save the workflow
- Click Execute workflow. The Manual Trigger will fire and the whole pipeline should run
- The Apify node takes 1-3 minutes (it is scraping LinkedIn in real time)
- Watch the nodes light up as data flows through. If Gemini throws a 503 error, just run it again. Gemini 2.5 Flash gets overloaded sometimes. You can also switch to
gemini-2.0-flashin the URL if it keeps happening - Once done, check your Google Sheet. You should see new rows in both tabs
For production: Replace the Manual Trigger with a Schedule Trigger node set to run every 12 hours. Then activate the workflow (toggle in the top right). It will now run automatically.
This workflow is set up for Product Manager roles in Bangalore, but you can change it for anything:
- Different job titles: Edit the
jobTitlesarray in the Apify node's JSON body - Different location: Change
locationsandgeoIdsin the Apify node. Find your LinkedIn geoId by searching on LinkedIn and checking the URL - Different filters: Update the
PM_KEYWORDSandDISQUALIFY_KEYWORDSarrays in the Filter: PM Relevance Check code node - More or fewer results: Change
maxItemsin the Apify node (higher = more Apify credits used)
This is the cool part. Every job gets two things stored in Qdrant:
- Payload: All 21 extracted fields as searchable metadata
- Vector: A 768-dimension numerical representation of the job's primary skills
When a new job is saved, the workflow runs a similarity search against all previously stored jobs. If another job has a skill vector with cosine similarity >= 0.70, it gets flagged as a "similar job" and logged to the Similar Jobs tab.
This means over time, as more jobs accumulate, the similar jobs feature gets better and better. You start seeing patterns like "hey, this Amazon PM role and this Flipkart PM role need almost the same skills."
- Gemini 503 errors: Gemini 2.5 Flash gets overloaded during peak hours. The workflow has retry logic (3 attempts, 5s wait), but if it keeps failing, switch to
gemini-2.0-flashin the URL - Apify rate limits: Free tier gives $5/month. Each run costs $0.10-0.20. Running every 12h with 20 maxItems will cost roughly $6-12/month. Reduce
maxItemsor increase the schedule interval if you want to stay on free tier - n8n stuck executions: If n8n crashes mid-workflow, executions get stuck as "running" forever. Fix: go to Executions tab, delete the stuck ones, restart n8n on Railway
- Google Sheets OAuth expires: Sometimes the OAuth token expires. Just re-authenticate by clicking the credential in n8n and signing in again
- Workflow runtime: With 20 jobs, the workflow takes 30-60 minutes because jobs are processed sequentially (each one makes ~8 API calls). This is fine for a scheduled workflow
| # | Field | What It Is |
|---|---|---|
| 1 | post_id |
Unique LinkedIn job ID |
| 2 | role |
Job title (e.g., Senior Product Manager) |
| 3 | company_name |
Who is hiring |
| 4 | location |
Job location |
| 5 | primary_skills |
Core skills they want |
| 6 | secondary_skills |
Nice-to-have skills |
| 7 | must_have_skills |
Non-negotiable requirements |
| 8 | years_of_experience |
Experience range (e.g., 3-5 years) |
| 9 | looking_for_college_students |
Boolean flag |
| 10 | intern |
Boolean flag |
| 11 | salary_package |
Salary range if mentioned |
| 12 | email |
Application email if listed |
| 13 | phone |
Contact phone if listed |
| 14 | hiring_intent |
One-sentence summary of the role |
| 15 | author_name |
Recruiter or company name |
| 16 | author_linkedin_url |
Recruiter's LinkedIn profile |
| 17 | post_url |
Direct link to the job listing |
| 18 | date_posted |
When the job was posted on LinkedIn |
| 19 | date_processed |
When our workflow picked it up |
| 20 | keyword_matched |
Search keywords that matched |
| 21 | hiring_urgency_score |
AI-scored urgency from 0-10 |
Manual/Schedule Trigger
|
v
Apify: Scrape LinkedIn (8 PM title variations x Bangalore)
|
v
Code: Flatten + filter to Bangalore/India/Remote only
|
v
Code: PM Relevance Filter (keywords + Gemini fallback)
|
v
Loop: Process each job one by one
|
v
Qdrant: Already processed? -- Yes --> Skip, next job
|
No
v
Gemini 2.5 Flash: Extract 21 structured fields from JD
|
v
Gemini Embedding: Convert skills to 768-dim vector
|
v
Valid job? (has hiring intent) -- No --> Skip, next job
|
Yes
v
Qdrant: Save job (vector + payload)
|
v
Qdrant: Find similar jobs (cosine similarity >= 0.7)
|
v
Google Sheets: Log to Similar Jobs tab
|
v
Google Sheets: Log to Job Listings tab
|
v
Loop back for next job
MIT
Built by Jatin during a job search. If this saves you time, star the repo.