master 5729dc092e45 cached
5 files
50.6 KB
10.6k tokens
24 symbols
1 requests
Download .txt
Repository: NathanDuma/LinkedIn-Easy-Apply-Bot
Branch: master
Commit: 5729dc092e45
Files: 5
Total size: 50.6 KB

Directory structure:
gitextract_veta8mik/

├── README.md
├── config.yaml
├── linkedineasyapply.py
├── main.py
└── requirements.txt

================================================
FILE CONTENTS
================================================

================================================
FILE: README.md
================================================
# LinkedIn Easy Apply Bot
Automatically apply to LinkedIn Easy Apply jobs. This bot answers the application questions as well!

This is for educational purposes only. I am not responsible if your LinkedIn account gets suspended or for anything else.

This bot is written in Python using Selenium.

## Setup 

To run the bot, open the command line in the cloned repo directory and install the requirements using pip with the following command:
```bash
pip install -r requirements.txt
```

Next, you need to fill out the config.yaml file. Most of this is self-explanatory but if you need explanations please see the end of this README.

```yaml
email: email@domain.com
password: yourpassword

disableAntiLock: False

remote: False

experienceLevel:
 internship: False
 entry: True
 associate: False
 mid-senior level: False
 director: False
 executive: False
 
jobTypes:
 full-time: True
 contract: False
 part-time: False
 temporary: False
 internship: False
 other: False
 volunteer: False
 
date:
 all time: True
 month: False
 week: False
 24 hours: False
 
positions:
 #- First position
 #- A second position
 #- A third position
 #- ...
locations:
 #- First location
 #- A second location
 #- A third location
 #- ...
distance: 25

outputFileDirectory: C:\Users\myDirectory\

companyBlacklist:
 #- company
 #- company2

titleBlacklist:
 #- word1
 #- word2

uploads:
 resume: C:\Users\myDirectory\Resume.pdf
 # Cover letter is optional
 #coverLetter: C:\Users\myDirectory\CoverLettter.pdf


# ------------ QA section -------------------

# ------------ Additional parameters: checkboxes ---------------
checkboxes:
 # Do you have a valid driver's license? (yes/no checkbox)
 driversLicence: True
 # Will you now, or in the future, require sponsorship for employment visa status (e.g. H-1B visa status)? (yes/no checkbox)
 # This is relative to the location and your citizenship applying above, and same with legallyAuthorized.
 requireVisa: False
 # Are you legally authorized to work in COUNTRY? (yes/no checkbox)
 legallyAuthorized: True
 # We must fill this position urgently. Can you start immediately? (yes/no checkbox)
 urgentFill: True
 # Are you comfortable commuting to this job's location? (yes/no checkbox)
 commute: True
 # Have you completed the following level of education: DEGREE TYPE? (yes/no checkbox)
 degreeCompleted:
  - High School Diploma
  - Bachelor's Degree
  # - Associate's Degree
  # - Master's Degree
  # - Master of Business Administration
  # - Doctor of Philosophy
  # - Doctor of Medicine
  # - Doctor of Law
 # Are you willing to undergo a background check, in accordance with local law/regulations?
 backgroundCheck: True

# ------------ Additional parameters: univeristyGpa ---------------
universityGpa: 4.0

# ------------ Additional parameters: languages ---------------
languages:
 english: Native or bilingual # None, Conversational, Professional, Native or bilingual


# ------------ Additional parameters: years of INDUSTRY experience ---------------
# How many years of TECHNOLOGY experience do you currently have? (whole numbers only)
industry:
 # normal ones
 Accounting/Auditing: 0
 Administrative : 0
 Advertising : 0
 Analyst : 0
 Art/Creative: 0
 Business Development: 0
 Consulting: 0
 Customer Service: 0
 Distribution Design: 0
 Education: 0
 Engineering: 0
 Finance: 0
 General Business: 0
 Health Care Provider: 0
 Human Resources: 0
 Information Technology: 0
 Legal: 0
 Management: 0
 Manufacturing: 0
 Marketing: 0
 Public Relations: 0
 Purchasing: 0
 Product Management: 0
 Project Management: 0
 Production: 0
 Quality Assurance: 0
 Research: 0
 Sales: 0
 Science: 0
 Strategy/Planning: 0
 Supply Chain: 0
 Training: 0
 Writing/Editing: 0
 # end normal ones
 # put your custom ones here
 #C++: 0
 #Python: 1
 # default to put for any skill that you did not list
 default: 0
 # end custom ones


# ------------ Additional parameters: years of technology experience ---------------
# How many years of work experience do you have using TECHNOLOGY? (whole numbers only)
technology:
 #python: 0
 #selenium: 0
 # default to put for any skill that you did not list
 default: 0
# ------------ Additional parameters: personal info ---------------
personalInfo:
 First Name: FirstName
 Last Name: LastName
 Phone Country Code: Canada (+1) # See linkedin for your country code, must be exact
 Mobile Phone Number: 1234567890
 Street address: 123 Fake Street
 City: Red Deer, Alberta # Include the state/province as well!
 State: YourState
 Zip: YourZip/Postal
 Linkedin: https://www.linkedin.com/in/my-linkedin-profile
 Website: https://www.my-website.com # github/website is interchangable here

# ------------ Additional parameters: USA employment crap ---------------
eeo:
 gender: None
 race: None
 vetran: None
 disability: None
 citizenship: Canadian
```


## Execute

To run the bot, run the following in the command line:
```
python3 main.py
```

## Config.yaml Explanations
Just fill in your email and password for linkedin.
```yaml
email: email@domain.com
password: yourpassword
```
This prevents your computer from going to sleep so the bot can keep running when you are not using it. Set this to True if you want this disabled.
```yaml
disableAntiLock: False
```
Set this to True if you want to look for remote jobs only.
```yaml
remote: False
```
This is for what level of jobs you want the search to contain. You must choose at least one.
```yaml
experienceLevel:
 internship: False
 entry: True
 associate: False
 mid-senior level: False
 director: False
 executive: False
```
This is for what type of job you are looking for. You must choose at least one.
```yaml
jobTypes:
 full-time: True
 contract: False
 part-time: False
 temporary: False
 internship: False
 other: False
 volunteer: False
```
How far back you want to search. You must choose only one.
```yaml
date:
 all time: True
 month: False
 week: False
 24 hours: False
 ```
A list of positions you want to apply for. You must include at least one.
```yaml
positions:
 #- First position
 #- A second position
 #- A third position
 #- ...
 ```
A list of locations you are applying to. You must include at least one.
```yaml
locations:
 #- First location
 #- A second location
 #- A third location
 #- ...
 ```
How far out of the location you want your search to go. You can only input 0, 5, 10, 25, 50, 100 miles.
```yaml
distance: 25
 ```
This is the directory where all the job application stats will go to.
```yaml
outputFileDirectory: C:\Users\myDirectory\
 ```
A list of companies to not apply to.
```yaml
companyBlacklist:
 #- company
 #- company2
 ```
A list of words that will be used to skip over jobs with any of these words in there.
```yaml
titleBlacklist:
 #- word1
 #- word2
 ```
A path to your resume and cover letter.
```yaml
uploads:
 resume: C:\Users\myDirectory\Resume.pdf
 # Cover letter is optional
 #coverLetter: C:\Users\myDirectory\CoverLettter.pdf
 ```
Answer these questions with regards to the company you are applying to. 
For the degrees part uncomment which degrees you have, and do not add other ones since the linkedin questions are generic.
```yaml
# ------------ Additional parameters: checkboxes ---------------
checkboxes:
 # Do you have a valid driver's license? (yes/no checkbox)
 driversLicence: True
 # Will you now, or in the future, require sponsorship for employment visa status (e.g. H-1B visa status)? (yes/no checkbox)
 # This is relative to the location and your citizenship applying above, and same with legallyAuthorized.
 requireVisa: False
 # Are you legally authorized to work in COUNTRY? (yes/no checkbox)
 legallyAuthorized: True
 # We must fill this position urgently. Can you start immediately? (yes/no checkbox)
 urgentFill: True
 # Are you comfortable commuting to this job's location? (yes/no checkbox)
 commute: True
 # Have you completed the following level of education: DEGREE TYPE? (yes/no checkbox)
 degreeCompleted:
  - High School Diploma
  - Bachelor's Degree
  #- Associate's Degree
  #- Master's Degree
  #- Master of Business Administration
  #- Doctor of Philosophy
  #- Doctor of Medicine
  #- Doctor of Law
 # Are you willing to undergo a background check, in accordance with local law/regulations?
 backgroundCheck: True
 ```
Input your university gpa. Must be a decimal value to one decimal point.
```yaml
# ------------ Additional parameters: univeristyGpa ---------------
universityGpa: 4.0
 ```
List all your languages. You must put the profinenciy as either: None, Conversational, Professional, Native or bilingual
```yaml
# ------------ Additional parameters: languages ---------------
languages:
 english: Native or bilingual # None, Conversational, Professional, Native or bilingual
 ```
Answer the following question for the default industries.
Next, input your custom ones. This can include technologies, programming languages, frameworks, etc.
The years of experience needs to be a whole number. Fill in the default for industries you did not list (keep in mind if it's not zero, you will get your application seen more often).
```yaml
# ------------ Additional parameters: years of INDUSTRY experience ---------------
# How many years of TECHNOLOGY experience do you currently have? (whole numbers only)
industry:
 # normal ones
 Accounting/Auditing: 0
 Administrative : 0
 Advertising : 0
 Analyst : 0
 Art/Creative: 0
 Business Development: 0
 Consulting: 0
 Customer Service: 0
 Distribution Design: 0
 Education: 0
 Engineering: 0
 Finance: 0
 General Business: 0
 Health Care Provider: 0
 Human Resources: 0
 Information Technology: 0
 Legal: 0
 Management: 0
 Manufacturing: 0
 Marketing: 0
 Public Relations: 0
 Purchasing: 0
 Product Management: 0
 Project Management: 0
 Production: 0
 Quality Assurance: 0
 Research: 0
 Sales: 0
 Science: 0
 Strategy/Planning: 0
 Supply Chain: 0
 Training: 0
 Writing/Editing: 0
 # put your custom ones here
 #C++: 0
 #Python: 1
 # end custom ones
 # default to put for any skill that you did not list
 default: 0
 ```
Answer the following question for your tools and technologies. 
Things like programming languages, frameworks, etc.
The years of experience needs to be a whole number.
Fill in the default for technologies you did not list (keep in mind if it's not zero, you will get your application seen more often).
```yaml
# ------------ Additional parameters: years of technology experience ---------------
# How many years of work experience do you have using TECHNOLOGY? (whole numbers only)
technology:
 #python: 0
 #selenium: 0
 # default to put for any skill that you did not list
 default: 0
  ```
Input your personal info. Include the state/province in the city name to not get the wrong city when choosing from a dropdown.
The phone country code needs to be exact for the one that is on linkedin.
The website is interchangable for github/portfolio/website.
```yaml
# ------------ Additional parameters: personal info ---------------
personalInfo:
 First Name: FirstName
 Last Name: LastName
 Phone Country Code: Canada (+1) # See linkedin for your country code, must be exact
 Mobile Phone Number: 1234567890
 Street address: 123 Fake Street
 City: Red Deer, Alberta # Include the state/province as well!
 State: YourState
 Zip: YourZip/Postal
 Linkedin: https://www.linkedin.com/in/my-linkedin-profile
 Website: https://www.my-website.com # github/website is interchangable here
  ```
This is unused at the moment. For the EEO the bot will try to decine to answer for everything.
```yaml
# ------------ Additional parameters: USA employment crap ---------------
eeo:
 gender: None
 race: None
 vetran: None
 disability: None
 citizenship: Canadian
```



================================================
FILE: config.yaml
================================================
email: email@domain.com
password: yourpassword

disableAntiLock: False

remote: False

experienceLevel:
 internship: False
 entry: True
 associate: False
 mid-senior level: False
 director: False
 executive: False
 
jobTypes:
 full-time: True
 contract: False
 part-time: False
 temporary: False
 internship: False
 other: False
 volunteer: False
 
date:
 all time: True
 month: False
 week: False
 24 hours: False
 
positions:
 #- First position
 #- A second position
 #- A third position
 #- ...
locations:
 #- First location
 #- A second location
 #- A third location
 #- ...
distance: 25

outputFileDirectory: C:\Users\myDirectory\

companyBlacklist:
 #- company
 #- company2

titleBlacklist:
 #- word1
 #- word2

uploads:
 resume: C:\Users\myDirectory\Resume.pdf
 # Cover letter is optional
 #coverLetter: C:\Users\myDirectory\CoverLettter.pdf


# ------------ QA section -------------------

# ------------ Additional parameters: checkboxes ---------------
checkboxes:
 # Do you have a valid driver's license? (yes/no checkbox)
 driversLicence: True
 # Will you now, or in the future, require sponsorship for employment visa status (e.g. H-1B visa status)? (yes/no checkbox)
 # This is relative to the location and your citizenship applying above, and same with legallyAuthorized.
 requireVisa: False
 # Are you legally authorized to work in COUNTRY? (yes/no checkbox)
 legallyAuthorized: True
 # We must fill this position urgently. Can you start immediately? (yes/no checkbox)
 urgentFill: True
 # Are you comfortable commuting to this job's location? (yes/no checkbox)
 commute: True
 # Have you completed the following level of education: DEGREE TYPE? (yes/no checkbox)
 degreeCompleted:
  - High School Diploma
  - Bachelor's Degree
  # - Associate's Degree
  # - Master's Degree
  # - Master of Business Administration
  # - Doctor of Philosophy
  # - Doctor of Medicine
  # - Doctor of Law
 # Are you willing to undergo a background check, in accordance with local law/regulations?
 backgroundCheck: True

# ------------ Additional parameters: univeristyGpa ---------------
universityGpa: 4.0

# ------------ Additional parameters: languages ---------------
languages:
 english: Native or bilingual # None, Conversational, Professional, Native or bilingual


# ------------ Additional parameters: years of INDUSTRY experience ---------------
# How many years of TECHNOLOGY experience do you currently have? (whole numbers only)
industry:
 # normal ones
 Accounting/Auditing: 0
 Administrative : 0
 Advertising : 0
 Analyst : 0
 Art/Creative: 0
 Business Development: 0
 Consulting: 0
 Customer Service: 0
 Distribution Design: 0
 Education: 0
 Engineering: 0
 Finance: 0
 General Business: 0
 Health Care Provider: 0
 Human Resources: 0
 Information Technology: 0
 Legal: 0
 Management: 0
 Manufacturing: 0
 Marketing: 0
 Public Relations: 0
 Purchasing: 0
 Product Management: 0
 Project Management: 0
 Production: 0
 Quality Assurance: 0
 Research: 0
 Sales: 0
 Science: 0
 Strategy/Planning: 0
 Supply Chain: 0
 Training: 0
 Writing/Editing: 0
 # end normal ones
 # put your custom ones here
 #C++: 0
 #Python: 1
 # default to put for any skill that you did not list
 default: 0
 # end custom ones


# ------------ Additional parameters: years of technology experience ---------------
# How many years of work experience do you have using TECHNOLOGY? (whole numbers only)
technology:
 #python: 0
 #selenium: 0
 # default to put for any skill that you did not list
 default: 0
# ------------ Additional parameters: personal info ---------------
personalInfo:
 First Name: FirstName
 Last Name: LastName
 Phone Country Code: Canada (+1) # See linkedin for your country code, must be exact
 Mobile Phone Number: 1234567890
 Street address: 123 Fake Street
 City: Red Deer, Alberta # Include the state/province as well!
 State: YourState
 Zip: YourZip/Postal
 Linkedin: https://www.linkedin.com/in/my-linkedin-profile
 Website: https://www.my-website.com # github/website is interchangable here

# ------------ Additional parameters: USA employment crap ---------------
eeo:
 gender: None
 race: None
 vetran: None
 disability: None
 citizenship: Canadian

================================================
FILE: linkedineasyapply.py
================================================
import time, random, csv, pyautogui, pdb, traceback, sys
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select
from datetime import date
from itertools import product


class LinkedinEasyApply:
    def __init__(self, parameters, driver):
        self.browser = driver
        self.email = parameters['email']
        self.password = parameters['password']
        self.disable_lock = parameters['disableAntiLock']
        self.company_blacklist = parameters.get('companyBlacklist', []) or []
        self.title_blacklist = parameters.get('titleBlacklist', []) or []
        self.positions = parameters.get('positions', [])
        self.locations = parameters.get('locations', [])
        self.base_search_url = self.get_base_search_url(parameters)
        self.seen_jobs = []
        self.file_name = "output"
        self.output_file_directory = parameters['outputFileDirectory']
        self.resume_dir = parameters['uploads']['resume']
        if 'coverLetter' in parameters['uploads']:
            self.cover_letter_dir = parameters['uploads']['coverLetter']
        else:
            self.cover_letter_dir = ''
        self.checkboxes = parameters.get('checkboxes', [])
        self.university_gpa = parameters['universityGpa']
        self.languages = parameters.get('languages', [])
        self.industry = parameters.get('industry', [])
        self.technology = parameters.get('technology', [])
        self.personal_info = parameters.get('personalInfo', [])
        self.eeo = parameters.get('eeo', [])
        self.technology_default = self.technology['default']
        self.industry_default = self.industry['default']


    def login(self):
        try:
            self.browser.get("https://www.linkedin.com/login")
            time.sleep(random.uniform(5, 10))
            self.browser.find_element_by_id("username").send_keys(self.email)
            self.browser.find_element_by_id("password").send_keys(self.password)
            self.browser.find_element_by_css_selector(".btn__primary--large").click()
            time.sleep(random.uniform(5, 10))
        except TimeoutException:
            raise Exception("Could not login!")

    def security_check(self):
        current_url = self.browser.current_url
        page_source = self.browser.page_source

        if '/checkpoint/challenge/' in current_url or 'security check' in page_source:
            input("Please complete the security check and press enter in this console when it is done.")
            time.sleep(random.uniform(5.5, 10.5))

    def start_applying(self):
        searches = list(product(self.positions, self.locations))
        random.shuffle(searches)

        page_sleep = 0
        minimum_time = 60*15
        minimum_page_time = time.time() + minimum_time

        for (position, location) in searches:
            location_url = "&location=" + location
            job_page_number = -1

            print("Starting the search for " + position + " in " + location + ".")

            try:
                while True:
                    page_sleep += 1
                    job_page_number += 1
                    print("Going to job page " + str(job_page_number))
                    self.next_job_page(position, location_url, job_page_number)
                    time.sleep(random.uniform(1.5, 3.5))
                    print("Starting the application process for this page...")
                    self.apply_jobs(location)
                    print("Applying to jobs on this page has been completed!")

                    time_left = minimum_page_time - time.time()
                    if time_left > 0:
                        print("Sleeping for " + str(time_left) + " seconds.")
                        time.sleep(time_left)
                        minimum_page_time = time.time() + minimum_time
                    if page_sleep % 5 == 0:
                        sleep_time = random.randint(500, 900)
                        print("Sleeping for " + str(sleep_time/60) + " minutes.")
                        time.sleep(sleep_time)
                        page_sleep += 1
            except:
                traceback.print_exc()
                pass

            time_left = minimum_page_time - time.time()
            if time_left > 0:
                print("Sleeping for " + str(time_left) + " seconds.")
                time.sleep(time_left)
                minimum_page_time = time.time() + minimum_time
            if page_sleep % 5 == 0:
                sleep_time = random.randint(500, 900)
                print("Sleeping for " + str(sleep_time/60) + " minutes.")
                time.sleep(sleep_time)
                page_sleep += 1


    def apply_jobs(self, location):
        no_jobs_text = ""
        try:
            no_jobs_element = self.browser.find_element_by_class_name('jobs-search-two-pane__no-results-banner--expand')
            no_jobs_text = no_jobs_element.text
        except:
            pass
        if 'No matching jobs found' in no_jobs_text:
            raise Exception("No more jobs on this page")

        if 'unfortunately, things aren' in self.browser.page_source.lower():
            raise Exception("No more jobs on this page")

        try:
            job_results = self.browser.find_element_by_class_name("jobs-search-results")
            self.scroll_slow(job_results)
            self.scroll_slow(job_results, step=300, reverse=True)

            job_list = self.browser.find_elements_by_class_name('jobs-search-results__list')[0].find_elements_by_class_name('jobs-search-results__list-item')
        except:
            raise Exception("No more jobs on this page")

        if len(job_list) == 0:
            raise Exception("No more jobs on this page")

        for job_tile in job_list:
            job_title, company, job_location, apply_method, link = "", "", "", "", ""

            try:
                job_title = job_tile.find_element_by_class_name('job-card-list__title').text
                link = job_tile.find_element_by_class_name('job-card-list__title').get_attribute('href').split('?')[0]
            except:
                pass
            try:
                company = job_tile.find_element_by_class_name('job-card-container__company-name').text
            except:
                pass
            try:
                job_location = job_tile.find_element_by_class_name('job-card-container__metadata-item').text
            except:
                pass
            try:
                apply_method = job_tile.find_element_by_class_name('job-card-container__apply-method').text
            except:
                pass

            contains_blacklisted_keywords = False
            job_title_parsed = job_title.lower().split(' ')

            for word in self.title_blacklist:
                if word.lower() in job_title_parsed:
                    contains_blacklisted_keywords = True
                    break

            if company.lower() not in [word.lower() for word in self.company_blacklist] and \
               contains_blacklisted_keywords is False and link not in self.seen_jobs:
                try:
                    job_el = job_tile.find_element_by_class_name('job-card-list__title')
                    job_el.click()

                    time.sleep(random.uniform(3, 5))


                    try:
                        done_applying = self.apply_to_job()
                        if done_applying:
                            print("Done applying to the job!")
                        else:
                            print('Already applied to the job!')
                    except:
                        temp = self.file_name
                        self.file_name = "failed"
                        print("Failed to apply to job! Please submit a bug report with this link: " + link)
                        print("Writing to the failed csv file...")
                        try:
                            self.write_to_file(company, job_title, link, job_location, location)
                        except:
                            pass
                        self.file_name = temp

                    try:
                        self.write_to_file(company, job_title, link, job_location, location)
                    except Exception:
                        print("Could not write the job to the file! No special characters in the job title/company is allowed!")
                        traceback.print_exc()
                except:
                    traceback.print_exc()
                    print("Could not apply to the job!")
                    pass
            else:
                print("Job contains blacklisted keyword or company name!")
            self.seen_jobs += link

    def apply_to_job(self):
        easy_apply_button = None

        try:
            easy_apply_button = self.browser.find_element_by_class_name('jobs-apply-button')
        except:
            return False

        try:
            job_description_area = self.browser.find_element_by_class_name("jobs-search__job-details--container")
            self.scroll_slow(job_description_area, end=1600)
            self.scroll_slow(job_description_area, end=1600, step=400, reverse=True)
        except:
            pass

        print("Applying to the job....")
        easy_apply_button.click()

        button_text = ""
        submit_application_text = 'submit application'
        while submit_application_text not in button_text.lower():
            retries = 3
            while retries > 0:
                try:
                    self.fill_up()
                    next_button = self.browser.find_element_by_class_name("artdeco-button--primary")
                    button_text = next_button.text.lower()
                    if submit_application_text in button_text:
                        try:
                            self.unfollow()
                        except:
                            print("Failed to unfollow company!")
                    time.sleep(random.uniform(1.5, 2.5))
                    next_button.click()
                    time.sleep(random.uniform(3.0, 5.0))

                    if 'please enter a valid answer' in self.browser.page_source.lower() or 'file is required' in self.browser.page_source.lower():
                        retries -= 1
                        print("Retrying application, attempts left: " + str(retries))
                    else:
                        break
                except:
                    traceback.print_exc()
                    raise Exception("Failed to apply to job!")
            if retries == 0:
                traceback.print_exc()
                self.browser.find_element_by_class_name('artdeco-modal__dismiss').click()
                time.sleep(random.uniform(3, 5))
                self.browser.find_elements_by_class_name('artdeco-modal__confirm-dialog-btn')[1].click()
                time.sleep(random.uniform(3, 5))
                raise Exception("Failed to apply to job!")

        closed_notification = False
        time.sleep(random.uniform(3, 5))
        try:
            self.browser.find_element_by_class_name('artdeco-modal__dismiss').click()
            closed_notification = True
        except:
            pass
        try:
            self.browser.find_element_by_class_name('artdeco-toast-item__dismiss').click()
            closed_notification = True
        except:
            pass
        time.sleep(random.uniform(3, 5))

        if closed_notification is False:
            raise Exception("Could not close the applied confirmation window!")

        return True

    def home_address(self, element):
        try:
            groups = element.find_elements_by_class_name('jobs-easy-apply-form-section__grouping')
            if len(groups) > 0:
                for group in groups:
                    lb = group.find_element_by_tag_name('label').text.lower()
                    input_field = group.find_element_by_tag_name('input')
                    if 'street' in lb:
                        self.enter_text(input_field, self.personal_info['Street address'])
                    elif 'city' in lb:
                        self.enter_text(input_field, self.personal_info['City'])
                        time.sleep(3)
                        input_field.send_keys(Keys.DOWN)
                        input_field.send_keys(Keys.RETURN)
                    elif 'zip' in lb or 'postal' in lb:
                        self.enter_text(input_field, self.personal_info['Zip'])
                    elif 'state' in lb or 'province' in lb:
                        self.enter_text(input_field, self.personal_info['State'])
                    else:
                        pass
        except:
            pass

    def get_answer(self, question):
        if self.checkboxes[question]:
            return 'yes'
        else:
            return 'no'

    def additional_questions(self):
        #pdb.set_trace()
        frm_el = self.browser.find_elements_by_class_name('jobs-easy-apply-form-section__grouping')
        if len(frm_el) > 0:
            for el in frm_el:
                # Radio check
                try:
                    radios = el.find_element_by_class_name('jobs-easy-apply-form-element').find_elements_by_class_name('fb-radio')

                    radio_text = el.text.lower()
                    radio_options = [text.text.lower() for text in radios]
                    answer = "yes"

                    if 'driver\'s licence' in radio_text or 'driver\'s license' in radio_text:
                        answer = self.get_answer('driversLicence')
                    elif 'gender' in radio_text or 'veteran' in radio_text or 'race' in radio_text or 'disability' in radio_text or 'latino' in radio_text:
                        answer = ""
                        for option in radio_options:
                            if 'prefer' in option.lower() or 'decline' in option.lower() or 'don\'t' in option.lower() or 'specified' in option.lower() or 'none' in option.lower():
                                answer = option

                        if answer == "":
                            answer = radio_options[len(radio_options) - 1]
                    elif 'north korea' in radio_text:
                        answer = 'no'
                    elif 'sponsor' in radio_text:
                        answer = self.get_answer('requireVisa')
                    elif 'authorized' in radio_text or 'authorised' in radio_text or 'legally' in radio_text:
                        answer = self.get_answer('legallyAuthorized')
                    elif 'urgent' in radio_text:
                        answer = self.get_answer('urgentFill')
                    elif 'commuting' in radio_text:
                        answer = self.get_answer('commute')
                    elif 'background check' in radio_text:
                        answer = self.get_answer('backgroundCheck')
                    elif 'level of education' in radio_text:
                        for degree in self.checkboxes['degreeCompleted']:
                            if degree.lower() in radio_text:
                                answer = "yes"
                                break
                    elif 'level of education' in radio_text:
                        for degree in self.checkboxes['degreeCompleted']:
                            if degree.lower() in radio_text:
                                answer = "yes"
                                break
                    elif 'data retention' in radio_text:
                        answer = 'no'
                    else:
                        answer = radio_options[len(radio_options) - 1]

                    i = 0
                    to_select = None
                    for radio in radios:
                        if answer in radio.text.lower():
                            to_select = radios[i]
                        i += 1

                    if to_select is None:
                        to_select = radios[len(radios)-1]

                    self.radio_select(to_select, answer, len(radios) > 2)

                    if radios != []:
                        continue
                except:
                    pass
                # Questions check
                try:
                    question = el.find_element_by_class_name('jobs-easy-apply-form-element')
                    question_text = question.find_element_by_class_name('fb-form-element-label').text.lower()

                    txt_field_visible = False
                    try:
                        txt_field = question.find_element_by_class_name('fb-single-line-text__input')

                        txt_field_visible = True
                    except:
                        try:
                            txt_field = question.find_element_by_class_name('fb-textarea')

                            txt_field_visible = True
                        except:
                            pass

                    if txt_field_visible != True:
                        txt_field = question.find_element_by_class_name('multi-line-text__input')

                    text_field_type = txt_field.get_attribute('name').lower()
                    if 'numeric' in text_field_type:
                        text_field_type = 'numeric'
                    elif 'text' in text_field_type:
                        text_field_type = 'text'

                    to_enter = ''
                    if 'experience do you currently have' in question_text:
                        no_of_years = self.industry_default

                        for industry in self.industry:
                            if industry.lower() in question_text:
                                no_of_years = self.industry[industry]
                                break

                        to_enter = no_of_years
                    elif 'many years of work experience do you have using' in question_text:
                        no_of_years = self.technology_default

                        for technology in self.technology:
                            if technology.lower() in question_text:
                                no_of_years = self.technology[technology]

                        to_enter = no_of_years
                    elif 'grade point average' in question_text:
                        to_enter = self.university_gpa
                    elif 'first name' in question_text:
                        to_enter = self.personal_info['First Name']
                    elif 'last name' in question_text:
                        to_enter = self.personal_info['Last Name']
                    elif 'name' in question_text:
                        to_enter = self.personal_info['First Name'] + " " + self.personal_info['Last Name']
                    elif 'phone' in question_text:
                        to_enter = self.personal_info['Mobile Phone Number']
                    elif 'linkedin' in question_text:
                        to_enter = self.personal_info['Linkedin']
                    elif 'website' in question_text or 'github' in question_text or 'portfolio' in question_text:
                        to_enter = self.personal_info['Website']
                    else:
                        if text_field_type == 'numeric':
                            to_enter = 0
                        else:
                            to_enter = " ‏‏‎ "

                    if text_field_type == 'numeric':
                        if not isinstance(to_enter, (int, float)):
                            to_enter = 0
                    elif to_enter == '':
                        to_enter = " ‏‏‎ "

                    self.enter_text(txt_field, to_enter)
                    continue
                except:
                    pass
                # Date Check
                try:
                    date_picker = el.find_element_by_class_name('artdeco-datepicker__input ')
                    date_picker.clear()
                    date_picker.send_keys(date.today().strftime("%m/%d/%y"))
                    time.sleep(3)
                    date_picker.send_keys(Keys.RETURN)
                    time.sleep(2)
                    continue
                except:
                    pass
                # Dropdown check
                try:
                    question = el.find_element_by_class_name('jobs-easy-apply-form-element')
                    question_text = question.find_element_by_class_name('fb-form-element-label').text.lower()

                    dropdown_field = question.find_element_by_class_name('fb-dropdown__select')

                    select = Select(dropdown_field)

                    options = [options.text for options in select.options]

                    if 'proficiency' in question_text:
                        proficiency = "Conversational"

                        for language in self.languages:
                            if language.lower() in question_text:
                                proficiency = self.languages[language]
                                break

                        self.select_dropdown(dropdown_field, proficiency)
                    elif 'country code' in question_text:
                        self.select_dropdown(dropdown_field, self.personal_info['Phone Country Code'])
                    elif 'north korea' in question_text:

                        choice = ""

                        for option in options:
                            if 'no' in option.lower():
                                choice = option

                        if choice == "":
                            choice = options[len(options) - 1]

                        self.select_dropdown(dropdown_field, choice)
                    elif 'sponsor' in question_text:
                        answer = self.get_answer('requireVisa')

                        choice = ""

                        for option in options:
                            if answer == 'yes':
                                choice = option
                            else:
                                if 'no' in option.lower():
                                    choice = option

                        if choice == "":
                            choice = options[len(options) - 1]

                        self.select_dropdown(dropdown_field, choice)
                    elif 'authorized' in question_text or 'authorised' in question_text:
                        answer = self.get_answer('legallyAuthorized')

                        choice = ""

                        for option in options:
                            if answer == 'yes':
                                # find some common words
                                choice = option
                            else:
                                if 'no' in option.lower():
                                    choice = option

                        if choice == "":
                            choice = options[len(options) - 1]

                        self.select_dropdown(dropdown_field, choice)
                    elif 'citizenship' in question_text:
                        answer = self.get_answer('legallyAuthorized')

                        choice = ""

                        for option in options:
                            if answer == 'yes':
                                if 'no' in option.lower():
                                    choice = option

                        if choice == "":
                            choice = options[len(options) - 1]

                        self.select_dropdown(dropdown_field, choice)
                    elif 'gender' in question_text or 'veteran' in question_text or 'race' in question_text or 'disability' in question_text or 'latino' in question_text:

                        choice = ""

                        for option in options:
                            if 'prefer' in option.lower() or 'decline' in option.lower() or 'don\'t' in option.lower() or 'specified' in option.lower() or 'none' in option.lower():
                                choice = option

                        if choice == "":
                            choice = options[len(options) - 1]

                        self.select_dropdown(dropdown_field, choice)
                    else:
                        choice = ""

                        for option in options:
                            if 'yes' in option.lower():
                                choice = option

                        if choice == "":
                            choice = options[len(options) - 1]

                        self.select_dropdown(dropdown_field, choice)
                    continue
                except:
                    pass

                # Checkbox check for agreeing to terms and service
                try:
                    question = el.find_element_by_class_name('jobs-easy-apply-form-element')

                    clickable_checkbox = question.find_element_by_tag_name('label')

                    clickable_checkbox.click()
                except:
                    pass

    def unfollow(self):
        try:
            follow_checkbox = self.browser.find_element(By.XPATH, "//label[contains(.,\'to stay up to date with their page.\')]").click()
            follow_checkbox.click()
        except:
            pass

    def send_resume(self):
        try:
            file_upload_elements = (By.CSS_SELECTOR, "input[name='file']")
            if len(self.browser.find_elements(file_upload_elements[0], file_upload_elements[1])) > 0:
                input_buttons = self.browser.find_elements(file_upload_elements[0], file_upload_elements[1])
                for upload_button in input_buttons:
                    upload_type = upload_button.find_element(By.XPATH, "..").find_element(By.XPATH, "preceding-sibling::*")
                    if 'resume' in upload_type.text.lower():
                        upload_button.send_keys(self.resume_dir)
                    elif 'cover' in upload_type.text.lower():
                        if self.cover_letter_dir != '':
                            upload_button.send_keys(self.cover_letter_dir)
                        elif 'required' in upload_type.text.lower():
                            upload_button.send_keys(self.resume_dir)
        except:
            print("Failed to upload resume or cover letter!")
            pass


    def enter_text(self, element, text):
        element.clear()
        element.send_keys(text)

    def select_dropdown(self, element, text):
        select = Select(element)
        select.select_by_visible_text(text)

    # Radio Select
    def radio_select(self, element, label_text, clickLast=False):
        label = element.find_element_by_tag_name('label')
        if label_text in label.text.lower() or clickLast == True:
            label.click()
        else:
            pass

    # Contact info fill-up
    def contact_info(self):
        frm_el = self.browser.find_elements_by_class_name('jobs-easy-apply-form-section__grouping')
        if len(frm_el) > 0:
            for el in frm_el:
                text = el.text.lower()
                if 'email address' in text:
                    continue
                elif 'phone number' in text:
                    try:
                        country_code_picker = el.find_element_by_class_name('fb-dropdown__select')
                        self.select_dropdown(country_code_picker, self.personal_info['Phone Country Code'])
                    except:
                        print("Country code " + self.personal_info['Phone Country Code'] + " not found! Make sure it is exact.")
                    try:
                        phone_number_field = el.find_element_by_class_name('fb-single-line-text__input')
                        self.enter_text(phone_number_field, self.personal_info['Mobile Phone Number'])
                    except:
                        print("Could not input phone number.")

    def fill_up(self):
        try:
            easy_apply_content = self.browser.find_element_by_class_name('jobs-easy-apply-content')
            b4 = easy_apply_content.find_element_by_class_name('pb4')
            pb4 = easy_apply_content.find_elements_by_class_name('pb4')
            if len(pb4) > 0:
                for pb in pb4:
                    try:
                        label = pb.find_element_by_tag_name('h3').text.lower()
                        try:
                            self.additional_questions()
                        except:
                            pass

                        try:
                            self.send_resume()
                        except:
                            pass

                        if 'home address' in label:
                            self.home_address(pb)
                        elif 'contact info' in label:
                            self.contact_info()
                    except:
                        pass
        except:
            pass

    def write_to_file(self, company, job_title, link, location, search_location):
        to_write = [company, job_title, link, location]
        file_path = self.output_file_directory + self.file_name + search_location + ".csv"

        with open(file_path, 'a') as f:
            writer = csv.writer(f)
            writer.writerow(to_write)

    def scroll_slow(self, scrollable_element, start=0, end=3600, step=100, reverse=False):
        if reverse:
            start, end = end, start
            step = -step

        for i in range(start, end, step):
            self.browser.execute_script("arguments[0].scrollTo(0, {})".format(i), scrollable_element)
            time.sleep(random.uniform(1.0, 2.6))

    def avoid_lock(self):
        if self.disable_lock:
            return

        pyautogui.keyDown('ctrl')
        pyautogui.press('esc')
        pyautogui.keyUp('ctrl')
        time.sleep(1.0)
        pyautogui.press('esc')

    def get_base_search_url(self, parameters):
        remote_url = ""

        if parameters['remote']:
            remote_url = "f_CF=f_WRA"

        level = 1
        experience_level = parameters.get('experienceLevel', [])
        experience_url = "f_E="
        for key in experience_level.keys():
            if experience_level[key]:
                experience_url += "%2C" + str(level)
            level += 1

        distance_url = "?distance=" + str(parameters['distance'])

        job_types_url = "f_JT="
        job_types = parameters.get('experienceLevel', [])
        for key in job_types:
            if job_types[key]:
                job_types_url += "%2C" + key[0].upper()

        date_url = ""
        dates = {"all time": "", "month": "&f_TPR=r2592000", "week": "&f_TPR=r604800", "24 hours": "&f_TPR=r86400"}
        date_table = parameters.get('date', [])
        for key in date_table.keys():
            if date_table[key]:
                date_url = dates[key]
                break

        easy_apply_url = "&f_LF=f_AL"

        extra_search_terms = [distance_url, remote_url, job_types_url, experience_url]
        extra_search_terms_str = '&'.join(term for term in extra_search_terms if len(term) > 0) + easy_apply_url + date_url

        return extra_search_terms_str

    def next_job_page(self, position, location, job_page):
        self.browser.get("https://www.linkedin.com/jobs/search/" + self.base_search_url +
                         "&keywords=" + position + location + "&start=" + str(job_page*25))

        self.avoid_lock()



================================================
FILE: main.py
================================================
import yaml, pdb
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
from linkedineasyapply import LinkedinEasyApply
from validate_email import validate_email

def init_browser():
    browser_options = Options()
    options = ['--disable-blink-features', '--no-sandbox', '--start-maximized', '--disable-extensions',
               '--ignore-certificate-errors', '--disable-blink-features=AutomationControlled']

    for option in options:
        browser_options.add_argument(option)

    driver = webdriver.Chrome(ChromeDriverManager().install(), chrome_options=browser_options)

    driver.set_window_position(0, 0)
    driver.maximize_window()

    return driver


def validate_yaml():
    with open("config.yaml", 'r') as stream:
        try:
            parameters = yaml.safe_load(stream)
        except yaml.YAMLError as exc:
            raise exc

    mandatory_params = ['email', 'password', 'disableAntiLock', 'remote', 'experienceLevel', 'jobTypes', 'date',
                        'positions', 'locations', 'distance', 'outputFileDirectory', 'checkboxes', 'universityGpa',
                        'languages', 'industry', 'technology', 'personalInfo', 'eeo', 'uploads']

    for mandatory_param in mandatory_params:
        if mandatory_param not in parameters:
            raise Exception(mandatory_param + ' is not inside the yml file!')

    assert validate_email(parameters['email'])
    assert len(str(parameters['password'])) > 0

    assert isinstance(parameters['disableAntiLock'], bool)

    assert isinstance(parameters['remote'], bool)

    assert len(parameters['experienceLevel']) > 0
    experience_level = parameters.get('experienceLevel', [])
    at_least_one_experience = False
    for key in experience_level.keys():
        if experience_level[key]:
            at_least_one_experience = True
    assert at_least_one_experience

    assert len(parameters['jobTypes']) > 0
    job_types = parameters.get('jobTypes', [])
    at_least_one_job_type = False
    for key in job_types.keys():
        if job_types[key]:
            at_least_one_job_type = True
    assert at_least_one_job_type

    assert len(parameters['date']) > 0
    date = parameters.get('date', [])
    at_least_one_date = False
    for key in date.keys():
        if date[key]:
            at_least_one_date = True
    assert at_least_one_date

    approved_distances = {0, 5, 10, 25, 50, 100}
    assert parameters['distance'] in approved_distances

    assert len(parameters['positions']) > 0
    assert len(parameters['locations']) > 0

    assert len(parameters['uploads']) >= 1 and 'resume' in parameters['uploads']

    assert len(parameters['checkboxes']) > 0

    checkboxes = parameters.get('checkboxes', [])
    assert isinstance(checkboxes['driversLicence'], bool)
    assert isinstance(checkboxes['requireVisa'], bool)
    assert isinstance(checkboxes['legallyAuthorized'], bool)
    assert isinstance(checkboxes['urgentFill'], bool)
    assert isinstance(checkboxes['commute'], bool)
    assert isinstance(checkboxes['backgroundCheck'], bool)
    assert 'degreeCompleted' in checkboxes

    assert isinstance(parameters['universityGpa'], (int, float))

    languages = parameters.get('languages', [])
    language_types = {'none', 'conversational', 'professional', 'native or bilingual'}
    for language in languages:
        assert languages[language].lower() in language_types

    industry = parameters.get('industry', [])

    for skill in industry:
        assert isinstance(industry[skill], int)
    assert 'default' in industry

    technology = parameters.get('technology', [])

    for tech in technology:
        assert isinstance(technology[tech], int)
    assert 'default' in technology

    assert len(parameters['personalInfo'])
    personal_info = parameters.get('personalInfo', [])
    for info in personal_info:
        assert personal_info[info] != ''

    assert len(parameters['eeo'])
    eeo = parameters.get('eeo', [])
    for survey_question in eeo:
        assert eeo[survey_question] != ''

    return parameters


if __name__ == '__main__':
    parameters = validate_yaml()
    browser = init_browser()

    bot = LinkedinEasyApply(parameters, browser)
    bot.login()
    bot.security_check()
    bot.start_applying()






================================================
FILE: requirements.txt
================================================
selenium
pyautogui
webdriver_manager
PyYAML
validate_email
Download .txt
gitextract_veta8mik/

├── README.md
├── config.yaml
├── linkedineasyapply.py
├── main.py
└── requirements.txt
Download .txt
SYMBOL INDEX (24 symbols across 2 files)

FILE: linkedineasyapply.py
  class LinkedinEasyApply (line 10) | class LinkedinEasyApply:
    method __init__ (line 11) | def __init__(self, parameters, driver):
    method login (line 40) | def login(self):
    method security_check (line 51) | def security_check(self):
    method start_applying (line 59) | def start_applying(self):
    method apply_jobs (line 110) | def apply_jobs(self, location):
    method apply_to_job (line 203) | def apply_to_job(self):
    method home_address (line 274) | def home_address(self, element):
    method get_answer (line 297) | def get_answer(self, question):
    method additional_questions (line 303) | def additional_questions(self):
    method unfollow (line 573) | def unfollow(self):
    method send_resume (line 580) | def send_resume(self):
    method enter_text (line 599) | def enter_text(self, element, text):
    method select_dropdown (line 603) | def select_dropdown(self, element, text):
    method radio_select (line 608) | def radio_select(self, element, label_text, clickLast=False):
    method contact_info (line 616) | def contact_info(self):
    method fill_up (line 635) | def fill_up(self):
    method write_to_file (line 663) | def write_to_file(self, company, job_title, link, location, search_loc...
    method scroll_slow (line 671) | def scroll_slow(self, scrollable_element, start=0, end=3600, step=100,...
    method avoid_lock (line 680) | def avoid_lock(self):
    method get_base_search_url (line 690) | def get_base_search_url(self, parameters):
    method next_job_page (line 727) | def next_job_page(self, position, location, job_page):

FILE: main.py
  function init_browser (line 8) | def init_browser():
  function validate_yaml (line 24) | def validate_yaml():
Condensed preview — 5 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (54K chars).
[
  {
    "path": "README.md",
    "chars": 11631,
    "preview": "# LinkedIn Easy Apply Bot\nAutomatically apply to LinkedIn Easy Apply jobs. This bot answers the application questions as"
  },
  {
    "path": "config.yaml",
    "chars": 4167,
    "preview": "email: email@domain.com\npassword: yourpassword\n\ndisableAntiLock: False\n\nremote: False\n\nexperienceLevel:\n internship: Fal"
  },
  {
    "path": "linkedineasyapply.py",
    "chars": 31647,
    "preview": "import time, random, csv, pyautogui, pdb, traceback, sys\nfrom selenium.common.exceptions import TimeoutException\nfrom se"
  },
  {
    "path": "main.py",
    "chars": 4351,
    "preview": "import yaml, pdb\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\nfrom webdriver_man"
  },
  {
    "path": "requirements.txt",
    "chars": 58,
    "preview": "selenium\npyautogui\nwebdriver_manager\nPyYAML\nvalidate_email"
  }
]

About this extraction

This page contains the full source code of the NathanDuma/LinkedIn-Easy-Apply-Bot GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 5 files (50.6 KB), approximately 10.6k tokens, and a symbol index with 24 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!