You've already forked parser_1cmycloud
mirror of
https://github.com/vanyagoncharov/parser_1cmycloud.git
synced 2026-06-19 17:18:10 +02:00
Добавление в GIT
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
# Парсеры документации 1С:Предприятие.Элемент
|
||||
|
||||
Два парсера для сбора документации с сайта 1cmycloud.com:
|
||||
- `parser.py` - для парсинга "Руководства разработчика" (иерархическое меню)
|
||||
- `parser stdlib.py` - для парсинга "Стандартной библиотеки" (плоская структура)
|
||||
|
||||
## 📋 Требования
|
||||
|
||||
### Установка зависимостей
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Необходимые компоненты
|
||||
|
||||
- Python 3.7+
|
||||
- Google Chrome (для Selenium)
|
||||
- Интернет-соединение
|
||||
|
||||
## 🚀 Быстрый старт
|
||||
|
||||
### 1. Парсинг Руководства разработчика (`parser.py`)
|
||||
|
||||
Парсит иерархическую структуру документации с раскрывающимися категориями:
|
||||
|
||||
```bash
|
||||
python parser.py --url https://1cmycloud.com/console/help/element/8.1/docs/topics/1c-element-overview/ --username username --password password --use-selenium --visible-browser --save-by-pages --max-pages 10000 --pages-dir ManualDevelop
|
||||
```
|
||||
|
||||
**⚠️ ВАЖНО:** После запуска:
|
||||
1. Откроется окно браузера Chrome
|
||||
2. Скрипт автоматически заполнит логин и пароль
|
||||
3. **ВРУЧНУЮ** нажмите кнопку "Войти" в браузере
|
||||
4. Дождитесь загрузки страницы документации
|
||||
5. Парсер автоматически начнет работу
|
||||
|
||||
**Особенности:**
|
||||
- Выполняет depth-first обход по меню
|
||||
- Раскрывает категории по клику
|
||||
- Пропускает разделы: "Устройство сервера" и "Панель управления сервера"
|
||||
- Сохраняет каждую страницу в отдельную папку
|
||||
|
||||
### 2. Парсинг Стандартной библиотеки (`parser stdlib.py`)
|
||||
|
||||
Парсит плоскую структуру ссылок из меню:
|
||||
|
||||
```bash
|
||||
python "parser stdlib.py" --url https://1cmycloud.com/console/help/element/8.1/docs/stdlib/element/ --username username --password password --use-selenium --visible-browser --save-by-pages --max-pages 10000 --pages-dir StdLib
|
||||
```
|
||||
|
||||
**⚠️ ВАЖНО:** Также требует ручного нажатия кнопки "Войти" после заполнения формы!
|
||||
|
||||
**Особенности:**
|
||||
- Собирает ВСЕ ссылки из меню (рекурсивно)
|
||||
- Обходит страницы по порядку
|
||||
- Подходит для плоской структуры документации
|
||||
|
||||
## 📂 Структура выходных данных
|
||||
|
||||
После парсинга создается:
|
||||
|
||||
```
|
||||
<pages-dir>/
|
||||
├── page_001_Название_страницы/
|
||||
│ ├── page.html # Исходный HTML
|
||||
│ └── content.md # Извлеченный контент в Markdown
|
||||
├── page_002_Другая_страница/
|
||||
│ ├── page.html
|
||||
│ └── content.md
|
||||
└── ...
|
||||
|
||||
documentation.json # JSON с метаданными всех страниц
|
||||
documentation.txt # Весь текст в одном файле
|
||||
```
|
||||
|
||||
## ⚙️ Параметры командной строки
|
||||
|
||||
### Общие параметры (для обоих парсеров)
|
||||
|
||||
| Параметр | Описание | По умолчанию |
|
||||
|----------|----------|--------------|
|
||||
| `--url` | URL начальной страницы | см. примеры выше |
|
||||
| `--username` | Логин для авторизации | - |
|
||||
| `--password` | Пароль для авторизации | - |
|
||||
| `--use-selenium` | Использовать Selenium (обязательно!) | false |
|
||||
| `--visible-browser` | Показывать окно браузера | false |
|
||||
| `--save-by-pages` | Сохранять страницы по папкам | false |
|
||||
| `--max-pages` | Максимум страниц для парсинга | 1000 |
|
||||
| `--pages-dir` | Директория для сохранения | `pages` |
|
||||
| `--output` | Имя текстового файла | `documentation.txt` |
|
||||
| `--debug` | Режим отладки (сохранение HTML) | false |
|
||||
|
||||
## 🛠️ Вспомогательные скрипты
|
||||
|
||||
### 1. Удаление номеров страниц из имен папок
|
||||
|
||||
```bash
|
||||
python remove_page_numbers.py <директория>
|
||||
```
|
||||
|
||||
Убирает:
|
||||
- Префикс `page_XXX_` из имен папок
|
||||
- Суффикс ` _ 1С_Предприятие.Элемент`
|
||||
|
||||
**Пример:**
|
||||
```bash
|
||||
python remove_page_numbers.py ManualDevelop --apply
|
||||
```
|
||||
|
||||
### 2. Удаление HTML файлов
|
||||
|
||||
```bash
|
||||
python delete_html_files.py <директория>
|
||||
```
|
||||
|
||||
Удаляет все `page.html` файлы, оставляя только `content.md`.
|
||||
|
||||
**Пример:**
|
||||
```bash
|
||||
python delete_html_files.py ManualDevelop --apply
|
||||
```
|
||||
|
||||
### 3. Реорганизация папок по иерархии
|
||||
|
||||
```bash
|
||||
python reorganize_folders.py
|
||||
```
|
||||
|
||||
Перемещает папки в иерархическую структуру на основе URL из `documentation.json`.
|
||||
|
||||
## 📝 Примеры использования
|
||||
|
||||
### Полный цикл работы с Руководством разработчика
|
||||
|
||||
```bash
|
||||
# 1. Парсинг (с ручным входом!)
|
||||
python parser.py --url https://1cmycloud.com/console/help/element/8.1/docs/topics/1c-element-overview/ --username +79168704546 --password duMX9cW6 --use-selenium --visible-browser --save-by-pages --max-pages 500 --pages-dir ManualDevelop
|
||||
|
||||
# 2. Удаление номеров из имен папок
|
||||
python remove_page_numbers.py ManualDevelop --apply
|
||||
|
||||
# 3. Удаление HTML файлов (если не нужны)
|
||||
python delete_html_files.py ManualDevelop --apply
|
||||
```
|
||||
|
||||
### Полный цикл работы со Стандартной библиотекой
|
||||
|
||||
```bash
|
||||
# 1. Парсинг (с ручным входом!)
|
||||
python "parser stdlib.py" --url https://1cmycloud.com/console/help/element/8.1/docs/stdlib/element/ --username +79168704546 --password duMX9cW6 --use-selenium --visible-browser --save-by-pages --max-pages 5000 --pages-dir StdLib
|
||||
|
||||
# 2. Реорганизация по иерархии
|
||||
python reorganize_folders.py
|
||||
|
||||
# 3. Очистка
|
||||
python remove_page_numbers.py StdLib --apply
|
||||
python delete_html_files.py StdLib --apply
|
||||
```
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Скрипт для удаления всех HTML файлов из директории и подпапок
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import io
|
||||
from pathlib import Path
|
||||
|
||||
# Настройка UTF-8 для вывода в консоль (для Windows)
|
||||
if sys.platform == 'win32':
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
|
||||
|
||||
def delete_html_files(target_dir, dry_run=True):
|
||||
"""
|
||||
Удаляет все HTML файлы из директории и подпапок
|
||||
|
||||
Args:
|
||||
target_dir: Целевая директория
|
||||
dry_run: Если True - только показывает что будет удалено
|
||||
"""
|
||||
target_path = Path(target_dir)
|
||||
|
||||
if not target_path.exists():
|
||||
print(f"❌ Директория {target_dir} не найдена!")
|
||||
return
|
||||
|
||||
print(f"🔍 Поиск HTML файлов в {target_dir}...\n")
|
||||
|
||||
# Рекурсивно находим все HTML файлы
|
||||
html_files = list(target_path.rglob('*.html'))
|
||||
|
||||
print(f"📊 Найдено HTML файлов: {len(html_files)}\n")
|
||||
|
||||
if not html_files:
|
||||
print("✅ HTML файлов не найдено!")
|
||||
return
|
||||
|
||||
# Показываем первые 20 файлов
|
||||
print("Примеры файлов для удаления (первые 20):")
|
||||
for i, file_path in enumerate(html_files[:20]):
|
||||
relative_path = file_path.relative_to(target_path)
|
||||
print(f" 🗑️ {relative_path}")
|
||||
|
||||
if len(html_files) > 20:
|
||||
print(f" ... и еще {len(html_files) - 20} файлов")
|
||||
|
||||
print()
|
||||
|
||||
if dry_run:
|
||||
print("⚠️ Режим DRY RUN - файлы не будут удалены")
|
||||
print(" Запустите с --apply для применения изменений")
|
||||
return
|
||||
|
||||
# Удаляем файлы
|
||||
print("🚀 Удаляю файлы...\n")
|
||||
deleted = 0
|
||||
errors = 0
|
||||
|
||||
for file_path in html_files:
|
||||
try:
|
||||
if file_path.exists():
|
||||
file_path.unlink()
|
||||
deleted += 1
|
||||
|
||||
if deleted <= 20 or deleted % 100 == 0:
|
||||
relative_path = file_path.relative_to(target_path)
|
||||
print(f" ✓ Удалено: {relative_path}")
|
||||
except Exception as e:
|
||||
errors += 1
|
||||
print(f" ❌ Ошибка при удалении {file_path.name}: {e}")
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"✅ Удаление завершено!")
|
||||
print(f" Удалено: {deleted}")
|
||||
print(f" Ошибок: {errors}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='Удаление всех HTML файлов из директории')
|
||||
parser.add_argument('target_dir', type=str, nargs='?',
|
||||
default=r'E:\Programs\1C\УчетКинолога\Документация\ManualDevelop',
|
||||
help='Целевая директория')
|
||||
parser.add_argument('--apply', action='store_true',
|
||||
help='Применить изменения (по умолчанию: только показать план)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
print("🗑️ Удаление HTML файлов\n")
|
||||
delete_html_files(args.target_dir, dry_run=not args.apply)
|
||||
|
||||
|
||||
|
||||
+1629
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Скрипт для удаления номеров страниц из названий папок
|
||||
Переименовывает: page_123_Название -> Название
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import io
|
||||
from pathlib import Path
|
||||
|
||||
# Настройка UTF-8 для вывода в консоль (для Windows)
|
||||
if sys.platform == 'win32':
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
|
||||
|
||||
def remove_page_numbers(pages_dir='pages', dry_run=True):
|
||||
"""
|
||||
Удаляет номера страниц из названий папок
|
||||
|
||||
Args:
|
||||
pages_dir: Директория с папками страниц
|
||||
dry_run: Если True - только показывает что будет сделано
|
||||
"""
|
||||
pages_path = Path(pages_dir)
|
||||
|
||||
if not pages_path.exists():
|
||||
print(f"❌ Директория {pages_dir} не найдена!")
|
||||
return
|
||||
|
||||
print(f"🔍 Сканирую папки в {pages_dir}...\n")
|
||||
|
||||
# Рекурсивно находим все папки с форматом page_XXX_*
|
||||
all_folders = []
|
||||
|
||||
def scan_folder(folder_path, depth=0):
|
||||
"""Рекурсивно сканирует папки"""
|
||||
try:
|
||||
for item in sorted(folder_path.iterdir()):
|
||||
if item.is_dir():
|
||||
# Проверяем формат: page_XXX_*
|
||||
match = re.match(r'page_(\d+)_(.+)', item.name)
|
||||
if match:
|
||||
page_num = match.group(1)
|
||||
new_name = match.group(2)
|
||||
|
||||
# Убираем " _ 1С_Предприятие.Элемент" в конце
|
||||
if new_name.endswith(' _ 1С_Предприятие.Элемент'):
|
||||
new_name = new_name[:-len(' _ 1С_Предприятие.Элемент')]
|
||||
|
||||
all_folders.append({
|
||||
'path': item,
|
||||
'old_name': item.name,
|
||||
'new_name': new_name,
|
||||
'page_num': page_num,
|
||||
'depth': depth
|
||||
})
|
||||
|
||||
# Рекурсивно сканируем вложенные папки
|
||||
scan_folder(item, depth + 1)
|
||||
except PermissionError as e:
|
||||
print(f"⚠️ Нет доступа к папке: {folder_path}")
|
||||
|
||||
scan_folder(pages_path)
|
||||
|
||||
print(f"📊 Найдено папок для переименования: {len(all_folders)}\n")
|
||||
|
||||
if not all_folders:
|
||||
print("✅ Папок для переименования не найдено!")
|
||||
return
|
||||
|
||||
# Сортируем по глубине (от глубоких к верхним) чтобы избежать конфликтов
|
||||
all_folders.sort(key=lambda x: x['depth'], reverse=True)
|
||||
|
||||
print("📋 План переименования:\n")
|
||||
|
||||
# Группируем по глубине для красивого вывода
|
||||
by_depth = {}
|
||||
for folder in all_folders:
|
||||
depth = folder['depth']
|
||||
if depth not in by_depth:
|
||||
by_depth[depth] = []
|
||||
by_depth[depth].append(folder)
|
||||
|
||||
for depth in sorted(by_depth.keys()):
|
||||
print(f" Уровень {depth}: {len(by_depth[depth])} папок")
|
||||
|
||||
print()
|
||||
|
||||
# Показываем первые 10 примеров
|
||||
print("Примеры переименования (первые 10):")
|
||||
for i, folder in enumerate(all_folders[:10]):
|
||||
indent = " " * folder['depth']
|
||||
print(f"{indent}📦 {folder['old_name']}")
|
||||
print(f"{indent} → {folder['new_name']}")
|
||||
|
||||
if len(all_folders) > 10:
|
||||
print(f" ... и еще {len(all_folders) - 10} папок")
|
||||
|
||||
print()
|
||||
|
||||
if dry_run:
|
||||
print("⚠️ Режим DRY RUN - файлы не будут переименованы")
|
||||
print(" Запустите с --apply для применения изменений")
|
||||
return
|
||||
|
||||
# Применяем переименование
|
||||
print("🚀 Применяю переименование...\n")
|
||||
renamed = 0
|
||||
errors = 0
|
||||
skipped = 0
|
||||
|
||||
for folder in all_folders:
|
||||
try:
|
||||
old_path = folder['path']
|
||||
new_path = old_path.parent / folder['new_name']
|
||||
|
||||
# Проверяем что источник существует
|
||||
if not old_path.exists():
|
||||
print(f" ⚠️ Пропускаю (не существует): {folder['old_name']}")
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
# Проверяем что цель не существует
|
||||
if new_path.exists() and new_path != old_path:
|
||||
print(f" ⚠️ Пропускаю (уже существует): {folder['new_name']}")
|
||||
skipped += 1
|
||||
continue
|
||||
|
||||
# Переименовываем
|
||||
old_path.rename(new_path)
|
||||
renamed += 1
|
||||
|
||||
if renamed <= 20 or renamed % 100 == 0:
|
||||
indent = " " * folder['depth']
|
||||
print(f"{indent}✓ {folder['old_name']} → {folder['new_name']}")
|
||||
|
||||
except Exception as e:
|
||||
errors += 1
|
||||
print(f" ❌ Ошибка при переименовании {folder['old_name']}: {e}")
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"✅ Переименование завершено!")
|
||||
print(f" Переименовано: {renamed}")
|
||||
print(f" Пропущено: {skipped}")
|
||||
print(f" Ошибок: {errors}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='Удаление номеров страниц из названий папок')
|
||||
parser.add_argument('--pages-dir', type=str, default='pages',
|
||||
help='Директория с папками страниц (по умолчанию: pages)')
|
||||
parser.add_argument('--apply', action='store_true',
|
||||
help='Применить изменения (по умолчанию: только показать план)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
print("🗂️ Удаление номеров страниц из названий папок\n")
|
||||
remove_page_numbers(args.pages_dir, dry_run=not args.apply)
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Скрипт для реорганизации папок со страницами в иерархическую структуру
|
||||
на основе URL зависимостей из documentation.json
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import json
|
||||
import sys
|
||||
import io
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse
|
||||
|
||||
# Настройка UTF-8 для вывода в консоль (для Windows)
|
||||
if sys.platform == 'win32':
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
|
||||
|
||||
def load_documentation_json(json_file='documentation.json'):
|
||||
"""Загружает documentation.json и возвращает список записей"""
|
||||
try:
|
||||
with open(json_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
print(f"✓ Загружено {len(data)} записей из {json_file}")
|
||||
return data
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка при загрузке {json_file}: {e}")
|
||||
return []
|
||||
|
||||
def normalize_title(title):
|
||||
"""Нормализует title для сопоставления с именем папки"""
|
||||
# Убираем " | " разделитель (title в JSON: "Название | 1С:Предприятие.Элемент")
|
||||
# В папке: "page_XXX_Название _ 1С_Предприятие.Элемент"
|
||||
if ' | ' in title:
|
||||
title = title.replace(' | ', ' _ ')
|
||||
|
||||
# :: заменяется на __ (двойное подчеркивание)
|
||||
# : заменяется на _ (одинарное подчеркивание)
|
||||
normalized = title.replace('::', '__').replace(':', '_')
|
||||
normalized = normalized.replace('/', '_').replace('\\', '_')
|
||||
return normalized
|
||||
|
||||
def get_url_depth_and_parent(url, base_path='/console/help/element/8.1/docs/stdlib/element/'):
|
||||
"""
|
||||
Определяет глубину URL и возможного родителя
|
||||
|
||||
Например:
|
||||
.../element/ -> depth=0, parent=None
|
||||
.../element/Std/ -> depth=1, parent=.../element/
|
||||
.../element/Std/V8/ -> depth=2, parent=.../element/Std/
|
||||
.../element/Std/V8/Администрирование/ -> depth=3, parent=.../element/Std/V8/
|
||||
"""
|
||||
if not url:
|
||||
return 0, None
|
||||
|
||||
parsed = urlparse(url)
|
||||
path = parsed.path
|
||||
|
||||
# Убираем базовый путь
|
||||
if base_path in path:
|
||||
relative_path = path.split(base_path)[1]
|
||||
else:
|
||||
# Если base_path не найден, возвращаем depth=0
|
||||
return 0, None
|
||||
|
||||
# Убираем trailing slash и разбиваем на части
|
||||
parts = [p for p in relative_path.strip('/').split('/') if p]
|
||||
depth = len(parts)
|
||||
|
||||
# Определяем родительский URL
|
||||
if depth > 1:
|
||||
parent_parts = parts[:-1]
|
||||
parent_path = base_path + '/'.join(parent_parts) + '/'
|
||||
# Восстанавливаем полный URL
|
||||
parent_url = f"{parsed.scheme}://{parsed.netloc}{parent_path}"
|
||||
return depth, parent_url
|
||||
|
||||
return depth, None
|
||||
|
||||
def build_hierarchy_map(pages_dir, json_file='documentation.json'):
|
||||
"""
|
||||
Строит карту иерархии: URL -> папка
|
||||
Использует documentation.json для получения URL
|
||||
"""
|
||||
pages_path = Path(pages_dir)
|
||||
url_to_folder = {}
|
||||
folder_to_url = {}
|
||||
|
||||
print("\n🔍 Загружаю documentation.json...")
|
||||
doc_entries = load_documentation_json(json_file)
|
||||
|
||||
# Строим карту: нормализованный title -> URL
|
||||
title_to_url = {}
|
||||
for entry in doc_entries:
|
||||
url = entry.get('url')
|
||||
title = entry.get('title', '')
|
||||
if url and title:
|
||||
normalized_title = normalize_title(title)
|
||||
title_to_url[normalized_title] = url
|
||||
|
||||
print(f" ✓ Построена карта: {len(title_to_url)} title -> URL")
|
||||
|
||||
print("\n🔍 Сопоставляю папки с URL...")
|
||||
|
||||
# Собираем информацию о всех папках
|
||||
all_folders = list(pages_path.rglob('page.html'))
|
||||
print(f" Найдено {len(all_folders)} папок с page.html")
|
||||
|
||||
matched = 0
|
||||
unmatched = 0
|
||||
|
||||
for html_file in sorted(all_folders):
|
||||
folder = html_file.parent
|
||||
folder_name = folder.name
|
||||
|
||||
# Извлекаем title из имени папки (формат: page_XXX_Title)
|
||||
match = re.match(r'page_\d+_(.+)', folder_name)
|
||||
if not match:
|
||||
continue
|
||||
|
||||
title_from_folder = match.group(1)
|
||||
|
||||
# Ищем соответствующий URL в documentation.json
|
||||
found_url = title_to_url.get(title_from_folder)
|
||||
|
||||
if found_url:
|
||||
url_to_folder[found_url] = folder
|
||||
folder_to_url[folder_name] = found_url
|
||||
matched += 1
|
||||
# print(f" ✓ {folder_name[:50]:50s} -> {found_url}")
|
||||
else:
|
||||
unmatched += 1
|
||||
print(f" ⚠️ {folder_name[:50]:50s} -> URL не найден в documentation.json")
|
||||
|
||||
print(f"\n📊 Сопоставлено папок: {matched}")
|
||||
print(f" Не сопоставлено: {unmatched}")
|
||||
return url_to_folder, folder_to_url
|
||||
|
||||
def reorganize_folders(pages_dir, json_file='documentation.json', dry_run=True):
|
||||
"""
|
||||
Реорганизует папки в иерархическую структуру
|
||||
|
||||
Args:
|
||||
pages_dir: Директория с папками страниц
|
||||
json_file: Путь к documentation.json
|
||||
dry_run: Если True - только показывает что будет сделано, не меняет файлы
|
||||
"""
|
||||
url_to_folder, folder_to_url = build_hierarchy_map(pages_dir, json_file)
|
||||
|
||||
# Строим иерархию: определяем для каждого URL его родителя
|
||||
hierarchy = {} # url -> (folder, parent_url, depth)
|
||||
|
||||
print("\n🔗 Определяю иерархические связи...")
|
||||
for url, folder in url_to_folder.items():
|
||||
depth, parent_url = get_url_depth_and_parent(url)
|
||||
hierarchy[url] = {
|
||||
'folder': folder,
|
||||
'parent_url': parent_url,
|
||||
'depth': depth,
|
||||
'children': []
|
||||
}
|
||||
# print(f" {folder.name[:40]:40s} depth={depth} parent={parent_url if parent_url else 'None'}")
|
||||
|
||||
# Заполняем список детей для каждого родителя
|
||||
for url, info in hierarchy.items():
|
||||
if info['parent_url'] and info['parent_url'] in hierarchy:
|
||||
hierarchy[info['parent_url']]['children'].append(url)
|
||||
|
||||
# Группируем по уровням (для удобства обработки)
|
||||
by_depth = {}
|
||||
for url, info in hierarchy.items():
|
||||
depth = info['depth']
|
||||
if depth not in by_depth:
|
||||
by_depth[depth] = []
|
||||
by_depth[depth].append((url, info))
|
||||
|
||||
print(f"\n📊 Распределение по уровням:")
|
||||
for depth in sorted(by_depth.keys()):
|
||||
print(f" Уровень {depth}: {len(by_depth[depth])} папок")
|
||||
|
||||
# Планируем перемещения (от глубоких к верхним, чтобы не было конфликтов)
|
||||
moves = []
|
||||
|
||||
print("\n📋 Планирую перемещения...")
|
||||
for depth in sorted(by_depth.keys(), reverse=True):
|
||||
for url, info in by_depth[depth]:
|
||||
parent_url = info['parent_url']
|
||||
|
||||
# Если есть родитель и он найден
|
||||
if parent_url and parent_url in url_to_folder:
|
||||
parent_folder = url_to_folder[parent_url]
|
||||
current_folder = info['folder']
|
||||
|
||||
# Новый путь: внутри родительской папки
|
||||
new_folder_path = parent_folder / current_folder.name
|
||||
|
||||
# Проверяем, нужно ли перемещать
|
||||
if current_folder.parent != parent_folder:
|
||||
moves.append({
|
||||
'from': current_folder,
|
||||
'to': new_folder_path,
|
||||
'url': url,
|
||||
'parent_url': parent_url
|
||||
})
|
||||
print(f" 📦 {current_folder.name[:40]:40s} -> {parent_folder.name}/{current_folder.name}")
|
||||
|
||||
print(f"\n📊 Всего перемещений: {len(moves)}")
|
||||
|
||||
if not moves:
|
||||
print("✅ Все папки уже в правильной иерархии!")
|
||||
return
|
||||
|
||||
if dry_run:
|
||||
print("\n⚠️ Режим DRY RUN - файлы не будут перемещены")
|
||||
print(" Запустите с --apply для применения изменений")
|
||||
return
|
||||
|
||||
# Применяем перемещения
|
||||
print("\n🚀 Применяю перемещения...")
|
||||
moved = 0
|
||||
errors = 0
|
||||
|
||||
for move in moves:
|
||||
try:
|
||||
src = move['from']
|
||||
dst = move['to']
|
||||
|
||||
# Проверяем что источник существует
|
||||
if not src.exists():
|
||||
print(f" ⚠️ Пропускаю (не существует): {src.name}")
|
||||
continue
|
||||
|
||||
# Проверяем что цель не существует
|
||||
if dst.exists():
|
||||
print(f" ⚠️ Пропускаю (уже существует): {dst}")
|
||||
continue
|
||||
|
||||
# Перемещаем
|
||||
shutil.move(str(src), str(dst))
|
||||
moved += 1
|
||||
print(f" ✓ Перемещено: {src.name} -> {dst.parent.name}/{dst.name}")
|
||||
|
||||
except Exception as e:
|
||||
errors += 1
|
||||
print(f" ❌ Ошибка при перемещении {move['from'].name}: {e}")
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"✅ Реорганизация завершена!")
|
||||
print(f" Перемещено: {moved}")
|
||||
print(f" Ошибок: {errors}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='Реорганизация папок в иерархическую структуру')
|
||||
parser.add_argument('--pages-dir', type=str, default='pages',
|
||||
help='Директория с папками страниц (по умолчанию: pages)')
|
||||
parser.add_argument('--json', type=str, default='documentation.json',
|
||||
help='Путь к documentation.json (по умолчанию: documentation.json)')
|
||||
parser.add_argument('--apply', action='store_true',
|
||||
help='Применить изменения (по умолчанию: только показать план)')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
print("🗂️ Реорганизация папок документации\n")
|
||||
reorganize_folders(args.pages_dir, args.json, dry_run=not args.apply)
|
||||
@@ -0,0 +1,16 @@
|
||||
# Основные зависимости для парсеров документации 1С:Предприятие.Элемент
|
||||
|
||||
# Работа с HTTP запросами
|
||||
requests>=2.31.0
|
||||
|
||||
# Парсинг HTML
|
||||
beautifulsoup4>=4.12.0
|
||||
|
||||
# Selenium для работы с JavaScript
|
||||
selenium>=4.15.0
|
||||
|
||||
# Автоматическая установка драйвера Chrome
|
||||
webdriver-manager>=4.0.0
|
||||
|
||||
# Дополнительные зависимости
|
||||
lxml>=4.9.0 # Быстрый парсер для BeautifulSoup
|
||||
Reference in New Issue
Block a user