Files
hotels/create_spb_processed_regex.py

269 lines
10 KiB
Python
Raw Normal View History

#!/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()