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
gitextract_veta8mik/ ├── README.md ├── config.yaml ├── linkedineasyapply.py ├── main.py └── requirements.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.