Merge branch 'main' into bug/rate-limit-error
This commit is contained in:
commit
146c5be84d
2
.gitignore
vendored
2
.gitignore
vendored
@ -12,4 +12,4 @@ generated_cv*
|
|||||||
chrome_profile
|
chrome_profile
|
||||||
answers.json
|
answers.json
|
||||||
virtual/*
|
virtual/*
|
||||||
data_folder/*
|
data*
|
13
README.md
13
README.md
@ -105,6 +105,8 @@ LinkedIn_AIHawk steps in as a game-changing solution to these challenges. It's n
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
**Please watch this video to set up your LinkedIn_AIHawk: [How to set up LinkedIn_AIHawk](https://youtu.be/gdW9wogHEUM) - https://youtu.be/gdW9wogHEUM**
|
||||||
|
0. **Confirmed succesfull runs OSs & Python**: Python 3.10, 3.11.9(64b), 3.12.5(64b) . Windows 10, Ubuntu 22
|
||||||
1. **Download and Install Python:**
|
1. **Download and Install Python:**
|
||||||
|
|
||||||
Ensure you have the last Python version installed. If not, download and install it from Python's official website. For detailed instructions, refer to the tutorials:
|
Ensure you have the last Python version installed. If not, download and install it from Python's official website. For detailed instructions, refer to the tutorials:
|
||||||
@ -505,11 +507,16 @@ TODO ):
|
|||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
- **Carefully read logs and output :** Most of the errors are verbosely reflected just watch the output and try to find the root couse.
|
||||||
|
- **If nothing works by unknown reason:** Use tested OS. Reboot and/or update OS. Use new clean venv. Try update Python to the tested version.
|
||||||
- **ChromeDriver Issues:** Ensure ChromeDriver is compatible with your installed Chrome version.
|
- **ChromeDriver Issues:** Ensure ChromeDriver is compatible with your installed Chrome version.
|
||||||
- **Missing Files:** Verify that all necessary files are present in the data folder.
|
- **Missing Files:** Verify that all necessary files are present in the data folder.
|
||||||
- **Invalid YAML:** Check your YAML files for syntax errors.
|
- **Invalid YAML:** Check your YAML files for syntax errors . Try to use external YAML validators e.g. https://www.yamllint.com/
|
||||||
|
- **OpenAI endpoint isues**: Try to check possible limits\blocking at their side
|
||||||
If you encounter any issues, you can open an issue on [GitHub](https://github.com/feder-cr/linkedIn_auto_jobs_applier_with_AI/issues). I'll be more than happy to assist you!
|
|
||||||
|
If you encounter any issues, you can open an issue on [GitHub](https://github.com/feder-cr/linkedIn_auto_jobs_applier_with_AI/issues).
|
||||||
|
Please add valuable details to the subject and to the description. If you need new feature then please reflect this.
|
||||||
|
I'll be more than happy to assist you!
|
||||||
|
|
||||||
## Conclusion
|
## Conclusion
|
||||||
|
|
||||||
|
@ -135,11 +135,7 @@ class LoggerChatModel:
|
|||||||
|
|
||||||
class GPTAnswerer:
|
class GPTAnswerer:
|
||||||
def __init__(self, openai_api_key):
|
def __init__(self, openai_api_key):
|
||||||
self.llm_cheap = LoggerChatModel(
|
self.llm_cheap = LoggerChatModel(ChatOpenAI(model_name="gpt-4o-mini", openai_api_key=openai_api_key, temperature=0.4)
|
||||||
ChatOpenAI(
|
|
||||||
model_name="gpt-4o-mini", openai_api_key=openai_api_key, temperature=0.8
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def job_description(self):
|
def job_description(self):
|
||||||
|
@ -30,7 +30,6 @@ class LinkedInEasyApplier:
|
|||||||
self.resume_generator_manager = resume_generator_manager
|
self.resume_generator_manager = resume_generator_manager
|
||||||
self.all_data = self._load_questions_from_json()
|
self.all_data = self._load_questions_from_json()
|
||||||
|
|
||||||
|
|
||||||
def _load_questions_from_json(self) -> List[dict]:
|
def _load_questions_from_json(self) -> List[dict]:
|
||||||
output_file = 'answers.json'
|
output_file = 'answers.json'
|
||||||
try:
|
try:
|
||||||
@ -49,7 +48,6 @@ class LinkedInEasyApplier:
|
|||||||
tb_str = traceback.format_exc()
|
tb_str = traceback.format_exc()
|
||||||
raise Exception(f"Error loading questions data from JSON file: \nTraceback:\n{tb_str}")
|
raise Exception(f"Error loading questions data from JSON file: \nTraceback:\n{tb_str}")
|
||||||
|
|
||||||
|
|
||||||
def job_apply(self, job: Any):
|
def job_apply(self, job: Any):
|
||||||
self.driver.get(job.link)
|
self.driver.get(job.link)
|
||||||
time.sleep(random.uniform(3, 5))
|
time.sleep(random.uniform(3, 5))
|
||||||
@ -91,7 +89,6 @@ class LinkedInEasyApplier:
|
|||||||
attempt += 1
|
attempt += 1
|
||||||
raise Exception("No clickable 'Easy Apply' button found")
|
raise Exception("No clickable 'Easy Apply' button found")
|
||||||
|
|
||||||
|
|
||||||
def _get_job_description(self) -> str:
|
def _get_job_description(self) -> str:
|
||||||
try:
|
try:
|
||||||
see_more_button = self.driver.find_element(By.XPATH, '//button[@aria-label="Click to see more description"]')
|
see_more_button = self.driver.find_element(By.XPATH, '//button[@aria-label="Click to see more description"]')
|
||||||
@ -107,7 +104,6 @@ class LinkedInEasyApplier:
|
|||||||
tb_str = traceback.format_exc()
|
tb_str = traceback.format_exc()
|
||||||
raise Exception(f"Error getting Job description: \nTraceback:\n{tb_str}")
|
raise Exception(f"Error getting Job description: \nTraceback:\n{tb_str}")
|
||||||
|
|
||||||
|
|
||||||
def _get_job_recruiter(self):
|
def _get_job_recruiter(self):
|
||||||
try:
|
try:
|
||||||
hiring_team_section = WebDriverWait(self.driver, 10).until(
|
hiring_team_section = WebDriverWait(self.driver, 10).until(
|
||||||
@ -253,16 +249,12 @@ class LinkedInEasyApplier:
|
|||||||
if radios:
|
if radios:
|
||||||
question_text = section.text.lower()
|
question_text = section.text.lower()
|
||||||
options = [radio.text.lower() for radio in radios]
|
options = [radio.text.lower() for radio in radios]
|
||||||
|
|
||||||
existing_answer = None
|
existing_answer = None
|
||||||
for item in self.all_data:
|
for item in self.all_data:
|
||||||
if self._sanitize_text(question_text) in item['question'] and item['type'] == 'radio':
|
if self._sanitize_text(question_text) in item['question'] and item['type'] == 'radio':
|
||||||
existing_answer = item
|
existing_answer = item
|
||||||
break
|
self._select_radio(radios, existing_answer['answer'])
|
||||||
if existing_answer:
|
return True
|
||||||
self._select_radio(radios, existing_answer['answer'])
|
|
||||||
return True
|
|
||||||
|
|
||||||
answer = self.gpt_answerer.answer_question_from_options(question_text, options)
|
answer = self.gpt_answerer.answer_question_from_options(question_text, options)
|
||||||
self._save_questions_to_json({'type': 'radio', 'question': question_text, 'answer': answer})
|
self._save_questions_to_json({'type': 'radio', 'question': question_text, 'answer': answer})
|
||||||
self._select_radio(radios, answer)
|
self._select_radio(radios, answer)
|
||||||
@ -283,12 +275,10 @@ class LinkedInEasyApplier:
|
|||||||
answer = self.gpt_answerer.answer_question_textual_wide_range(question_text)
|
answer = self.gpt_answerer.answer_question_textual_wide_range(question_text)
|
||||||
existing_answer = None
|
existing_answer = None
|
||||||
for item in self.all_data:
|
for item in self.all_data:
|
||||||
if item['question'] == self._sanitize_text(question_text) and item['type'] == question_type:
|
if 'cover' not in item['question'] and item['question'] == self._sanitize_text(question_text) and item['type'] == question_type:
|
||||||
existing_answer = item
|
existing_answer = item
|
||||||
break
|
self._enter_text(text_field, existing_answer['answer'])
|
||||||
if existing_answer:
|
return True
|
||||||
self._enter_text(text_field, existing_answer['answer'])
|
|
||||||
return True
|
|
||||||
self._save_questions_to_json({'type': question_type, 'question': question_text, 'answer': answer})
|
self._save_questions_to_json({'type': question_type, 'question': question_text, 'answer': answer})
|
||||||
self._enter_text(text_field, answer)
|
self._enter_text(text_field, answer)
|
||||||
return True
|
return True
|
||||||
@ -302,15 +292,12 @@ class LinkedInEasyApplier:
|
|||||||
answer_date = self.gpt_answerer.answer_question_date()
|
answer_date = self.gpt_answerer.answer_question_date()
|
||||||
answer_text = answer_date.strftime("%Y-%m-%d")
|
answer_text = answer_date.strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
|
||||||
existing_answer = None
|
existing_answer = None
|
||||||
for item in self.all_data:
|
for item in self.all_data:
|
||||||
if self._sanitize_text(question_text) in item['question'] and item['type'] == 'date':
|
if self._sanitize_text(question_text) in item['question'] and item['type'] == 'date':
|
||||||
existing_answer = item
|
existing_answer = item
|
||||||
break
|
self._enter_text(date_field, existing_answer['answer'])
|
||||||
if existing_answer:
|
return True
|
||||||
self._enter_text(date_field, existing_answer['answer'])
|
|
||||||
return True
|
|
||||||
|
|
||||||
self._save_questions_to_json({'type': 'date', 'question': question_text, 'answer': answer_text})
|
self._save_questions_to_json({'type': 'date', 'question': question_text, 'answer': answer_text})
|
||||||
self._enter_text(date_field, answer_text)
|
self._enter_text(date_field, answer_text)
|
||||||
@ -325,16 +312,12 @@ class LinkedInEasyApplier:
|
|||||||
if dropdown:
|
if dropdown:
|
||||||
select = Select(dropdown)
|
select = Select(dropdown)
|
||||||
options = [option.text for option in select.options]
|
options = [option.text for option in select.options]
|
||||||
|
|
||||||
existing_answer = None
|
existing_answer = None
|
||||||
for item in self.all_data:
|
for item in self.all_data:
|
||||||
if self._sanitize_text(question_text) in item['question'] and item['type'] == 'dropdown':
|
if self._sanitize_text(question_text) in item['question'] and item['type'] == 'dropdown':
|
||||||
existing_answer = item
|
existing_answer = item
|
||||||
break
|
self._select_dropdown_option(dropdown, existing_answer['answer'])
|
||||||
if existing_answer:
|
return True
|
||||||
self._select_dropdown_option(dropdown, existing_answer['answer'])
|
|
||||||
return True
|
|
||||||
|
|
||||||
answer = self.gpt_answerer.answer_question_from_options(question_text, options)
|
answer = self.gpt_answerer.answer_question_from_options(question_text, options)
|
||||||
self._save_questions_to_json({'type': 'dropdown', 'question': question_text, 'answer': answer})
|
self._save_questions_to_json({'type': 'dropdown', 'question': question_text, 'answer': answer})
|
||||||
self._select_dropdown_option(dropdown, answer)
|
self._select_dropdown_option(dropdown, answer)
|
||||||
@ -385,7 +368,6 @@ class LinkedInEasyApplier:
|
|||||||
tb_str = traceback.format_exc()
|
tb_str = traceback.format_exc()
|
||||||
raise Exception(f"Error saving questions data to JSON file: \nTraceback:\n{tb_str}")
|
raise Exception(f"Error saving questions data to JSON file: \nTraceback:\n{tb_str}")
|
||||||
|
|
||||||
|
|
||||||
def _sanitize_text(self, text: str) -> str:
|
def _sanitize_text(self, text: str) -> str:
|
||||||
sanitized_text = text.lower()
|
sanitized_text = text.lower()
|
||||||
sanitized_text = sanitized_text.strip()
|
sanitized_text = sanitized_text.strip()
|
||||||
|
@ -53,18 +53,6 @@ class LinkedInJobManager:
|
|||||||
def set_resume_generator_manager(self, resume_generator_manager):
|
def set_resume_generator_manager(self, resume_generator_manager):
|
||||||
self.resume_generator_manager = resume_generator_manager
|
self.resume_generator_manager = resume_generator_manager
|
||||||
|
|
||||||
""" def old_question(self):
|
|
||||||
self.set_old_answers = {}
|
|
||||||
file_path = 'data_folder/output/old_Questions.csv'
|
|
||||||
if os.path.exists(file_path):
|
|
||||||
with open(file_path, 'r', newline='', encoding='utf-8', errors='ignore') as file:
|
|
||||||
csv_reader = csv.reader(file, delimiter=',', quotechar='"')
|
|
||||||
for row in csv_reader:
|
|
||||||
if len(row) == 3:
|
|
||||||
answer_type, question_text, answer = row
|
|
||||||
self.set_old_answers[(answer_type.lower(), question_text.lower())] = answer"""
|
|
||||||
|
|
||||||
|
|
||||||
def start_applying(self):
|
def start_applying(self):
|
||||||
self.easy_applier_component = LinkedInEasyApplier(self.driver, self.resume_path, self.set_old_answers, self.gpt_answerer, self.resume_generator_manager)
|
self.easy_applier_component = LinkedInEasyApplier(self.driver, self.resume_path, self.set_old_answers, self.gpt_answerer, self.resume_generator_manager)
|
||||||
searches = list(product(self.positions, self.locations))
|
searches = list(product(self.positions, self.locations))
|
||||||
|
Loading…
Reference in New Issue
Block a user