From 96ba0fa5b70467c67927b7a519c5eab10350afaa Mon Sep 17 00:00:00 2001 From: krapes Date: Sat, 11 Jul 2020 08:51:49 -0400 Subject: [PATCH 1/8] Feature: max combo lenght --- easyapplybot.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/easyapplybot.py b/easyapplybot.py index d85e135..1052358 100644 --- a/easyapplybot.py +++ b/easyapplybot.py @@ -110,6 +110,8 @@ def start_apply(self, positions, locations): print(f"Applying to {position}: {location}") location = "&location=" + location self.applications_loop(position, location) + if len(combos) > 20: + break self.finish_apply() def applications_loop(self, position, location): From fe5766a42d20e7dd8682f8ba52e59f05a5878343 Mon Sep 17 00:00:00 2001 From: krapes Date: Sun, 12 Jul 2020 16:21:43 -0400 Subject: [PATCH 2/8] Features: JobIDs only looking back one day --- .gitignore | 1 + easyapplybot.py | 75 ++++++++++++++++++++++++++++++++----------------- 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index f4a6d8d..bf0f43a 100644 --- a/.gitignore +++ b/.gitignore @@ -140,6 +140,7 @@ dmypy.json # User files output.csv +output*.csv venv8/ quickstart.py states.json \ No newline at end of file diff --git a/easyapplybot.py b/easyapplybot.py index 1052358..28b82c2 100644 --- a/easyapplybot.py +++ b/easyapplybot.py @@ -1,4 +1,4 @@ -import time, random, os, csv, datetime, platform +import time, random, os, csv, platform from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.common.exceptions import TimeoutException @@ -15,6 +15,7 @@ from webdriver_manager.chrome import ChromeDriverManager import re import yaml +from datetime import datetime, timedelta driver = webdriver.Chrome(ChromeDriverManager().install()) @@ -26,28 +27,35 @@ class EasyApplyBot: blacklist = ["Staffigo"] - def __init__(self, username, password, language, resumeloctn=None, filename='output.csv'): + def __init__(self, username, password, cover_letter_loctn=None, filename='output.csv'): print("\nWelcome to Easy Apply Bot\n") dirpath = os.getcwd() print("current directory is : " + dirpath) - self.resumeloctn = resumeloctn - self.language = language - self.appliedJobIDs = self.get_appliedIDs(filename) + self.cover_letter_loctn = cover_letter_loctn + self.appliedJobIDs = self.get_appliedIDs(filename)#if self.get_appliedIDs(filename) != None else [] self.filename = filename self.options = self.browser_options() self.browser = driver self.wait = WebDriverWait(self.browser, 30) - self.start_linkedin(username,password) + self.start_linkedin(username, password) def get_appliedIDs(self, filename): + print(filename) try: df = pd.read_csv(filename, header=None, - names=['timestamp', 'jobID', 'job', 'company', 'attempted', 'result']) - return list(df.jobID) + names=['timestamp', 'jobID', 'job', 'company', 'attempted', 'result'], + lineterminator='\n', + encoding = 'utf-8') + + df['timestamp'] = pd.to_datetime(df['timestamp'], format="%Y-%m-%d %H:%M:%S.%f") + df = df[df['timestamp'] > (datetime.now() - timedelta(days=2))] + jobIDs = list(df.jobID) + print(f"{len(jobIDs)} jobIDs found") + return jobIDs except Exception as e: print(str(e) + " jobIDs could not be loaded from CSV {}".format(filename)) return None @@ -78,8 +86,6 @@ def start_linkedin(self,username,password): print("TimeoutException! Username/password field or login button not found") def wait_for_login(self): - - time.sleep(1) while True: @@ -94,7 +100,7 @@ def fill_data(self): self.browser.set_window_size(0, 0) self.browser.set_window_position(2000, 2000) - print(self.resumeloctn) + print(self.cover_letter_loctn) def start_apply(self, positions, locations): start = time.time() @@ -158,16 +164,20 @@ def applications_loop(self, position, location): IDs = set(IDs) # remove already applied jobs + before = len(IDs) jobIDs = [x for x in IDs if x not in self.appliedJobIDs] + after = len(jobIDs) + print(f"""There were {before} jobIDs found + but {before - after} were removed because they were found on the + appliedJobsID list""") - if len(jobIDs) == 0: + if len(jobIDs) == 0 and len(IDs) > 24: jobs_per_page = jobs_per_page + 25 count_job = 0 self.avoid_lock() self.browser, jobs_per_page = self.next_jobs_page(position, location, jobs_per_page) - # loop over IDs to apply for i, jobID in enumerate(jobIDs): count_job += 1 @@ -196,7 +206,7 @@ def applications_loop(self, position, location): print(f'\n\n********count_application: {count_application}************\n\n') print(f"Time for a nap - see you in:{int(sleepTime/60)} min") print('\n\n****************************************\n\n') - time.sleep (sleepTime) + time.sleep(sleepTime) # go to new page if all jobs are done if count_job == len(jobIDs): @@ -212,6 +222,7 @@ def applications_loop(self, position, location): if len(jobIDs) == 0 or i == (len(jobIDs) - 1): break + def write_to_file(self, button, jobID, browserTitle, result): def re_extract(text, pattern): target = re.search(pattern, text) @@ -219,7 +230,7 @@ def re_extract(text, pattern): target = target.group(1) return target - timestamp = datetime.datetime.now() + timestamp = datetime.now() attempted = False if button == False else True job = re_extract(browserTitle.split(' | ')[0], r"\(?\d?\)?\s?(\w.*)") company = re_extract(browserTitle.split(' | ')[1], r"(\w.*)" ) @@ -294,22 +305,36 @@ def is_present(button_locator): try: time.sleep(3) #print(f"Navigating... ") - next_locater = (By.CSS_SELECTOR, "button[aria-label='Continue to next step']") - review_locater = (By.CSS_SELECTOR, "button[aria-label='Review your application']") - submit_locater = (By.CSS_SELECTOR, "button[aria-label='Submit application']") - submit_application_locator = (By.CSS_SELECTOR, "button[aria-label='Submit application']") - error_locator = (By.CSS_SELECTOR, "p[data-test-form-element-error-message='true']") - + next_locater = (By.CSS_SELECTOR, + "button[aria-label='Continue to next step']") + review_locater = (By.CSS_SELECTOR, + "button[aria-label='Review your application']") + submit_locater = (By.CSS_SELECTOR, + "button[aria-label='Submit application']") + submit_application_locator = (By.CSS_SELECTOR, + "button[aria-label='Submit application']") + error_locator = (By.CSS_SELECTOR, + "p[data-test-form-element-error-message='true']") + cover_letter = (By.CSS_SELECTOR, "input[name='file']") + submitted = False while True: button = None - #self.browser.find_element_by_xpath('//*[@id="file-browse-input"]').send_keys(self.resumeloctn) + + for i, button_locator in enumerate([next_locater, review_locater, submit_locater, submit_application_locator]): - #print(i) + + if is_present(cover_letter): + input_button = self.browser.find_elements(cover_letter[0], + cover_letter[1]) + + input_button[0].send_keys(self.cover_letter_loctn) + time.sleep(6) + if is_present(button_locator): #print("button found") button = self.wait.until(EC.element_to_be_clickable(button_locator)) - + if is_present(error_locator): for element in self.browser.find_elements(error_locator[0], error_locator[1]): @@ -343,7 +368,7 @@ def is_present(button_locator): except Exception as e: print(e) print("cannot apply to this job") - #raise(e) + raise(e) return submitted From c67e30882a97bdb69fa5529da7f2075f13522380 Mon Sep 17 00:00:00 2001 From: krapes Date: Sun, 12 Jul 2020 16:45:40 -0400 Subject: [PATCH 3/8] Feature: Reorganize send_resume while loop --- easyapplybot.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/easyapplybot.py b/easyapplybot.py index 28b82c2..9646141 100644 --- a/easyapplybot.py +++ b/easyapplybot.py @@ -303,7 +303,7 @@ def is_present(button_locator): button_locator[1])) > 0 try: - time.sleep(3) + time.sleep(random.uniform(1.5, 2.5)) #print(f"Navigating... ") next_locater = (By.CSS_SELECTOR, "button[aria-label='Continue to next step']") @@ -319,20 +319,21 @@ def is_present(button_locator): submitted = False while True: - button = None - - for i, button_locator in enumerate([next_locater, review_locater, submit_locater, submit_application_locator]): + # Upload Cover Letter if possible + if is_present(cover_letter): + input_button = self.browser.find_elements(cover_letter[0], + cover_letter[1]) - if is_present(cover_letter): - input_button = self.browser.find_elements(cover_letter[0], - cover_letter[1]) - - input_button[0].send_keys(self.cover_letter_loctn) - time.sleep(6) + input_button[0].send_keys(self.cover_letter_loctn) + time.sleep(random.uniform(4.5, 6.5)) + # Click Next or submitt button if possible + button = None + buttons = [next_locater, review_locater, + submit_locater, submit_application_locator] + for i, button_locator in enumerate(buttons): if is_present(button_locator): - #print("button found") button = self.wait.until(EC.element_to_be_clickable(button_locator)) if is_present(error_locator): @@ -340,8 +341,6 @@ def is_present(button_locator): error_locator[1]): text = element.text if "Please enter a valid answer" in text: - #print("Error Found") - #print(element.get_attribute('class')) button = None break if button: From 21110d7d9e59220016e1776aa29b6708a16ac050 Mon Sep 17 00:00:00 2001 From: krapes Date: Sun, 12 Jul 2020 16:50:43 -0400 Subject: [PATCH 4/8] Refactor: Remove unused function got_easy_button --- easyapplybot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/easyapplybot.py b/easyapplybot.py index 9646141..c01f5f5 100644 --- a/easyapplybot.py +++ b/easyapplybot.py @@ -256,7 +256,7 @@ def get_job_page(self, jobID): self.browser.get(job) self.job_page = self.load_page(sleep=0.5) return self.job_page - + ''' def got_easy_apply(self, page): #button = page.find("button", class_="jobs-apply-button artdeco-button jobs-apply-button--top-card artdeco-button--3 ember-view") @@ -268,7 +268,7 @@ def got_easy_apply(self, page): return EasyApplyButton else : return False - + ''' def get_easy_apply_button(self): try : From 6937826bc3493f85cb3107c82115986a5b3693eb Mon Sep 17 00:00:00 2001 From: krapes Date: Sun, 12 Jul 2020 16:57:04 -0400 Subject: [PATCH 5/8] Refactor: remove unused function --- easyapplybot.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/easyapplybot.py b/easyapplybot.py index c01f5f5..69d28d5 100644 --- a/easyapplybot.py +++ b/easyapplybot.py @@ -240,14 +240,6 @@ def re_extract(text, pattern): writer = csv.writer(f) writer.writerow(toWrite) - def get_job_links(self, page): - links = [] - for link in page.find_all('a'): - url = link.get('href') - if url: - if '/jobs/view' in url: - links.append(url) - return set(links) def get_job_page(self, jobID): #root = 'www.linkedin.com' @@ -256,19 +248,7 @@ def get_job_page(self, jobID): self.browser.get(job) self.job_page = self.load_page(sleep=0.5) return self.job_page - ''' - def got_easy_apply(self, page): - #button = page.find("button", class_="jobs-apply-button artdeco-button jobs-apply-button--top-card artdeco-button--3 ember-view") - button = self.browser.find_elements_by_xpath( - '//button[contains(@class, "jobs-apply")]/span[1]' - ) - EasyApplyButton = button [0] - if EasyApplyButton.text in "Easy Apply" : - return EasyApplyButton - else : - return False - ''' def get_easy_apply_button(self): try : From fb1530819090fbb302844dd8496b9f821b49cbe5 Mon Sep 17 00:00:00 2001 From: krapes Date: Sun, 12 Jul 2020 17:07:47 -0400 Subject: [PATCH 6/8] Refactor: delete useless code --- easyapplybot.py | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/easyapplybot.py b/easyapplybot.py index 69d28d5..9452e96 100644 --- a/easyapplybot.py +++ b/easyapplybot.py @@ -85,16 +85,6 @@ def start_linkedin(self,username,password): except TimeoutException: print("TimeoutException! Username/password field or login button not found") - def wait_for_login(self): - time.sleep(1) - - while True: - if self.browser.title != title: - print("\nStarting LinkedIn bot\n") - break - else: - time.sleep(1) - print("\nPlease Login to your LinkedIn account\n") def fill_data(self): self.browser.set_window_size(0, 0) @@ -167,9 +157,7 @@ def applications_loop(self, position, location): before = len(IDs) jobIDs = [x for x in IDs if x not in self.appliedJobIDs] after = len(jobIDs) - print(f"""There were {before} jobIDs found - but {before - after} were removed because they were found on the - appliedJobsID list""") + if len(jobIDs) == 0 and len(IDs) > 24: jobs_per_page = jobs_per_page + 25 @@ -262,20 +250,6 @@ def get_easy_apply_button(self): return EasyApplyButton - def easy_apply_xpath(self): - button = self.get_easy_apply_button() - button_inner_html = str(button) - list_of_words = button_inner_html.split() - next_word = [word for word in list_of_words if "ember" in word and "id" in word] - ember = next_word[0][:-1] - xpath = '//*[@'+ember+']/button' - return xpath - - def click_button(self, xpath): - triggerDropDown = self.browser.find_element_by_xpath(xpath) - time.sleep(0.5) - triggerDropDown.click() - time.sleep(1) def send_resume(self): def is_present(button_locator): From 8321477aa0ce6d5162e05df9751fb0e0ce755192 Mon Sep 17 00:00:00 2001 From: krapes Date: Sun, 12 Jul 2020 17:08:53 -0400 Subject: [PATCH 7/8] Feature: support for if output.csv doesnt exist --- easyapplybot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/easyapplybot.py b/easyapplybot.py index 9452e96..cc4b477 100644 --- a/easyapplybot.py +++ b/easyapplybot.py @@ -34,7 +34,7 @@ def __init__(self, username, password, cover_letter_loctn=None, filename='output print("current directory is : " + dirpath) self.cover_letter_loctn = cover_letter_loctn - self.appliedJobIDs = self.get_appliedIDs(filename)#if self.get_appliedIDs(filename) != None else [] + self.appliedJobIDs = self.get_appliedIDs(filename) if self.get_appliedIDs(filename) != None else [] self.filename = filename self.options = self.browser_options() self.browser = driver From cbe5a8946c4ccb9dc6703ec88203f0b78a32cc21 Mon Sep 17 00:00:00 2001 From: krapes Date: Sun, 12 Jul 2020 17:43:49 -0400 Subject: [PATCH 8/8] Feature: config support for cover letter and output file --- config.yaml | 16 +++++++++++----- easyapplybot.py | 12 ++++++++++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/config.yaml b/config.yaml index c549cd7..483220b 100644 --- a/config.yaml +++ b/config.yaml @@ -1,11 +1,17 @@ -username: # Insert your username here -password: # Insert your password here +username: +password: positions: -- # Position you want to search for +- # positions you want to search for - # Another position you want to search for - # A third position you want to search for locations: -- # Location you want to search in -- # A second location you want to search in \ No newline at end of file +- # Location you want to search for +- # A second location you want to search in + +cover_letter_loctn: +- # '/home/PATH_TO_FILE' + +output_filename: +- # PATH TO OUTPUT FILE (default output.csv) \ No newline at end of file diff --git a/easyapplybot.py b/easyapplybot.py index cc4b477..d6c6e32 100644 --- a/easyapplybot.py +++ b/easyapplybot.py @@ -374,9 +374,17 @@ def finish_apply(self): assert parameters['username'] is not None assert parameters['password'] is not None + + print(parameters) + cover_letter_loctn = parameters.get('cover_letter_loctn', [None])[0] + output_filename = parameters.get('output_filename', ['output.csv'])[0] + bot = EasyApplyBot(parameters['username'], parameters['password'], - parameters['locations'] + cover_letter_loctn=cover_letter_loctn, + filename=output_filename ) - bot.start_apply(parameters['positions'], parameters['locations']) \ No newline at end of file + locations = [l for l in parameters['locations'] if l != None] + positions = [p for p in parameters['positions'] if p != None] + bot.start_apply(positions, locations) \ No newline at end of file