init: Parser v1 — Lead Generation Engine
Парсер лидов МБ РФ: Яндекс.Карты + HH.ru + обогащение DaData/ЕГРЮЛ/Rusprofile + Streamlit CRM. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,773 @@
|
||||
# Parser v1 — Lead Generation Engine
|
||||
|
||||
Парсер лидов малого бизнеса РФ с многоуровневым обогащением, скорингом и **CRM-приложением** для ручной работы с лидами.
|
||||
|
||||
**Источники:**
|
||||
- 🗺 **Яндекс.Карты** — компании по нишам (кафе, автосервис, стоматология...). Сканирует карточку на email и доп.телефоны (в описаниях / «О компании»).
|
||||
- 💼 **HH.ru** — компании, ищущие «руки» по signal-запросам (оператор, администратор записи) = нет автоматизации.
|
||||
- 🔍 **HH employer-pages** — для лидов от HH парсит страницу работодателя → website + доп.контакты.
|
||||
- 🔎 **DDG-find-sites** — для лидов без website ищет сайт через DuckDuckGo (бесплатно, без капчи) + верифицирует по содержимому (`name fuzzy-match` + blocklist 80+ агрегаторов).
|
||||
- 🌐 **Tier 2 enrichment** — анализ сайта: CMS, чат, онлайн-запись, аналитика + email/телефоны из `<a href="mailto:...">` / `<a href="tel:...">` + **ИНН/ОГРН/КПП из footer** (152-ФЗ disclosure, пробует 7 типичных contact-страниц).
|
||||
- 🏛 **Tier 3 ЕГРЮЛ** — **DaData** (primary, 10К запросов/день бесплатно, индексирует бренды) → **Rusprofile** (fallback). Возвращает ИНН/ОГРН/КПП/директор/адрес/дата регистрации/ОКВЭД.
|
||||
- 🚫 **Blacklist крупных компаний** — автоматически отсекает банки (Газпром/Альфа/Сбер), ритейл-сети (ВкусВилл/X5/Магнит), госструктуры (Правительство Москвы/ФГАОУ) и пр. публичные компании ПАО которым outreach бесполезен.
|
||||
|
||||
**Два режима работы:**
|
||||
- 🤖 **Парсер** — собирает лидов в `leads.db` через `launch.bat` (TUI-меню) или `python main.py …`
|
||||
- 🎯 **CRM-приложение** (Streamlit) — открывается через `launch_crm.bat`, читает/пишет ту же `leads.db`: фильтры по статусу/региону/score, форма касания (звонок/email/VK/TG → реакция), история всех касаний, заметки о лиде.
|
||||
|
||||
**Стек:** Python 3.12 + Botasaurus + SQLite + requests + colorlog + Streamlit (для CRM) + DaData Suggestions API
|
||||
**Текущая БД:** 2874 лида (2676 HH + 198 Я.Карты), из них **143 excluded** (blacklist крупных компаний), **2731 доступных для outreach**, **1587 hot** (score≥5). У ~520 лидов есть директор/ИНН.
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Быстрый старт
|
||||
|
||||
**Два двойных клика:**
|
||||
- **`launch.bat`** — запускает парсер через TUI-меню (`questionary`): источники, категории, локация, опции pipeline.
|
||||
- **`launch_crm.bat`** — открывает CRM-приложение в браузере (`localhost:8501`): таблица лидов, фильтры, форма касания.
|
||||
|
||||
```bash
|
||||
# 1. Один раз: установка
|
||||
python -m venv .venv
|
||||
.venv\Scripts\activate # Windows
|
||||
pip install -r requirements.txt
|
||||
playwright install chromium
|
||||
|
||||
# 2a. Парсер через TUI-меню (рекомендуется)
|
||||
launch.bat
|
||||
# или: python launcher.py
|
||||
|
||||
# 2b. Или напрямую через CLI
|
||||
python main.py --full --category "автосервис,салон красоты,стоматология" --limit 20
|
||||
|
||||
# 3. Работать с собранными лидами в CRM
|
||||
launch_crm.bat
|
||||
# или: streamlit run app/app.py
|
||||
|
||||
# 4. CSV-отчёты прогонов
|
||||
explorer exports
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Содержание
|
||||
|
||||
1. [Установка](#установка)
|
||||
2. [Smoke-тесты](#smoke-тесты)
|
||||
3. [Команды](#команды)
|
||||
- [📖 Справочник по всем флагам](#-справочник-по-всем-флагам)
|
||||
4. [География: город / район / регион](#география)
|
||||
5. [Pipeline и его шаги](#pipeline)
|
||||
6. [Скоринг лидов](#скоринг)
|
||||
7. [Что в БД](#что-в-бд)
|
||||
8. [CRM-приложение (Streamlit)](#crm-приложение)
|
||||
9. [Структура проекта](#структура)
|
||||
10. [PyCharm флоу](#pycharm)
|
||||
11. [Troubleshooting](#troubleshooting)
|
||||
12. [Прогресс по фазам](#прогресс-по-фазам)
|
||||
|
||||
---
|
||||
|
||||
## Установка
|
||||
|
||||
```bash
|
||||
# Создать venv и активировать
|
||||
python -m venv .venv
|
||||
.venv\Scripts\activate # Windows
|
||||
# source .venv/bin/activate # Linux/Mac
|
||||
|
||||
# Зависимости
|
||||
pip install -r requirements.txt
|
||||
playwright install chromium # обязательно — это браузер для Botasaurus
|
||||
```
|
||||
|
||||
**Python:** 3.12 (3.13 тоже работает; 3.14 — рискованно, не все wheel'ы есть).
|
||||
|
||||
---
|
||||
|
||||
## Smoke-тесты
|
||||
|
||||
Перед первым запуском проверь что модули работают:
|
||||
|
||||
```bash
|
||||
python normalization.py # тесты телефона / домена / рейтинга
|
||||
python database.py # тест дедупликации
|
||||
python scoring.py # тест скоринга
|
||||
```
|
||||
|
||||
Все 3 должны напечатать `✅ ... — пройдены`. Если упало — баг.
|
||||
|
||||
---
|
||||
|
||||
## Команды
|
||||
|
||||
> 💡 Можно вообще не помнить флаги — двойной клик по **`launch.bat`** откроет TUI-меню (`launcher.py`) с навигацией стрелками + чекбоксами + 50+ категорий и 40+ локаций. Описание ниже — для запуска через PowerShell/Terminal или PyCharm Run Configuration.
|
||||
|
||||
|
||||
### Полный pipeline (всё одной командой)
|
||||
|
||||
`--full` эквивалент `--source X --find-sites --enrich --enrich-egrul --rescore --export` (+ `--hh-enrich-websites` если source=hh). Делает:
|
||||
1. **Парсинг** одного источника (HH или Я.Карты)
|
||||
2. **HH employer-pages** (если HH) — заходит на страницу работодателя, тащит website + контакты
|
||||
3. **DDG find-sites** — для лидов без website ищет сайт через DuckDuckGo + верифицирует
|
||||
4. **Tier 2** — анализ сайтов (CMS, чат, запись, аналитика) + извлечение ИНН/ОГРН/КПП из footer (152-ФЗ)
|
||||
5. **Tier 3** — ЕГРЮЛ через DaData (primary) → Rusprofile (fallback). Если на сайте нашли ИНН — обогащение по ИНН (точно).
|
||||
6. **Blacklist filter** — автоматически отсекает крупные публичные компании (банки, ритейл-сети, госструктуры)
|
||||
7. **Rescore** всех лидов в БД (включая старых)
|
||||
8. **Export** CSV
|
||||
|
||||
```bash
|
||||
# Я.Карты — нужно явно указать --source yandex (default)
|
||||
python main.py --full --category "автосервис" --limit 30
|
||||
|
||||
# HH — компании которые ищут "руки" (нет CRM)
|
||||
python main.py --full --source hh --category "оператор колл-центра" --limit 5
|
||||
|
||||
# По всем signal-запросам HH (~30-40 минут)
|
||||
python main.py --full --source hh
|
||||
```
|
||||
|
||||
**Источники запускаются раздельно** — БД одна, но каждый источник имеет свои параметры,
|
||||
скорость, риски блокировок. Так удобнее контролировать прогоны.
|
||||
|
||||
**БД общая (`leads.db`)** — после двух раздельных прогонов компании из Я.Карт и HH
|
||||
автоматически смерджатся через дедуп по ИНН/телефону/домену (после ЕГРЮЛ-обогащения).
|
||||
|
||||
### По шагам
|
||||
|
||||
| Команда | Что делает |
|
||||
|---|---|
|
||||
| `--source yandex --category "кафе" --limit 30` | Парсинг 1 категории на Я.Картах |
|
||||
| `--source yandex` (без `--category`) | Все 10 категорий из `config.CATEGORIES` |
|
||||
| `--source hh` | HH.ru — все signal-запросы (`оператор`, `администратор записи`, ...) |
|
||||
| `--source hh --category "оператор колл-центра"` | HH.ru — только один запрос |
|
||||
| `--hh-enrich-websites` | Для HH-лидов без сайта зайти на employer-страницу, взять website + контакты |
|
||||
| `--find-sites` | Для лидов без website искать сайт через DuckDuckGo (бесплатно) + верифицировать по INN/name |
|
||||
| `--enrich` | Tier 2 — обогатить непроверенные сайты (CMS / контакты / footer ИНН) |
|
||||
| `--enrich-egrul` | Tier 3 — обогатить через DaData (primary) → Rusprofile (fallback) |
|
||||
| `--rescore` | Пересчитать score у всех лидов |
|
||||
| `--export --min-score 5` | Только экспорт горячих лидов |
|
||||
| `--stats` | Показать статистику БД |
|
||||
| `--cleanup-directors` | Очистить лиды где director_name = "Генеральный директор" / "Председатель" / etc. (запустится повторное обогащение) |
|
||||
|
||||
### 📖 Справочник по всем флагам
|
||||
|
||||
Каждый флаг можно использовать отдельно или **комбинировать в любом порядке**.
|
||||
Pipeline запускается последовательно: parse → enrich → enrich-egrul → rescore → export.
|
||||
|
||||
#### 🎯 Pipeline-флаги (запускают шаги обработки)
|
||||
|
||||
| Флаг | Default | Что делает | Зависит от |
|
||||
|---|---|---|---|
|
||||
| `--source {yandex,hh}` | — | Какой источник парсить. Без него парсинг не идёт. *(2GIS/VK/WB — отключены, см. DECISIONS D13/D17)* | — |
|
||||
| `--hh-enrich-websites` | false | Для HH-лидов без сайта: зайти на employer-страницу, забрать `website` + доп.контакты | Лиды source=hh без website |
|
||||
| `--find-sites` | false | Для лидов без website: искать сайт через DuckDuckGo HTML search + верифицировать (INN на странице ИЛИ fuzzy-match названия + blocklist 80+ доменов агрегаторов) | Лиды без website |
|
||||
| `--find-sites-source {yandex,hh,all}` | `all` | Ограничить `--find-sites` лидами из конкретного источника | используется с `--find-sites` |
|
||||
| `--find-sites-limit N` | — | Ограничить `--find-sites` первыми N лидами (для теста) | используется с `--find-sites` |
|
||||
| `--enrich` | false | Tier 2: анализ сайта (CMS, чат, запись, аналитика) **+ сбор email/доп.телефонов + ИНН/ОГРН/КПП из footer (152-ФЗ)** для лидов где `website` есть, но `site_checked_at` ещё пуст. Если на сайте нашли ИНН — авто-запуск ЕГРЮЛ по ИНН. | Лиды с `website` в БД |
|
||||
| `--enrich-egrul` | false | Tier 3: ЕГРЮЛ через **DaData** (primary, 10K/день бесплатно) → **Rusprofile** (fallback). Для лидов где `egrul_checked_at` пуст | Лиды с `name` в БД |
|
||||
| `--rescore` | false | Пересчитать score у **всех** лидов в БД (нужно после изменения `config.SCORE_WEIGHTS`) | — |
|
||||
| `--export` | false | CSV каждого прогона сессии в `exports/YYYY-MM/leads_<src>_<query>_<city>_<ts>.csv` с шапкой-метаданными. Если парсинга в сессии не было — плоский CSV по `min_score`. | — |
|
||||
| `--export-master` | false | Snapshot всей БД в `exports/_master/all_leads.csv` (перезапись). Можно вызвать в любой момент. | — |
|
||||
| `--export-run N` | — | Пересоздать CSV конкретного прогона по его ID из `sources_log` (если файл удалили). | — |
|
||||
| `--full` | false | **Shortcut** — раскрывается в `--source X --find-sites --enrich --enrich-egrul --rescore --export` (+ `--hh-enrich-websites` если `--source hh`). Default source = `yandex`. | — |
|
||||
| `--stats` | false | Показать сводку БД и **выйти** (другие флаги игнорируются) | — |
|
||||
| `--cleanup-directors` | false | Утилита: очистить кривые ФИО директоров ("Генеральный директор" и т.п.) и сбросить `egrul_checked_at` чтобы `--enrich-egrul` повторно их прогонял | — |
|
||||
| `--rescan-sites` | false | Одноразовая утилита: сбросить `site_checked_at` у всех лидов → следующий `--enrich` пройдёт по сайтам заново. Нужно после расширения сбора контактов / обновления `analyze_website`. | — |
|
||||
|
||||
#### 🔍 Параметры парсинга (модификаторы для `--source`)
|
||||
|
||||
| Флаг | Default | Что делает | Где работает |
|
||||
|---|---|---|---|
|
||||
| `--category "X"` | — | Конкретный поисковый запрос. Можно несколько через запятую: `"автосервис,салон красоты,стоматология"`. Если не указано — берётся профильный список (`CATEGORIES` для Я.Карт, `HH_SIGNAL_QUERIES` для HH) | yandex, hh |
|
||||
| `--city "X"` | `"Москва"` | Город из `config.CITIES` (Москва / Москва и МО / СПб). Если указан незнакомый — fallback на "Москва и МО" + город как район | yandex, hh |
|
||||
| `--district "X"` | — | Район Москвы или другой топоним для уточнения. Свободная строка (Митино, Бутово, Зеленоград). Добавляется к поисковому запросу + сохраняется в БД отдельной колонкой | yandex |
|
||||
| `--limit N` | 100 | Для Я.Карт — **максимум карточек на категорию**. Для HH — **максимум страниц** (1 страница ≈ 50 вакансий). Не действует на enrichment (он всегда обрабатывает всех непроверенных) | yandex, hh |
|
||||
|
||||
#### 📤 Параметры экспорта
|
||||
|
||||
| Флаг | Default | Что делает |
|
||||
|---|---|---|
|
||||
| `--min-score N` | 0 | В CSV попадут только лиды с `score >= N`. Например `--min-score 5` оставит только hot leads |
|
||||
|
||||
---
|
||||
|
||||
### 💡 Какие флаги можно комбинировать
|
||||
|
||||
#### ✅ Типичные комбинации
|
||||
|
||||
```bash
|
||||
# Только парсинг, без обогащения
|
||||
python main.py --source yandex --category "кафе" --limit 20
|
||||
|
||||
# Полная цепочка по Я.Картам — парсинг + find-sites + Tier2 + DaData + score + CSV
|
||||
python main.py --full --source yandex --category "кафе" --limit 20
|
||||
|
||||
# Полная цепочка по HH (включая employer-pages)
|
||||
python main.py --full --source hh --category "оператор колл-центра" --limit 5
|
||||
|
||||
# Обогатить и пересчитать без нового парсинга (например после правки скоринга)
|
||||
python main.py --find-sites --enrich --enrich-egrul --rescore --export
|
||||
|
||||
# Только найти сайты для HH-лидов (лимит 50 для теста)
|
||||
python main.py --find-sites --find-sites-source hh --find-sites-limit 50
|
||||
|
||||
# Только пересчитать score (быстро — секунды)
|
||||
python main.py --rescore --export
|
||||
|
||||
# Утилита очистки + повторный enrichment + rescore + CSV
|
||||
python main.py --cleanup-directors --enrich-egrul --rescore --export
|
||||
|
||||
# Только посмотреть текущее состояние
|
||||
python main.py --stats
|
||||
|
||||
# Перепрогон enricher на ВСЕХ лидах с website (после расширения сбора контактов)
|
||||
python main.py --rescan-sites --enrich --rescore --export-master
|
||||
|
||||
# Master-файл всех лидов в один CSV
|
||||
python main.py --export-master
|
||||
|
||||
# Восстановить удалённый CSV прогона #5
|
||||
python main.py --export-run 5
|
||||
```
|
||||
|
||||
#### ❌ Бессмысленные комбинации
|
||||
|
||||
- `--stats` + что угодно — `--stats` всегда выходит сразу
|
||||
- `--full` без `--source` (точнее, без явного source — возьмёт default `yandex`)
|
||||
- `--enrich` без лидов с `website` в БД — нечего обогащать
|
||||
- `--enrich-egrul` без лидов в БД — то же самое
|
||||
- `--district "X"` для `--source hh` — игнорируется (HH парсит по area_id)
|
||||
|
||||
---
|
||||
|
||||
### HH.ru — отдельный workflow
|
||||
|
||||
```bash
|
||||
# Полный цикл одной командой (Парсинг → Tier 2 → ЕГРЮЛ → Rescore → CSV)
|
||||
python main.py --full --source hh
|
||||
|
||||
# Только парсинг по одному signal-запросу (для теста)
|
||||
python main.py --source hh --category "оператор колл-центра" --limit 2
|
||||
|
||||
# Без --category — пройдёт по всем 13 signal-запросам из config.HH_SIGNAL_QUERIES
|
||||
python main.py --source hh
|
||||
```
|
||||
|
||||
**Принцип:** если компания ищет "оператора ПК" / "администратора записи" / "менеджера без CRM" — у них нет автоматизации. Это **прямой сигнал боли** = `+3 hh_signal` в скоринг. Дедупликация по ИНН автоматически свяжет их с лидами из Я.Карт если совпадут (обычно не пересекаются — разные срезы рынка).
|
||||
|
||||
**Важно:** HH парсер работает через **Botasaurus браузер** (не api.hh.ru — он 403'ит даже с Chrome TLS-fingerprint). Открывает страницы поиска `hh.ru/search/vacancy` как реальный пользователь. Региональный модал "Вы из Москвы?" закрывается автоматически.
|
||||
|
||||
**Что HH даёт vs что нет:**
|
||||
| Поле | HH | Что компенсирует |
|
||||
|---|---|---|
|
||||
| name | ✅ | — |
|
||||
| employer_id, source_url | ✅ | — |
|
||||
| phone | ❌ | HH публично не показывает |
|
||||
| website | ❌ | — |
|
||||
| ИНН + директор + дата регистрации | через `--enrich-egrul` | Rusprofile по name (~65% покрытие) |
|
||||
|
||||
### Геолокация — `--city` и `--district`
|
||||
|
||||
См. подробно в [разделе ниже](#география). Кратко:
|
||||
|
||||
```bash
|
||||
# Известный город (есть в config.CITIES)
|
||||
python main.py --full --city "Москва" --category "стоматология" --limit 15
|
||||
|
||||
# Любой город / район — через district + общая зона
|
||||
python main.py --full --city "Москва и МО" --district "Жуковский" --category "автосервис"
|
||||
python main.py --full --city "Москва" --district "Митино" --category "салон красоты"
|
||||
```
|
||||
|
||||
### Категории — несколько через запятую
|
||||
|
||||
```bash
|
||||
python main.py --full --category "автосервис,салон красоты,стоматология" --limit 20
|
||||
```
|
||||
|
||||
Парсятся последовательно. Между категориями — пауза `random(30, 60)` сек (anti-bot).
|
||||
|
||||
---
|
||||
|
||||
## География
|
||||
|
||||
### 3 уровня геолокации
|
||||
|
||||
| Уровень | CLI | Поле в БД | Пример |
|
||||
|---|---|---|---|
|
||||
| **Регион** (агрегатор) | автоматом из конфига | `region` | "Москва", "Московская область" |
|
||||
| **Город** | `--city "Москва"` | `city` | "Москва", "Мытищи" |
|
||||
| **Район** | `--district "Митино"` | `district` | "Митино", "Жуковский" |
|
||||
|
||||
### Что в `config.CITIES`
|
||||
|
||||
```python
|
||||
CITIES = {
|
||||
"Москва": {"yandex_id": 213, ...}, # ✓ проверено
|
||||
"Москва и МО": {"yandex_id": 1, ...}, # ✓ проверено
|
||||
"Санкт-Петербург": {"yandex_id": 2, ...}, # ✓ проверено
|
||||
}
|
||||
```
|
||||
|
||||
### Fallback для незнакомого города
|
||||
|
||||
Если указать `--city` которого **нет** в `config.CITIES` (и при этом `--district` пуст), парсер автоматически:
|
||||
1. Использует `Москва и МО` (geo_id 1) как базу
|
||||
2. Город уходит в `--district`
|
||||
3. В лог пишет ⚠️ предупреждение
|
||||
|
||||
```bash
|
||||
# Указано "Жуковский" — нет в config → fallback на "Москва и МО" + район "Жуковский"
|
||||
python main.py --full --city "Жуковский" --category "автосервис"
|
||||
```
|
||||
|
||||
⚠️ Если указать **и** неизвестный city, **и** district одновременно — будет ошибка с подсказкой.
|
||||
|
||||
### Как добавить точный город (5 минут)
|
||||
|
||||
1. Открыть https://yandex.ru/maps/ → найти город в поиске
|
||||
2. **Кликнуть** на первый результат ("Мытищи, городской округ")
|
||||
3. URL станет `https://yandex.ru/maps/10743/mytishchi/?...`
|
||||
4. Вставить в `config.CITIES`:
|
||||
```python
|
||||
"Мытищи": {"yandex_id": 10743, "yandex_slug": "mytishchi",
|
||||
"vk_id": 1, "region": "Московская область"},
|
||||
```
|
||||
5. Готово: `python main.py --full --city "Мытищи" --category "автосервис"`
|
||||
|
||||
---
|
||||
|
||||
## Pipeline
|
||||
|
||||
```
|
||||
┌─────────┐ ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌────────┐ ┌──────┐
|
||||
│1. Parse │→ │2. HH-emp│→ │3. Find- │→ │4. Tier2 │→ │5. Tier 3 │→ │6. Black- │→ │7. Re- │→ │8.CSV │
|
||||
│ Я.Карты │ │ pages │ │ Sites │ │ website │ │ DaData → │ │ list │ │ score │ │ │
|
||||
│ + HH │ │ employer│ │ DuckDuck │ │ analyzer│ │ Rusprof │ │ filter │ │ all │ │ │
|
||||
│ + body │ │website+ │ │ + verify │ │ + footer│ │ fallback │ │ 143 leads│ │ leads │ │ │
|
||||
│ emails │ │contacts │ │ by INN/ │ │ INN/OGRN│ │ + INN-by-│ │ excluded │ │ │ │ │
|
||||
│ │ │ │ │ name │ │ /KPP │ │ site │ │ │ │ │ │ │
|
||||
└─────────┘ └─────────┘ └──────────┘ └─────────┘ └──────────┘ └──────────┘ └────────┘ └──────┘
|
||||
--source --hh-enrich --find- --enrich --enrich- (auto в --rescore --export
|
||||
-websites sites egrul upsert)
|
||||
```
|
||||
|
||||
Каждый шаг можно запустить **независимо** или **в комбинации**. Флаг `--full` запускает всю цепочку.
|
||||
|
||||
**Сбор контактов в pipeline (важно):**
|
||||
- **Шаг 1** парсит карточки Я.Карт + HH-вакансии. Я.Карты помимо `.orgpage-phones-view__phone-number` сканирует **весь видимый текст карточки** через `document.body.innerText` → email/доп.телефоны из описания / «О компании». Технические домены Яндекса фильтруются.
|
||||
- **Шаг 2 (HH employer-pages)** для HH-лидов без сайта — заходит на страницу работодателя на hh.ru, тащит website + социальные сети + телефоны.
|
||||
- **Шаг 3 (Find-Sites)** для всех лидов без `website` — ищет сайт через DuckDuckGo HTML search. Верифицирует двумя способами: (а) ИНН лида должен встречаться на странице, или (б) название компании fuzzy-match (с очисткой от ОПФ-префиксов). Blocklist 80+ доменов агрегаторов (`hh.ru`, `2gis.ru`, `yell.ru`, `rusprofile.ru` и т.п.).
|
||||
- **Шаг 4 (Tier 2)** `analyze_website` запрашивает сайт компании: CMS-сигнатуры (Tilda/Wix/1С-Bitrix/InSales), online-чат, online-запись, аналитика. Извлекает email/телефоны из `mailto:`/`tel:`. Сканирует footer + пробует 7 типичных contact-страниц (`/contacts/`, `/kontakty/`, `/o-kompanii/`, `/rekvizity/`, ...) на наличие **ИНН/ОГРН/КПП** (152-ФЗ disclosure). Если ИНН найден — авто-запуск `enrich_egrul_by_inn(inn)` (точное обогащение по ИНН без fuzzy-поиска).
|
||||
- **Шаг 5 (Tier 3)** ЕГРЮЛ-обогащение: сначала **DaData Suggestions API** (10K запросов/день бесплатно, индексирует бренды → находит «ВкусВилл» / «Кофемания» / «The Бык»), при неудаче — **Rusprofile** fallback. С учётом kladr_id для приоритета по городу (Москва/СПб). Возвращает: ИНН, ОГРН, КПП, директор (ФИО), адрес, дата регистрации, ОКВЭД.
|
||||
- **Шаг 6 (Blacklist)** при `upsert_lead` проверяется через `blacklist.is_blacklisted(name)` — три стратегии: точное имя (~140 компаний), keyword-fragments (~30 паттернов), prefix-matching. Лид помечается `outreach_status='excluded'` и пропускается фильтрами CRM.
|
||||
- Если расширил `analyze_website` (добавил новые сигнатуры/детекторы) — прогони `--rescan-sites --enrich` чтобы обогатить уже собранных лидов заново.
|
||||
|
||||
---
|
||||
|
||||
## Скоринг
|
||||
|
||||
Формула **v5** — **«решаемая нами боль» × ICP**, шкала **0-10** (логика в `scoring.py`, веса в `config.py`).
|
||||
Полное обоснование и история — **D17** в [`../DECISIONS.md`](../DECISIONS.md).
|
||||
|
||||
**Семантика:** score отвечает на ОДИН вопрос — есть ли у компании проблемы, которые закрывают НАШИ продукты, и насколько остро. Не «качество лида», не «дозвонибельность».
|
||||
|
||||
```
|
||||
score = pain(решаемая боль) × icp_fit(наш ли это размер)
|
||||
```
|
||||
|
||||
### Детекторы боли → продукт (`config.SCORE_WEIGHTS`)
|
||||
|
||||
| Тема | Сигнал | Вес | Продукт | Применяется к |
|
||||
|---|---|---|---|---|
|
||||
| Запись/входящие | нет онлайн-записи | 2.0 | **P4** | только услуги с записью (`APPT_CATEGORIES`) |
|
||||
| | нет онлайн-чата | 1.0 | **P4** | всем |
|
||||
| Репутация | рейтинг <3.5 / <4.0 / <4.5 | 2.0 / 1.5 / 1.0 | **P3** | всем (4.5+ = не боль) |
|
||||
| | мало отзывов <10 / <30 | 1.0 / 0.5 | **P3** | всем |
|
||||
| Веб | нет сайта / сайт мёртв | 1.5 / 2.5 | **P10** | всем |
|
||||
| | сайт на конструкторе (вкл. авто-визитки Я.Бизнес) | 0.5 | **P10** | всем (премиум топит ICP) |
|
||||
| Маркетинг | нет соцсетей (VK/Telegram) | 1.0 | **P12** | только B2C (`SOCIAL_SALES_CATEGORIES`) |
|
||||
| | нет веб-аналитики | 0.5 | **P12** | всем |
|
||||
| Инфра | почта на бесплатном домене | 0.5 | **P2** | всем |
|
||||
|
||||
Внутри темы — насыщение `max + 0.4·остальные` (коррелированные сигналы не стакаются линейно). Сумма тем → `raw_pain`, нормировка `/PAIN_NORM·10`, cap 10. `pain_products` (JSON) = «с чем заходить» для CRM.
|
||||
|
||||
### ICP-гейт — прогрессивный штраф за «зрелость» (множитель 0..1)
|
||||
|
||||
ЦА = малый/средний бизнес, которому нужна автоматизация. Чем больше отзывов **и** выше рейтинг — тем сильнее снижаем балл (процветающим/раскрученным труднее продать):
|
||||
|
||||
```
|
||||
icp = 1 − 0.85 · rf · (0.65 + 0.35·gf)
|
||||
rf = min(1, отзывы / 6000) # линейно по объёму (усилен 2026-06-05)
|
||||
gf = clamp((avg − 4.0)/(5.0 − 4.0)) # по рейтингу
|
||||
```
|
||||
|
||||
Отзывы штрафуют всегда (база 0.5), высокий рейтинг усиливает до полного. Мало отзывов → ≈1 (новый/борющийся бизнес сохраняет балл). На Я.Картах рейтинги зажаты 4.5-5.0 → главный рычаг это объём отзывов. Параметры — `ICP_*` в `config.py`.
|
||||
|
||||
«Уже автоматизирован» отдельно не режем — у такого лида просто нет болевых дыр (severity→0).
|
||||
|
||||
### ⚠️ Категорийная релевантность (читай при добавлении категории)
|
||||
|
||||
Часть болей применима не ко всем типам бизнеса. Подробная инструкция — в блоке `config.py`:
|
||||
- **booking (P4)** — только `APPT_CATEGORIES` (салон, клиника, кафе, автосервис…)
|
||||
- **social (P12)** — только `SOCIAL_SALES_CATEGORIES` (бьюти, HoReCa, розница…)
|
||||
- Новая/неизвестная категория по умолчанию НЕ получает booking/social, пока не впишешь ключевое слово в нужный список (консервативно — чтобы не завышать ложно).
|
||||
- Instagram в РФ забанен → в соцсетях учитываются только VK/Telegram.
|
||||
|
||||
### Полнота диагностики
|
||||
|
||||
`diagnostic_coverage` (0..1) — доля проверенных тем. «Не проверяли» ≠ «нет боли»: при coverage < `MIN_COVERAGE` (0.5) лид помечается «нужно обогащение», а не ранжируется фальшиво.
|
||||
|
||||
### Бэнды и пороги
|
||||
|
||||
- 🔥 **hot** — score ≥ `BAND_HOT` (6) · 🟡 **warm** — 4-5 · ⚪ **cold** — ≤3
|
||||
- `HOT_LEAD_THRESHOLD = BAND_HOT` (совместимость с CRM / экспортом)
|
||||
- `PAIN_NORM = 5.0` — нормировочный делитель · `SCORE_MAX = 10` — hard cap
|
||||
|
||||
### Аудит скоринга
|
||||
|
||||
`python audit_scores.py` — независимая проверка всей БД (пересчёт каждого лида + санитарные правила: боль vs данные, устаревший score, целостность). Код возврата 1 при HARD-аномалиях — можно вешать на хук/CI.
|
||||
|
||||
---
|
||||
|
||||
## Что в БД
|
||||
|
||||
Таблица `leads` — все поля:
|
||||
|
||||
```
|
||||
Идентичность: id, name, inn, ogrn, director_name
|
||||
Контакты: phones (доверенные — Я.Карты), phones_extra (с сайта, к проверке),
|
||||
emails (JSON), phone_primary, email_primary
|
||||
Онлайн: website, vk_url, telegram_url, instagram_url, youtube_url
|
||||
Гео: address, city, region, district
|
||||
Бизнес: category
|
||||
Сигналы: reviews_count, reviews_avg, has_website, has_vk, has_telegram
|
||||
Скоринг: score, score_breakdown (JSON)
|
||||
Email валидация: email_valid, email_checked_at
|
||||
Tier 2: site_alive, site_status_code, cms_type,
|
||||
has_live_chat, has_online_booking, has_analytics,
|
||||
email_domain_type, site_checked_at
|
||||
Tier 3: registration_date, egrul_checked_at, egrul_status
|
||||
Outreach (auto): outreach_status, outreach_channel, outreach_sent_at, outreach_replied_at
|
||||
CRM (ручной): comments, last_action, last_reaction, last_touched_at
|
||||
Системные: source, source_id, source_url, parsed_at, updated_at,
|
||||
phone_dedup_key, domain_dedup_key
|
||||
```
|
||||
|
||||
Дополнительные таблицы:
|
||||
- `sources_log` — история прогонов (`source`, `query`, `city`, `started_at`, `finished_at`, счётчики, статус)
|
||||
- `outreach_events` — история всех касаний (звонок/email/VK/TG → реакция → комментарий). Пишется из CRM-приложения и в будущем — из Phase 3 auto-pipeline.
|
||||
- `lead_in_run` — junction-таблица «прогон ↔ лид» с пометкой `inserted`/`merged`. Нужна для `--export-run N` — пересоздания CSV конкретного прогона.
|
||||
|
||||
**Статусы лида (`outreach_status`):**
|
||||
- `inbox` / `new` — не разобран (default для свежих лидов)
|
||||
- `triaged` — посмотрел, оставил в работе
|
||||
- `in_work` — идёт работа (звоним, переписываемся)
|
||||
- `done` — закрыт (стал клиентом или отказался окончательно)
|
||||
- `skip` — пропустить (не наша ЦА)
|
||||
- `queued` / `sent` / `replied` / `converted` — для будущего auto-pipeline (Phase 3)
|
||||
|
||||
### Дедупликация
|
||||
|
||||
`upsert_lead()` ищет дубль в порядке: **ИНН → телефон (10 цифр) → домен**. Найденный — мержит (объединяет phones/emails, заполняет пустые поля).
|
||||
|
||||
UNIQUE constraints на `inn` и `phone_dedup_key`.
|
||||
|
||||
---
|
||||
|
||||
## CRM-приложение
|
||||
|
||||
Streamlit-приложение для работы со звонящего/писавшего человека: фильтр лидов, форма касания, история всех взаимодействий, заметки.
|
||||
|
||||
### Запуск
|
||||
|
||||
```powershell
|
||||
# Двойной клик по launch_crm.bat либо:
|
||||
streamlit run app/app.py
|
||||
```
|
||||
|
||||
При первом запуске `launch_crm.bat` ставит `streamlit>=1.35.0` через pip (~30 сек) и создаёт пустой `~/.streamlit/credentials.toml` чтобы Streamlit не задавал вопрос про email.
|
||||
|
||||
Откроется браузер на `http://localhost:8501`. Работает на той же `leads.db` что и парсер — изменения видны сразу с обеих сторон.
|
||||
|
||||
### Интерфейс
|
||||
|
||||
**Левая панель — фильтры:**
|
||||
- Быстрый фильтр: 📥 Inbox / 🔥 Hot (≥6) / ⚙️ В работе / ✅ Готовые / 🚫 Пропущенные / 📋 Все
|
||||
- Источник (multiselect)
|
||||
- Регион (multiselect)
|
||||
- Район содержит (текстовый поиск)
|
||||
- Категория (multiselect)
|
||||
- Статус (multiselect)
|
||||
- Диапазон score (slider 0–10)
|
||||
- Поиск по имени
|
||||
|
||||
**Сверху — 5 метрик:** Всего в БД, Inbox, В работе, Готовых, Под текущий фильтр.
|
||||
|
||||
**Таблица лидов** — клик по строке открывает детальную карточку под таблицей.
|
||||
|
||||
**Карточка лида:**
|
||||
- 3 колонки: контакты (телефон/email/сайт/VK/TG/Instagram), бизнес (категория/отзывы/директор/ИНН), статус (score / последнее касание).
|
||||
- Форма **«➕ Записать касание»**: действие (звонок/email/VK/TG/WhatsApp/SMS) + реакция (не ответили / отказ / согласились / перешли в TG / перезвонить / не наша ЦА / спам) + комментарий + новый статус лида.
|
||||
- **«📝 Заметки»** — свободные заметки о лиде (отдельно от истории касаний).
|
||||
- **«📜 История касаний»** — все события по этому лиду из таблицы `outreach_events`.
|
||||
|
||||
### Автомиграция БД
|
||||
|
||||
При запуске `app.py` вызывается `database.init_db(...)` — это автоматически догоняет схему: добавляет 4 CRM-колонки, таблицы `outreach_events` и `lead_in_run` если их ещё нет. То есть приложение работает на старой БД без подготовки.
|
||||
|
||||
### Файлы
|
||||
|
||||
- `app/app.py` — Streamlit UI (~380 строк)
|
||||
- `app/db_layer.py` — слой работы с БД (~270 строк): фильтры, детали, история, запись касаний, метрики
|
||||
- `launch_crm.bat` — Windows-launcher с автоустановкой streamlit и автокредами
|
||||
|
||||
---
|
||||
|
||||
## Структура
|
||||
|
||||
```
|
||||
parser_v1/
|
||||
├── README.md ← этот файл
|
||||
├── launch.bat ← 🚀 запуск парсера через TUI-меню (двойной клик)
|
||||
├── launch_crm.bat ← 🎯 запуск CRM-приложения в браузере (двойной клик)
|
||||
├── launcher.py ← TUI-меню на questionary (запуск парсера)
|
||||
├── requirements.txt
|
||||
├── .gitignore
|
||||
│
|
||||
├── config.py # CITIES, CATEGORIES, веса скоринга, задержки
|
||||
├── database.py # SQLite: схема, миграции, upsert, start/finish_source_run, update_lead_contacts
|
||||
├── normalization.py # Телефоны (E.164), домены, ratings, extract_emails/phones_from_text
|
||||
├── scoring.py # Формула скоринга v4 (0-10, hard cap)
|
||||
├── logger_setup.py # Цветной вывод (colorlog)
|
||||
├── main.py # CLI — точка входа парсера
|
||||
├── leads.db # ⚠️ создаётся при первом запуске
|
||||
│
|
||||
├── parsers/
|
||||
│ ├── base.py # BaseParser: retry, sleep, captcha-counter
|
||||
│ ├── yandex_maps.py # Я.Карты (Botasaurus + JS-скролл + body-text email/phones scan)
|
||||
│ └── hh.py # HH.ru — signal-запросы через Botasaurus → employers как лиды
|
||||
│
|
||||
├── enricher/
|
||||
│ ├── website_analyzer.py # Tier 2: CMS, чат, запись, аналитика + email/phones + ИНН/ОГРН/КПП из footer (152-ФЗ, 7 contact-страниц)
|
||||
│ ├── contacts_finder.py # 🔎 Поиск сайта через DuckDuckGo HTML + верификация (INN на странице или fuzzy-match имени + blocklist 80+ агрегаторов)
|
||||
│ ├── dadata_enricher.py # 🏛 ЕГРЮЛ через DaData Suggestions API (primary, 10K/день free, kladr_id для Москва/СПб приоритета)
|
||||
│ ├── egrul_enricher.py # 🏛 ЕГРЮЛ через Rusprofile (fallback) + enrich_egrul_by_inn (точное обогащение)
|
||||
│ └── blacklist.py # 🚫 Чёрный список крупных компаний — 143 лида исключено (банки/ритейл/гос)
|
||||
│
|
||||
├── export/
|
||||
│ └── csv_export.py # CSV: export_run (per-прогон), export_master (snapshot всей БД), export_to_csv (плоский по min_score)
|
||||
│
|
||||
├── app/ ← 🎯 CRM-приложение (Streamlit)
|
||||
│ ├── app.py # UI: фильтры + таблица + карточка лида + форма касания
|
||||
│ └── db_layer.py # Слой работы с БД (фильтры, история, запись касаний)
|
||||
│
|
||||
├── exports/ # ⚠️ создаётся при первом экспорте
|
||||
│ ├── 2026-05/ # Файлы прогонов сгруппированы по месяцу
|
||||
│ │ └── leads_<src>_<query>_<city>_<timestamp>.csv
|
||||
│ └── _master/
|
||||
│ └── all_leads.csv # Snapshot всей БД (--export-master, перезапись)
|
||||
│
|
||||
└── _archive/
|
||||
└── phase0/ # тесты Phase 0 (Botasaurus discovery)
|
||||
├── test_yandex.py
|
||||
├── test_yandex_v2.py
|
||||
└── test_yandex_v3.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PyCharm
|
||||
|
||||
### Run Configuration
|
||||
|
||||
1. **Run → Edit Configurations → main**
|
||||
2. **Script parameters:** например `--full --category "кафе" --limit 15`
|
||||
3. **Working directory:** `parser_v1` (полный путь). **Важно** — иначе `leads.db` создастся не там
|
||||
4. **Apply → Run**
|
||||
|
||||
### Terminal
|
||||
|
||||
`Alt+F12` (или View → Tool Windows → Terminal). venv активируется автоматически. Удобно для произвольных команд:
|
||||
|
||||
```bash
|
||||
python main.py --full --city "Москва" --district "Митино" --category "автосервис" --limit 10
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Скролл подгружает мало карточек
|
||||
|
||||
**Симптом:** в логе видно `Скролл #1: карточек 5 (+0)` подряд → стоп через 3 итерации.
|
||||
|
||||
**Причина:** Яндекс кеширует страницу при `reuse_driver=True` или замедляется.
|
||||
|
||||
**Что делать:**
|
||||
- Подожди 30 сек, попробуй заново — обычно помогает
|
||||
- Закрой все окна Chrome, перезапусти
|
||||
|
||||
### `404` на URL вида `/maps/{geo_id}/{slug}/`
|
||||
|
||||
**Причина:** неправильный `yandex_id` или `yandex_slug` в `config.CITIES`.
|
||||
|
||||
**Что делать:** удали запись для этого города из `config.CITIES` — fallback автоматически переключит на "Москва и МО" + район. Или добавь точные значения через [инструкцию выше](#как-добавить-точный-город-5-минут).
|
||||
|
||||
### Captcha от Яндекса
|
||||
|
||||
**Симптом:** `⚠️ CAPTCHA. Останавливаемся.` или `⚠️ Бот-детектор: cloudflare`.
|
||||
|
||||
**Что делать:**
|
||||
- Подожди 1-2 часа (Яндекс снимает блок сам)
|
||||
- Уменьши `--limit` (например 10 вместо 50)
|
||||
- Увеличь задержки в `config.MIN_DELAY` / `config.MAX_DELAY`
|
||||
- Если повторяется — переключиться на VPS или использовать Tor
|
||||
|
||||
### DaData/Rusprofile не находит компанию (`не найдено в ЕГРЮЛ`)
|
||||
|
||||
Это **нормально для ~70%** компаний (текущий success rate ~29.6%). Причины:
|
||||
- Юр.название отличается от названия в Я.Картах / HH (например, бренд «Кофемания» = ООО «КАФЕ К» — DaData находит, Rusprofile нет)
|
||||
- Кавычки или специальные символы (буква "ъ" в "Кафе Пушкинъ" мешает)
|
||||
- Это ИП без публичного бренда
|
||||
|
||||
**Что помогает:**
|
||||
- На сайте может быть ИНН в footer — `analyze_website` его извлечёт автоматически (152-ФЗ), и тогда обогащение идёт по ИНН (точно).
|
||||
- Запустить `--find-sites` чтобы сначала найти сайты, потом `--enrich` (поднимет много ИНН с footer'ов).
|
||||
|
||||
### DaData 401/403
|
||||
|
||||
**Симптом:** `DaData error: 401 Unauthorized`.
|
||||
|
||||
**Причина:** не задан `DADATA_API_KEY` / `DADATA_SECRET_KEY` в `.env`.
|
||||
|
||||
**Решение:** скопировать `.env.example` → `.env`, вставить ключи с https://dadata.ru/profile/#info. Файл `.env` — в `.gitignore`, в git не попадёт.
|
||||
|
||||
### `IntegrityError: UNIQUE constraint failed: leads.inn`
|
||||
|
||||
**Симптом:** при ЕГРЮЛ-обогащении одного из лидов.
|
||||
|
||||
**Причина:** этот ИНН уже в БД у другого лида (одна сеть с разными филиалами).
|
||||
|
||||
**Что делает парсер:** ловит исключение → пишет остальные поля **без** ИНН → помечает в логе `(DUP по ИНН)`. Прогон не падает.
|
||||
|
||||
### `Connection to remote host was lost. - goodbye`
|
||||
|
||||
**Не ошибка** — это сообщение когда Botasaurus закрывает CDP-соединение с Chrome в конце прогона.
|
||||
|
||||
### "Должность" вместо ФИО директора
|
||||
|
||||
**Симптом:** в БД `director_name = "Генеральный директор"` или похожее.
|
||||
|
||||
**Причина:** старые записи, обогащённые до фикса валидатора ФИО.
|
||||
|
||||
**Решение:** запустить
|
||||
```bash
|
||||
python main.py --cleanup-directors --enrich-egrul --rescore --export
|
||||
```
|
||||
|
||||
Это очистит мусорные ФИО + повторно прогонит ЕГРЮЛ для очищенных + пересчитает.
|
||||
|
||||
### HH `403 Forbidden` для api.hh.ru
|
||||
|
||||
**Симптом:** в логе `HH 403 даже с TLS-fingerprint Chrome`.
|
||||
|
||||
**Причина:** Cloudflare защищает api.hh.ru от bulk-запросов. Не помогают ни `requests`, ни `botasaurus_requests` с Chrome TLS.
|
||||
|
||||
**Решение:** парсер уже использует **Botasaurus браузер** (через сайт `hh.ru/search/vacancy`). См. D10 в `../DECISIONS.md`. Если ошибка осталась после этого — закрой Chrome и перезапусти.
|
||||
|
||||
### HH модал "Вы из Москвы?"
|
||||
|
||||
**Симптом:** окно Chrome зависло на регионального модале.
|
||||
|
||||
**Что делает парсер:** автоматически закрывает через `_dismiss_hh_modals()` (JS клик на "Да, верно"). После первого закрытия cookie сохраняется в `user_data/` — модал больше не появляется.
|
||||
|
||||
**Если всё равно показывается:** закрой руками в окне Chrome — парсер продолжит.
|
||||
|
||||
### HH парсинг карточек "зависает" на каждой странице
|
||||
|
||||
**Симптом:** `Sleeping for 3 seconds...` потом тишина 1-2 минуты, потом результат.
|
||||
|
||||
**Причина:** Botasaurus `select_all` без `wait=None` ждёт 5 сек на каждом элементе (50 карточек × 3 поля = 12 минут зависа).
|
||||
|
||||
**Что делает парсер:** в `parsers/hh.py` все `select` внутри циклов карточек идут с `wait=None`. На странице 5-10 секунд вместо 12 минут.
|
||||
|
||||
---
|
||||
|
||||
## Прогресс по фазам
|
||||
|
||||
### Phase 1 — MVP ✅ DONE (2026-05-03)
|
||||
- [x] Скелет (config, normalization, database, base, scoring)
|
||||
- [x] Yandex.Maps парсер (Botasaurus + JS-скролл)
|
||||
- [x] CSV экспорт (utf-8-sig для Excel)
|
||||
- [x] CLI (main.py)
|
||||
- [x] Tier 2 enrichment (website analyzer — CMS / чат / запись / аналитика)
|
||||
- [x] Tier 3 enrichment (ЕГРЮЛ / Rusprofile + валидатор ФИО)
|
||||
- [x] Pipeline `--full`
|
||||
- [x] Геолокация (`--city` + `--district` + fallback на "Москва и МО")
|
||||
- [x] Цветные логи (colorlog)
|
||||
- [x] **DoD: 100+ уникальных лидов в CSV** ✅ (116 лидов)
|
||||
|
||||
### Phase 2 — Scale 🟢 ACTIVE
|
||||
- [x] **HH.ru парсер через Botasaurus** ✅ DONE 2026-05-04 — signal-запросы → employers как лиды
|
||||
- [x] **CRM-блок** ✅ DONE 2026-05-17 — БД-поля статусов/действий/реакций, таблица `outreach_events`, junction `lead_in_run`, Streamlit-приложение (`app/`), `launch_crm.bat`. Подробнее — D11 в `../DECISIONS.md`.
|
||||
- [x] **Расширенный сбор контактов** ✅ DONE 2026-05-17 — Я.Карты сканируют тело карточки на email/доп.телефоны, website-analyzer сохраняет найденные email/телефоны в лида через `update_lead_contacts`. Флаг `--rescan-sites` для перепрогона старых лидов. Подробнее — D12.
|
||||
- [x] **CSV экспорт двух уровней** ✅ DONE 2026-05-17 — `export_run()` (per-прогон с шапкой в `exports/YYYY-MM/`) + `export_master()` (snapshot всей БД).
|
||||
- [x] **DDG Find-Sites** ✅ DONE 2026-05-20 — `contacts_finder.py`: поиск сайта через DuckDuckGo HTML + верификация (INN/fuzzy-name + blocklist 80+ агрегаторов). Подробнее — D16.
|
||||
- [x] **DaData primary + Rusprofile fallback** ✅ DONE 2026-05-20 — `dadata_enricher.py`: индексирует бренды (находит «ВкусВилл»/«Кофемания»), kladr_id для приоритета по городу. Success rate вырос с ~10% до ~29.6%. Подробнее — D14.
|
||||
- [x] **INN из footer сайта (152-ФЗ)** ✅ DONE 2026-05-20 — `analyze_website` пробует 7 contact-страниц, тащит ИНН/ОГРН/КПП → авто-`enrich_egrul_by_inn` (точно). Подробнее — D16.
|
||||
- [x] **Blacklist крупных компаний** ✅ DONE 2026-05-20 — `blacklist.py`: 143 лида с `outreach_status='excluded'`. Три стратегии: точные имена / keyword-fragments / prefix. Подробнее — D15.
|
||||
- [x] **WB парсер удалён** ✅ DONE 2026-05-20 — селлеры маркетплейса не наша ЦА. Подробнее — D17.
|
||||
- [ ] **🔔 Скоринг: SPA-слепота** — `website_analyzer` (requests) не исполняет JS → у JS-SPA сайтов (React/Vue/Next, напр. `niqa.ru` = 759 байт каркас) видит пустышку → ложные «всё отсутствует» → завышенный балл. Фикс: тонкий каркас → сигналы `unknown`, не `0` (+ опц. вес авто-визитки 0.5→1.5). Кейс: Niqa должен быть < Кафе Гурмэ. Решения ①②③ — см. DECISIONS/память.
|
||||
- [ ] **🔔 Финансы (оборот + численность)** — обвяз готов (колонки `employee_count`/`revenue`, шаг `--enrich-finance`, CRM-вывод), но бесплатный DaData финансы НЕ отдаёт (проверено: всё null). Нужен источник: Rusprofile-добор (проверить первым) / ФНС открытые данные bulk / платный DaData. `bo.nalog.ru` = JS-SPA с токеном (простой REST не идёт). При рабочем источнике — сбросить `finance_checked_at`.
|
||||
- [ ] **Свежие ИП** — Rusprofile «Новые компании» как самостоятельный источник лидов
|
||||
- [ ] 2ГИС парсер — в доработке (постпонировано на потом)
|
||||
- [ ] VK парсер — в доработке (постпонировано на потом)
|
||||
- [ ] Avito парсер (anti-bot самый агрессивный)
|
||||
- [ ] Telegram парсер (TGStat API)
|
||||
- [ ] SMTP-валидация email
|
||||
- [ ] LLM Pain Detection (gpt-4o-mini анализ отзывов Я.Карт)
|
||||
- [ ] Migration SQLite → Supabase
|
||||
|
||||
### Phase 3 — Outreach ⬜ pending
|
||||
- [x] Ручной CRM-режим через Streamlit-приложение ✅ DONE в Phase 2
|
||||
- [ ] n8n pipeline для авто-рассылок
|
||||
- [ ] Telegram bot для управления
|
||||
|
||||
### Phase 4 — Monitor ⬜ pending
|
||||
- [ ] Cron еженедельного парсинга
|
||||
- [ ] Дельта-синхронизация (только новые)
|
||||
- [ ] Telegram-отчёты Теме
|
||||
|
||||
---
|
||||
|
||||
## Anti-bot настройки
|
||||
|
||||
- Задержки между запросами: `random.uniform(MIN_DELAY, MAX_DELAY)` — по умолчанию 2-7 сек
|
||||
- Между категориями: `random.uniform(CATEGORY_PAUSE_MIN, CATEGORY_PAUSE_MAX)` — по умолчанию 30-60 сек
|
||||
- Botasaurus имитирует браузер на уровне fingerprint
|
||||
- При срабатывании captcha N раз — стоп с алертом (`config.MAX_BLOCKED_TRIES = 3`)
|
||||
- **Tor / прокси не используем** (D4 в `../DECISIONS.md`). Подключим если домашний IP начнёт лочить.
|
||||
|
||||
---
|
||||
|
||||
## Внешние ссылки
|
||||
|
||||
- **Botasaurus:** https://github.com/omkarcloud/botasaurus
|
||||
- **Yandex.Maps URL формат:** `https://yandex.ru/maps/{geo_id}/{slug}/search/{query}/`
|
||||
- **Rusprofile (ЕГРЮЛ fallback):** https://www.rusprofile.ru/
|
||||
- **DaData Suggestions API (primary ЕГРЮЛ):** https://dadata.ru/api/suggest/party/ (10K/день free)
|
||||
- **DuckDuckGo HTML (find-sites):** https://html.duckduckgo.com/html/
|
||||
- **VK API:** https://dev.vk.com/reference (постпонировано)
|
||||
- **2ГИС API:** https://docs.2gis.com/ru/api/search/3.0 (постпонировано)
|
||||
- **HH:** через Botasaurus браузер (api.hh.ru 403'ит)
|
||||
|
||||
---
|
||||
|
||||
*Версия: v7.1 (Прогрессивный ICP-штраф за зрелость) | Обновлено: 2026-06-01*
|
||||
*Changelog:*
|
||||
*- v7.1 (2026-06-01): ICP-гейт стал прогрессивным — штраф за отзывы×рейтинг (D18): линейная шкала отзывов, рейтинг усиливает, база гарантирует штраф сетям. Порог hot 7→6. Калибровка под цель «Кафе Гурмэ=6».*
|
||||
*- v7 (2026-06-01): Скоринг переписан на v5 — «решаемая нами боль» × ICP (D17). Категорийная релевантность детекторов (booking/social по белым спискам, дефолт консервативный). Фикс потери URL сайта в ЕГРЮЛ-обогащении + восстановление 29 лидов. Рейтинг 4.5+ больше не боль. Instagram исключён (бан РФ). Новые колонки `pain_products`/`band`/`diagnostic_coverage`. CRM: бэнды + «с чем заходить» + фильтр по продукту. Детект конструкторов по домену (Я.Бизнес `.clients.site`). Инструмент `audit_scores.py`.*
|
||||
*- v6 (2026-05-21): WB парсер удалён (D17). DaData primary + Rusprofile fallback (D14). DDG Find-Sites (`contacts_finder.py` D16). INN из footer сайта (152-ФЗ). Blacklist крупных компаний (`blacklist.py` D15, 143 leads excluded). `--full` теперь включает `--find-sites` + `--hh-enrich-websites` если source=hh.*
|
||||
*- v5 (2026-05-17): CRM-блок + расширенный сбор контактов (Я.Карты body-text + website email/phones).*
|
||||
Reference in New Issue
Block a user