1
0
Files
ollama/rag/2_html_to_md.py
Anthony Axenov ed123da101 Мелочи по rag
- переименована папка
- написан новый скрипт quickstart
- отказ от requirements.txt
- добавлен забытый и актуализированный README
2025-08-19 15:42:09 +08:00

326 lines
11 KiB
Python
Raw 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
"""
RAG System for Local Ollama
Конвертирует html-файлы в markdown, очищая от лишней разметки
Скрипт сгенерирован claude-sonnet-4
"""
import os
import re
import json
from bs4 import BeautifulSoup
from pathlib import Path
def clean_confluence_html(soup):
"""
Удаляет Confluence-специфичные элементы и очищает HTML.
"""
# Удаляем Confluence макросы (structured-macro)
for macro in soup.find_all('ac:structured-macro'):
macro_name = macro.get('ac:name', '')
# Сохраняем содержимое некоторых макросов
if macro_name == 'note':
# Преобразуем заметки в блоки внимания
rich_text = macro.find('ac:rich-text-body')
if rich_text:
note_content = rich_text.get_text(strip=True)
note_tag = soup.new_tag('div', class_='note')
note_tag.string = f"📝 **Примечание:** {note_content}"
macro.replace_with(note_tag)
else:
macro.decompose()
elif macro_name == 'toc':
# Заменяем TOC на текст
toc_tag = soup.new_tag('div')
toc_tag.string = "**Содержание** (автогенерируется)"
macro.replace_with(toc_tag)
elif macro_name == 'drawio':
# Заменяем диаграммы на заглушку
diagram_name = macro.find('ac:parameter', {'ac:name': 'diagramName'})
if diagram_name:
diagram_text = diagram_name.get_text()
else:
diagram_text = 'Диаграмма'
diagram_tag = soup.new_tag('div')
diagram_tag.string = f"🖼️ **Диаграмма:** {diagram_text}"
macro.replace_with(diagram_tag)
else:
# Удаляем остальные макросы
macro.decompose()
# Удаляем другие Confluence элементы
for element in soup.find_all(True):
if element.name and element.name.startswith('ac:'):
element.decompose()
return soup
def convert_table_to_markdown(table):
"""
Конвертирует HTML таблицу в Markdown формат.
"""
rows = table.find_all('tr')
if not rows:
return ""
markdown_lines = []
# Обработка первой строки как заголовка
first_row = rows[0]
header_cells = first_row.find_all(['th', 'td'])
if not header_cells:
return ""
# Заголовок таблицы
header_line = "|"
separator_line = "|"
for cell in header_cells:
# Получаем текст и очищаем его
cell_text = cell.get_text(separator=' ', strip=True)
cell_text = re.sub(r'\s+', ' ', cell_text) # Заменяем множественные пробелы
cell_text = cell_text.replace('|', '\\|') # Экранируем pipe символы
header_line += f" {cell_text} |"
separator_line += " --- |"
markdown_lines.append(header_line)
markdown_lines.append(separator_line)
# Обработка остальных строк
for row in rows[1:]:
data_cells = row.find_all(['td', 'th'])
if not data_cells:
continue
data_line = "|"
for i, cell in enumerate(data_cells):
if i >= len(header_cells): # Не больше столбцов чем в заголовке
break
cell_text = cell.get_text(separator=' ', strip=True)
cell_text = re.sub(r'\s+', ' ', cell_text)
cell_text = cell_text.replace('|', '\\|')
data_line += f" {cell_text} |"
# Дополняем недостающие столбцы
missing_cols = len(header_cells) - len(data_cells)
for _ in range(missing_cols):
data_line += " |"
markdown_lines.append(data_line)
return "\n".join(markdown_lines)
def extract_json_blocks(soup):
"""
Извлекает и форматирует JSON блоки.
"""
json_blocks = []
# Ищем потенциальные JSON блоки в pre, code и script тегах
for element in soup.find_all(['pre', 'code', 'script']):
text_content = element.get_text(strip=True)
# Простая проверка на JSON
if text_content and (
(text_content.startswith('{') and text_content.endswith('}')) or
(text_content.startswith('[') and text_content.endswith(']'))
):
try:
# Пытаемся парсить как JSON
json_data = json.loads(text_content)
formatted_json = json.dumps(json_data, indent=2, ensure_ascii=False)
# Заменяем элемент на форматированный JSON блок
json_tag = soup.new_tag('pre')
json_tag.string = f"```json\n{formatted_json}\n```"
element.replace_with(json_tag)
json_blocks.append(formatted_json)
except json.JSONDecodeError:
# Если не JSON, оставляем как code block
if element.name in ['pre', 'code']:
element.string = f"```\n{text_content}\n```"
return json_blocks
def html_to_markdown(html_content):
"""
Основная функция конвертации HTML в Markdown.
"""
soup = BeautifulSoup(html_content, 'html.parser')
# Удаляем скрипты и стили
for element in soup(['script', 'style']):
element.decompose()
# Очищаем Confluence элементы
soup = clean_confluence_html(soup)
# Извлекаем JSON блоки
json_blocks = extract_json_blocks(soup)
# Конвертируем таблицы
for table in soup.find_all('table'):
markdown_table = convert_table_to_markdown(table)
if markdown_table:
# Заменяем таблицу на Markdown
table_div = soup.new_tag('div', class_='markdown-table')
table_div.string = f"\n{markdown_table}\n"
table.replace_with(table_div)
# Обработка заголовков
for level in range(1, 7):
for header in soup.find_all(f'h{level}'):
header_text = header.get_text(strip=True)
markdown_header = '#' * level + ' ' + header_text
header.string = markdown_header
# Обработка списков
for ul in soup.find_all('ul'):
list_items = ul.find_all('li', recursive=False)
if list_items:
markdown_list = []
for li in list_items:
item_text = li.get_text(strip=True)
markdown_list.append(f"- {item_text}")
ul.string = '\n'.join(markdown_list)
for ol in soup.find_all('ol'):
list_items = ol.find_all('li', recursive=False)
if list_items:
markdown_list = []
for i, li in enumerate(list_items, 1):
item_text = li.get_text(strip=True)
markdown_list.append(f"{i}. {item_text}")
ol.string = '\n'.join(markdown_list)
# Обработка жирного и курсивного текста
for strong in soup.find_all(['strong', 'b']):
text = strong.get_text()
strong.string = f"**{text}**"
for em in soup.find_all(['em', 'i']):
text = em.get_text()
em.string = f"*{text}*"
# Получаем финальный текст
text = soup.get_text(separator='\n', strip=True)
# Постобработка
lines = []
for line in text.split('\n'):
line = line.strip()
if line:
lines.append(line)
# Убираем лишние пустые строки
result_lines = []
prev_empty = False
for line in lines:
if not line:
if not prev_empty:
result_lines.append('')
prev_empty = True
else:
result_lines.append(line)
prev_empty = False
return '\n'.join(result_lines)
def process_files(input_dir, output_dir):
"""
Обрабатывает все HTML-файлы в директории.
"""
input_path = Path(input_dir)
output_path = Path(output_dir)
if not input_path.exists():
print(f"❌ Директория {input_dir} не найдена")
return
# Создаем выходную директорию
output_path.mkdir(parents=True, exist_ok=True)
html_files = list(input_path.glob('*.html'))
if not html_files:
print(f"❌ HTML файлы не найдены в {input_dir}")
return
print(f"📁 Найдено {len(html_files)} HTML файлов")
successful = 0
failed = 0
failed_files = []
for html_file in html_files:
print(f"🔄 Обработка: {html_file.name}")
try:
# Читаем HTML файл
with open(html_file, 'r', encoding='utf-8') as f:
html_content = f.read()
# Проверяем, что файл не пустой
if not html_content.strip():
print(f"⚠️ Пропущен: {html_file.name} (пустой файл)")
continue
# Конвертируем в Markdown
markdown_content = html_to_markdown(html_content)
# Проверяем результат конвертации
if not markdown_content.strip():
print(f"⚠️ Предупреждение: {html_file.name} - результат конвертации пустой")
# Сохраняем результат
md_filename = html_file.stem + '.md'
md_filepath = output_path / md_filename
with open(md_filepath, 'w', encoding='utf-8') as f:
f.write(markdown_content)
print(f"✅ Сохранено: {md_filename}")
successful += 1
except UnicodeDecodeError as e:
print(f"❌ Ошибка кодировки в {html_file.name}: {str(e)}")
failed += 1
failed_files.append((html_file.name, f"Ошибка кодировки: {str(e)}"))
except Exception as e:
print(f"❌ Ошибка при обработке {html_file.name}: {str(e)}")
failed += 1
failed_files.append((html_file.name, str(e)))
print(f"\n📊 Результат:")
print(f"✅ Успешно обработано: {successful}")
print(f"❌ Ошибок: {failed}")
if failed_files:
print(f"\n📋 Список файлов с ошибками:")
for filename, error in failed_files:
print(f"{filename}: {error}")
print(f"📂 Результаты сохранены в: {output_dir}")
if __name__ == "__main__":
input_directory = "./input_html"
output_directory = "./output_md"
print("🚀 Запуск конвертера HTML → Markdown")
print("=" * 50)
process_files(input_directory, output_directory)