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