diff --git a/hh_bot/core/job_application_manager.py b/hh_bot/core/job_application_manager.py index d2eb13d..8771177 100644 --- a/hh_bot/core/job_application_manager.py +++ b/hh_bot/core/job_application_manager.py @@ -250,7 +250,7 @@ class JobApplicationManager: def __init__(self): LoggingConfigurator.setup_logging( - log_file="logs/hh_bot.log", console_output=False + log_file="logs/hh_bot.log", console_output=True ) self.orchestrator = AutomationOrchestrator() diff --git a/hh_bot/services/browser_service.py b/hh_bot/services/browser_service.py index b885932..2fcb5f7 100644 --- a/hh_bot/services/browser_service.py +++ b/hh_bot/services/browser_service.py @@ -412,41 +412,44 @@ class VacancyApplicator: logger.warning("Форма отклика не найдена в модальном окне - пропускаем") return SubmissionResult.SKIPPED - self._add_cover_letter_if_possible(vacancy) - time.sleep(1) - + submit_button = None for selector in submit_selectors: try: submit_button = WebDriverWait(self.driver, 3).until( EC.element_to_be_clickable((By.CSS_SELECTOR, selector)) ) if submit_button: - button_text = submit_button.text.strip().lower() - - if self._is_already_applied(button_text): - logger.warning( - f"⚠️ Кнопка указывает что уже откликались: " - f"{submit_button.text.strip()}" - ) - return SubmissionResult.ALREADY_APPLIED - - logger.info( - f"Нажимаем кнопку отправки: " - f"{submit_button.text.strip()}" - ) - self.driver.execute_script( - "arguments[0].click();", submit_button - ) - time.sleep(3) - if self._check_success_message(): - return SubmissionResult.SUCCESS - else: - return SubmissionResult.FAILED + break except Exception: continue - logger.warning("Кнопка отправки в модальном окне не найдена") - return SubmissionResult.FAILED + if not submit_button: + logger.warning("Кнопка отправки в модальном окне не найдена") + return SubmissionResult.FAILED + + button_text = submit_button.text.strip().lower() + if self._is_already_applied(button_text): + logger.warning( + f"⚠️ Кнопка указывает что уже откликались: " + f"{submit_button.text.strip()}" + ) + return SubmissionResult.ALREADY_APPLIED + + self._add_cover_letter_if_possible(vacancy) + time.sleep(1) + + logger.info( + f"Нажимаем кнопку отправки: " + f"{submit_button.text.strip()}" + ) + self.driver.execute_script( + "arguments[0].click();", submit_button + ) + time.sleep(3) + if self._check_success_message(): + return SubmissionResult.SUCCESS + else: + return SubmissionResult.FAILED except Exception as e: logger.error(f"Ошибка в модальном окне: {e}") @@ -458,6 +461,8 @@ class VacancyApplicator: if not settings.application.use_ai_cover_letters: logger.info("ИИ-сопроводительные письма отключены в настройках") return + + logger.info("Ищем кнопку сопроводительного письма...") cover_letter_button_selectors = [ '[data-qa="add-cover-letter"]', 'button[data-qa*="cover-letter"]', @@ -486,13 +491,14 @@ class VacancyApplicator: continue if not cover_letter_button: - logger.info("Кнопка сопроводительного письма не найдена") + logger.info("📝 Кнопка сопроводительного письма не найдена") return - logger.info("Найдена кнопка сопроводительного письма, нажимаем...") + logger.info("📝 Найдена кнопка сопроводительного письма, нажимаем...") self.driver.execute_script("arguments[0].click();", cover_letter_button) time.sleep(2) + logger.info("Ищем поле для ввода письма...") cover_letter_field_selectors = [ 'textarea[data-qa*="cover-letter"]', 'textarea[name*="letter"]', @@ -509,15 +515,16 @@ class VacancyApplicator: EC.presence_of_element_located((By.CSS_SELECTOR, selector)) ) if cover_letter_field: + logger.info(f"Поле найдено: {selector}") break except Exception: continue if not cover_letter_field: - logger.warning("Поле для сопроводительного письма не найдено") + logger.warning("📝 Поле для сопроводительного письма не найдено") return - logger.info("Генерация сопроводительного письма...") + logger.info("📝 Генерация сопроводительного письма...") from ..services.gemini_service import GeminiAIService @@ -525,15 +532,15 @@ class VacancyApplicator: cover_letter_text = gemini_service.generate_cover_letter(vacancy) if cover_letter_text: - logger.info("Заполняем сопроводительное письмо...") + logger.info("📝 Заполняем сопроводительное письмо...") cover_letter_field.clear() cover_letter_field.send_keys(cover_letter_text) logger.info("✅ Сопроводительное письмо добавлено") else: - logger.warning("Не удалось сгенерировать сопроводительное письмо") + logger.warning("📝 Не удалось сгенерировать сопроводительное письмо") except Exception as e: - logger.warning(f"Ошибка при добавлении сопроводительного письма: {e}") + logger.warning(f"📝 Ошибка при добавлении сопроводительного письма: {e}") logger.info("Продолжаем без сопроводительного письма") def _check_success_message(self) -> bool: diff --git a/hh_bot/services/gemini_service.py b/hh_bot/services/gemini_service.py index 7c32fe9..18627c3 100644 --- a/hh_bot/services/gemini_service.py +++ b/hh_bot/services/gemini_service.py @@ -23,29 +23,36 @@ class RateLimiter: def wait_if_needed(self) -> None: """Ожидание если превышен лимит запросов""" + while True: + current_time = time.time() + self._cleanup_old_requests(current_time) + + if len(self.request_times) < self.max_requests: + break + + oldest_request_time = self.request_times[0] + wait_time = self.window_seconds - (current_time - oldest_request_time) + 0.1 + + logger.info(f"⏳ Достигнут лимит {self.max_requests} запросов. " + f"Ожидание {wait_time:.1f} секунд...") + time.sleep(wait_time) + + def record_request(self) -> None: + """Записать новый запрос""" current_time = time.time() - + self._cleanup_old_requests(current_time) + self.request_times.append(current_time) + + def _cleanup_old_requests(self, current_time: float) -> None: + """Удаление старых запросов из окна""" while (self.request_times and current_time - self.request_times[0] >= self.window_seconds): self.request_times.popleft() - - if len(self.request_times) >= self.max_requests: - wait_time = self.window_seconds - (current_time - self.request_times[0]) - if wait_time > 0: - logger.info(f"⏳ Достигнут лимит {self.max_requests} запросов в минуту. " - f"Ожидание {wait_time:.1f} секунд...") - time.sleep(wait_time + 0.1) - - self.request_times.append(current_time) def get_remaining_requests(self) -> int: """Количество оставшихся запросов в текущем окне""" current_time = time.time() - - while (self.request_times and - current_time - self.request_times[0] >= self.window_seconds): - self.request_times.popleft() - + self._cleanup_old_requests(current_time) return max(0, self.max_requests - len(self.request_times)) def get_status(self) -> str: @@ -71,8 +78,8 @@ class GeminiApiClient: def generate_content(self, prompt: str) -> Optional[Dict]: """Генерация контента через Gemini API""" - # Применяем rate limiting self.rate_limiter.wait_if_needed() + self.rate_limiter.record_request() url = f"{self.base_url}/models/{self.model}:generateContent" @@ -88,7 +95,8 @@ class GeminiApiClient: } try: - logger.info(f"Отправка запроса к Gemini API. {self.rate_limiter.get_status()}") + status_after = self.rate_limiter.get_status() + logger.info(f"Отправка запроса к Gemini API. {status_after}") response = requests.post( url, headers=headers,