2025-10-16 10:52:09 +03:00
|
|
|
|
# 🤖 Что делает краулер - пошаговый процесс
|
|
|
|
|
|
|
|
|
|
|
|
## 📋 КРАТКИЙ ОТВЕТ
|
|
|
|
|
|
|
|
|
|
|
|
Краулер делает **ТОЛЬКО парсинг и сохранение в БД**. Никаких эмбеддингов, векторизации или анализа!
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🔄 ПОЛНЫЙ ПРОЦЕСС (шаг за шагом)
|
|
|
|
|
|
|
|
|
|
|
|
### 1️⃣ **Получение списка отелей** (`get_unprocessed_hotels`)
|
|
|
|
|
|
```sql
|
|
|
|
|
|
SELECT id, full_name, region_name, website_address
|
|
|
|
|
|
FROM hotel_main
|
|
|
|
|
|
WHERE website_address IS NOT NULL
|
|
|
|
|
|
AND id NOT IN (SELECT hotel_id FROM hotel_website_processed)
|
|
|
|
|
|
ORDER BY id
|
|
|
|
|
|
LIMIT 50 -- пачками по 50
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Что делает:**
|
|
|
|
|
|
- Берёт отели с сайтами
|
|
|
|
|
|
- Исключает уже обработанные
|
|
|
|
|
|
- Обрабатывает пачками по 50 штук
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 2️⃣ **Краулинг сайта** (`crawl_hotel`)
|
|
|
|
|
|
|
|
|
|
|
|
#### 2.1. Запуск браузера Playwright
|
|
|
|
|
|
- Открывает headless браузер
|
|
|
|
|
|
- User-Agent: Mozilla/5.0 (Windows...)
|
|
|
|
|
|
- Параллельно: 5 браузеров (`MAX_CONCURRENT = 5`)
|
|
|
|
|
|
|
|
|
|
|
|
#### 2.2. Загрузка главной страницы
|
|
|
|
|
|
```python
|
|
|
|
|
|
await page.goto(website, wait_until='domcontentloaded', timeout=20000)
|
|
|
|
|
|
```
|
|
|
|
|
|
- Таймаут: 20 секунд
|
|
|
|
|
|
- Ждёт загрузки DOM
|
|
|
|
|
|
|
|
|
|
|
|
#### 2.3. Извлечение контента главной
|
|
|
|
|
|
```python
|
|
|
|
|
|
html = await page.content() # Сырой HTML
|
|
|
|
|
|
cleaned_text = TextCleaner.clean_html(html) # Очищенный текст
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**`TextCleaner.clean_html()` делает:**
|
|
|
|
|
|
- Удаляет `<script>`, `<style>`, `<meta>`, `<link>`, `<noscript>`
|
|
|
|
|
|
- Извлекает текст через BeautifulSoup
|
|
|
|
|
|
- Убирает лишние пробелы/переносы
|
|
|
|
|
|
- Возвращает чистый текст
|
|
|
|
|
|
|
|
|
|
|
|
#### 2.4. Сбор внутренних ссылок
|
|
|
|
|
|
```python
|
|
|
|
|
|
links = await page.evaluate('''() => {
|
|
|
|
|
|
return Array.from(document.querySelectorAll('a[href]'))
|
|
|
|
|
|
.map(a => a.href)
|
|
|
|
|
|
.filter(href => href && !href.startsWith('mailto:') && !href.startsWith('tel:'))
|
|
|
|
|
|
}''')
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Фильтрация:**
|
|
|
|
|
|
- Только внутренние ссылки (тот же домен)
|
|
|
|
|
|
- Исключает `mailto:`, `tel:`
|
|
|
|
|
|
- Убирает дубли
|
|
|
|
|
|
- **Лимит: 19 ссылок** (+ главная = 20 страниц)
|
|
|
|
|
|
|
|
|
|
|
|
#### 2.5. Обход внутренних страниц
|
|
|
|
|
|
```python
|
|
|
|
|
|
for link in internal_links[:19]: # Максимум 19 + главная = 20
|
|
|
|
|
|
page2 = await context.new_page()
|
|
|
|
|
|
await page2.goto(link, timeout=20000)
|
|
|
|
|
|
html2 = await page2.content()
|
|
|
|
|
|
text2 = TextCleaner.clean_html(html2)
|
|
|
|
|
|
# Сохраняем в pages_data
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Для каждой страницы:**
|
|
|
|
|
|
- Открывает новую вкладку
|
|
|
|
|
|
- Загружает страницу (20 сек таймаут)
|
|
|
|
|
|
- Извлекает HTML
|
|
|
|
|
|
- Очищает текст
|
|
|
|
|
|
- Добавляет в `pages_data[]`
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
### 3️⃣ **Сохранение в БД** (`save_to_db`)
|
|
|
|
|
|
|
|
|
|
|
|
#### 3.1. Метаданные → `hotel_website_meta`
|
|
|
|
|
|
```sql
|
|
|
|
|
|
INSERT INTO hotel_website_meta
|
|
|
|
|
|
(hotel_id, domain, main_url, pages_crawled, crawl_status, crawl_finished_at)
|
|
|
|
|
|
VALUES (...)
|
|
|
|
|
|
ON CONFLICT (hotel_id) DO UPDATE ...
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Сохраняет:**
|
|
|
|
|
|
- `hotel_id` - UUID отеля
|
|
|
|
|
|
- `domain` - Домен сайта
|
|
|
|
|
|
- `main_url` - Главный URL
|
|
|
|
|
|
- `pages_crawled` - Количество страниц
|
|
|
|
|
|
- `crawl_status` - 'completed'
|
|
|
|
|
|
- `crawl_finished_at` - Время завершения
|
|
|
|
|
|
|
|
|
|
|
|
#### 3.2. Сырой HTML → `hotel_website_raw`
|
|
|
|
|
|
```sql
|
|
|
|
|
|
INSERT INTO hotel_website_raw
|
|
|
|
|
|
(hotel_id, url, html, status_code, crawled_at)
|
|
|
|
|
|
VALUES (...)
|
|
|
|
|
|
ON CONFLICT (hotel_id, url) DO UPDATE ...
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Для КАЖДОЙ страницы сохраняет:**
|
|
|
|
|
|
- `hotel_id` - UUID отеля
|
|
|
|
|
|
- `url` - URL страницы
|
|
|
|
|
|
- `html` - **Полный сырой HTML**
|
|
|
|
|
|
- `status_code` - HTTP код (200, 404, etc)
|
|
|
|
|
|
- `crawled_at` - Время краулинга
|
|
|
|
|
|
|
|
|
|
|
|
#### 3.3. Очищенный текст → `hotel_website_processed`
|
|
|
|
|
|
```sql
|
|
|
|
|
|
INSERT INTO hotel_website_processed
|
|
|
|
|
|
(hotel_id, url, cleaned_text, processed_at)
|
|
|
|
|
|
VALUES (...)
|
|
|
|
|
|
ON CONFLICT (hotel_id, url) DO UPDATE ...
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Для КАЖДОЙ страницы сохраняет:**
|
|
|
|
|
|
- `hotel_id` - UUID отеля
|
|
|
|
|
|
- `url` - URL страницы
|
|
|
|
|
|
- `cleaned_text` - **Очищенный текст (без HTML тегов)**
|
|
|
|
|
|
- `processed_at` - Время обработки
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## ⚡ ПРОИЗВОДИТЕЛЬНОСТЬ
|
|
|
|
|
|
|
|
|
|
|
|
### Настройки:
|
|
|
|
|
|
```python
|
|
|
|
|
|
MAX_PAGES_PER_SITE = 20 # Максимум страниц с одного сайта
|
|
|
|
|
|
PAGE_TIMEOUT = 20000 # 20 секунд на загрузку страницы
|
|
|
|
|
|
MAX_CONCURRENT = 5 # 5 браузеров параллельно
|
|
|
|
|
|
BATCH_SIZE = 50 # Обрабатывать по 50 отелей
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### Время на 1 отель:
|
|
|
|
|
|
- **Быстрый сайт (1-5 страниц):** ~10-30 секунд
|
|
|
|
|
|
- **Средний сайт (10-15 страниц):** ~1-3 минуты
|
|
|
|
|
|
- **Большой сайт (20 страниц):** ~3-5 минут
|
|
|
|
|
|
- **Недоступный сайт:** ~20 секунд (таймаут)
|
|
|
|
|
|
|
|
|
|
|
|
### Скорость обработки:
|
|
|
|
|
|
- **Текущая:** ~50-100 отелей/день
|
|
|
|
|
|
- **Теоретическая:** ~400-500 отелей/день (если все сайты быстрые)
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## ❌ ЧТО КРАУЛЕР **НЕ ДЕЛАЕТ**
|
|
|
|
|
|
|
|
|
|
|
|
1. ❌ **НЕ создаёт эмбеддинги** (векторы для поиска)
|
|
|
|
|
|
2. ❌ **НЕ делает чанки** (разбивку на части)
|
|
|
|
|
|
3. ❌ **НЕ анализирует контент** (нет AI/NLP)
|
|
|
|
|
|
4. ❌ **НЕ извлекает структурированные данные** (телефоны, email, etc)
|
|
|
|
|
|
5. ❌ **НЕ проверяет критерии аудита**
|
|
|
|
|
|
6. ❌ **НЕ делает скриншоты**
|
|
|
|
|
|
7. ❌ **НЕ проверяет SSL/сертификаты**
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 📊 ЧТО В ИТОГЕ В БД
|
|
|
|
|
|
|
|
|
|
|
|
### После краулинга 1 отеля с 15 страницами:
|
|
|
|
|
|
|
|
|
|
|
|
**`hotel_website_meta`:** 1 запись
|
|
|
|
|
|
- Метаданные: домен, количество страниц, статус
|
|
|
|
|
|
|
|
|
|
|
|
**`hotel_website_raw`:** 15 записей
|
|
|
|
|
|
- 15 × полный HTML (может быть 100-500 KB каждый)
|
|
|
|
|
|
- Всего: ~1-7 MB сырых данных
|
|
|
|
|
|
|
|
|
|
|
|
**`hotel_website_processed`:** 15 записей
|
|
|
|
|
|
- 15 × очищенный текст (обычно 1-10 KB каждый)
|
|
|
|
|
|
- Всего: ~15-150 KB текста
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 🚀 СЛЕДУЮЩИЕ ЭТАПЫ (отдельно от краулера)
|
|
|
|
|
|
|
|
|
|
|
|
После того как краулер заполнит БД, **ОТДЕЛЬНО** нужно будет:
|
|
|
|
|
|
|
|
|
|
|
|
1. **Создать эмбеддинги** (`process_all_hotels_embeddings.py`)
|
|
|
|
|
|
- Разбить текст на чанки
|
|
|
|
|
|
- Создать векторы через BGE-M3
|
|
|
|
|
|
- Сохранить в `hotel_website_chunks`
|
|
|
|
|
|
|
|
|
|
|
|
2. **Запустить аудит** (`hybrid_audit_chukotka.py`)
|
|
|
|
|
|
- Семантический поиск
|
|
|
|
|
|
- Регулярные выражения
|
|
|
|
|
|
- Natasha NER
|
|
|
|
|
|
- Сохранить в `hotel_audit_results`
|
|
|
|
|
|
|
|
|
|
|
|
3. **Интеграция с n8n**
|
|
|
|
|
|
- AI Agent для анализа
|
|
|
|
|
|
- Автоматизация проверок
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 💡 ВЫВОД
|
|
|
|
|
|
|
|
|
|
|
|
**Краулер = простой парсер:**
|
|
|
|
|
|
- Открывает сайты
|
|
|
|
|
|
- Скачивает HTML
|
|
|
|
|
|
- Чистит текст
|
|
|
|
|
|
- Кладёт в БД
|
|
|
|
|
|
|
|
|
|
|
|
**Всё остальное (AI, векторы, аудит) - это отдельные процессы!**
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
**Дата:** 2025-10-14
|
|
|
|
|
|
**Автор:** Фёдор + AI Assistant
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-10-27 22:49:42 +03:00
|
|
|
|
|