Регулярный сбор ДомКлика по Москве и области #3545
4 changed files with 96 additions and 2 deletions
|
|
@ -1008,6 +1008,12 @@ def build_product_handlers(ctx: SchedulerContext) -> dict[str, Handler]:
|
||||||
),
|
),
|
||||||
# Override kit-native (#3264) — инъекция куки сессии, см. докстринг job'а выше.
|
# Override kit-native (#3264) — инъекция куки сессии, см. докстринг job'а выше.
|
||||||
"domclick_city_sweep": Handler(_job_domclick_city_sweep, "domclick_city_sweep"),
|
"domclick_city_sweep": Handler(_job_domclick_city_sweep, "domclick_city_sweep"),
|
||||||
|
# Вне ЕКБ каждый регион получает СВОЙ source ("domclick_city_sweep_moskva"),
|
||||||
|
# потому что scrape_schedules.source UNIQUE. Wildcard обязан стоять и здесь,
|
||||||
|
# а не только в _default_kit_handlers: kit-native тело ходит БЕЗ кук сессии,
|
||||||
|
# и московская строка молча резолвилась бы на версию без инъекции (#3264),
|
||||||
|
# то есть на ту, что упирается в QRATOR. Ключ продукта перекрывает kit-native.
|
||||||
|
"domclick_city_sweep_*": Handler(_job_domclick_city_sweep, "domclick_city_sweep_*"),
|
||||||
"cadastral_geo_match": Handler(_job_cadastral_geo_match, "cadastral_geo_match"),
|
"cadastral_geo_match": Handler(_job_cadastral_geo_match, "cadastral_geo_match"),
|
||||||
"osm_poi_ekb_refresh": Handler(_job_osm_poi_ekb_refresh, "osm_poi_ekb_refresh"),
|
"osm_poi_ekb_refresh": Handler(_job_osm_poi_ekb_refresh, "osm_poi_ekb_refresh"),
|
||||||
"dtp_stat_refresh": Handler(_job_dtp_stat_refresh, "dtp_stat_refresh"),
|
"dtp_stat_refresh": Handler(_job_dtp_stat_refresh, "dtp_stat_refresh"),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
-- 306_scrape_schedules_seed_domclick_msk.sql
|
||||||
|
-- Регулярный сбор ДомКлика по Москве (77) и Московской области (50).
|
||||||
|
--
|
||||||
|
-- Apply after: 305_scrape_schedules_seed_msk_city_sweeps.sql
|
||||||
|
--
|
||||||
|
-- WHY:
|
||||||
|
-- Миграция 305 завела регулярный сбор 77/50 для avito / cian / yandex и прямо
|
||||||
|
-- отложила ДомКлик: "у него нет wildcard-хендлера, а единственная возможная
|
||||||
|
-- строка source='domclick_city_sweep' занята Екатеринбургом. Нужна правка кода".
|
||||||
|
-- Правка сделана в этом же PR — "domclick_city_sweep_*" добавлен в
|
||||||
|
-- _default_kit_handlers (scraper-kit) И в build_product_handlers (app-side
|
||||||
|
-- override с инъекцией куки Sber ID, #3264). Эта миграция — вторая половина.
|
||||||
|
--
|
||||||
|
-- Цена вопроса: ДомКлик даёт ≈23% корпуса обоих регионов. Без него свежий срез
|
||||||
|
-- 77/50 теряет почти четверть массы, а вместе с ней — часть домов, по которым
|
||||||
|
-- вообще существуют аналоги.
|
||||||
|
--
|
||||||
|
-- Гео-скоуп задаётся ТОЛЬКО region_code: профиль региона
|
||||||
|
-- (providers/domclick/serp.py DOMCLICK_GEO_PROFILES) несёт address GUID,
|
||||||
|
-- страницу прогрева QRATOR, bbox гарда и имя города. Профили 77 и 50 заведены и
|
||||||
|
-- проверены живыми запросами 12-15.09.2026 (#3533). city_id здесь НЕ задаётся
|
||||||
|
-- намеренно: он vestigial — run_domclick_city_sweep штампует город из профиля,
|
||||||
|
-- а не из city_id (pipeline.py, комментарий у save_listings). Неизвестный
|
||||||
|
-- region_code роняет прогон ДО первого запроса, тихого отката на ЕКБ нет.
|
||||||
|
--
|
||||||
|
-- pages_per_anchor=100 — как у ЕКБ-строки: ДомКлик citywide, anchor-loop у него
|
||||||
|
-- отсутствует (fetch_around → NotImplementedError), "pages" здесь означает
|
||||||
|
-- глубину пагинации на комнатный бакет, а не число якорей. Москва отдаёт ≈23 690
|
||||||
|
-- лотов вторички, то есть глубину надо брать полную, а не свипную.
|
||||||
|
--
|
||||||
|
-- Окна 0-3 и 3-6 UTC: 305 занял 16-23, ЕКБ-домклик сидит в 3-6. Ставим Москву в
|
||||||
|
-- 0-3 (пусто), область — в 3-6 рядом с ЕКБ; прогоны всё равно сериализуются
|
||||||
|
-- планировщиком, окно лишь ограничивает старт.
|
||||||
|
--
|
||||||
|
-- interval_days=3 — как у остальных строк 305: корпус живёт 14 суток
|
||||||
|
-- (LISTINGS_FRESH_DAYS), трёхсуточный такт даёт четырёхкратный запас на
|
||||||
|
-- пропуски из-за QRATOR-банов.
|
||||||
|
--
|
||||||
|
-- ВКЛЮЧЕНЫ СРАЗУ (enabled=true) по той же причине, что и 305: регионы уходят
|
||||||
|
-- тестировщикам, корпус истекает.
|
||||||
|
--
|
||||||
|
-- ИДЕМПОТЕНТНОСТЬ: ON CONFLICT (source) DO NOTHING — повторный прогон no-op и не
|
||||||
|
-- сбрасывает enabled, если строку потом выключили вручную.
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
SET LOCAL lock_timeout = '5s';
|
||||||
|
|
||||||
|
INSERT INTO scrape_schedules (source, enabled, window_start_hour, window_end_hour, default_params)
|
||||||
|
VALUES
|
||||||
|
('domclick_city_sweep_moskva', true, 0, 3,
|
||||||
|
'{"region_code": 77, "pages_per_anchor": 100, "interval_days": 3,
|
||||||
|
"request_delay_sec": 6}'::jsonb),
|
||||||
|
('domclick_city_sweep_moskovskaya_oblast', true, 3, 6,
|
||||||
|
'{"region_code": 50, "pages_per_anchor": 100, "interval_days": 3,
|
||||||
|
"request_delay_sec": 6}'::jsonb)
|
||||||
|
ON CONFLICT (source) DO NOTHING;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
@ -108,6 +108,7 @@ _KIT_NATIVE_SOURCES = {
|
||||||
"cian_city_sweep_*",
|
"cian_city_sweep_*",
|
||||||
"cian_full_load",
|
"cian_full_load",
|
||||||
"domclick_city_sweep",
|
"domclick_city_sweep",
|
||||||
|
"domclick_city_sweep_*",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -180,9 +181,11 @@ def test_real_build_product_handlers_covers_all_scheduled_sources() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_kit_native_handler_set() -> None:
|
def test_kit_native_handler_set() -> None:
|
||||||
"""Ровно 11 kit-native sweep-обработчиков зарегистрированы встроенно (8 + 3 oblast
|
"""Ровно 12 kit-native sweep-обработчиков зарегистрированы встроенно (8 + 4 wildcard).
|
||||||
|
|
||||||
"*_city_sweep_*" wildcard, #179).
|
"*_city_sweep_*" — per-city/per-region source'ы вне ЕКБ: avito/cian/yandex завела
|
||||||
|
oblast B1 rollout (region 66, #179), domclick — регулярный сбор 77/50 (миграция 306),
|
||||||
|
у него суффикс несёт регион, а не город.
|
||||||
"""
|
"""
|
||||||
registry = build_registry()
|
registry = build_registry()
|
||||||
assert set(registry) == _KIT_NATIVE_SOURCES
|
assert set(registry) == _KIT_NATIVE_SOURCES
|
||||||
|
|
@ -190,6 +193,31 @@ def test_kit_native_handler_set() -> None:
|
||||||
assert resolve_handler(src, registry).log_name == src
|
assert resolve_handler(src, registry).log_name == src
|
||||||
|
|
||||||
|
|
||||||
|
def test_domclick_region_wildcard_resolves_to_product_handler() -> None:
|
||||||
|
"""'domclick_city_sweep_moskva' резолвится на ПРОДУКТОВЫЙ handler, не на kit-native.
|
||||||
|
|
||||||
|
Развилка не косметическая: kit-native тело ходит на BFF без кук сессии Sber ID, и
|
||||||
|
московская строка, севшая на него, упёрлась бы в QRATOR ровно так, как было до #3264.
|
||||||
|
Продуктовый ключ обязан перекрывать kit-native и на wildcard, а не только на точном
|
||||||
|
'domclick_city_sweep' — иначе перекрытие есть у ЕКБ и молча отсутствует у регионов.
|
||||||
|
"""
|
||||||
|
from app.services.product_handlers import (
|
||||||
|
_job_domclick_city_sweep as product_job,
|
||||||
|
)
|
||||||
|
from app.services.product_handlers import build_product_handlers
|
||||||
|
|
||||||
|
# ctx=None безопасен — job'ы его не замыкают при сборке dict (см. тест выше).
|
||||||
|
registry = build_registry(build_product_handlers(ctx=None)) # type: ignore[arg-type]
|
||||||
|
for src in (
|
||||||
|
"domclick_city_sweep",
|
||||||
|
"domclick_city_sweep_moskva",
|
||||||
|
"domclick_city_sweep_moskovskaya_oblast",
|
||||||
|
):
|
||||||
|
handler = resolve_handler(src, registry)
|
||||||
|
assert handler is not None, src
|
||||||
|
assert handler.job is product_job, src
|
||||||
|
|
||||||
|
|
||||||
def test_unknown_source_resolves_none() -> None:
|
def test_unknown_source_resolves_none() -> None:
|
||||||
registry, _ = _build_recording_registry()
|
registry, _ = _build_recording_registry()
|
||||||
assert resolve_handler("totally_unknown_source", registry) is None
|
assert resolve_handler("totally_unknown_source", registry) is None
|
||||||
|
|
|
||||||
|
|
@ -1296,6 +1296,7 @@ def _default_kit_handlers() -> dict[str, Handler]:
|
||||||
"cian_city_sweep_*": Handler(_job_cian_city_sweep, "cian_city_sweep_*"),
|
"cian_city_sweep_*": Handler(_job_cian_city_sweep, "cian_city_sweep_*"),
|
||||||
"cian_full_load": Handler(_job_cian_full_load, "cian_full_load"),
|
"cian_full_load": Handler(_job_cian_full_load, "cian_full_load"),
|
||||||
"domclick_city_sweep": Handler(_job_domclick_city_sweep, "domclick_city_sweep"),
|
"domclick_city_sweep": Handler(_job_domclick_city_sweep, "domclick_city_sweep"),
|
||||||
|
"domclick_city_sweep_*": Handler(_job_domclick_city_sweep, "domclick_city_sweep_*"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue