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:
Aks
2026-06-09 12:56:06 +03:00
commit f78f35fb3f
33 changed files with 9198 additions and 0 deletions
+278
View File
@@ -0,0 +1,278 @@
"""Конфигурация парсера лидов.
Все настройки в одном месте. Меняем здесь — отражается во всех парсерах.
"""
# ───────────────────────────────────────────────────────────────────────
# Города и их Yandex.Maps ID
# ───────────────────────────────────────────────────────────────────────
# Каждая точка парсинга (город / городской округ / регион):
# yandex_id — geo ID в Яндекс.Картах (https://yandex.ru/maps/{yandex_id}/{slug}/...)
# yandex_slug — slug в URL (moscow, mytishchi, ...). Декоративный — главное yandex_id
# vk_id — city ID в VK API (1=Москва, 2=СПб, ...)
# region — агрегатор для группировки в БД (Москва / Московская область / ...)
#
# Активный регион выбирается через CLI: python main.py --city "Мытищи"
# По умолчанию используется ACTIVE_CITY.
#
# Geo ID можно проверить, открыв https://yandex.ru/maps/{geo_id}/ в браузере.
CITIES = {
# ── Только проверенные значения (geo_id подтверждены) ───────────
"Москва": {"yandex_id": 213, "yandex_slug": "moscow", "vk_id": 1, "region": "Москва"},
"Москва и МО": {"yandex_id": 1, "yandex_slug": "moscow-and-moscow-oblast", "vk_id": 1, "region": "Москва и МО"},
"Санкт-Петербург": {"yandex_id": 2, "yandex_slug": "saint-petersburg", "vk_id": 2, "region": "Санкт-Петербург"},
# ── Любой другой город / район — через --district ───────────────
# Парсер автоматически использует "Москва и МО" + название как район.
# Пример:
# python main.py --full --city "Москва и МО" --district "Химки" --category "стоматология"
# python main.py --full --city "Москва и МО" --district "Мытищи" --category "автосервис"
# python main.py --full --city "Москва" --district "Митино" --category "салон красоты"
#
# Чтобы добавить точный город — найди его geo_id через браузер:
# 1. Открой https://yandex.ru/maps/
# 2. Найди город в поиске → кликни на первый результат
# 3. URL станет вида: https://yandex.ru/maps/{geo_id}/{slug}/?...
# 4. Скопируй сюда:
#
# "Мытищи": {"yandex_id": 10743, "yandex_slug": "mytishchi", "vk_id": 1, "region": "Московская область"},
}
# Активный регион (можно переопределить через CLI --city)
ACTIVE_CITY = "Москва"
# Категории — фокус-ЦА 44AS: локальный сервисный бизнес с онлайн-записью и
# отзывами. Бьёт прямо в наши продукты: P3 AI Reputation (отзывы Я.Карт/2ГИС),
# P4 AI Consultant (входящие + онлайн-запись), P12 AI SMM (Instagram/контент).
CATEGORIES = [
# 🎯 Бьюти — главная цель (онлайн-запись = боль, Instagram = канал продаж, отзывы решают)
"салон красоты",
"барбершоп",
"ногтевой сервис",
"студия массажа",
"косметология",
"спа-салон",
# 🍽 HoReCa — отзывы = выручка, бронь столиков, визуал блюд
"кафе",
"ресторан",
# 🏥 Клиники / запись — доверие через отзывы + онлайн-запись
"стоматология",
"фитнес-клуб",
]
# ───────────────────────────────────────────────────────────────────────
# HH.ru — signal-запросы (компании ищут "руки" = нет автоматизации)
# ───────────────────────────────────────────────────────────────────────
# Принцип: если компания ищет ЭТИ должности — у неё нет CRM / нет
# онлайн-записи / ручная коммуникация. Это +3 к hh_signal в скоринге.
HH_SIGNAL_QUERIES = [
# Ручная коммуникация → нет CRM / нет автоответов
"оператор ПК",
"оператор колл-центра",
"оператор технической поддержки",
"менеджер чата",
"менеджер по продажам без CRM",
# Личный помощник / административка → "помоги мне разобраться"
"помощник руководителя",
"ассистент руководителя",
"офис-менеджер",
# Запись клиентов руками → нет онлайн-booking
"администратор записи",
"администратор салона красоты",
"администратор клиники",
"ресепшн",
# Бухгалтерия "руками" → не автоматизирован документооборот
"бухгалтер 1С",
"помощник бухгалтера",
]
# Период поиска (дней) — свежие вакансии
HH_PERIOD_DAYS = 30
HH_MAX_PAGES_PER_QUERY = 5 # 5 страниц × 100 = до 500 вакансий на запрос
# ───────────────────────────────────────────────────────────────────────
# Anti-bot задержки (секунды)
# 2026-05-18: снижены ~30% после успешного прогона 2634 лидов без блокировок.
# Если домашний IP начнёт ловить captcha — поднять обратно к 2.0/7.0 + 30/60.
# ───────────────────────────────────────────────────────────────────────
MIN_DELAY = 1.5 # минимум между запросами
MAX_DELAY = 4.0 # максимум между запросами
CATEGORY_PAUSE_MIN = 15 # пауза между категориями
CATEGORY_PAUSE_MAX = 30
# ───────────────────────────────────────────────────────────────────────
# Лимиты безопасности
# ───────────────────────────────────────────────────────────────────────
MAX_BLOCKED_TRIES = 3 # сколько раз получить captcha → останавливаемся
MAX_SCROLLS = 15 # максимум скроллов списка Я.Карт
MAX_CARDS_PER_CATEGORY = 100 # сколько карточек открывать на 1 категорию
# ───────────────────────────────────────────────────────────────────────
# Пути
# ───────────────────────────────────────────────────────────────────────
DB_PATH = "leads.db"
EXPORT_DIR = "exports"
# ───────────────────────────────────────────────────────────────────────
# Скоринг лидов v5 — «решаемая нами боль» (шкала 0-10)
# ───────────────────────────────────────────────────────────────────────
# Семантика (решение 2026-06-01): score = есть ли у компании проблемы,
# которые закрывают НАШИ продукты, и насколько остро.
# score = pain(решаемая боль) × icp_fit(наш ли это размер)
# Не дозвонибельность, не «качество лида» — только боль под продукты 44AS.
#
# Логика в scoring.py:
# • каждый детектор боли привязан к продукту (P-код) и теме;
# • внутри темы сигналы агрегируются с насыщением (max + k·остальные),
# чтобы коррелированные признаки не складывались линейно;
# • сумма тем → raw_pain, нормируется к 0-10 через PAIN_NORM;
# • ICP-гейт множителем топит крупняк/премиум (см. ICP_* ниже);
# • «уже автоматизирован» отдельно НЕ гейтим — у такого лида просто нет
# болевых дыр, severity→0 естественно.
#
# Веса = бизнес-приоритет (severity в «сырых» баллах). Крутим здесь.
SCORE_WEIGHTS = {
# ── Запись / входящие → P4 AI-Consultant, P1 Text Agent ──────────
"no_online_booking": 2.0, # нет онлайн-записи (для услуг — критично)
"no_live_chat": 1.0, # нет онлайн-чата → входящие теряются
# ── Репутация → P3 AI-Reputation (континуум по рейтингу) ─────────
"rating_very_low": 2.0, # avg < 3.5
"rating_low": 1.5, # 3.5 ≤ avg < 4.0
"rating_mid": 1.0, # 4.0 ≤ avg < 4.5 (4.5+ = здоровая репутация, не боль)
"few_reviews": 1.0, # < 10 отзывов — репутацией не занимаются
"some_reviews": 0.5, # 10..30 отзывов
# ── Веб → P10 Smart Web ──────────────────────────────────────────
"no_website": 1.5, # нет сайта вовсе
"site_dead": 2.5, # сайт не отвечает / 404 (платят, не работает)
"site_constructor": 0.5, # tilda/wix И только для малого бизнеса (см. scoring)
# ── Маркетинг → P12 AI SMM ───────────────────────────────────────
"no_social": 1.0, # ни vk, ни telegram, ни instagram
"no_analytics": 0.5, # не меряют трафик
# ── Инфраструктура → P2 AI-Office ────────────────────────────────
"free_email": 0.5, # корп.почта на mail.ru/gmail — нет своей
}
# Каждый детектор → тема (для тематической агрегации с насыщением).
PAIN_THEME = {
"no_online_booking": "booking", "no_live_chat": "booking",
"rating_very_low": "reputation", "rating_low": "reputation",
"rating_mid": "reputation",
"few_reviews": "reputation", "some_reviews": "reputation",
"no_website": "web", "site_dead": "web", "site_constructor": "web",
"no_social": "marketing", "no_analytics": "marketing",
"free_email": "infra",
}
# Детектор → наш продукт (для подсказки CRM «с чем заходить»).
PAIN_PRODUCT = {
"no_online_booking": "P4", "no_live_chat": "P4",
"rating_very_low": "P3", "rating_low": "P3", "rating_mid": "P3",
"few_reviews": "P3", "some_reviews": "P3",
"no_website": "P10", "site_dead": "P10", "site_constructor": "P10",
"no_social": "P12", "no_analytics": "P12",
"free_email": "P2",
}
# Человекочитаемые причины (для reasons в breakdown и CRM).
PAIN_REASON = {
"no_online_booking": "нет онлайн-записи",
"no_live_chat": "нет онлайн-чата",
"rating_very_low": "низкий рейтинг (<3.5)",
"rating_low": "слабый рейтинг (<4.0)",
"rating_mid": "средний рейтинг (<4.5)",
"few_reviews": "мало отзывов (<10)",
"some_reviews": "немного отзывов (<30)",
"no_website": "нет сайта",
"site_dead": "сайт не отвечает",
"site_constructor": "сайт на конструкторе",
"no_social": "нет соцсетей (VK/Telegram)",
"no_analytics": "нет веб-аналитики",
"free_email": "почта на бесплатном домене",
}
# ════════════════════════════════════════════════════════════════════
# 📌 КАТЕГОРИЙНАЯ РЕЛЕВАНТНОСТЬ ДЕТЕКТОРОВ — ЧИТАЙ ПРИ ДОБАВЛЕНИИ КАТЕГОРИИ
# ════════════════════════════════════════════════════════════════════
# Часть болей применима НЕ ко всем типам бизнеса:
# • «нет онлайн-записи» (P4) — только услуги с записью (салон, клиника,
# кафе, автосервис). Магазину / опту / бухгалтерии запись не нужна.
# • «нет соцсетей» (P12) — только B2C, где соцсети = канал продаж
# (бьюти, HoReCa, розница, фитнес). У B2B (бухгалтерия, стройка) — не боль.
# Универсальные боли (сайт, репутация, чат, аналитика, почта) применяются ВСЕГДА.
#
# Модель — БЕЛЫЕ СПИСКИ по ключевым словам (матч по подстроке в lead.category):
# детектор срабатывает ТОЛЬКО если категория попала в его множество.
# Неизвестная / новая категория по умолчанию НЕ получает booking/social —
# консервативно: лучше не начислить, чем ложно завысить (как было с кейсом
# «магазин одежды → нет онлайн-записи»).
#
# 👉 ДОБАВЛЯЕШЬ НОВУЮ КАТЕГОРИЮ (в CATEGORIES выше или новым --category)?
# Впиши её ключевое слово сюда:
# — есть запись клиентов? → в APPT_CATEGORIES
# — продаётся через соцсети? → в SOCIAL_SALES_CATEGORIES
# Чистый B2B без записи и соцпродаж — НЕ добавляй никуда (получит только
# универсальные боли — это правильно).
# ════════════════════════════════════════════════════════════════════
# Услуги с записью клиентов → применяется детектор «нет онлайн-записи» (P4).
APPT_CATEGORIES = {
"салон", "барбершоп", "ногт", "маникюр", "педикюр", "массаж", "косметолог",
"спа", "парикмахер", "эпиляц", "депиляц", "тату", "броу", "ресниц",
"стоматолог", "клиник", "медицин", "врач", "ветеринар", "груминг",
"фитнес", "йога", "пилатес", "танц", "бассейн", "студи",
"кафе", "ресторан", "бар", "кофейн", "пиццери", "суши", "кальян", "банкет",
"автосервис", "автомойка", "шиномонтаж", "детейлинг", "сервис", "ремонт",
"юридическ", "нотариус", "адвокат", "консультац",
}
# B2C, где соцсети = канал продаж → применяется детектор «нет соцсетей» (P12).
SOCIAL_SALES_CATEGORIES = {
"салон", "барбершоп", "ногт", "маникюр", "массаж", "косметолог", "спа",
"парикмахер", "тату", "броу", "ресниц", "студи",
"кафе", "ресторан", "бар", "кофейн", "пиццери", "суши", "кальян",
"магазин", "розниц", "бутик", "шоурум", "одежд", "обув", "цвет",
"украшен", "подарк", "парфюм",
"фитнес", "йога", "танц", "бассейн",
"стоматолог", "клиник", "космет",
"фото", "видео", "свадьб", "ивент", "праздник", "декор",
"автосервис", "детейлинг",
}
# Насыщение внутри темы: theme_value = max(severities) + k·sum(остальные).
# k < 1 → коррелированные сигналы одной темы не складываются линейно.
THEME_SATURATION = 0.4
# Нормировка raw_pain → шкала 0-10. PAIN_NORM = «практический максимум боли»
# у сильного малого лида (нет записи+чата + слабый сайт + нет соцсетей ≈ 4-5).
# Делим на него, чтобы такой лид попадал в hot, а шкала растягивалась.
PAIN_NORM = 5.0
# Hard cap для финального score
SCORE_MAX = 10
# ── ICP-гейт: ПРОГРЕССИВНЫЙ штраф за «зрелость» (отзывы × рейтинг) ────
# ЦА = малый/средний бизнес, которому нужна автоматизация. Чем больше отзывов
# И выше рейтинг — тем сильнее снижаем балл (процветающим/раскрученным мы менее
# нужны и труднее продать). Формула в scoring.icp_fit:
# icp = 1 ICP_PMAX · rf · (ICP_BASE + (1ICP_BASE)·gf)
# rf = min(1, отзывы / ICP_REVIEWS_FULL) — линейно по объёму
# gf = clamp((avg ICP_RATING_MIN)/(5 ICP_RATING_MIN)) — по рейтингу
# Отзывы штрафуют ВСЕГДА (доля ICP_BASE), высокий рейтинг усиливает до полного.
# Мало отзывов → множитель ≈1 (новый/борющийся бизнес сохраняет балл).
# На Я.Картах рейтинги зажаты 4.5-5.0, поэтому главный рычаг — объём отзывов,
# рейтинг лишь усиливает. Заменил прежний ступенчатый гейт (D18).
ICP_PMAX = 0.85 # макс. доля снижения (при отзывы≥FULL и avg=5.0)
ICP_REVIEWS_FULL = 6000 # отзывов для максимального review-фактора (усилен 2026-06-05: 10000→6000)
ICP_BASE = 0.65 # доля штрафа от объёма, не зависящая от рейтинга (усилен: 0.5→0.65)
ICP_RATING_MIN = 4.0 # ниже этого рейтинг не усиливает штраф
# ── Бэнды (для CRM-сортировки и outreach-очереди) ───────────────────
BAND_HOT = 6 # score >= 6 → 🔥 hot
BAND_WARM = 4 # 4..5 → 🟡 warm, ниже → ⚪ cold
HOT_LEAD_THRESHOLD = BAND_HOT # совместимость: get_stats / csv_export / CRM
# Полнота диагностики ниже порога → лид помечается «нужно обогащение».
MIN_COVERAGE = 0.5