Fix Rate Limiter

This commit is contained in:
itqop 2025-06-28 20:35:35 +03:00
parent 4585d181f8
commit c0a5d97e6c
3 changed files with 66 additions and 51 deletions

View File

@ -250,7 +250,7 @@ class JobApplicationManager:
def __init__(self): def __init__(self):
LoggingConfigurator.setup_logging( LoggingConfigurator.setup_logging(
log_file="logs/hh_bot.log", console_output=False log_file="logs/hh_bot.log", console_output=True
) )
self.orchestrator = AutomationOrchestrator() self.orchestrator = AutomationOrchestrator()

View File

@ -412,41 +412,44 @@ class VacancyApplicator:
logger.warning("Форма отклика не найдена в модальном окне - пропускаем") logger.warning("Форма отклика не найдена в модальном окне - пропускаем")
return SubmissionResult.SKIPPED return SubmissionResult.SKIPPED
self._add_cover_letter_if_possible(vacancy) submit_button = None
time.sleep(1)
for selector in submit_selectors: for selector in submit_selectors:
try: try:
submit_button = WebDriverWait(self.driver, 3).until( submit_button = WebDriverWait(self.driver, 3).until(
EC.element_to_be_clickable((By.CSS_SELECTOR, selector)) EC.element_to_be_clickable((By.CSS_SELECTOR, selector))
) )
if submit_button: if submit_button:
button_text = submit_button.text.strip().lower() break
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
except Exception: except Exception:
continue continue
logger.warning("Кнопка отправки в модальном окне не найдена") if not submit_button:
return SubmissionResult.FAILED 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: except Exception as e:
logger.error(f"Ошибка в модальном окне: {e}") logger.error(f"Ошибка в модальном окне: {e}")
@ -458,6 +461,8 @@ class VacancyApplicator:
if not settings.application.use_ai_cover_letters: if not settings.application.use_ai_cover_letters:
logger.info("ИИ-сопроводительные письма отключены в настройках") logger.info("ИИ-сопроводительные письма отключены в настройках")
return return
logger.info("Ищем кнопку сопроводительного письма...")
cover_letter_button_selectors = [ cover_letter_button_selectors = [
'[data-qa="add-cover-letter"]', '[data-qa="add-cover-letter"]',
'button[data-qa*="cover-letter"]', 'button[data-qa*="cover-letter"]',
@ -486,13 +491,14 @@ class VacancyApplicator:
continue continue
if not cover_letter_button: if not cover_letter_button:
logger.info("Кнопка сопроводительного письма не найдена") logger.info("📝 Кнопка сопроводительного письма не найдена")
return return
logger.info("Найдена кнопка сопроводительного письма, нажимаем...") logger.info("📝 Найдена кнопка сопроводительного письма, нажимаем...")
self.driver.execute_script("arguments[0].click();", cover_letter_button) self.driver.execute_script("arguments[0].click();", cover_letter_button)
time.sleep(2) time.sleep(2)
logger.info("Ищем поле для ввода письма...")
cover_letter_field_selectors = [ cover_letter_field_selectors = [
'textarea[data-qa*="cover-letter"]', 'textarea[data-qa*="cover-letter"]',
'textarea[name*="letter"]', 'textarea[name*="letter"]',
@ -509,15 +515,16 @@ class VacancyApplicator:
EC.presence_of_element_located((By.CSS_SELECTOR, selector)) EC.presence_of_element_located((By.CSS_SELECTOR, selector))
) )
if cover_letter_field: if cover_letter_field:
logger.info(f"Поле найдено: {selector}")
break break
except Exception: except Exception:
continue continue
if not cover_letter_field: if not cover_letter_field:
logger.warning("Поле для сопроводительного письма не найдено") logger.warning("📝 Поле для сопроводительного письма не найдено")
return return
logger.info("Генерация сопроводительного письма...") logger.info("📝 Генерация сопроводительного письма...")
from ..services.gemini_service import GeminiAIService from ..services.gemini_service import GeminiAIService
@ -525,15 +532,15 @@ class VacancyApplicator:
cover_letter_text = gemini_service.generate_cover_letter(vacancy) cover_letter_text = gemini_service.generate_cover_letter(vacancy)
if cover_letter_text: if cover_letter_text:
logger.info("Заполняем сопроводительное письмо...") logger.info("📝 Заполняем сопроводительное письмо...")
cover_letter_field.clear() cover_letter_field.clear()
cover_letter_field.send_keys(cover_letter_text) cover_letter_field.send_keys(cover_letter_text)
logger.info("✅ Сопроводительное письмо добавлено") logger.info("✅ Сопроводительное письмо добавлено")
else: else:
logger.warning("Не удалось сгенерировать сопроводительное письмо") logger.warning("📝 Не удалось сгенерировать сопроводительное письмо")
except Exception as e: except Exception as e:
logger.warning(f"Ошибка при добавлении сопроводительного письма: {e}") logger.warning(f"📝 Ошибка при добавлении сопроводительного письма: {e}")
logger.info("Продолжаем без сопроводительного письма") logger.info("Продолжаем без сопроводительного письма")
def _check_success_message(self) -> bool: def _check_success_message(self) -> bool:

View File

@ -23,29 +23,36 @@ class RateLimiter:
def wait_if_needed(self) -> None: 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() 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 while (self.request_times and
current_time - self.request_times[0] >= self.window_seconds): current_time - self.request_times[0] >= self.window_seconds):
self.request_times.popleft() 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: def get_remaining_requests(self) -> int:
"""Количество оставшихся запросов в текущем окне""" """Количество оставшихся запросов в текущем окне"""
current_time = time.time() current_time = time.time()
self._cleanup_old_requests(current_time)
while (self.request_times and
current_time - self.request_times[0] >= self.window_seconds):
self.request_times.popleft()
return max(0, self.max_requests - len(self.request_times)) return max(0, self.max_requests - len(self.request_times))
def get_status(self) -> str: def get_status(self) -> str:
@ -71,8 +78,8 @@ class GeminiApiClient:
def generate_content(self, prompt: str) -> Optional[Dict]: def generate_content(self, prompt: str) -> Optional[Dict]:
"""Генерация контента через Gemini API""" """Генерация контента через Gemini API"""
# Применяем rate limiting
self.rate_limiter.wait_if_needed() self.rate_limiter.wait_if_needed()
self.rate_limiter.record_request()
url = f"{self.base_url}/models/{self.model}:generateContent" url = f"{self.base_url}/models/{self.model}:generateContent"
@ -88,7 +95,8 @@ class GeminiApiClient:
} }
try: 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( response = requests.post(
url, url,
headers=headers, headers=headers,