diff --git a/README.md b/README.md index c826aab..871a782 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,47 @@ -# Парсер реестров СРО ААС +# 📋 Парсер реестров СРО ААС -Автоматизированный инструмент для сбора данных из реестров Саморегулируемой организации аудиторов Ассоциация «Содружество» (СРО ААС). +Это Python-приложение для автоматизированного сбора данных из 14 официальных реестров **Саморегулируемой организации аудиторов Ассоциация «Содружество»** (СРО ААС). Проект реализует веб-скрейпинг с поддержкой пагинации, обработкой ошибок и экспортом в Excel. -## Возможности +## 🎯 Основные возможности -- Парсинг всех 14 официальных реестров СРО ААС -- Два режима работы: быстрое сканирование (первая страница) и полное сканирование (все страницы) -- Экспорт данных в формат Excel (.xlsx) с форматированием -- Система логирования и обработки ошибок -- Поддержка пагинации и повторных попыток при сбоях +- **14 реестров** — полное покрытие всех публичных реестров СРО ААС +- **Два режима парсинга:** + - 🚀 **Быстрый** (первая страница, ~50 записей, 1-2 сек) + - 🔍 **Полный** (все страницы с пагинацией, тысячи записей, несколько минут) +- **Экспорт в Excel** — автоматическое форматирование, заголовки, стилизация +- **Логирование** — подробные логи всех операций в `logs/` +- **Обработка ошибок** — повторные попытки (3x) при сбоях сети +- **CLI-интерфейс** — интерактивное меню для выбора реестра +- **Режим Cron** — автоматизация парсинга через аргументы командной строки +- **Неинтерактивный режим** — для запуска по расписанию без пользовательского ввода -## Поддерживаемые реестры +## 📊 Поддерживаемые реестры -**Действующие члены:** +**Действующие члены (7 реестров):** 1. Реестр аудиторов и индивидуальных аудиторов (14,691+ записей) 2. Реестр аудиторских организаций (256+ записей) -3. Реестр квалификационных аттестатов -4. Перечень индивидуальных аудиторов -5. Реестр учебно-методических центров -6. Сведения о подтверждении ОППК -7. Сведения о прохождении ПК руководителем аудита ОЗО ФР +3. Квалификационные аттестаты +4. Индивидуальные аудиторы +5. Учебно-методические центры +6. Подтверждение ОППК +7. Прохождение ПК руководителем аудита ОЗО ФР -**Исключенные члены:** +**Исключенные члены (4 реестра):** 8. Аудиторы, прекратившие членство 9. Аудиторские организации, прекратившие членство -10. Аннулированные квалификационные аттестаты -11. УМЦ, исключенные из реестра +10. Аннулированные аттестаты +11. Исключенные УМЦ -**Дисциплинарные меры:** +**Дисциплинарные меры (2 реестра):** 12. Меры воздействия к аудиторам 13. Меры воздействия к организациям -**Сети:** +**Сети (1 реестр):** 14. Российские и международные сети аудиторских организаций -## Установка +## 🚀 Быстрый старт + +### Установка ```bash # Клонирование репозитория @@ -51,19 +58,80 @@ venv\Scripts\activate # Windows pip install -r requirements.txt ``` -## Использование - -### Интерактивный режим +### Запуск +**Интерактивный режим:** ```bash python main.py +# Выбор реестра из меню → выбор режима (y/n) ``` -Выберите реестр из меню и режим парсинга: +**Через скрипт (Fish Shell):** +```bash +./run.fish +``` + +### Режим командной строки (для Cron) + +**Просмотр доступных реестров:** +```bash +python main.py --list +``` + +**Запуск парсинга без интерактивного режима:** +```bash +# Быстрый парсинг аудиторов +python main.py --registry auditors --mode quick + +# Полный парсинг организаций +python main.py --registry organizations --mode full + +# Сокращенный синтаксис +python main.py -r certificates -m quick +``` + +**Доступные реестры для --registry:** +- `auditors`, `organizations`, `certificates` +- `individual_auditors`, `training_centers`, `oppk_confirmation`, `ozo_training` +- `excluded_auditors`, `excluded_organizations`, `cancelled_certificates`, `excluded_training_centers` +- `disciplinary_auditors`, `disciplinary_organizations` +- `audit_networks` + +**Режимы (--mode):** +- `quick` — быстрый (первая страница, ~50 записей) +- `full` — полный (все страницы) + +### Автоматизация с Cron + +Примеры cron записей находятся в файле `cron_examples.sh`. + +**Настройка автоматического парсинга:** + +```bash +# Открыть редактор cron +crontab -e + +# Добавить задачу (парсинг аудиторов каждый день в 02:00) +0 2 * * * cd /path/to/osint_parser_ada && venv/bin/python main.py --registry auditors --mode quick >> logs/cron_auditors.log 2>&1 + +# Полный парсинг организаций каждое воскресенье в 01:00 +0 1 * * 0 cd /path/to/osint_parser_ada && venv/bin/python main.py --registry organizations --mode full >> logs/cron_organizations_full.log 2>&1 +``` + +**Просмотр логов:** +```bash +tail -f logs/cron_auditors.log +``` + +📖 **Подробное руководство:** См. [CRON_GUIDE.md](CRON_GUIDE.md) для детальных инструкций по настройке автоматизации. + +### Использование + +**Выбор режима парсинга:** - `n` (No) — быстрое сканирование (только первая страница, ~50 записей, 1-2 сек) - `y` (Yes) — полное сканирование (все страницы, все записи, несколько минут) -### Программный режим +**Программный режим:** ```python from parsers.auditors_parser import AuditorsParser @@ -81,12 +149,16 @@ exporter = ExcelExporter() exporter.export_to_excel(data, "auditors", "output.xlsx") ``` -## Структура проекта +## 🏗️ Архитектура проекта ``` osint_parser_ada/ ├── config.py # Конфигурация и URL реестров -├── main.py # CLI-интерфейс +├── main.py # CLI-интерфейс с поддержкой Cron +├── run.fish / run.sh # Скрипты быстрого запуска +├── run_cron.fish # Скрипт для Cron (Fish Shell) +├── cron_examples.sh # Примеры Cron записей +├── CRON_GUIDE.md # Подробное руководство по Cron ├── parsers/ # Парсеры реестров │ ├── base_parser.py # Базовый класс с пагинацией │ ├── auditors_parser.py # Парсер аудиторов @@ -108,6 +180,7 @@ osint_parser_ada/ - **Python:** 3.8+ - **Зависимости:** requests, beautifulsoup4, lxml, pandas, openpyxl - **Архитектура:** Паттерн Template Method, модульная структура +- **Режимы работы:** Интерактивный CLI и неинтерактивный (Cron) - **Обработка ошибок:** Повторные попытки (3x), логирование всех операций - **Производительность:** ~1.5 сек/страница с задержками для защиты от блокировки diff --git a/arch.md b/arch.md new file mode 100644 index 0000000..52e5ae8 --- /dev/null +++ b/arch.md @@ -0,0 +1,69 @@ +# Архитектура проекта + +Ниже — схема на уровне компонентов в формате, аналогичном примеру. + +``` + [CLI (main.py / run.fish / run.sh / run_cron.fish)] + | + v + Fetcher — загрузка HTML и проверка кода ответа + [Fetcher: HTTP GET (requests.Session)] + (timeout / retries / delay) + | + v + • Parser — извлечение таблиц/списков и распознавание колонок (BS4+lxml) + • Normalizer — очистка значений, trim, удаление префиксов, приведение типов/дат + • Model — маппинг в dataclasses (Auditor, Organization, …) + • Exporter — выгрузка pandas.DataFrame в Excel через openpyxl + | + v +[Pipeline] +[Parser (BeautifulSoup + эвристики)] --> [Normalizer] --> [Model] --> [DataFrame] --> [Exporter (XLSX)] +``` + +## Соответствие компонент файлам + +- CLI / запуск: + - `main.py` — CLI-меню, режим неинтерактивного запуска через аргументы (`--registry`, `--mode`, `--list`). + - `run.fish`, `run.sh` — быстрый старт; `run_cron.fish` — обёртка для cron (Fish). + +- Fetcher: + - `parsers/base_parser.py` → `BaseParser._make_request()` (requests.Session, заголовок User-Agent, таймаут, задержка, повторные попытки). + - Пагинация: `BaseParser._get_pagination_urls()`. + +- Parser: + - Специализированные: `parsers/auditors_parser.py`, `parsers/organizations_parser.py`. + - Универсальный: `parsers/generic_parser.py` (табличные реестры, автоматическое определение заголовков). + - Общая точка входа: `BaseParser.parse_registry(detailed=...)`. + +- Normalizer: + - Локальные шаги очистки/приведения: обрезка пробелов, извлечение чисел/дат, нормализация полей (внутри парсеров). + - Приведение к словарям/рубрикам, единообразные ключи для экспорта. + +- Model: + - `models/auditor.py`, `models/organization.py`, и др. — dataclasses, метод `to_dict()` для экспорта. + +- DataFrame + Exporter: + - Формирование `pandas.DataFrame` и экспорт: `utils/excel_exporter.py` → `ExcelExporter.export_to_excel()`. + - Форматирование XLSX (ширина колонок, шапка, стили): `ExcelExporter._format_excel()`. + +- Конфигурация и логирование: + - `config.py` — URL реестров (`REGISTRIES`), сетевые и экспортные настройки (`PARSER_CONFIG`, `EXPORT_CONFIG`, `LOGGING_CONFIG`). + - `utils/logger.py` — консольный и файловый логгеры (`logs/parser_YYYY-MM-DD.log`). + +## Потоки выполнения + +- Режим запуска: + - Интерактивный CLI: выбор реестра и детализации из меню (`main.py`). + - Неинтерактивный (для cron): `python main.py --registry --mode `. + +- Детализация: + - `mode=quick` — первая страница (без обхода пагинации и детальных карточек). + - `mode=full` — все страницы (пагинация) и, при поддержке, парсинг детальных карточек записей. + +- Надёжность: + - Повторные попытки при ошибках сети, управляемая задержка между запросами. + - Логи INFO/DEBUG в консоль и файл. + +- Выходные данные: + - Сырые записи → нормализованные словари → модели (dataclasses) → `pandas.DataFrame` → XLSX в `data/exports/`. diff --git a/main.py b/main.py index c916261..c670410 100755 --- a/main.py +++ b/main.py @@ -3,10 +3,21 @@ Парсер реестров СРО ААС (Саморегулируемая организация аудиторов) Автор: [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 @@ -29,6 +40,66 @@ def print_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) @@ -245,26 +316,11 @@ def not_implemented(): def main(): - """Главная функция приложения""" + """Главная функция - интерактивный режим""" print_banner() # Маппинг выбора на реестры - registry_map = { - "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", "Перечень сетей аудиторских организаций"), - } + registry_map = get_registry_map() while True: print_menu() @@ -304,9 +360,114 @@ def main(): 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: - main() + 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) diff --git a/run.fish b/run.fish deleted file mode 100755 index 143044e..0000000 --- a/run.fish +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env fish - -# Скрипт быстрого запуска для Fish Shell -# Использование: ./run.fish - -echo "==============================================" -echo " ПАРСЕР РЕЕСТРОВ СРО ААС" -echo "==============================================" -echo "" - -# Проверка наличия Python -if not command -v python3 &> /dev/null - echo "❌ Python 3 не найден. Установите Python 3.8 или выше." - exit 1 -end - -echo "✅ Python найден: "(python3 --version) -echo "" - -# Проверка виртуального окружения -if not test -d venv - echo "📦 Виртуальное окружение не найдено. Создание..." - python3 -m venv venv - echo "✅ Виртуальное окружение создано." - echo "" -end - -# Активация виртуального окружения -echo "🔧 Активация виртуального окружения..." -source venv/bin/activate.fish - -# Проверка зависимостей -if not python -c "import requests" 2>/dev/null - echo "📥 Установка зависимостей..." - pip install -q --upgrade pip - pip install -q -r requirements.txt - echo "✅ Зависимости установлены." - echo "" -end - -# Создание необходимых директорий -mkdir -p data/exports -mkdir -p logs - -# Запуск парсера -echo "🚀 Запуск парсера..." -echo "" -python main.py - -# Деактивация виртуального окружения -deactivate