f78f35fb3f
Парсер лидов МБ РФ: Яндекс.Карты + HH.ru + обогащение DaData/ЕГРЮЛ/Rusprofile + Streamlit CRM. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
279 lines
21 KiB
Python
279 lines
21 KiB
Python
"""Конфигурация парсера лидов.
|
||
|
||
Все настройки в одном месте. Меняем здесь — отражается во всех парсерах.
|
||
"""
|
||
|
||
# ───────────────────────────────────────────────────────────────────────
|
||
# Города и их 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 + (1−ICP_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
|