Files

477 lines
20 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Парсер реестров СРО ААС (Саморегулируемая организация аудиторов)
Автор: [ada]
Использование:
Интерактивный режим:
python main.py
Режим cron (автоматический):
python main.py --registry auditors --mode quick
python main.py --registry organizations --mode full
python main.py -r certificates -m quick
python main.py --list # Показать доступные реестры
"""
import sys
import os
import argparse
from datetime import datetime
from config import REGISTRIES
from parsers.organizations_parser import OrganizationsParser
from parsers.auditors_parser import AuditorsParser
from parsers.generic_parser import GenericRegistryParser
from utils.excel_exporter import ExcelExporter
from utils.logger import setup_logger
def print_banner():
"""Вывод баннера приложения"""
banner = """
╔═══════════════════════════════════════════════════════════╗
║ ПАРСЕР РЕЕСТРОВ СРО ААС ║
║ Саморегулируемая организация аудиторов ║
║ Ассоциация «Содружество» ║
╚═══════════════════════════════════════════════════════════╝
"""
print(banner)
def get_registry_map():
"""Получить маппинг реестров"""
return {
"1": ("auditors", "Реестр аудиторов и индивидуальных аудиторов"),
"2": ("organizations", "Реестр аудиторских организаций"),
"3": ("certificates", "Реестр квалификационных аттестатов"),
"4": ("individual_auditors", "Перечень индивидуальных аудиторов"),
"5": ("training_centers", "Реестр учебно-методических центров"),
"6": ("oppk_confirmation", "Сведения о подтверждении ОППК"),
"7": ("ozo_training", "Сведения о прохождении ПК руководителем"),
"8": ("excluded_auditors", "Реестр исключенных аудиторов"),
"9": ("excluded_organizations", "Реестр исключенных организаций"),
"10": ("cancelled_certificates", "Реестр аннулированных аттестатов"),
"11": ("excluded_training_centers", "УМЦ исключенные из реестра"),
"12": ("disciplinary_auditors", "Дисциплинарные меры к аудиторам"),
"13": ("disciplinary_organizations", "Дисциплинарные меры к организациям"),
"14": ("audit_networks", "Перечень сетей аудиторских организаций"),
}
def list_registries():
"""Вывести список доступных реестров"""
print("\n" + "=" * 60)
print("ДОСТУПНЫЕ РЕЕСТРЫ ДЛЯ --registry ПАРАМЕТРА:")
print("=" * 60)
registry_map = get_registry_map()
print("\n📋 ДЕЙСТВИТЕЛЬНЫЕ ЧЛЕНЫ:")
print(" auditors - Реестр аудиторов")
print(" organizations - Реестр организаций")
print(" certificates - Квалификационные аттестаты")
print(" individual_auditors - Индивидуальные аудиторы")
print(" training_centers - Учебно-методические центры")
print(" oppk_confirmation - Подтверждение ОППК")
print(" ozo_training - Прохождение ПК руководителем")
print("\n❌ ИСКЛЮЧЕННЫЕ ЧЛЕНЫ:")
print(" excluded_auditors - Исключенные аудиторы")
print(" excluded_organizations - Исключенные организации")
print(" cancelled_certificates - Аннулированные аттестаты")
print(" excluded_training_centers - Исключенные УМЦ")
print("\n⚠️ ДИСЦИПЛИНАРНЫЕ МЕРЫ:")
print(" disciplinary_auditors - Меры к аудиторам")
print(" disciplinary_organizations - Меры к организациям")
print("\n🌐 СЕТИ:")
print(" audit_networks - Сети организаций")
print("\n" + "=" * 60)
print("Режимы (--mode):")
print(" quick - Быстрый (первая страница)")
print(" full - Полный (все страницы)")
print("=" * 60)
print("\nПример:")
print(" python main.py --registry auditors --mode quick")
print(" python main.py -r organizations -m full\n")
def print_menu():
"""Вывод главного меню"""
print("\n" + "=" * 60)
print("ДОСТУПНЫЕ РЕЕСТРЫ:")
print("=" * 60)
print("\n📋 ДЕЙСТВИТЕЛЬНЫЕ ЧЛЕНЫ И ВЫДАННЫЕ АТТЕСТАТЫ:")
print(" 1. Реестр членов — аудиторов и индивидуальных аудиторов ⭐")
print(" 2. Реестр членов — аудиторских организаций ⭐")
print(" 3. Реестр выданных СРО ААС квалификационных аттестатов ⭐")
print(" 4. Перечень индивидуальных аудиторов ⭐")
print(" 5. Реестр учебно-методических центров ⭐")
print(" 6. Сведения о подтверждении ОППК аудиторами ⭐")
print(" 7. Сведения о прохождении ПК руководителем аудита ОЗО ФР ⭐")
print("\n❌ ИСКЛЮЧЕННЫЕ ЧЛЕНЫ И АННУЛИРОВАННЫЕ АТТЕСТАТЫ:")
print(" 8. Реестр аудиторов, прекративших членство в СРО ААС ⭐")
print(" 9. Реестр аудиторских организаций, прекративших членство ⭐")
print(" 10. Реестр аннулированных квалификационных аттестатов ⭐")
print(" 11. УМЦ, исключенные из реестра СРО ААС ⭐")
print("\n⚠️ ДИСЦИПЛИНАРНЫЕ МЕРЫ:")
print(" 12. Меры дисциплинарного воздействия к аудиторам ⭐")
print(" 13. Меры дисциплинарного воздействия к организациям ⭐")
print("\n🌐 СЕТИ АУДИТОРСКИХ ОРГАНИЗАЦИЙ:")
print(" 14. Перечень российских и международных сетей ⭐")
print("\n" + "=" * 60)
print(" 0. Выход")
print("=" * 60)
print("\n⭐ - Полностью реализованный парсер")
def parse_organizations(detailed=False):
"""
Парсинг реестра аудиторских организаций
Args:
detailed: Парсить ли детальные страницы
"""
logger = setup_logger("main")
print("\n" + "=" * 60)
print("ПАРСИНГ РЕЕСТРА АУДИТОРСКИХ ОРГАНИЗАЦИЙ")
print("=" * 60)
# Подтверждение пользователя
if detailed:
print("\n⚠️ ВНИМАНИЕ: Детальный парсинг может занять продолжительное время!")
choice = input("Продолжить? (y/n): ").lower()
if choice != "y":
print("Операция отменена.")
return
try:
# Создание парсера
parser = OrganizationsParser()
# Парсинг данных
print("\n🔄 Начало парсинга...")
organizations = parser.parse_to_objects(detailed=detailed)
if not organizations:
print("\n❌ Не удалось получить данные из реестра.")
return
print(f"\n✅ Успешно собрано записей: {len(organizations)}")
# Экспорт в Excel
print("\n📊 Экспорт данных в Excel...")
exporter = ExcelExporter()
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
filename = f"organizations_{timestamp}"
filepath = exporter.export_organizations(organizations, filename=filename)
if filepath:
print(f"\n✅ Данные успешно экспортированы!")
print(f"📁 Файл: {os.path.abspath(filepath)}")
print(f"📊 Количество записей: {len(organizations)}")
else:
print("\n❌ Ошибка при экспорте данных.")
except KeyboardInterrupt:
print("\n\n⚠️ Операция прервана пользователем.")
except Exception as e:
logger.error(f"Ошибка при парсинге: {e}")
print(f"\n❌ Произошла ошибка: {e}")
def parse_generic_registry(registry_key: str, registry_name: str, detailed=False):
"""
Универсальная функция парсинга любого реестра
Args:
registry_key: Ключ реестра из config.REGISTRIES
registry_name: Название реестра для отображения
detailed: Парсить ли детальные страницы
"""
logger = setup_logger("main")
print("\n" + "=" * 60)
print(f"ПАРСИНГ: {registry_name.upper()}")
print("=" * 60)
# Подтверждение пользователя
if detailed:
print("\n⚠️ ВНИМАНИЕ: Детальный парсинг может занять продолжительное время!")
choice = input("Продолжить? (y/n): ").lower()
if choice != "y":
print("Операция отменена.")
return
try:
# Создание парсера
parser = GenericRegistryParser(registry_key)
# Парсинг данных
print("\n🔄 Начало парсинга...")
data = parser.parse_registry(detailed=detailed)
if not data:
print("\n❌ Не удалось получить данные из реестра.")
return
print(f"\n✅ Успешно собрано записей: {len(data)}")
# Экспорт в Excel
print("\n📊 Экспорт данных в Excel...")
exporter = ExcelExporter()
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
filename = f"{registry_key}_{timestamp}"
filepath = exporter.export_to_excel(
data=data,
filename=filename,
sheet_name=registry_name[:30], # Ограничение длины для Excel
)
if filepath:
print(f"\n✅ Данные успешно экспортированы!")
print(f"📁 Файл: {os.path.abspath(filepath)}")
print(f"📊 Количество записей: {len(data)}")
else:
print("\n❌ Ошибка при экспорте данных.")
except KeyboardInterrupt:
print("\n\n⚠️ Операция прервана пользователем.")
except Exception as e:
logger.error(f"Ошибка при парсинге: {e}")
print(f"\n❌ Произошла ошибка: {e}")
def parse_auditors(detailed=False):
"""Парсинг реестра аудиторов"""
logger = setup_logger("main")
print("\n" + "=" * 60)
print("ПАРСИНГ РЕЕСТРА АУДИТОРОВ")
print("=" * 60)
if detailed:
print("\n⚠️ ВНИМАНИЕ: Детальный парсинг может занять продолжительное время!")
choice = input("Продолжить? (y/n): ").lower()
if choice != "y":
print("Операция отменена.")
return
try:
parser = AuditorsParser()
print("\n🔄 Начало парсинга...")
auditors = parser.parse_to_objects(detailed=detailed)
if not auditors:
print("\n❌ Не удалось получить данные из реестра.")
return
print(f"\n✅ Успешно собрано записей: {len(auditors)}")
# Экспорт в Excel
print("\n📊 Экспорт данных в Excel...")
exporter = ExcelExporter()
timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
filename = f"auditors_{timestamp}"
# Преобразуем в словари
data = [auditor.to_dict() for auditor in auditors]
filepath = exporter.export_to_excel(
data=data, filename=filename, sheet_name="Аудиторы"
)
if filepath:
print(f"\n✅ Данные успешно экспортированы!")
print(f"📁 Файл: {os.path.abspath(filepath)}")
print(f"📊 Количество записей: {len(auditors)}")
else:
print("\n❌ Ошибка при экспорте данных.")
except KeyboardInterrupt:
print("\n\n⚠️ Операция прервана пользователем.")
except Exception as e:
logger.error(f"Ошибка при парсинге: {e}")
print(f"\n❌ Произошла ошибка: {e}")
def not_implemented():
"""Заглушка для нереализованных функций"""
print("\n⚠️ Данный парсер находится в разработке.")
print("💡 Вы можете реализовать его по аналогии с OrganizationsParser.")
def main():
"""Главная функция - интерактивный режим"""
print_banner()
# Маппинг выбора на реестры
registry_map = get_registry_map()
while True:
print_menu()
choice = input("\n👉 Выберите реестр (0-14): ").strip()
if choice == "0":
print("\n👋 До свидания!")
sys.exit(0)
elif choice == "1":
# Реестр аудиторов - специализированный парсер
print(f"\n📋 Выбран: {registry_map[choice][1]}")
detail_choice = input("Парсить детальные страницы? (y/n): ").lower()
detailed = detail_choice == "y"
parse_auditors(detailed=detailed)
elif choice == "2":
# Реестр аудиторских организаций - специализированный парсер
print(f"\n📋 Выбран: {registry_map[choice][1]}")
detail_choice = input("Парсить детальные страницы? (y/n): ").lower()
detailed = detail_choice == "y"
parse_organizations(detailed=detailed)
elif choice in registry_map:
# Все остальные реестры - универсальный парсер
registry_key, registry_name = registry_map[choice]
print(f"\n📋 Выбран: {registry_name}")
detail_choice = input("Парсить детальные страницы? (y/n): ").lower()
detailed = detail_choice == "y"
parse_generic_registry(registry_key, registry_name, detailed=detailed)
else:
print("\n❌ Неверный выбор. Попробуйте снова.")
# Пауза перед возвратом в меню
input("\n⏸️ Нажмите Enter для возврата в меню...")
def run_cron_mode(registry_key: str, mode: str):
"""
Запуск в режиме cron (неинтерактивный)
Args:
registry_key: Ключ реестра (auditors, organizations, и т.д.)
mode: Режим парсинга (quick или full)
"""
logger = setup_logger("cron")
# Проверка валидности реестра
if registry_key not in REGISTRIES:
logger.error(f"Неизвестный реестр: {registry_key}")
print(f"❌ Ошибка: Реестр '{registry_key}' не найден.")
print("Используйте --list для просмотра доступных реестров.")
sys.exit(1)
# Определение режима детализации
detailed = mode.lower() == "full"
mode_str = "полный" if detailed else "быстрый"
registry_name = REGISTRIES[registry_key]["name"]
logger.info(f"Запуск в режиме cron: {registry_key} ({mode_str})")
print(f"\n🤖 РЕЖИМ CRON")
print("=" * 60)
print(f"Реестр: {registry_name}")
print(f"Режим: {mode_str}")
print(f"Детализация: {'Да' if detailed else 'Нет'}")
print("=" * 60 + "\n")
try:
# Выбор парсера в зависимости от реестра
if registry_key == "auditors":
parse_auditors(detailed=detailed)
elif registry_key == "organizations":
parse_organizations(detailed=detailed)
else:
parse_generic_registry(registry_key, registry_name, detailed=detailed)
logger.info(f"Парсинг {registry_key} успешно завершен")
print("\n✅ Парсинг успешно завершен")
sys.exit(0)
except Exception as e:
logger.error(f"Ошибка при парсинге в режиме cron: {e}")
print(f"\n❌ Ошибка: {e}")
sys.exit(1)
def parse_args():
"""Парсинг аргументов командной строки"""
parser = argparse.ArgumentParser(
description="Парсер реестров СРО ААС",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Примеры использования:
# Интерактивный режим
python main.py
# Режим cron - быстрый парсинг аудиторов
python main.py --registry auditors --mode quick
# Режим cron - полный парсинг организаций
python main.py --registry organizations --mode full
# Список доступных реестров
python main.py --list
""",
)
parser.add_argument(
"-r", "--registry", type=str, help="Ключ реестра для парсинга (см. --list)"
)
parser.add_argument(
"-m",
"--mode",
type=str,
choices=["quick", "full"],
default="quick",
help="Режим парсинга: quick (быстрый) или full (полный)",
)
parser.add_argument(
"-l", "--list", action="store_true", help="Показать список доступных реестров"
)
return parser.parse_args()
if __name__ == "__main__":
try:
args = parse_args()
# Режим показа списка реестров
if args.list:
list_registries()
sys.exit(0)
# Режим cron (неинтерактивный)
if args.registry:
run_cron_mode(args.registry, args.mode)
# Интерактивный режим (по умолчанию)
else:
main()
except KeyboardInterrupt:
print("\n\n👋 Программа завершена.")
sys.exit(0)
except Exception as e:
print(f"\n❌ Критическая ошибка: {e}")
sys.exit(1)