Files
hotels/check_rkn_registry.py
Фёдор 0cf3297290 Проект аудита отелей: основные скрипты и документация
- Краулеры: 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
2025-10-16 10:52:09 +03:00

321 lines
12 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
"""
Проверка отелей в реестре операторов персональных данных Роскомнадзора
Проверяет только отели с сайтами
"""
import asyncio
import psycopg2
from psycopg2.extras import RealDictCursor
from playwright.async_api import async_playwright
from urllib.parse import unquote
from datetime import datetime
import logging
import re
# Настройка логирования
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(f'rkn_check_{datetime.now().strftime("%Y%m%d_%H%M%S")}.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")
}
# Конфигурация
REQUEST_DELAY = 3 # Задержка между запросами (секунды)
PAGE_TIMEOUT = 30000
class RKNChecker:
"""Проверка в реестре РКН"""
def __init__(self):
self.db_conn = None
self.browser = None
self.page = None
def connect_db(self):
"""Подключение к БД"""
try:
self.db_conn = psycopg2.connect(**DB_CONFIG)
logger.info("✓ Подключено к PostgreSQL")
except Exception as e:
logger.error(f"✗ Ошибка подключения к БД: {e}")
raise
def close_db(self):
"""Закрытие соединения с БД"""
if self.db_conn:
self.db_conn.close()
async def init_browser(self):
"""Инициализация браузера"""
playwright = await async_playwright().start()
self.browser = await playwright.chromium.launch(headless=True)
self.page = await self.browser.new_page()
await self.page.set_viewport_size({"width": 1920, "height": 1080})
await self.page.set_extra_http_headers({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
})
logger.info("✓ Браузер инициализирован")
async def close_browser(self):
"""Закрытие браузера"""
if self.browser:
await self.browser.close()
async def check_inn_in_registry(self, inn: str) -> dict:
"""Проверка ИНН в реестре РКН"""
if not inn or inn == '-':
return {
'found': False,
'status': 'no_inn',
'message': 'ИНН не указан'
}
try:
# Формируем URL
url = f'https://pd.rkn.gov.ru/operators-registry/operators-list/?act=search&inn={inn}'
logger.info(f" 🔍 Проверка ИНН: {inn}")
# Задержка перед запросом
await asyncio.sleep(REQUEST_DELAY)
# Загружаем страницу
response = await self.page.goto(url, timeout=PAGE_TIMEOUT, wait_until='networkidle')
if response.status != 200:
return {
'found': False,
'status': 'error',
'message': f'HTTP {response.status}'
}
# Ждем загрузки
await asyncio.sleep(1)
# Получаем текст страницы
text = await self.page.evaluate('() => document.body.innerText')
# Проверяем наличие результатов
if 'Не найдено' in text or 'не найдено' in text.lower():
logger.info(f"Не найден в реестре")
return {
'found': False,
'status': 'not_found',
'message': 'Не найден в реестре РКН'
}
# Пытаемся извлечь данные
# Ищем регистрационный номер (разные форматы: 41-14-000746 или 10-0107355)
reg_number_match = re.search(r'(\d{2}-\d{2,4}-\d{6,7})', text)
reg_number = reg_number_match.group(1) if reg_number_match else None
# Ищем дату регистрации
date_match = re.search(r'Приказ.*?(\d{2}\.\d{2}\.\d{4})', text)
reg_date = date_match.group(1) if date_match else None
# Ищем название организации
org_match = re.search(r'(?:Общество|Индивидуальный предприниматель|Акционерное общество).*?(?=ИНН:|$)', text, re.IGNORECASE)
org_name = org_match.group(0).strip() if org_match else None
if reg_number:
logger.info(f" ✅ Найден: {reg_number} ({reg_date})")
return {
'found': True,
'status': 'found',
'reg_number': reg_number,
'reg_date': reg_date,
'org_name': org_name,
'message': f'Зарегистрирован: {reg_number}'
}
else:
logger.info(f" ⚠️ Страница загружена, но данные не распознаны")
return {
'found': None,
'status': 'unclear',
'message': 'Результат неясен'
}
except Exception as e:
logger.error(f" ✗ Ошибка проверки: {e}")
return {
'found': False,
'status': 'error',
'message': str(e)
}
def save_result(self, hotel_id: str, result: dict):
"""Сохранение результата в БД"""
try:
cur = self.db_conn.cursor()
cur.execute('''
UPDATE hotel_main
SET
rkn_registry_status = %s,
rkn_registry_number = %s,
rkn_registry_date = %s,
rkn_checked_at = %s
WHERE id = %s
''', (
result['status'],
result.get('reg_number'),
result.get('reg_date'),
datetime.now(),
hotel_id
))
self.db_conn.commit()
cur.close()
except Exception as e:
logger.error(f" ✗ Ошибка сохранения в БД: {e}")
self.db_conn.rollback()
async def process_hotels(self, region_name=None):
"""Обработка отелей"""
# Получаем отели с сайтами
cur = self.db_conn.cursor(cursor_factory=RealDictCursor)
where_clause = ""
params = []
if region_name:
where_clause = "AND h.region_name ILIKE %s"
params = [f'%{region_name}%']
query = f'''
SELECT DISTINCT h.id, h.full_name, h.owner_inn, h.website_address, h.region_name
FROM hotel_main h
WHERE h.owner_inn IS NOT NULL
AND h.owner_inn != ''
AND h.owner_inn != '-'
AND (h.rkn_checked_at IS NULL OR h.rkn_checked_at < NOW() - INTERVAL '30 days')
{where_clause}
ORDER BY h.region_name, h.full_name
'''
cur.execute(query, params)
hotels = cur.fetchall()
cur.close()
logger.info(f"\n{'='*70}")
logger.info(f"🏨 Отелей для проверки: {len(hotels)}")
logger.info(f"⏱️ Примерное время: {len(hotels) * REQUEST_DELAY / 60:.1f} минут")
logger.info(f"{'='*70}\n")
# Обрабатываем отели
results = {
'found': 0,
'not_found': 0,
'error': 0,
'unclear': 0,
'no_inn': 0
}
for i, hotel in enumerate(hotels, 1):
logger.info(f"\n[{i}/{len(hotels)}] {'='*50}")
logger.info(f"🏨 {hotel['full_name']}")
logger.info(f"📍 {hotel['region_name']}")
logger.info(f"🌐 {hotel['website_address']}")
logger.info(f"🔢 ИНН: {hotel['owner_inn']}")
# Проверяем в реестре
result = await self.check_inn_in_registry(hotel['owner_inn'])
# Сохраняем результат
self.save_result(hotel['id'], result)
# Обновляем статистику
if result['found'] == True:
results['found'] += 1
elif result['found'] == False:
if result['status'] == 'no_inn':
results['no_inn'] += 1
elif result['status'] == 'not_found':
results['not_found'] += 1
else:
results['error'] += 1
else:
results['unclear'] += 1
# Итоги
logger.info(f"\n{'='*70}")
logger.info("📊 ИТОГИ ПРОВЕРКИ:")
logger.info(f" ✅ Найдено в реестре: {results['found']}")
logger.info(f"Не найдено в реестре: {results['not_found']}")
logger.info(f" ⚠️ Ошибки: {results['error']}")
logger.info(f" ❓ Неясно: {results['unclear']}")
logger.info(f" 🔢 Нет ИНН: {results['no_inn']}")
logger.info(f"{'='*70}")
return results
async def main():
"""Основная функция"""
import sys
region = sys.argv[1] if len(sys.argv) > 1 else None
checker = RKNChecker()
try:
# Подключаемся к БД
checker.connect_db()
# Добавляем колонки для РКН (если их нет)
cur = checker.db_conn.cursor()
cur.execute('''
ALTER TABLE hotel_main
ADD COLUMN IF NOT EXISTS rkn_registry_status VARCHAR(50);
''')
cur.execute('''
ALTER TABLE hotel_main
ADD COLUMN IF NOT EXISTS rkn_registry_number VARCHAR(50);
''')
cur.execute('''
ALTER TABLE hotel_main
ADD COLUMN IF NOT EXISTS rkn_registry_date VARCHAR(20);
''')
cur.execute('''
ALTER TABLE hotel_main
ADD COLUMN IF NOT EXISTS rkn_checked_at TIMESTAMP;
''')
checker.db_conn.commit()
cur.close()
logger.info("✓ Колонки для РКН добавлены")
# Инициализируем браузер
await checker.init_browser()
# Обрабатываем отели
await checker.process_hotels(region)
except Exception as e:
logger.error(f"❌ Критическая ошибка: {e}")
finally:
await checker.close_browser()
checker.close_db()
if __name__ == "__main__":
asyncio.run(main())