fix: устранены все найденные аудитом баги и тихие падения

- SQL injection паттерн → параметризованные запросы во всех местах
- except: pass/continue → logger.warning() везде, ничего не тонет молча
- WAL mode + индекс domain_dedup_key в database.py
- try/finally для conn в main.py, утечка соединения устранена
- backoff 30с при 403/429 от Rusprofile/ЕГРЮЛ
- ликвидированные компании → egrul_status="liquidated"
- max_candidates в contacts_finder считает только реальных кандидатов
- DB_PATH абсолютный (Path(__file__).parent), HH_PAUSE_BETWEEN_QUERIES в config
- HH_SIGNAL_QUERIES дубль убран из launcher.py → импорт из config
- path traversal защита в egrul_enricher debug_dump_html

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Aks
2026-06-09 13:19:52 +03:00
parent f78f35fb3f
commit 98309dcc96
9 changed files with 208 additions and 186 deletions
+20 -9
View File
@@ -7,7 +7,7 @@
Стратегия запросов:
- Случайный User-Agent
- timeout 10 сек
- При 403/429 — пропускаем (логируем как 'error')
- При 403/429 — пауза 30 сек + пропускаем (логируем как 'error')
- Пауза между лидами в run_egrul_enrichment, не здесь
Ограничения:
@@ -18,6 +18,7 @@
"""
import logging
import re
import time
from datetime import datetime
from typing import Optional
from urllib.parse import quote
@@ -379,7 +380,8 @@ def enrich_egrul_by_inn(
allow_redirects=True,
)
if resp.status_code in (403, 429):
logger.warning(f" Rusprofile blocked us ({resp.status_code}) для ИНН {inn}")
logger.warning(f"[egrul] Rusprofile вернул {resp.status_code}, пауза 30 сек (ИНН {inn})")
time.sleep(30)
return result
if resp.status_code != 200:
return result
@@ -610,10 +612,11 @@ def enrich_egrul(
)
if resp.status_code in (403, 429):
logger.warning(f" Rusprofile blocked us ({resp.status_code}) для '{name}'")
logger.warning(f"[egrul] Rusprofile вернул {resp.status_code}, пауза 30 сек ('{name}')")
time.sleep(30)
return result
if resp.status_code != 200:
logger.debug(f" Rusprofile вернул {resp.status_code} для '{name}'")
logger.warning(f"[egrul] Rusprofile вернул {resp.status_code} для '{name}'")
return result
# Если редирект на /id/N или /ip/N — Rusprofile уверен в матче.
@@ -657,12 +660,20 @@ def enrich_egrul(
# Debug: сохранить HTML для отладки regex'ов
if debug_dump_html:
from pathlib import Path
dump_path = Path(debug_dump_html).resolve()
try:
with open(debug_dump_html, "w", encoding="utf-8") as f:
f.write(html)
logger.info(f" [debug] HTML сохранён в {debug_dump_html}")
except Exception as e:
logger.warning(f" [debug] Не удалось сохранить HTML: {e}")
dump_path.relative_to(Path.cwd())
except ValueError:
logger.warning(f"[egrul] debug_dump_html путь вне CWD, пропускаю: {debug_dump_html}")
debug_dump_html = None
if debug_dump_html:
try:
with open(dump_path, "w", encoding="utf-8") as f:
f.write(html)
logger.info(f" [debug] HTML сохранён в {dump_path}")
except Exception as e:
logger.warning(f" [debug] Не удалось сохранить HTML: {e}")
# Извлекаем все поля (один общий парсер для by_name и by_inn).
# Передаём company_name=name_for_search (без HR-суффиксов) чтобы