All checks were successful
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 1m2s
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
app.services.scrapers.{cian,cian_detail,yandex_realty,domclick,domclick_exceptions,
avito_houses} имели 0 runtime-импортёров после E1 (scrape_pipeline.py удалён,
#2405) — admin.py/cian_history_backfill.py уже на scraper_kit.providers.*.
Удаление:
- 6 модулей целиком (avito_houses.py, cian.py, cian_detail.py, domclick.py,
domclick_exceptions.py, yandex_realty.py)
- 3 осиротевших dev-sweep скрипта (local-sweep-ekb{,-cian,-yandex}.py) —
эксклюзивно драйвили удалённые легаси-скраперы, kit-путь их заменяет
Тесты (18 файлов DELETE — эксклюзивно тестировали удалённые модули или были
чистым legacy-vs-kit parity, чей легаси-эталон исчез):
test_cian_detail.py, test_cian_serp_scraper.py, tests/scrapers/test_cian_exhaustive.py,
test_yandex_realty_serp.py, tests/scrapers/test_domclick.py,
test_avito_houses_dedup.py, test_avito_houses_parse.py,
test_houses_rating_filter_entries_1789.py, test_scraper_kit_domclick_golden_parity.py,
tests/scrapers/test_admin_{avito,cian,yandex}_kit_parity.py.
CONVERT (файл покрывает СМЕШАННЫЙ live+legacy набор — вырезаны только
секции удалённых модулей, live-покрытие сохранено):
- test_scraper_kit_cian_golden_parity.py — SERP/`_format_address`/detail
(cian.py/cian_detail.py) убраны; extract_state (cian_state_parser, live)/
valuation (cian_valuation, live)/newbuilding (cian_newbuilding, live)/
no-app-imports guard — остались
- test_scraper_kit_yandex_golden_parity.py — SERP `_entity_to_lot`/
`_parse_gate_json`/`_build_url` (yandex_realty) убраны; normalize_house_type
(live)/newbuilding parse (yandex_newbuilding, live)/no-app-imports guard —
остались
- test_scraper_kit_bisection_parity.py — cian/domclick/yandex-ветки убраны;
avito-ветка (live, единственная оставшаяся легаси-бисекция) — осталась
полностью, включая full-seed-grid
- test_scraper_kit_group_c_backfill_kit_parity.py — cian fetch_detail
browser_fetcher parity-тест убран; avito/house_imv_backfill/
cian_history_backfill/ekb_geoportal тесты — остались
- test_scraper_kit_pricehistory_session_parity.py — fetch_detail
config-injection/proxy-wiring тесты (cian_detail) убраны; extract_state
(cian_state_parser, live)/ScrapedLot (base, live) parity — остались
- test_yandex_scrapers_delay_wiring.py — yandex_realty delay-тест убран
(kit-эквивалент уже покрыт test_kit_serp_proxy_pool.py); yandex_newbuilding
(live)/yandex_detail/yandex_valuation (kit) тесты — остались
- test_scraper_proxy.py — yandex_realty BrowserFetcher-тест убран (redundant
с test_kit_serp_proxy_pool.py::test_yandex_serp_no_pool_by_default)
RETARGET на kit (символ живёт, только источник поменялся):
- test_domclick_sweep.py — `_map_item` → scraper_kit.providers.domclick.serp
- test_yandex_city_sweep.py — DEFAULT_PRICE_RANGES/ROOM_PATH →
scraper_kit.providers.yandex.serp
- test_pipeline_browser_routing.py — fetch_house_catalog →
scraper_kit.providers.avito.houses (+ KitAvitoBlockedError для firewall-теста)
- test_snapshot_writer.py — DetailEnrichment/save_detail_enrichment →
scraper_kit.providers.cian.detail (byte-identical strangler-копия)
- test_backfill_wave2.py — DetailEnrichment → scraper_kit.providers.cian.detail
- tests/scrapers/test_avito_unix_date_tz_consistency.py — avito_houses →
scraper_kit.providers.avito.houses (houses-ветка _unix_to_date/RUS_MONTHS
consolidation-проверки)
Stale-комментарии поправлены в cian_price_history.py, tests/scrapers/
test_domclick_detail.py (описывали ещё-живой domclick.py/domclick_exceptions.py).
grep -rn scrapers\.\(cian\|cian_detail\|yandex_realty\|domclick\|domclick_exceptions\|
avito_houses\) app/ scripts/ packages/ — только historical footnotes
("strangler-копия app.services.scrapers.X", past-tense "удалён/deleted").
Backend suite: 2811 passed, 6 skipped, 1 failed (known flake test_search_cache_hit
#2208, не регрессия). ruff check чист на всех изменённых файлах.
123 lines
5.8 KiB
Python
123 lines
5.8 KiB
Python
"""Golden-parity: `scraper_kit.providers.yandex.*` парсинг ≡ ещё живым
|
||
`app.services.scrapers.yandex_*`.
|
||
|
||
Strangler-инвариант (#2133): новая scraper_kit-копия yandex-провайдера должна давать
|
||
БАЙТ-ИДЕНТИЧНЫЙ результат парсинга старому боевому коду на одинаковом входе.
|
||
Развязка (settings→ScraperConfig, get_scraper_delay→delay_provider,
|
||
house_type_normalizer→providers.yandex.shared, base/browser_fetcher/helpers→scraper_kit.*)
|
||
НЕ меняет распарсенные данные.
|
||
|
||
`app.services.scrapers.yandex_realty` удалён (#2397 Part E2, 0 runtime-импортёров).
|
||
SERP `_entity_to_lot`/`_parse_gate_json`/`_build_url` parity-тесты (сравнение с
|
||
`YandexRealtyScraper`) удалены вместе с ним (легаси-эталон исчез).
|
||
|
||
Оставшееся сравнивает kit ≡ ЕЩЁ ЖИВЫЕ легаси-модули:
|
||
- `normalize_house_type` — `app.services.scrapers.house_type_normalizer` (живой);
|
||
- newbuilding `parse` (ЖК-лендинг → YandexNewbuildingInfo) —
|
||
`app.services.scrapers.yandex_newbuilding` (живой);
|
||
- strangler-guard: kit-провайдер не импортирует `app.*`.
|
||
|
||
Легаси `app.services.scrapers.yandex_detail` / `yandex_valuation` удалены (#2277
|
||
финальный шаг scraper_kit-миграции, 0 runtime importers) — detail/valuation
|
||
parity-проверки убраны отсюда ранее. Kit-side coverage для detail/valuation
|
||
`.parse()` сохранена без изменений в `tests/test_yandex_detail.py` /
|
||
`tests/test_yandex_detail_structural.py` / `tests/test_yandex_valuation.py` /
|
||
`tests/test_yandex_valuation_save.py` (переведены на kit-эквиваленты тем же PR).
|
||
|
||
Идентичность результата = kit-парсер верно скопирован (развязка не задела логику).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import inspect
|
||
import os
|
||
|
||
# Старый app.services.scrapers.house_type_normalizer/yandex_newbuilding импортирует
|
||
# app.core.config.settings=Settings(), которому нужен DATABASE_URL. Офлайн-парсинг
|
||
# БД не трогает — фиктивный DSN достаточен.
|
||
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost/test_db")
|
||
|
||
# НОВЫЙ (scraper_kit) и СТАРЫЙ (app.services.scrapers) yandex-парсеры — оба в одном
|
||
# тесте; ruff сортирует scraper_kit (first-party) выше app-импортов.
|
||
from scraper_kit.providers.yandex.newbuilding import (
|
||
YandexNewbuildingScraper as NewNewbuildingScraper,
|
||
)
|
||
from scraper_kit.providers.yandex.shared import normalize_house_type as new_normalize_house_type
|
||
|
||
from app.services.scrapers.house_type_normalizer import (
|
||
normalize_house_type as old_normalize_house_type,
|
||
)
|
||
from app.services.scrapers.yandex_newbuilding import (
|
||
YandexNewbuildingScraper as OldNewbuildingScraper,
|
||
)
|
||
|
||
# ── normalize_house_type parity ──────────────────────────────────────────────
|
||
|
||
|
||
def test_normalize_house_type_parity() -> None:
|
||
"""SCREAMING / camelCase / канон / мусор → идентичный результат."""
|
||
raws = [
|
||
None,
|
||
"",
|
||
"MONOLIT",
|
||
"BRICK",
|
||
"PANEL",
|
||
"MONOLIT_BRICK",
|
||
"monolithBrick",
|
||
"gasSilicateBlock",
|
||
"monolith_brick",
|
||
"monolith",
|
||
"stalin",
|
||
"OTHER",
|
||
" MONOLIT ",
|
||
]
|
||
for raw in raws:
|
||
assert old_normalize_house_type(raw) == new_normalize_house_type(raw)
|
||
|
||
|
||
# ── newbuilding parse parity (синтетика) ─────────────────────────────────────
|
||
|
||
_NEWBUILDING_HTML = """
|
||
<html><body>
|
||
<h1>ЖК «Татлин»</h1>
|
||
Екатеринбург, ул. Черепанова 4.3 из 5 1505 оценок Смотреть все 353 отзыва
|
||
56.855312 60.576668
|
||
<h2>О комплексе</h2>
|
||
<div>Дом комфорт-класса, монолитный. Введён в эксплуатацию в июнь 2023.
|
||
35-этажные башни на участке 1,5 га. Три корпуса.</div>
|
||
</body></html>
|
||
"""
|
||
|
||
|
||
def test_newbuilding_parse_parity() -> None:
|
||
"""ЖК-лендинг → YandexNewbuildingInfo идентичен."""
|
||
old_scraper = OldNewbuildingScraper()
|
||
new_scraper = NewNewbuildingScraper()
|
||
old_res = old_scraper.parse(
|
||
_NEWBUILDING_HTML,
|
||
jk_slug="tatlin",
|
||
jk_id="1592987",
|
||
source_url="https://realty.yandex.ru/ekaterinburg/kupit/novostrojka/tatlin-1592987/",
|
||
)
|
||
new_res = new_scraper.parse(
|
||
_NEWBUILDING_HTML,
|
||
jk_slug="tatlin",
|
||
jk_id="1592987",
|
||
source_url="https://realty.yandex.ru/ekaterinburg/kupit/novostrojka/tatlin-1592987/",
|
||
)
|
||
assert old_res.model_dump() == new_res.model_dump()
|
||
|
||
|
||
# ── strangler-guard: kit-провайдер не импортирует app.* ──────────────────────
|
||
|
||
|
||
def test_kit_yandex_has_no_app_imports() -> None:
|
||
"""Ни один модуль scraper_kit.providers.yandex.* не импортирует app.* (развязка)."""
|
||
from scraper_kit.providers.yandex import detail, newbuilding, serp, shared, valuation
|
||
|
||
for mod in (serp, detail, valuation, newbuilding, shared):
|
||
source = inspect.getsource(mod)
|
||
for line in source.splitlines():
|
||
stripped = line.strip()
|
||
assert not stripped.startswith("from app"), f"{mod.__name__}: {line!r}"
|
||
assert not stripped.startswith("import app"), f"{mod.__name__}: {line!r}"
|