mirror of
https://github.com/ada-dmitry/osint_parser_ada.git
synced 2026-09-24 01:10:20 +00:00
477 lines
20 KiB
Python
Executable File
477 lines
20 KiB
Python
Executable File
#!/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)
|