1
0
mirror of https://github.com/vanyagoncharov/parser_1cmycloud.git synced 2026-06-19 17:18:10 +02:00

Добавление в GIT

This commit is contained in:
vanyagoncharov
2026-01-13 10:31:04 +03:00
committed by GitHub
commit 6a7085a2d7
7 changed files with 4160 additions and 0 deletions
+159
View File
@@ -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
```
+98
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+1822
View File
File diff suppressed because it is too large Load Diff
+164
View File
@@ -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)
+272
View File
@@ -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)
+16
View File
@@ -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