DEV Community

Cover image for I Built a LinkedIn Easy Apply Bot in Python Here’s What I Learned About Browser Automation
Arul Cornelious
Arul Cornelious

Posted on

I Built a LinkedIn Easy Apply Bot in Python Here’s What I Learned About Browser Automation

Job searching often involves repeating the same steps again and again.

Open LinkedIn. Search for roles. Filter by location. Check whether the job supports Easy Apply. Fill in the same contact details. Upload the same CV. Answer similar questions. Track which jobs were already applied to.

As a developer, I wanted to explore whether this repetitive workflow could be improved using browser automation — not as a spam tool, but as a controlled, human-supervised productivity assistant.

That led me to build LinkedIn Easy Apply Assistant, a Python-based automation project that uses Selenium to help with LinkedIn Easy Apply workflows.

The project is open source on GitHub:

https://github.com/Arul1998/linkedin-easy-apply
Enter fullscreen mode Exit fullscreen mode

The Problem

Applying for jobs online can become repetitive very quickly.

Many application forms ask for the same basic information:

  • First name
  • Last name
  • Email
  • Phone number
  • City
  • CV upload
  • Work authorization
  • Notice period
  • Years of experience
  • Salary expectation

When someone is actively searching for jobs, they may fill the same information many times across different listings.

The goal of this project was simple:

Can I build a small automation assistant that reduces repetitive form filling while keeping the user in control?

What the Project Does

The project is a Python CLI tool that opens Chrome, logs into LinkedIn, searches for Easy Apply jobs, fills simple application forms, uploads a CV when required, and records successful applications.

At a high level, the workflow is:

  1. Read user configuration from config.json
  2. Read LinkedIn login credentials from .env
  3. Open Chrome using Selenium
  4. Log in to LinkedIn
  5. Search for jobs using configured filters
  6. Find Easy Apply jobs
  7. Open each job application modal
  8. Fill known fields from saved answers
  9. Upload the configured CV
  10. Answer simple questions using saved answers and resume-derived information
  11. Submit the application only when the form is manageable
  12. Save the application record to avoid duplicates

The assistant also includes a --dry-run mode so the user can test login and search without submitting any applications.

Important Note About Responsible Use

This project is intended as a personal productivity and learning project.

It is not designed for spam applying, bypassing platform protections, or violating website rules. Browser automation should be used carefully and responsibly.

For that reason, I added safeguards such as:

  • Dry-run mode
  • Confirmation mode
  • Rate limiting between actions
  • Rate limiting between applications
  • Duplicate tracking
  • Manual CAPTCHA / 2FA handling
  • Skipping complex or unknown forms
  • Configuration validation before running

The goal is not to apply to hundreds of jobs blindly. The goal is to reduce repetitive work while keeping the process controlled and human-supervised.

Tech Stack

The project uses:

  • Python
  • Selenium
  • Chrome WebDriver
  • python-dotenv
  • webdriver-manager
  • pypdf
  • JSON / CSV tracking

The main project files are:

main.py                 # CLI entry point
config.py               # Loads and validates configuration
linkedin_automation.py  # Selenium browser automation
resume_profile.py       # Extracts resume information
tracker.py              # Tracks applied jobs
session_store.py        # Stores/reuses LinkedIn session cookies
errors.py               # User-friendly error handling
Enter fullscreen mode Exit fullscreen mode

Project Architecture

The project is split into small modules so that each file has a clear responsibility.

main.py

This is the entry point of the application.

It handles:

  • CLI arguments
  • Config loading
  • Validation
  • Browser startup
  • Login flow
  • Job search navigation
  • Application loop
  • Run summary

Some useful commands are:

python main.py --validate-only
Enter fullscreen mode Exit fullscreen mode

This checks the setup without opening the browser.

python main.py --dry-run
Enter fullscreen mode Exit fullscreen mode

This logs in and searches jobs but does not submit applications.

python main.py --confirm --pause-on-challenge --max-applications 5
Enter fullscreen mode Exit fullscreen mode

This runs the assistant with user confirmation, CAPTCHA/2FA support, and a maximum application limit.

Configuration Design

The project separates secrets from normal configuration.

LinkedIn credentials are stored in .env:

LINKEDIN_EMAIL=your-email@example.com
LINKEDIN_PASSWORD=your-password
Enter fullscreen mode Exit fullscreen mode

The job search settings and personal answers are stored in config.json:

{
  "search": {
    "keywords": "software engineer",
    "location": "United Kingdom",
    "work_type": "2",
    "job_type": "F",
    "date_posted": "r604800",
    "experience_level": "3,4"
  },
  "max_applications": 5,
  "resume_path": "C:/path/to/resume.pdf",
  "tracking": {
    "output_file": "applications.json",
    "format": "json"
  },
  "saved_answers": {
    "first_name": "Arul",
    "last_name": "Cornelious",
    "email": "your-email@example.com",
    "phone": "your-phone-number",
    "city": "St Albans",
    "salary": "Negotiable",
    "sponsorship": "Yes",
    "start_date": "Immediately"
  },
  "custom_answers": {
    "years of experience with angular": "3",
    "are you willing to relocate": "Yes"
  }
}
Enter fullscreen mode Exit fullscreen mode

This design keeps sensitive credentials out of the main configuration file.

Building the LinkedIn Job Search URL

Instead of manually clicking filters, the assistant builds a LinkedIn job search URL using query parameters.

For example, it can include:

  • Keywords
  • Location
  • Easy Apply filter
  • Work type
  • Job type
  • Date posted
  • Experience level
  • Few applicants filter
  • LinkedIn geo ID

The Easy Apply filter is applied through the URL so the assistant focuses only on jobs that support LinkedIn’s Easy Apply workflow.

This makes the search flow simpler and more predictable.

Selenium Automation

The browser automation is handled with Selenium.

The assistant opens Chrome, logs into LinkedIn, searches jobs, and interacts with the Easy Apply modal.

One challenge with browser automation is that websites often change their HTML structure. To make the project more stable, I used multiple CSS selectors and XPath fallbacks for important elements like:

  • Job cards
  • Easy Apply buttons
  • Modal buttons
  • Submit buttons
  • Next buttons
  • Review buttons

For example, the assistant does not rely on only one selector for the Easy Apply button. It checks multiple possible selectors and also uses text-based fallback logic.

This makes the automation more resilient when LinkedIn changes small parts of the UI.

Login and Session Reuse

Logging in every time can trigger extra verification.

To reduce that, the assistant stores session cookies after a successful login and reuses them in later runs.

The login flow supports:

  • Normal email/password login
  • Saved session reuse
  • Fresh login mode
  • CAPTCHA / 2FA pause mode

If LinkedIn asks for verification, the assistant can pause and allow the user to complete the challenge manually in the browser.

Example:

python main.py --pause-on-challenge
Enter fullscreen mode Exit fullscreen mode

This keeps the process human-supervised instead of trying to bypass security checks.

Resume-Based Question Answering

One of the most interesting parts of the project is the resume-based question answering system.

The assistant can read the configured CV and extract useful information such as:

  • Skills
  • Total years of experience
  • Skill-specific experience
  • Work authorization text
  • Notice period
  • Education level
  • Email
  • Phone number

It supports PDF extraction using pypdf.

The answer priority is:

custom_answers → resume-derived profile → saved_answers
Enter fullscreen mode Exit fullscreen mode

This means manually configured answers always win.

For example, if the application asks:

How many years of experience do you have with Angular?
Enter fullscreen mode Exit fullscreen mode

The assistant checks:

  1. Is there a matching custom answer?
  2. Is Angular found in the resume?
  3. Can it estimate experience from the resume?
  4. If not, should the question be skipped?

This prevents the assistant from guessing too aggressively.

Handling Unknown Questions

Not every application form is simple.

Some forms include custom questions, long text answers, dropdowns, multi-step flows, or questions that require human judgement.

The assistant is designed to skip forms it cannot confidently complete.

If it finds a question it cannot answer, the user can add it later to custom_answers.

Example:

{
  "custom_answers": {
    "do you require visa sponsorship": "Yes",
    "what is your expected salary": "Negotiable",
    "are you willing to work hybrid": "Yes"
  }
}
Enter fullscreen mode Exit fullscreen mode

This makes the system improve over time while still keeping the user in control.

Tracking Applications

The assistant records every successful application in a tracking file.

Example JSON output:

[
  {
    "job_title": "Software Engineer",
    "company_name": "Example Company",
    "job_url": "https://www.linkedin.com/jobs/view/123456789/",
    "date_applied": "2026-07-03 12:00:00",
    "status": "applied"
  }
]
Enter fullscreen mode Exit fullscreen mode

This solves two problems:

  1. The user can review application history.
  2. The assistant can avoid applying to the same job twice.

The project supports both JSON and CSV tracking.

Rate Limiting

Rate limiting is important in browser automation.

The project includes two types of delay:

delay_between_actions_sec
delay_between_applications_sec
Enter fullscreen mode Exit fullscreen mode

The first delay controls normal browser actions like clicks and page loads.

The second delay controls how long the assistant waits after submitting an application.

This helps keep the automation slower, safer, and more human-like.

CLI Flags

I added several CLI flags to make the tool safer and easier to test.

--dry-run
Enter fullscreen mode Exit fullscreen mode

Logs in and searches jobs but does not apply.

--confirm
Enter fullscreen mode Exit fullscreen mode

Shows a confirmation prompt before live application submission.

--max-applications 5
Enter fullscreen mode Exit fullscreen mode

Limits how many applications can be submitted in one run.

--pause-on-challenge
Enter fullscreen mode Exit fullscreen mode

Pauses when CAPTCHA or 2FA appears.

--fresh-login
Enter fullscreen mode Exit fullscreen mode

Ignores saved session cookies and logs in again.

--validate-only
Enter fullscreen mode Exit fullscreen mode

Checks .env and config.json without opening the browser.

These flags are useful because browser automation should be tested carefully before any real action is performed.

Challenges I Faced

1. LinkedIn UI changes

LinkedIn’s DOM can change, which means selectors can break.

To handle this, I used multiple selector strategies and fallbacks.

2. Easy Apply forms are not always the same

Some applications are one step. Some are multiple steps. Some ask custom questions. Some require dropdowns, radio buttons, or file uploads.

The assistant handles simple and predictable forms, but skips complex ones.

3. Avoiding duplicate applications

Raw LinkedIn job URLs can include tracking parameters, so the same job can appear with different URLs.

To fix this, I normalized job URLs into a cleaner format before tracking them.

4. Not over-automating

The project needed a balance between automation and responsibility.

That is why I added dry-run mode, confirmation mode, manual challenge handling, delays, and skipping logic.

What I Learned

This project helped me understand several practical engineering concepts:

  • Browser automation with Selenium
  • CLI design in Python
  • Configuration management
  • Environment variable handling
  • Resume parsing
  • Form-filling logic
  • URL normalization
  • Error handling
  • Rate limiting
  • Session cookie reuse
  • Designing safer automation workflows

It also reminded me that automation is not just about making things faster. Good automation should also be controlled, explainable, and respectful of user intent.

Future Improvements

Some improvements I would like to add next:

  • Better dashboard for application history
  • Export reports by date, company, and role
  • Better support for dropdowns and radio buttons
  • More detailed skipped-job reasons
  • Safer preview mode before submitting each application
  • Local encrypted credential storage
  • Unit tests for resume parsing and answer matching
  • GitHub Actions workflow for linting and tests
  • Optional manual review step before final submit

Final Thoughts

This project started as a simple idea: reduce repetitive job application steps.

But it became a useful engineering exercise in browser automation, form intelligence, configuration design, safety controls, and responsible automation.

The biggest lesson I learned is that automation should not remove human judgement. It should support it.

For job applications, that means helping with repetitive form filling while still allowing the applicant to choose the right roles, review their details, and stay in control.

GitHub repository:

https://github.com/Arul1998/linkedin-easy-apply
Enter fullscreen mode Exit fullscreen mode

Thanks for reading. I’m open to feedback, suggestions, and ideas for making this project safer and more useful.

Top comments (1)

Collapse
 
vic_xie_9bed0062d5fd73d12 profile image
vic xie

TextStow could be useful for this workflow — clipboard history + reusable favorites + prompt templates + cleanup for JSON/PDF/URLs. Local-first, free: textstow.com