Scripting the job search
The job hunt helper I built with a few lines of JavaScript
Refreshing job boards and reapplying filters every day is tedious.
When I reentered the job market after 8 years and began networking in earnest, I heard this gripe often. So, I built a JavaScript automation to simply the process.
Running in Google Apps Script, the script fetches and parses RSS feeds from LinkedIn, BuiltIn and Power to Fly. It extracts structured job data into a Google Sheet, filtering out duplicates and expired links—all without me lifting a finger!
To keep it flexible, I designed each parser as a modular component. If I, or anyone else, wants to add a new job board, it’s just a matter of plugging in a new parser class without touching the core logic.
The script uses regex and logic to accurately extract metadata, such as company name, job title, location, job board URL and (when available) an external job application URL. It can run at regular intervals (daily, hourly, etc.), and it filters out duplicate and expired postings.
The end result is not flashy, but it works. It shifts the focus from sifting through listings to applying for high-fit roles, saving effort and time.
I have included the source code below for anyone to explore the parser logic and the scheduler structure, and to extend it for other use cases.
If you're looking to simplify your job search, or just want a project to sharpen your JavaScript automation skills, this one’s easy to fire up and pays off quickly.
Feel free to reach out for more details or to share your automation success stories.
Fetch RSS code
<function fetchAllRSSJobs() { const feeds = [ { url: 'https://rss.app/feeds/v1.1/PVhoP8nk6gvgKH97.json', sheetName: 'LinkedIn PM Jobs', parser: parseLinkedInRSS }, { url: 'https://rss.app/feeds/v1.1/MDIHJNe31rKTxLyW.json', sheetName: 'BuiltIn BOS', parser: parseBuiltInBOSRSS }, { url: 'https://rss.app/feeds/v1.1/QPSAF6S4BNU3Oqmv.json', sheetName: 'Power to fly', parser: parsePowerToFlyRSS } ]; const spreadsheet = SpreadsheetApp.openByUrl('https://<destination Goolge Sheet URL here>'); feeds.forEach(feed => { const sheet = spreadsheet.getSheetByName(feed.sheetName); if (sheet.getLastRow() === 0) { sheet.appendRow(['Company', 'Job Title', 'Job Location', 'Job Board URL']); } const response = UrlFetchApp.fetch(feed.url); const data = JSON.parse(response.getContentText()); let existingLinks = []; if (sheet.getLastRow() > 1) { existingLinks = sheet.getRange(2, 4, sheet.getLastRow() - 1).getValues().flat(); } const parsedItems = feed.parser(data, existingLinks); parsedItems.forEach(row => { sheet.appendRow(row); }); }); }
LinkedIn parser code
} function parseLinkedInRSS(data, existingLinks) { return data.items .filter(item => !existingLinks.includes(item.url || 'No URL provided')) .map(item => { const rawTitle = item.title || 'No title provided'; const company = extractLinkedInCompany(rawTitle); const jobTitle = extractLinkedInJobTitle(rawTitle); const location = extractLinkedInLocation(rawTitle); const url = item.url || 'No LinkedIn URL provided'; return [company, jobTitle, location, url]; }); } function extractLinkedInCompany(rawTitle) { const match = rawTitle.match(/^(.*?) hiring/i); return match ? match[1].trim() : 'No company provided'; } function extractLinkedInJobTitle(rawTitle) { const match = rawTitle.match(/hiring (.*?) in/i); return match ? match[1].trim() : 'No job title provided'; } function extractLinkedInLocation(rawTitle) { const match = rawTitle.match(/in (.*?)(,|$)/i); return match ? match[1].trim() : 'No location provided'; }
Key features
Regex
extractLinkedInCompany - extracts the company name using at as the keyword anchor
extractLinkedInJobTitle - extracts job title as the string before at
extractLinkedInLocation - corrects logic for location extraction, ensuring full city names appear
Adjusted patterns to match the LinkedIn job title format:
Job Title at Company in Location
Specifically captures the Company after
at
and the Job Title beforeat
.Captures the Location after
in
.
Fallback defaults
Ensures that
No company provided
andNo job title provided
appear only when data is genuinely missing.
BuiltIn Boston parser code
} function parseBuiltInBOSRSS(data, existingLinks) { return data.items .filter(item => !existingLinks.includes(item.url || 'No URL provided')) .map(item => { const rawTitle = item.title || 'No title provided'; const content = item.content_text || ''; const url = item.url || 'No URL provided'; const company = extractBuiltInCompany(rawTitle, url); const jobTitle = extractBuiltInJobTitle(rawTitle, url); const location = extractBuiltInLocation(content); return [company, jobTitle, location, url]; }); } function extractBuiltInCompany(rawTitle, url) { const titleMatch = rawTitle.match(/-\s(.+?)\s(?:in|Remote|USA|$)/); if (titleMatch) return titleMatch[1].trim(); // Extract from URL if rawTitle doesn't provide company const urlMatch = url.match(/company\/([^/]+)/); return urlMatch ? decodeURIComponent(urlMatch[1].replace(/-/g, ' ')) : 'No company provided'; } function extractBuiltInJobTitle(rawTitle, url) { const titleMatch = rawTitle.match(/^(.+?)\s-/); if (titleMatch) return titleMatch[1].trim(); // Extract from URL if rawTitle doesn't provide job title const urlMatch = url.match(/jobs\/product\/search\/([^/]+)/); return urlMatch ? decodeURIComponent(urlMatch[1].replace(/-/g, ' ')) : 'No job title provided'; } function extractBuiltInLocation(content) { const locationMatch = content.match(/in\s(.+?),?\s(USA|MA|Remote)/i); return locationMatch ? locationMatch[1].trim() : 'No location provided'; }
Key features
Company extraction
Fallback to extract the company name from URLs containing
/company/
.Converts into spaces for a clean company name
Job Title extraction
Enhanced to parse data from both title and URL, if available.
Extracts Job Title and Company from specific patterns, including fallback logic for URL-based parsing.
Fallback to extract job titles from URLs containing
/jobs/product/search/
.
Location extraction
Left unchanged, as it relies on content or defaults to: "No location provided"
Expected behavior
For URLs like
https://www.builtinboston.com/company/liberty-mutual-insurance
, the company should now extract as Liberty Mutual Insurance.For URLs like
https://www.builtinboston.com/jobs/product/search/head-of-product
, the job title should now extract as Head of Product.
Power to Fly parser code
} function parsePowerToFlyRSS(data, existingLinks) { return data.items .filter(item => !existingLinks.includes(item.url || 'No URL provided')) .map(item => { const rawTitle = item.title || 'No title provided'; const content = item.content_text || ''; const url = item.url || 'No URL provided'; const company = extractPowerToFlyCompany(rawTitle, content); const jobTitle = extractPowerToFlyJobTitle(rawTitle, content); const location = extractPowerToFlyLocation(rawTitle, content); return [company, jobTitle, location, url]; }); } function extractPowerToFlyCompany(rawTitle, content) { const match = rawTitle.match(/at\s(.+?)\sin/i); return match ? match[1].trim() : 'No company provided'; } function extractPowerToFlyJobTitle(rawTitle, content) { const match = rawTitle.match(/^(.*?)\sat/i); return match ? match[1].trim() : 'No job title provided'; } function extractPowerToFlyLocation(rawTitle, content) { const match = rawTitle.match(/in\s(.+?),/i); return match ? match[1].trim() : 'No location provided'; }
Key features
Adjusted to account for specific patterns from Power to Fly JSON data
Uses similar fallback logic and tailored patterns for extracting the required fields from
title
,content
, andURL
Also has fallback logic for missing fields
Script testing
Follow these steps to test your script to ensure the RSS URL passes data to Google Sheets.
Step 1: Verify your script
Double-check it
Ensure the correct RSS JSON URL is set in the
url
variable:const url = 'YOUR_RSS_JSON_URL'; // Replace with your RSS JSON feed URL
Confirm the sheet name matches your actual Google Sheet tab name in this line:
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Sheet1');
Save it
Click File > Save and name your project (e.g.,
FetchRSSJobs
).
Note: If you want to specify a specific Google Sheet (e.g., your "daily job search automation" sheet), you need to replace the getActiveSpreadsheet()
call with openByUrl()
and use the URL of your Google Sheet.
Step 2: Test your script
Run it
In the Apps Script editor, select the function
fetchRSSJobs
from the dropdown menu.Click the Run button ▶️.
Authorize it
On the first run, you’ll need to authorize the script.
A prompt will appear asking for permission to access your spreadsheet and fetch data.
Click Review Permissions and log in with your Google account.
Choose Advanced Options > Go to Project (unsafe) if you see a warning.
Click Allow to authorize.
Check for errors
If the script runs successfully, it will append the data from the RSS JSON to your Google Sheet.
If there’s an error, the Execution Log (found under View > Execution Log) will provide debugging information.
Step 3: Verify data in Sheets
Open your Google Sheet and check if the data from the RSS feed appears in the appropriate columns.
If the data doesn’t look right…
Double-check the structure of the RSS JSON feed or
modify the
forEach
loop in the script to correctly reference JSON keys.
Example of adjusting keys
data.items.forEach((item) => { sheet.appendRow([item.title, item.company, item.location, item.link]); // Update keys as needed });
Step 4: Common issues and resolutions *
"TypeError: Cannot read property 'items' of undefined"
The structure of the JSON feed might differ from the script's expectations. Use the
Logger
to inspect the JSON structure:Logger.log(data);
View the log under View > Logs to identify the correct keys.
"Authorization Required" error
Ensure you’ve authorized the script to access Google Sheets and external services.
Step 5: Deploy the script (optional)
You don’t need to deploy the script for testing, but if you want it to be accessible as a web app or trigger-able externally, then…
Click Deploy > Test Deployments.
Follow the prompts to set up a deployment method.
* Additional errors and resolutions
"Error 401: deleted_client"
This indicates that the OAuth client (your script project) was deleted or is misconfigured, likely preventing Google from authorizing access to services like Google Sheets or external APIs. Here’s how to fix it and reauthorize the script:
Step 1: Reset your script's OAuth credentials
If the OAuth client was deleted, you need to reset the script's authorization settings.
Reauthorize the script
In the Apps Script editor, click Project Settings (gear icon on the left sidebar).
Scroll down to "Show" under Scopes and click it to see the script’s OAuth scopes.
Click Reset Authorization to reauthorize your script.
Re-save your script
After saving the script, try running it to re-trigger the authorization flow.
Step 2: Verify script permissions
Ensure that your script uses the following scopes:
Google Sheets API – For accessing your spreadsheet:
"https://www.googleapis.com/auth/spreadsheets"
External API (UrlFetchApp) – For fetching the RSS feed:
"https://www.googleapis.com/auth/script.external_request"
If you still see the error…
Click "Deploy > Test Deployment" to reinitialize the OAuth client.
Test the deployment using the test version.
Step 3: (If necessary) Create a new script project
If the OAuth client is permanently deleted and cannot be reinitialized…
Create a new Script Project
Open your Google Sheet.
Click Extensions > Apps Script to create a new script project.
Copy-paste your code
Copy and paste the code from your current script into the new project.
Reauthorize and run
Save and run the script as before.
Authorize the script again when prompted.
Step 4: Debugging
If you still encounter issues…
Check View > Logs in the Apps Script editor for error messages.
Verify that your Google account is authorized for both Google Sheets and external API calls.
"Google hasn’t verified this app"
This message appears because your Apps Script project hasn't been verified by Google, which is common for personal scripts. You can bypass this message for your own use without needing to verify the app. Here’s how.
Step 1: Proceed past the warning
When the "Google hasn’t verified this app" screen appears…
Click Advanced at the bottom of the warning.
Click Go to [Project Name] (unsafe).
Grant the requested permissions to allow your script to access your Google Sheets and external services.
Once permissions are granted, the script should run as expected.
Step 2: Confirm permissions granted
In the Apps Script editor, re-run your script.
If prompted again, authorize the permissions following the steps above.
Check your Execution Log (View > Execution Log) to ensure the script executes without errors.
Step 3: Debugging
If you see the warning in the Execution Log: "This project requires access to your Google Account to run. Please try again and allow it this time", this means authorization was not completed. Repeat Step 1 to bypass the verification warning and grant access.
Optional: Avoid future warnings
For smoother execution…
Use Triggers for Automation:
Set up a time-driven trigger to run the script automatically.
Triggers typically bypass the manual authorization process after the first setup.
Deploy as a Test Deployment:
Click Deploy > Test Deployments.
Test the deployment to ensure it works without further manual intervention.