def _get_job_information(self, job: Job): element_job_description = self.driver.find_element( By.XPATH, AngelConstants.XPath.JOB_DESCRIPTION) element_job_company = self.driver.find_element( By.XPATH, AngelConstants.XPath.JOB_COMPANY) job.description = element_job_description.text job.company = element_job_company.get_attribute('text').strip()
def answer_all_questions(self, driver: webdriver.Chrome, job: Job, dict_qle: Dict[str, QuestionLabelElements]): # Initialize names = list(dict_qle.keys()) i = 0 name = names[i] list_continues: List[FirefoxWebElement] = driver.find_elements( By.XPATH, IndeedConstants.XPath.BUTTON_CONT) while True: qle = dict_qle[name] state = self._answer_question(driver, job, qle) if state == self.AnswerState.CANNOT_ANSWER: job.error = RobotConstants.String.UNABLE_TO_ANSWER return False if state == self.AnswerState.NOT_VISIBLE: for element_continue in list_continues: try: element_continue.click() break except common.exceptions.ElementNotVisibleException as e: pass except common.exceptions.NoSuchElementException as e: job.error = str(e) return False else: if i == len(names) - 1: try: driver.find_element( By.XPATH, IndeedConstants.XPath.BUTTON_APPLY).click() return True except common.exceptions.NoSuchElementException as e: job.error = str(e) return False except common.exceptions.ElementNotVisibleException as e: # TODO: Figure out why this happens driver.find_element( By.XPATH, IndeedConstants.XPath.BUTTON_CONT).click() i -= 1 i += 1 name = names[i]
def search_with_api(self, params: dict): client = IndeedClient(publisher=self.user_config.INDEED_API_KEY) search_response = client.search(**params) total_number_hits = search_response['totalResults'] num_loops = int(total_number_hits / IndeedConstants.API.MAX_NUM_RESULTS_PER_REQUEST) counter_start = 0 print('Total number of hits: {0}'.format(total_number_hits)) count_jobs_added = 0 for i in range(0, num_loops): # We can get around MAX_NUM_RESULTS_PER_REQUEST by increasing our start location on each loop! params['start'] = counter_start search_response = client.search(**params) list_jobs = IndeedParser.get_jobs_from_response(search_response) for job in list_jobs: try: # TODO: This sucks, I'm just repeating myself... Job.create(key=job.key, website=job.website, link=job.link, title=job.title, company=job.company, city=job.city, state=job.state, country=job.country, location=job.location, posted_date=job.posted_date, expired=job.expired, easy_apply=job.easy_apply) count_jobs_added += 1 except peewee.IntegrityError as e: # TODO: Can I write a custom exception that catches UNIQUE Errors but not others? if 'UNIQUE' in str(e): pass else: print(str(e)) # Increment start counter_start += IndeedConstants.API.MAX_NUM_RESULTS_PER_REQUEST print('Added {0} new jobs'.format(count_jobs_added))
def _answer_question(self, driver: webdriver.Chrome, job: Job, qle: QuestionLabelElements): # Question should already be in database at this point with updated answer hopefully qle.question = Question.get( Question.label == qle.question.label, Question.tag_type == qle.question.input_type) if qle.question.answer is not None: if qle.question.secondary_input_type == HTMLConstants.InputTypes.RADIO or \ qle.question.secondary_input_type == HTMLConstants.InputTypes.CHECK_BOX: return self._answer_check_button(driver, job, qle) elif qle.question.input_type == HTMLConstants.TagType.SELECT: return self._answer_select(driver, job, qle) else: return self._answer_text(driver, job, qle) elif qle.question.question_type == ABCs.QuestionTypes.MESSAGE: return self._answer_message(driver, job, qle) elif qle.question.question_type == ABCs.QuestionTypes.ADDITIONAL_ATTACHMENTS: return self.AnswerState.CONTINUE elif qle.question.question_type == ABCs.QuestionTypes.LOCATION: if self.user_config.Default.CITY in str.lower(qle.question.label): qle.question.answer = 'Yes' return self._answer_text(driver, job, qle) elif qle.question.question_type == ABCs.QuestionTypes.RESUME: qle.question.answer = self.ab_builder.generate_resume( job.description) return self._answer_text(driver, job, qle) elif qle.question.question_type == ABCs.QuestionTypes.EXPERIENCE: qle.question.answer = self.user_config.Settings.DEFAULT_EXPERIENCE return self._answer_text(driver, job, qle) else: best_answer = ApplicationBuilder.generate_answer_from_questions( qle.question) if best_answer is not None: qle.question.answer = best_answer if qle.question.secondary_input_type == HTMLConstants.InputTypes.TEXT or \ qle.question.secondary_input_type == HTMLConstants.InputTypes.FILE or \ qle.question.secondary_input_type == HTMLConstants.InputTypes.EMAIL or \ qle.question.secondary_input_type == HTMLConstants.InputTypes.PHONE: return self._answer_text(driver, job, qle) elif qle.question.secondary_input_type == HTMLConstants.InputTypes.RADIO: return self._answer_check_button(driver, job, qle) elif qle.question.secondary_input_type == HTMLConstants.InputTypes.SELECT_ONE: return self._answer_select(driver, job, qle) job.error = RobotConstants.String.UNABLE_TO_ANSWER return self.AnswerState.CANNOT_ANSWER
def _answer_message(self, driver: webdriver.Chrome, job: Job, qle: QuestionLabelElements) -> Enum: message = self.ab_builder.generate_message(job.description, job.company) if message is not None: try: driver.find_element(By.NAME, qle.name).send_keys(message) job.message = message return self.AnswerState.CONTINUE except common.exceptions.ElementNotVisibleException as e: return self.AnswerState.NOT_VISIBLE except common.exceptions.NoSuchElementException as e: job.error = str(e) return self.AnswerState.CANNOT_ANSWER else: job.error = RobotConstants.String.NOT_ENOUGH_KEYWORD_MATCHES return self.AnswerState.CANNOT_ANSWER
def _apply_to_single_job(self, job: Job): """ Assuming you are on a job page, presses the apply button and switches to the application IFrame. If everything is working properly it call fill_application. Lastly, it saves any changes made to the job table :param job: :return: """ self.attempt_application(job) if job.easy_apply: self.driver.get(job.link) if does_element_exist(self.driver, By.XPATH, IndeedConstants.XPath.TOS_POPUP): self.driver.find_element( By.XPATH, IndeedConstants.XPath.TOS_POPUP).click() try: # Fill job information job.description = self.driver.find_element( By.ID, IndeedConstants.Id.JOB_SUMMARY).text self.driver.find_element( By.XPATH, IndeedConstants.XPath.APPLY_SPAN).click() # Switch to application form IFRAME, notice that it is a nested IFRAME time.sleep(RobotConstants.WAIT_MEDIUM) self.driver.switch_to.frame(1) self.driver.switch_to.frame(0) self.fill_application(job) except common.exceptions.NoSuchElementException as e: job.error = str(e) job.expired = True print(e) else: pass job.save()
def _apply_single_job(self, job: Job): self.driver.get(job.link) try: self._get_job_information(job) self.attempt_application(job) if 'unpaid' in job.description.lower(): job.error = RobotConstants.String.UNPAID self.failed_application(job) elif self._fill_application(job): self.successful_application( job, dry_run=self.user_config.Settings.IS_DRY_RUN) else: self.failed_application(job) except Exception as e: job.error = str(e) self.failed_application(job) job.save()
def gather(self, query_parameters): def extract_job_key(url: str) -> Optional[str]: last_part_of_url = url.rsplit('/', 1)[-1] match = re.match(AngelConstants.Regex.JOB_KEY_FROM_URL, last_part_of_url) if match: return match.group(1) return None assert self._is_authenticated() string_parameters = AngelBot.encode_parameters( dict_parameters=query_parameters) self.driver.get(AngelConstants.URL.JOBS + string_parameters) # Check if any jobs have loaded WebDriverWait(self.driver, AngelConstants.PauseTime.JOBS_LOADED).until( EC.presence_of_element_located( (By.XPATH, AngelConstants.XPath.JOB_LISTING_LINK))) scroll_infinitely(self.driver) elements_list = self.driver.find_elements( By.XPATH, AngelConstants.XPath.JOB_LISTING_LINK) for element in elements_list: job_link = element.get_attribute('href') job_title = element.get_attribute('innerText') job_key = extract_job_key(job_link) if job_key is None: pass else: try: Job.create(key=job_key, website=AngelConstants.WEBSITE_NAME, link=job_link, title=job_title) except peewee.IntegrityError as e: print(e)
def _get_job_from_result(job_result: dict) -> Optional[Job]: if job_result['indeedApply']: parsed_date = datetime.strptime(job_result['date'], '%a, %d %b %Y %H:%M:%S %Z') job = Job(key=job_result['jobkey'], website=IndeedConstants.WEBSITE_NAME, link=job_result['url'], title=job_result['jobtitle'], company=job_result['company'], city=job_result['city'], state=job_result['state'], country=job_result['country'], location=job_result['formattedLocation'], posted_date=parsed_date.date(), expired=job_result['expired'], easy_apply=job_result['indeedApply']) return job return None
def _answer_text(self, driver: webdriver.Chrome, job: Job, qle: QuestionLabelElements) -> Enum: try: element = driver.find_element(By.NAME, qle.name) element.clear() element.send_keys(qle.question.answer) return self.AnswerState.CONTINUE except common.exceptions.ElementNotVisibleException as e: return self.AnswerState.NOT_VISIBLE except common.exceptions.NoSuchElementException as e: job.error = str(e) except common.exceptions.InvalidElementStateException as e: return self.AnswerState.NOT_VISIBLE return self.AnswerState.CANNOT_ANSWER
def _answer_select(self, driver: webdriver.Chrome, job: Job, qle: QuestionLabelElements) -> Enum: select_name = qle.element_list[0].get_attribute( HTMLConstants.Attributes.NAME) try: select = Select(driver.find_element(By.NAME, select_name)) if qle.question.secondary_input_type == HTMLConstants.InputTypes.SELECT_ONE: select.select_by_value(qle.question.answer) else: raise NotImplementedError return self.AnswerState.CONTINUE except common.exceptions.ElementNotVisibleException as e: return self.AnswerState.NOT_VISIBLE except common.exceptions.NoSuchElementException as e: job.error = str(e) return self.AnswerState.CANNOT_ANSWER
def _fill_application(self, job: Job): user_note = self.application_builder.generate_message( company=job.company, description=job.description) if user_note is not None: try: apply_now_element = self.driver.find_element( By.CSS_SELECTOR, AngelConstants.CSSSelector.APPLY_NOW) apply_now_element.click() job.message = user_note element_user_note = self.driver.find_element( By.XPATH, AngelConstants.XPath.USER_NOTE) element_user_note.send_keys(user_note) if not self.user_config.Settings.IS_DRY_RUN: self.driver.find_element( By.XPATH, AngelConstants.XPath.APPLY).click() # PROMPT: Select the locations you are willing to relocate to: if does_element_exist(self.driver, By.XPATH, AngelConstants.XPath.UNSELECTED_CITIES): try: unselected_elements = self.driver.find_elements( By.XPATH, AngelConstants.XPath.UNSELECTED_CITIES) for element in unselected_elements: element.click() self.driver.find_element( By.XPATH, AngelConstants.XPath.DONE).click() except Exception as e: print(str(e)) job.error = str(e) return True except common.exceptions.NoSuchElementException as e: job.error = str(e) except common.exceptions.WebDriverException as e: if len(user_note ) > AngelConstants.Constraint.MAX_LENGTH_USER_NOTE: job.error = AngelConstants.Error.USER_NOTE_TOO_LONG else: job.error = str(e) else: job.error = RobotConstants.String.NOT_ENOUGH_KEYWORD_MATCHES return False
def _answer_check_button(self, driver: webdriver.Chrome, job: Job, qle: QuestionLabelElements) -> Enum: if qle.question.answer is not None: span_answers = qle.question.answer.split(',') span_answers = [answer.strip() for answer in span_answers] element_name = qle.element_list[0].get_attribute( HTMLConstants.Attributes.NAME) for span_answer in span_answers: try: xpath_checkbox_button = IndeedConstants.XPath.compute_xpath_check_button( element_name, span_answer) driver.find_element(By.XPATH, xpath_checkbox_button).click() except common.exceptions.ElementNotVisibleException: return self.AnswerState.NOT_VISIBLE except common.exceptions.NoSuchElementException as e: job.error = str(e) return self.AnswerState.CANNOT_ANSWER return self.AnswerState.CONTINUE # TODO: Check for prefilled answers else: raise NotImplementedError
def _create_tables(): # Create table if not exists Job.create_table(fail_silently=True) Question.create_table(fail_silently=True) Person.create_table(fail_silently=True)
def successful_application(job: Job, dry_run=False) -> str: if not dry_run: job.applied = True string = 'Successfully applied to {0} with {1} at {2}'.format(job.title, job.company, job.location) print(string) return string
def attempt_application(job: Job) -> str: job.attempted = True job.access_date = datetime.now().date() string = 'Attempting application for {0} with {1} at {2}'.format(job.title, job.company, job.location) print(string) return string