docs(autopodbor): спека «свой канал 2ГИС+Яндекс на виртуалке» (замена xf4)

Дизайн утверждён владельцем в брейншторме: рендер своим браузером с
маскировкой + ротация RU-прокси вместо платного xf4, чистый разбор 2ГИС
через перехват встроенного JSON (убирает трэш), постоянный браузер,
всё на выделенной виртуалке (не на боевом портале), за флагом self_render
с откатом на xf4. Планка приёмки — фирма-в-фирму с живым 2ГИС.
UI/кнопки/функции не трогаются. Замер: 25 мин xf4 → 9 мин свой рендер.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-07-05 17:17:11 +03:00
parent 5c8b344bcf
commit a2e5fdd7fd
@@ -0,0 +1,135 @@
# Свой канал 2ГИС+Яндекс на выделенной виртуалке — дизайн
**Дата:** 2026-07-05 · **Ветка:** `worktree-avtopodbor` (НЕ прод) · **Статус:** дизайн утверждён владельцем, готов к плану.
## 1. Цель и мотивация
Сбор конкурентов (шаг 1) и изучение (шаг 2) сейчас тянут данные 2ГИС через платный внешний сервис **xf4.ru**. Замер 05.07.2026 на реальных данных владельца (Красноярск, займы под залог авто):
| | xf4 (сейчас) | наш рендер (тест) |
|---|---|---|
| Полный шаг 1 | **25 мин** | **9 мин** |
| 2ГИС-листинг | 264с / 127 фирм | 53с / 51 фирма |
| Вскрытие карточек 2ГИС | 967с (64% всего времени) | часть 340с |
| Деньги за прогон | ~50–75 ₽ (при потоке — тысячи ₽/день) | ~0 (только ИИ) |
**Тормоз — сам сервис xf4:** лимит ~3–4 запроса/ключ (429 выше), рендер 20–60с/страница. Ключами не уравнять: 2ГИС-листинг (264с, ключами не ускоряется) один больше всей Яндекс-части (194с).
**Проверено вживую:**
- наш локальный Playwright с маскировкой (скрытый `webdriver` + свежий Chrome UA + флаг `AutomationControlled` + проскок заглушки «обновите браузер») **пробивает 2ГИС** — 600–680 КБ реальных данных, живые телефоны;
- залп 28 карточек по 5 параллельно с одного IP — **100% данные, IP выдержал** (баг был в тесте, не в IP);
- голый Playwright 2ГИС **блокирует** (капча/заглушка) — маскировка обязательна.
**Цель:** заменить xf4 на свой рендер на выделенной виртуалке — **быстро и качественно**, с ротацией прокси, за флагом, с откатом на xf4.
## 2. Планка приёмки (definition of done для включения флага)
На тест-клиенте новый канал обязан:
1. Находить фирм **не меньше**, чем xf4 (эталон ~127 для тестового запроса), а не 51.
2. **Ноль мусора** — только релевантные фирмы (никаких «Бургер Кинг», парковок, табачных из соседних зданий).
3. Телефоны/сайты/названия — **корректные, собственные данные 2ГИС**.
4. Ручная сверка **фирма-в-фирму с живым 2ГИС** по нескольким фирмам — совпадает.
Флаг включаем **только после** этой сверки владельцем.
## 3. Границы — что НЕ меняется (жёстко)
**UI, кнопки, функции, экраны автоподбора НЕ трогаются.** Правится только слой добычи сырых данных.
Остаются как есть: экраны «Конкурентное поле» / «Предложения» / «Источники в работе»; кнопки «Собрать конкурентов», «Дайте источники», «В источники →», «Изменить», «Удалить», «Добавить вручную», окно слияния «останется», «История слияний», «Вернуть карточку», перенос выбранных, поиск дублей; логика списания денег, дедуп, склейка, похожесть-%, метки ✓/🎭, шаги 1/2. Клиент работает через тот же портал Лидерра, разницы в интерфейсе не видит.
## 4. Архитектура (вариант A — весь автоподбор на виртуалке)
```
Клиент → Портал Лидерра → задача в очередь Redis (autopodbor-find / autopodbor-study)
ВИРТУАЛКА = второй рабочий автоподбора
├─ queue:work --queue=autopodbor-find / autopodbor-study
├─ Рисовалка: постоянный браузер + маскировка + прокси-ротация
├─ 2ГИС: перехват встроенного JSON → чистые фирмы
├─ Яндекс: та же маскировка + прокси (разбор прежний)
└─ пишет результат в ту же базу (Managed PG)
Клиент видит результат в портале
```
- Виртуалка запускает воркеры автоподбора и делает всю browser-работу; браузерный шторм физически не на боевом портале.
- Разнесённые очереди `autopodbor-find` / `autopodbor-study` (уже в коде) позволяют при желании развести шаги по разным воркерам/виртуалкам.
- Всё за флагом `self_render`; xf4-путь остаётся целым запасным.
## 5. Компоненты
### 5.1. Рисовалка (render engine)
- **Один браузер держим открытым**; на каждую страницу — отдельный лёгкий **контекст** (свои куки + свой прокси), отрисовали → закрыли контекст, браузер живёт. Убирает трату ~1–2с на запуск браузера × сотни карточек.
- **Маскировка** (проверена): скрытый `navigator.webdriver`, свежий Chrome UA, `--disable-blink-features=AutomationControlled`, `ru-RU`, реалистичный viewport; проскок заглушки «обновите браузер» (клик «перейти в 2ГИС»).
- **Прокси на каждый контекст** — из пула, round-robin.
- **Параллельность** — настройка под память виртуалки (~300–400 МБ на контекст). Слабая машина — 4, мощная — 16–30.
- **Ошибки:** тайм-аут/редкий блок → **повтор через другой прокси** (1–2 раза) → не вышло → пропуск карточки (как сейчас, поток не падает). Падение браузера → перезапуск.
### 5.2. 2ГИС через JSON (чистые данные)
- При загрузке страницы 2ГИС сам подтягивает **встроенные пакеты данных (JSON)** — его собственный структурированный список фирм (название, адрес, телефоны, сайт). **Перехватываем эти ответы** (Playwright видит сетевые ответы страницы) и читаем готовые данные, а не рисованный HTML.
- Новый разборщик читает JSON → чистый список фирм. Старый HTML-разборщик 2ГИС **остаётся для xf4-пути**, не трогается.
- **Первый шаг стройки:** поймать реальный JSON живого 2ГИС (листинг + карточка), зафиксировать формат как тест-фикстуру, на нём написать разборщик по TDD.
### 5.3. Яндекс
- Уже рисуется своим браузером и работает без мусора → **разбор не переделываем**. Добавляем только **прокси-ротацию** и перенос на виртуалку.
### 5.4. Прокси-слой
- Пул адресов в настройке (`.env`/config), round-robin per-context, одинаково для 2ГИС и Яндекса. **Провайдер-агностично** — любой сервис резидентных/мобильных RU-прокси (кандидаты: ProxyGRAM, Proxy.Market, Proxys.io, MangoProxy). Ключи только в `.env`, в гит не идут.
### 5.5. Флаг и откат
- Флаг `autopodbor.self_render` (env, по умолчанию ВЫКЛ). ВКЛ → 2ГИС+Яндекс через свой рендер+прокси. ВЫКЛ → текущий путь через xf4. Мгновенный откат тумблером.
### 5.6. Выкат на виртуалку
- Виртуалка = второй app-узел: копия приложения, доступ к Redis и Managed PG (внутренняя сеть облака), node+Playwright+Chromium, пул прокси в `.env`, воркеры `autopodbor-find`/`autopodbor-study`. Параллельность контекстов — по памяти виртуалки. Выкат через `prod-deploy-validator`.
## 6. Обработка ошибок
| Ситуация | Поведение |
|---|---|
| Тайм-аут/пустая страница | повтор через другой прокси ×1–2 → пропуск карточки |
| Заглушка «обновите браузер» | автоклик «перейти в 2ГИС» |
| Блок по IP | смена прокси (ротация) |
| Падение браузера | перезапуск постоянного браузера |
| Прокси недоступен | следующий из пула; пул пуст → лог + деградация (как xfetch без ключа) |
| 2ГИС сменил формат JSON | разбор пуст → лог; фикстуры-тесты ловят регресс |
## 7. Тестирование (TDD)
- **Разборщик 2ГИС** — юнит-тесты на **реальных сохранённых JSON-фикстурах** (листинг + карточка): фирма-в-фирму, телефоны, отсев мусора. По TDD.
- **Рисовалка** — интеграция: отдаёт данные с тест-прокси; маскировка/проскок работают.
- **Прокси-ротация** — юнит: раскидывает контексты по пулу по кругу.
- **Приёмка** — полный прогон на деве + ручная сверка «фирма-в-фирму» с живым 2ГИС (владелец).
- **Регресс:** все **434 текущих теста автоподбора зелёные** (xf4-путь не тронут).
## 8. Фазы стройки (каждая проверяема)
1. **Чистый разбор 2ГИС.** Поймать реальный JSON → разборщик по TDD. *Без виртуалки/прокси.*
2. **Рисовалка.** Постоянный браузер + маскировка + поддержка прокси. *Локально.*
3. **Сборка за флагом + приёмка.** Полный прогон на деве, сверка «фирма-в-фирму». *Ещё на деве, флаг ВЫКЛ по умолчанию.*
4. **Выкат на виртуалку.** Провижн виртуалки + пул прокси + `prod-deploy-validator` GO/NO-GO.
## 9. Требования к железу
- Ограничитель — **память** (~300–400 МБ на параллельный контекст) + процессор (~0,5–1 ядро/контекст).
- **Формула:** параллельных контекстов ≈ свободная память ÷ 350 МБ (и не больше ~1,5× ядер).
| Свободно памяти | Параллельно | Один прогон шаг 1 |
|---|---|---|
| 2,5 ГБ (dev сейчас) | 4 | ~9 мин |
| 6 ГБ | ~16 | ~34 мин |
| 12 ГБ | ~30 | ~2 мин |
- **Рекомендация для боевого:** выделенная виртуалка **8–16 ГБ свободной памяти + 4–8 ядер** под автоподбор → 16–30 контекстов → прогон 2–4 мин или 2–3 клиента разом.
- Поток (владелец): 5–6 клиентов/день, шаг 1 редкий (~1,2 прогона/клиент), шаг 2 частый но лёгкий (~70 конкурентов на всех), не одновременно — под это 8 ГБ виртуалки с запасом.
## 10. Открытые вопросы (решаются на этапе внедрения, не блокируют стройку кода)
- Конкретный провайдер прокси и бюджет (закупка; система агностична).
- Точный размер виртуалки (по факту нагрузки; старт с 8 ГБ).
- Провижн виртуалки (образ/деплой) — ops-задача фазы 4.
## 11. Артефакты, уже готовые в ветке
- Рабочий рецепт маскировки — `app/scripts/render-2gis-stealth.cjs` (образец, не подключён).
- Мульти-ключ xfetch (коммит `bdecd08d`) и раздельные очереди find/study (`43e29823`) — совместимы, остаются как запасной путь и основа для разнесения по виртуалкам.