# Copyright GEMINI import re # --- Ресурсы для алгоритма --- STOP_WORDS = {'пр.', 'лек.', 'лаб.', 'семинар'} TITLES = ('доц.', 'проф.', 'асс.', 'ст.пр.') SURNAME_ENDINGS = ('ов', 'ев', 'ин', 'ский', 'цкой', 'их', 'ых', 'ова', 'ева', 'ина', 'ская', 'ян', 'ко', "ня", "ин") def is_surname_string(s: str) -> bool: """ Классифицирует строку, определяя, содержит ли она фамилию. """ if not isinstance(s, str) or not s: return False # Шаг 1: Очистка s_clean = s.strip() # Шаг 2: Жесткие правила "НЕ ФАМИЛИЯ" if s_clean.lower() in STOP_WORDS: return False if s_clean.isupper() and len(s_clean) > 3: return False if re.search(r'[\(\)/«»%]', s_clean): return False words = s_clean.split() if len(words) > 3: return False # Шаг 3: Жесткие правила "ТОЧНО ФАМИЛИЯ" if re.search(r'\b[А-Я]\.?', s_clean): # Ищет "И.А." или "И.А" return True if s_clean.lower().startswith(TITLES): return True # Шаг 4: Эвристический анализ для оставшихся случаев score = 0 # Правило на капитализацию if words and words[0][0].isupper(): score += 5 else: # Если слово не с большой буквы, это почти точно не фамилия return False # Правило на окончания last_word = words[-1].lower() if last_word.endswith(SURNAME_ENDINGS): score += 6 # Правило на количество слов if len(words) in [1, 2]: score += 2 # Пороговое значение THRESHOLD = 8 return score >= THRESHOLD def extract_last_name(name_str: str) -> str or None: """ Извлекает из строки только фамилию. Справляется с приклеенными званиями (типа "ст.пр.Дмитриев") и отбрасывает инициалы. Ищет первое слово, написанное с заглавной буквы. Args: name_str: Исходная "грязная" строка с именем. Returns: Чистая фамилия в виде строки, или None, если фамилия не найдена. """ # Проверка, что на вход подана строка if not isinstance(name_str, str): return None # Паттерн для поиска: # [А-ЯЁ] - одна заглавная русская буква в начале # [а-яё]+ - одна или более строчных русских букв после неё # (?:-[А-ЯЁ][а-яё]+)? - опциональная часть для двойных фамилий (например, -Петров) pattern = r'[А-ЯЁ][а-яё]+(?:-[А-ЯЁ][а-яё]+)?' match = re.search(pattern, name_str) if match: return match.group(0) # Возвращаем найденное совпадение else: return None # Если ничего не найдено # --- Шаг 0: Константы --- POSITIVE_KEYWORDS = ['зал', 'ауд', 'каб', 'корп', 'кор.', "ЛК"] NEGATIVE_KEYWORDS = ['доц', 'проф', 'асс', 'лек', 'пр'] # Транслитерация для унификации TRANS_TABLE = str.maketrans('TCBAHIMK', 'ТКВАНІМК') # Латиница -> Кириллица def is_room_number(s: str) -> bool: """ Проверяет, является ли строка номером кабинета, по многоуровневому алгоритму. """ # --- Шаг 1: Быстрые негативные фильтры --- if not isinstance(s, str) or not s.strip(): return False # 1. Проверка на пустоту if ',' in s: return False # 2. Проверка на запятые (даты) # 3. Проверка на "очевидный мусор" first_word = s.strip().lower().split()[0] if first_word in NEGATIVE_KEYWORDS: return False # 4. Проверка на инициалы (напр. А.Е.) if re.search(r'\b[А-Я]\.[А-Я]\.?\b', s): return False # 5. Проверка на длинное слово без цифр if not any(char.isdigit() for char in s) and len(s.split()) == 1 and len(s) > 10: return False # --- Шаг 2: Быстрые позитивные фильтры --- s_lower = s.lower() if any(keyword in s_lower for keyword in POSITIVE_KEYWORDS): return True # 1. Проверка по ключевым словам # --- Шаг 3: Основной анализ --- if not any(char.isdigit() for char in s): return False # 1. Требуется наличие цифр # 2. Создание "чистой" версии clean_s = s.upper().translate(TRANS_TABLE).replace(' ', '') # 3. Комплексный паттерн для проверки # Пояснение: # ^...$ - шаблон должен соответствовать всей строке # [А-ЯЁ]?-? - необязательная буква и необязательный дефис в начале # \d+ - одна или более цифр (ядро номера) # (?:[.-]\d+)* - необязательные группы ".число" или "-число" # [А-ЯЁ]?$ - необязательная буква в конце room_pattern = re.compile(r'^[А-ЯЁ]?-?\d+(?:[.-]\d+)*[А-ЯЁ]?$') if room_pattern.fullmatch(clean_s): return True # --- Шаг 4: Финальное решение --- return False import requests from urllib.parse import urlsplit, urlunsplit, quote def download_file_from_url(url, output_filename): """ Скачивает файл по URL со спецсимволами и пробелами, сохраняя его под указанным именем. Args: url (str): Исходный URL, который может содержать пробелы и кириллицу. output_filename (str): Имя файла для сохранения (например, 'calc.xls'). """ try: # --- Шаг 1: Правильное кодирование URL --- # Разбираем URL на части: ('https', 'www.vstu.ru', '/path/to file.xls', '', '') parts = urlsplit(url) # Кодируем только путь, оставляя слэши '/' безопасными # Это превратит ' ' в '%20', 'В' в '%D0%92' и т.д. encoded_path = quote(parts.path, safe='/-_') # Собираем URL обратно из частей с уже закодированным путем encoded_url = urlunsplit((parts.scheme, parts.netloc, encoded_path, parts.query, parts.fragment)) # --- Шаг 2: Скачивание файла --- response = requests.get(encoded_url, stream=True) # Проверяем, успешен ли запрос (код 200 OK) # Если сервер вернет ошибку (404, 500 и т.д.), здесь возникнет исключение response.raise_for_status() # --- Шаг 3: Сохранение файла --- # Открываем файл для записи в бинарном режиме ('wb') # Использование 'with' гарантирует, что файл будет закрыт автоматически with open(output_filename, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) print(f"✅ Файл успешно скачан и сохранен как '{output_filename}'") except requests.exceptions.RequestException as e: print(f"❌ Ошибка скачивания: {e}") except Exception as e: print(f"❌ Произошла непредвиденная ошибка: {e}")