Files
hotels/create_spb_processed_regex.py
Фёдор 684fada337 🚀 Full project sync: Hotels RAG & Audit System
 Major Features:
- Complete RAG system for hotel website analysis
- Hybrid audit with BGE-M3 embeddings + Natasha NER
- Universal horizontal Excel reports with dashboards
- Multi-region processing (SPb, Orel, Chukotka, Kamchatka)

📊 Completed Regions:
- Орловская область: 100% (36/36)
- Чукотский АО: 100% (4/4)
- г. Санкт-Петербург: 93% (893/960)
- Камчатский край: 87% (89/102)

🔧 Infrastructure:
- PostgreSQL with pgvector extension
- BGE-M3 embeddings API
- Browserless for web scraping
- N8N workflows for automation
- S3/Nextcloud file storage

📝 Documentation:
- Complete DB schemas
- API documentation
- Setup guides
- Status reports
2025-10-27 22:49:42 +03:00

269 lines
10 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
"""
Создание hotel_website_processed для Санкт-Петербурга
ЭТАП 1: Очистка HTML через регулярки + многопоточность (как в Ореле/Чукотке)
"""
import psycopg2
import psycopg2.extras
import logging
import re
import html as html_module
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
from typing import Dict, List, Any
from urllib.parse import unquote
# Настройка логирования
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('spb_processed_regex.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# Конфигурация БД
DB_CONFIG = {
'host': '147.45.189.234',
'port': 5432,
'database': 'default_db',
'user': 'gen_user',
'password': unquote('2~~9_%5EkVsU%3F2%5CS')
}
# Многопоточность
MAX_WORKERS = 10 # Количество потоков для обработки (как в Ореле)
class SpbProcessor:
def __init__(self):
self.conn = None
self.cur = None
def connect_db(self):
"""Подключение к БД"""
try:
self.conn = psycopg2.connect(**DB_CONFIG)
self.cur = self.conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
logger.info("✅ Подключение к БД установлено")
except Exception as e:
logger.error(f"❌ Ошибка подключения к БД: {e}")
raise
def close_db(self):
"""Закрытие соединения с БД"""
if self.cur:
self.cur.close()
if self.conn:
self.conn.close()
logger.info("🔌 Соединение с БД закрыто")
def clean_html_with_regex(self, html: str) -> str:
"""Очистка HTML через регулярки (как в Ореле)"""
if not html:
return ""
try:
# Удаляем script и style теги
text = re.sub(r'<script[^>]*>.*?</script>', ' ', html, flags=re.DOTALL | re.IGNORECASE)
text = re.sub(r'<style[^>]*>.*?</style>', ' ', text, flags=re.DOTALL | re.IGNORECASE)
# Удаляем все HTML теги
text = re.sub(r'<[^>]+>', ' ', text)
# Декодируем HTML entities
text = html_module.unescape(text)
# Убираем лишние пробелы и переносы строк
text = re.sub(r'\s+', ' ', text).strip()
return text
except Exception as e:
logger.error(f"❌ Ошибка очистки HTML: {e}")
return ""
def process_page(self, page_data: Dict[str, Any]) -> Dict[str, Any]:
"""Обработка одной страницы"""
try:
page_id = page_data['id']
url = page_data['url']
html = page_data['html']
hotel_id = page_data['hotel_id']
# Очищаем HTML
cleaned_text = self.clean_html_with_regex(html)
if len(cleaned_text) < 100:
return {
'success': False,
'page_id': page_id,
'error': 'Слишком короткий текст',
'hotel_id': hotel_id,
'url': url
}
return {
'success': True,
'page_id': page_id,
'hotel_id': hotel_id,
'url': url,
'cleaned_text': cleaned_text,
'length': len(cleaned_text)
}
except Exception as e:
return {
'success': False,
'page_id': page_data.get('id', 'unknown'),
'error': str(e),
'hotel_id': page_data.get('hotel_id', 'unknown'),
'url': page_data.get('url', 'unknown')
}
def process_hotel_pages(self, hotel_id: str) -> int:
"""Обработка всех страниц одного отеля (многопоточно)"""
try:
# Получаем HTML страницы отеля
self.cur.execute("""
SELECT id, url, html
FROM hotel_website_raw
WHERE hotel_id = %s
AND html IS NOT NULL
ORDER BY id
""", (hotel_id,))
pages = self.cur.fetchall()
if not pages:
logger.warning(f"⚠️ Нет HTML для отеля {hotel_id}")
return 0
logger.info(f"📄 Найдено {len(pages)} страниц для отеля")
# Подготавливаем данные для многопоточности
page_data_list = []
for page in pages:
page_data_list.append({
'id': page['id'],
'url': page['url'],
'html': page['html'],
'hotel_id': hotel_id
})
processed_count = 0
# Многопоточная обработка страниц
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
# Отправляем задачи
future_to_page = {
executor.submit(self.process_page, page_data): page_data
for page_data in page_data_list
}
# Обрабатываем результаты
for future in as_completed(future_to_page):
result = future.result()
if result['success']:
# Сохраняем в hotel_website_processed
self.cur.execute("""
INSERT INTO hotel_website_processed (hotel_id, url, cleaned_text)
VALUES (%s, %s, %s)
ON CONFLICT DO NOTHING
""", (result['hotel_id'], result['url'], result['cleaned_text']))
processed_count += 1
logger.info(f" ✅ Страница {result['page_id']}: {result['length']} символов")
else:
logger.warning(f" ⚠️ Страница {result['page_id']}: {result['error']}")
logger.info(f"✅ Отель обработан: {processed_count}/{len(pages)} страниц")
return processed_count
except Exception as e:
logger.error(f"❌ Ошибка обработки отеля {hotel_id}: {e}")
return 0
def get_hotels_to_process(self) -> List[str]:
"""Получаем список отелей для обработки"""
try:
# Получаем отели из СПб, у которых есть HTML но нет обработанного текста
self.cur.execute("""
SELECT DISTINCT hwr.hotel_id
FROM hotel_website_raw hwr
LEFT JOIN hotel_website_processed hwp ON hwr.hotel_id = hwp.hotel_id
WHERE hwr.hotel_id::text LIKE 'spb_%'
AND hwr.html IS NOT NULL
AND hwp.hotel_id IS NULL
ORDER BY hwr.hotel_id
""")
hotels = [row['hotel_id'] for row in self.cur.fetchall()]
logger.info(f"📊 Найдено {len(hotels)} отелей для обработки")
return hotels
except Exception as e:
logger.error(f"❌ Ошибка получения списка отелей: {e}")
return []
def run(self):
"""Основной процесс обработки"""
try:
logger.info("🚀 Запуск обработки СПб через регулярки + многопоточность")
# Подключаемся к БД
self.connect_db()
# Получаем список отелей
hotels = self.get_hotels_to_process()
if not hotels:
logger.info("✅ Нет отелей для обработки")
return
total_hotels = len(hotels)
processed_hotels = 0
total_pages = 0
logger.info(f"📊 Начинаем обработку {total_hotels} отелей")
# Обрабатываем каждый отель
for i, hotel_id in enumerate(hotels, 1):
logger.info(f"🏨 [{i}/{total_hotels}] Обработка отеля: {hotel_id}")
pages_count = self.process_hotel_pages(hotel_id)
total_pages += pages_count
processed_hotels += 1
# Коммитим каждые 10 отелей
if processed_hotels % 10 == 0:
self.conn.commit()
logger.info(f"💾 Сохранено {processed_hotels} отелей, {total_pages} страниц")
# Небольшая пауза между отелями
time.sleep(0.1)
# Финальный коммит
self.conn.commit()
logger.info(f"🎉 Обработка завершена!")
logger.info(f"📊 Статистика:")
logger.info(f" - Обработано отелей: {processed_hotels}/{total_hotels}")
logger.info(f" - Обработано страниц: {total_pages}")
except Exception as e:
logger.error(f"❌ Критическая ошибка: {e}")
if self.conn:
self.conn.rollback()
finally:
self.close_db()
def main():
"""Главная функция"""
processor = SpbProcessor()
processor.run()
if __name__ == "__main__":
main()