├── .github └── FUNDING.yml ├── README.md ├── config.json └── main.py /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: aicoding 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: aicoding 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EasyApply-Linkedin 2 | 3 | With this tool you can easily automate the process of applying for jobs on LinkedIn! 4 | 5 | ## Getting started 6 | 7 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. 8 | 9 | ### Prerequisites 10 | 11 | 1. Install selenium. I used `pip` to install the selenium package. 12 | 13 | `pip install selenium` 14 | 15 | 2. Selenium requires a driver to interface with the chosen browser. Make sure the driver is in your path, you will need to add your `driver_path` to the `config.json` file. 16 | 17 | I used the Chrome driver, you can download it [here](https://sites.google.com/a/chromium.org/chromedriver/downloads). You can also download [Edge](https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/), [Firefox](https://github.com/mozilla/geckodriver/releases) or [Safari](https://webkit.org/blog/6900/webdriver-support-in-safari-10/). Depends on your preferred browser. 18 | 19 | ### Usage 20 | 21 | Fork and clone/download the repository and change the configuration file with: 22 | 23 | * Your email linked to LinkedIn. 24 | * Your password. 25 | * Keywords for finding specific job titles fx. Machine Learning Engineer, Data Scientist, etc. 26 | * The location where you are currently looking for a position. 27 | * The driver path to your downloaded webdriver. 28 | 29 | Run `python main.py`. 30 | 31 | Please feel free to comment or give suggestions/issues. 32 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "email" : "your_email", 3 | "password" : "your_password", 4 | "keywords" : "your_keywords", 5 | "location" : "your_location", 6 | "driver_path" : "your_path_to_webdriver" 7 | } -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.common.keys import Keys 3 | from selenium.webdriver.support import expected_conditions as EC 4 | from selenium.webdriver.support.ui import WebDriverWait 5 | from selenium.webdriver.common.by import By 6 | from selenium.common.exceptions import NoSuchElementException, ElementClickInterceptedException, NoSuchElementException 7 | from selenium.webdriver.common.action_chains import ActionChains 8 | import time 9 | import re 10 | import json 11 | 12 | class EasyApplyLinkedin: 13 | 14 | def __init__(self, data): 15 | """Parameter initialization""" 16 | 17 | self.email = data['email'] 18 | self.password = data['password'] 19 | self.keywords = data['keywords'] 20 | self.location = data['location'] 21 | self.driver = webdriver.Chrome(data['driver_path']) 22 | 23 | def login_linkedin(self): 24 | """This function logs into your personal LinkedIn profile""" 25 | 26 | # go to the LinkedIn login url 27 | self.driver.get("https://www.linkedin.com/login") 28 | 29 | # introduce email and password and hit enter 30 | login_email = self.driver.find_element_by_name('session_key') 31 | login_email.clear() 32 | login_email.send_keys(self.email) 33 | login_pass = self.driver.find_element_by_name('session_password') 34 | login_pass.clear() 35 | login_pass.send_keys(self.password) 36 | login_pass.send_keys(Keys.RETURN) 37 | 38 | def job_search(self): 39 | """This function goes to the 'Jobs' section a looks for all the jobs that matches the keywords and location""" 40 | 41 | # go to Jobs 42 | jobs_link = self.driver.find_element_by_link_text('Jobs') 43 | jobs_link.click() 44 | 45 | # search based on keywords and location and hit enter 46 | search_keywords = self.driver.find_element_by_css_selector(".jobs-search-box__text-input[aria-label='Search jobs']") 47 | search_keywords.clear() 48 | search_keywords.send_keys(self.keywords) 49 | search_location = self.driver.find_element_by_css_selector(".jobs-search-box__text-input[aria-label='Search location']") 50 | search_location.clear() 51 | search_location.send_keys(self.location) 52 | search_location.send_keys(Keys.RETURN) 53 | 54 | def filter(self): 55 | """This function filters all the job results by 'Easy Apply'""" 56 | 57 | # select all filters, click on Easy Apply and apply the filter 58 | all_filters_button = self.driver.find_element_by_xpath("//button[@data-control-name='all_filters']") 59 | all_filters_button.click() 60 | time.sleep(1) 61 | easy_apply_button = self.driver.find_element_by_xpath("//label[@for='f_LF-f_AL']") 62 | easy_apply_button.click() 63 | time.sleep(1) 64 | apply_filter_button = self.driver.find_element_by_xpath("//button[@data-control-name='all_filters_apply']") 65 | apply_filter_button.click() 66 | 67 | def find_offers(self): 68 | """This function finds all the offers through all the pages result of the search and filter""" 69 | 70 | # find the total amount of results (if the results are above 24-more than one page-, we will scroll trhough all available pages) 71 | total_results = self.driver.find_element_by_class_name("display-flex.t-12.t-black--light.t-normal") 72 | total_results_int = int(total_results.text.split(' ',1)[0].replace(",","")) 73 | print(total_results_int) 74 | 75 | time.sleep(2) 76 | # get results for the first page 77 | current_page = self.driver.current_url 78 | results = self.driver.find_elements_by_class_name("occludable-update.artdeco-list__item--offset-4.artdeco-list__item.p0.ember-view") 79 | 80 | # for each job add, submits application if no questions asked 81 | for result in results: 82 | hover = ActionChains(self.driver).move_to_element(result) 83 | hover.perform() 84 | titles = result.find_elements_by_class_name('job-card-search__title.artdeco-entity-lockup__title.ember-view') 85 | for title in titles: 86 | self.submit_apply(title) 87 | 88 | # if there is more than one page, find the pages and apply to the results of each page 89 | if total_results_int > 24: 90 | time.sleep(2) 91 | 92 | # find the last page and construct url of each page based on the total amount of pages 93 | find_pages = self.driver.find_elements_by_class_name("artdeco-pagination__indicator.artdeco-pagination__indicator--number") 94 | total_pages = find_pages[len(find_pages)-1].text 95 | total_pages_int = int(re.sub(r"[^\d.]", "", total_pages)) 96 | get_last_page = self.driver.find_element_by_xpath("//button[@aria-label='Page "+str(total_pages_int)+"']") 97 | get_last_page.send_keys(Keys.RETURN) 98 | time.sleep(2) 99 | last_page = self.driver.current_url 100 | total_jobs = int(last_page.split('start=',1)[1]) 101 | 102 | # go through all available pages and job offers and apply 103 | for page_number in range(25,total_jobs+25,25): 104 | self.driver.get(current_page+'&start='+str(page_number)) 105 | time.sleep(2) 106 | results_ext = self.driver.find_elements_by_class_name("occludable-update.artdeco-list__item--offset-4.artdeco-list__item.p0.ember-view") 107 | for result_ext in results_ext: 108 | hover_ext = ActionChains(self.driver).move_to_element(result_ext) 109 | hover_ext.perform() 110 | titles_ext = result_ext.find_elements_by_class_name('job-card-search__title.artdeco-entity-lockup__title.ember-view') 111 | for title_ext in titles_ext: 112 | self.submit_apply(title_ext) 113 | else: 114 | self.close_session() 115 | 116 | def submit_apply(self,job_add): 117 | """This function submits the application for the job add found""" 118 | 119 | print('You are applying to the position of: ', job_add.text) 120 | job_add.click() 121 | time.sleep(2) 122 | 123 | # click on the easy apply button, skip if already applied to the position 124 | try: 125 | in_apply = self.driver.find_element_by_xpath("//button[@data-control-name='jobdetails_topcard_inapply']") 126 | in_apply.click() 127 | except NoSuchElementException: 128 | print('You already applied to this job, go to next...') 129 | pass 130 | time.sleep(1) 131 | 132 | # try to submit if submit application is available... 133 | try: 134 | submit = self.driver.find_element_by_xpath("//button[@data-control-name='submit_unify']") 135 | submit.send_keys(Keys.RETURN) 136 | 137 | # ... if not available, discard application and go to next 138 | except NoSuchElementException: 139 | print('Not direct application, going to next...') 140 | try: 141 | discard = self.driver.find_element_by_xpath("//button[@data-test-modal-close-btn]") 142 | discard.send_keys(Keys.RETURN) 143 | time.sleep(1) 144 | discard_confirm = self.driver.find_element_by_xpath("//button[@data-test-dialog-primary-btn]") 145 | discard_confirm.send_keys(Keys.RETURN) 146 | time.sleep(1) 147 | except NoSuchElementException: 148 | pass 149 | 150 | time.sleep(1) 151 | 152 | def close_session(self): 153 | """This function closes the actual session""" 154 | 155 | print('End of the session, see you later!') 156 | self.driver.close() 157 | 158 | def apply(self): 159 | """Apply to job offers""" 160 | 161 | self.driver.maximize_window() 162 | self.login_linkedin() 163 | time.sleep(5) 164 | self.job_search() 165 | time.sleep(5) 166 | self.filter() 167 | time.sleep(2) 168 | self.find_offers() 169 | time.sleep(2) 170 | self.close_session() 171 | 172 | 173 | if __name__ == '__main__': 174 | 175 | with open('config.json') as config_file: 176 | data = json.load(config_file) 177 | 178 | bot = EasyApplyLinkedin(data) 179 | bot.apply() --------------------------------------------------------------------------------