- Краулеры: smart_crawler.py, regional_crawler.py - Аудит: audit_orel_to_excel.py, audit_chukotka_to_excel.py - РКН проверка: check_rkn_registry.py, recheck_unclear_rkn.py - Отчёты: create_orel_horizontal_report.py - Обработка: process_all_hotels_embeddings.py - Документация: README.md, DB_SCHEMA_REFERENCE.md
202 lines
6.7 KiB
Python
202 lines
6.7 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Обновление статусов доступности сайтов на основе результатов краулинга
|
||
"""
|
||
|
||
import psycopg2
|
||
from urllib.parse import unquote
|
||
import re
|
||
|
||
# Конфигурация БД
|
||
DB_CONFIG = {
|
||
'host': "147.45.189.234",
|
||
'port': 5432,
|
||
'database': "default_db",
|
||
'user': "gen_user",
|
||
'password': unquote("2~~9_%5EkVsU%3F2%5CS")
|
||
}
|
||
|
||
def update_website_statuses(log_file_path):
|
||
"""Обновление статусов на основе лог-файла краулера"""
|
||
|
||
conn = psycopg2.connect(**DB_CONFIG)
|
||
cur = conn.cursor()
|
||
|
||
# Паттерны для определения типов ошибок
|
||
error_patterns = {
|
||
'timeout': r'Timeout \d+ms exceeded',
|
||
'connection_refused': r'ERR_CONNECTION_REFUSED',
|
||
'dns_error': r'ERR_NAME_NOT_RESOLVED',
|
||
'ssl_error': r'ERR_SSL_PROTOCOL_ERROR|ERR_CERT_DATE_INVALID',
|
||
'http_error': r'Ошибка загрузки: (403|404|500)',
|
||
'invalid_url': r'Cannot navigate to invalid URL'
|
||
}
|
||
|
||
# Читаем лог-файл
|
||
with open(log_file_path, 'r', encoding='utf-8') as f:
|
||
log_content = f.read()
|
||
|
||
# Находим все отели и их ошибки
|
||
hotel_pattern = r'🏨 «(.+?)»\n.*?🌐 (.+?)\n'
|
||
hotels = re.findall(hotel_pattern, log_content, re.DOTALL)
|
||
|
||
stats = {
|
||
'accessible': 0,
|
||
'timeout': 0,
|
||
'connection_refused': 0,
|
||
'dns_error': 0,
|
||
'ssl_error': 0,
|
||
'http_error': 0,
|
||
'invalid_url': 0,
|
||
'not_checked': 0
|
||
}
|
||
|
||
for hotel_name, website in hotels:
|
||
# Нормализуем URL
|
||
website = website.strip()
|
||
if not website.startswith(('http://', 'https://')):
|
||
website = 'https://' + website
|
||
|
||
# Ищем секцию этого отеля в логе
|
||
hotel_section_pattern = f'🏨 «{re.escape(hotel_name)}».*?(?=🏨 «|======================================================================\\n📊 ИТОГИ:)'
|
||
hotel_section_match = re.search(hotel_section_pattern, log_content, re.DOTALL)
|
||
|
||
if not hotel_section_match:
|
||
continue
|
||
|
||
hotel_section = hotel_section_match.group(0)
|
||
|
||
# Определяем статус
|
||
status = 'not_checked'
|
||
|
||
# Проверяем на успешность
|
||
if '✓ Спарсено' in hotel_section and '✓ Сохранено' in hotel_section:
|
||
status = 'accessible'
|
||
stats['accessible'] += 1
|
||
else:
|
||
# Проверяем типы ошибок
|
||
for error_type, pattern in error_patterns.items():
|
||
if re.search(pattern, hotel_section):
|
||
status = error_type
|
||
stats[error_type] += 1
|
||
break
|
||
|
||
if status == 'not_checked':
|
||
stats['not_checked'] += 1
|
||
|
||
# Обновляем статус в БД
|
||
try:
|
||
cur.execute('''
|
||
UPDATE hotel_main
|
||
SET website_status = %s
|
||
WHERE website_address LIKE %s
|
||
OR website_address LIKE %s
|
||
OR website_address LIKE %s
|
||
''', (status, f'%{website.replace("https://", "").replace("http://", "").split("/")[0]}%',
|
||
f'%{website}%',
|
||
website.replace("https://", "").replace("http://", "").split("/")[0]))
|
||
|
||
except Exception as e:
|
||
print(f"Ошибка обновления для {hotel_name}: {e}")
|
||
|
||
conn.commit()
|
||
cur.close()
|
||
conn.close()
|
||
|
||
return stats
|
||
|
||
|
||
def generate_report(region_name='Камчатский край'):
|
||
"""Генерация отчета по доступности сайтов"""
|
||
|
||
conn = psycopg2.connect(**DB_CONFIG)
|
||
cur = conn.cursor()
|
||
|
||
# Получаем статистику
|
||
cur.execute('''
|
||
SELECT
|
||
website_status,
|
||
COUNT(*) as count,
|
||
ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER(), 2) as percentage
|
||
FROM hotel_main
|
||
WHERE region_name ILIKE %s
|
||
GROUP BY website_status
|
||
ORDER BY count DESC
|
||
''', (f'%{region_name}%',))
|
||
|
||
stats = cur.fetchall()
|
||
|
||
print(f"\n{'='*80}")
|
||
print(f"📊 ОТЧЕТ О ДОСТУПНОСТИ САЙТОВ: {region_name}")
|
||
print(f"{'='*80}\n")
|
||
|
||
status_labels = {
|
||
'accessible': '✅ Сайт доступен',
|
||
'no_website': '❌ Сайт отсутствует',
|
||
'timeout': '⏱️ Таймаут (сайт медленный)',
|
||
'connection_refused': '🚫 Соединение отклонено',
|
||
'dns_error': '🔍 DNS ошибка (домен не найден)',
|
||
'ssl_error': '🔒 SSL ошибка (проблема с сертификатом)',
|
||
'http_error': '⚠️ HTTP ошибка (403/404/500)',
|
||
'invalid_url': '❓ Неверный URL',
|
||
'not_checked': '⏳ Не проверено'
|
||
}
|
||
|
||
for status, count, percentage in stats:
|
||
label = status_labels.get(status, status)
|
||
print(f"{label:45} {count:5} ({percentage:5.2f}%)")
|
||
|
||
# Получаем список недоступных отелей
|
||
print(f"\n{'='*80}")
|
||
print("🔴 ОТЕЛИ С НЕДОСТУПНЫМИ САЙТАМИ:")
|
||
print(f"{'='*80}\n")
|
||
|
||
cur.execute('''
|
||
SELECT full_name, website_address, website_status
|
||
FROM hotel_main
|
||
WHERE region_name ILIKE %s
|
||
AND website_status NOT IN ('accessible', 'no_website', 'not_checked')
|
||
ORDER BY website_status, full_name
|
||
LIMIT 20
|
||
''', (f'%{region_name}%',))
|
||
|
||
problematic = cur.fetchall()
|
||
|
||
for name, website, status in problematic:
|
||
status_icon = {
|
||
'timeout': '⏱️',
|
||
'connection_refused': '🚫',
|
||
'dns_error': '🔍',
|
||
'ssl_error': '🔒',
|
||
'http_error': '⚠️',
|
||
'invalid_url': '❓'
|
||
}.get(status, '❓')
|
||
|
||
print(f"{status_icon} {name}")
|
||
print(f" 🌐 {website}")
|
||
print(f" 📋 Статус: {status}")
|
||
print()
|
||
|
||
cur.close()
|
||
conn.close()
|
||
|
||
|
||
if __name__ == "__main__":
|
||
import sys
|
||
|
||
if len(sys.argv) > 1:
|
||
log_file = sys.argv[1]
|
||
print(f"📖 Обработка лог-файла: {log_file}")
|
||
stats = update_website_statuses(log_file)
|
||
|
||
print("\n📊 СТАТИСТИКА ОБНОВЛЕНИЯ:")
|
||
for status, count in stats.items():
|
||
print(f" {status}: {count}")
|
||
|
||
# Генерируем отчет
|
||
generate_report('Камчатский край')
|
||
|
||
|
||
|
||
|