Skip to content

Commit

Permalink
Merge pull request nicolomantini#8 from krapes/send-coverletter
Browse files Browse the repository at this point in the history
Send coverletter
  • Loading branch information
krapes authored Jul 12, 2020
2 parents 70a8df2 + cbe5a89 commit 10a9ea3
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 81 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ dmypy.json

# User files
output.csv
output*.csv
venv8/
quickstart.py
states.json
16 changes: 11 additions & 5 deletions config.yaml
Original file line number Diff line number Diff line change
@@ -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
- # 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)
140 changes: 64 additions & 76 deletions easyapplybot.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -15,6 +15,7 @@
from webdriver_manager.chrome import ChromeDriverManager
import re
import yaml
from datetime import datetime, timedelta

driver = webdriver.Chrome(ChromeDriverManager().install())

Expand All @@ -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
Expand Down Expand Up @@ -77,24 +85,12 @@ 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)
self.browser.set_window_position(2000, 2000)

print(self.resumeloctn)
print(self.cover_letter_loctn)

def start_apply(self, positions, locations):
start = time.time()
Expand All @@ -110,6 +106,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):
Expand Down Expand Up @@ -156,16 +154,18 @@ 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)

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
Expand Down Expand Up @@ -194,7 +194,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):
Expand All @@ -210,14 +210,15 @@ 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)
if target:
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.*)" )
Expand All @@ -227,14 +228,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'
Expand All @@ -244,18 +237,6 @@ def get_job_page(self, jobID):
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 :
Expand All @@ -269,52 +250,51 @@ 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):
return len(self.browser.find_elements(button_locator[0],
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']")
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:

# Upload Cover Letter if possible
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(random.uniform(4.5, 6.5))

# Click Next or submitt button if possible
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)
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):
for element in self.browser.find_elements(error_locator[0],
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:
Expand All @@ -341,7 +321,7 @@ def is_present(button_locator):
except Exception as e:
print(e)
print("cannot apply to this job")
#raise(e)
raise(e)

return submitted

Expand Down Expand Up @@ -394,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'])
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)

0 comments on commit 10a9ea3

Please sign in to comment.