feat(tradein): exhaustive Yandex realty load — adaptive room×price + concurrency #931
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#931
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/yandex-exhaustive-price-room"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Апгрейд Yandex Realty scraper до EXHAUSTIVE-загрузки по образцу Cian (#926 + #929). Адаптивное room×price партиционирование + параллельная пагинация + инкрементальный save + выделенный прокси + IP-rotation + endpoint.
Что сделано
cian_proxy_url):yandex_proxy_url_env(aliasYANDEX_PROXY_URL),yandex_proxy_rotate_url(YANDEX_PROXY_ROTATE_URL), propertyyandex_proxy_url = YANDEX_PROXY_URL or scraper_proxy_url.__aenter__переключёнscraper_proxy_url→yandex_proxy_url._extract_total_count,_rotate_ip,_fetch_page_html,fetch_all_secondary,_walk_price_range(рекурсивный адаптивный binary-split [lo,hi] с MIN_BRACKET guard, параллельная пагинация leaf черезasyncio.gather+Semaphore, captcha-aware probe →_rotate_ip+ 1 retry,on_bucketper leaf).fetch_around_multi_room/MAX_PAGES(city-sweep) НЕ тронуты.YandexFullLoadCounters+run_yandex_full_load(региональный, on_bucket →save_listingsper-bucket commit + heartbeat + cooperative cancel →mark_donepartial, неmark_failed).YandexFullLoadRequest(price_cap_per_bucket=500 ge100 le575, request_delay_sec=2.0 ge1 le15, concurrency=4 ge1 le8),POST /scrape/yandex-full-load+/{run_id}/cancel, source=yandex_full_load.⚠️ totalItems — UNVERIFIED, есть fallback
Yandex scraper парсит отрендеренный DOM (state «chunked + evaporates after hydration» per docstring).
_extract_total_countпробует 6 state-путей (pageData.pager.totalItems,…pager.total,searchResults.pager.totalItems,pager.totalItems,pager.total,pageData.searchQuery.total) + DOM-regex fallback. Поля не подтверждены на живой prod-выдаче. Когда total недоступен →_walk_price_rangeпереходит на paginate-until-empty (probe-HTML page=0 переиспользуется, страницы 1,2,… до пустой /max_pages_per_bucket). Это основной safety-net и наиболее вероятный реальный путь.# TODO(yandex-total)оставлен в коде. После первого prod-прогона стоит свериться с логамиtotalItems=…vspaginate-until-empty fallbackи при необходимости поправить пути.Параметры
studio, 1, 2, 3, 4+(slugsstudiya/odnokomnatnaya/dvuhkomnatnaya/trehkomnatnaya/4-i-bolee)._rotate_ipна probe (yandex_proxy_rotate_url or avito_proxy_rotate_url, GET changeip, sleep 9), 1 retry; внутри leaf-gather captcha-страница → skip.Деплой (оператору)
Прокси креды НЕ в коде — задать в
.env.runtime:YANDEX_PROXY_URL+YANDEX_PROXY_ROTATE_URL. Без них — fallback на общийscraper_proxy_url(не падает).Тесты
pytest -k "yandex or full_load": 304 passed, 1369 deselected. Существующие yandex-тесты не сломаны.Не запускалось против prod.
✅ APPROVE — зеркало одобренных #926/#929 паттернов, additive, cross-file lesson применён. Мержу.
Verified
max_pages_per_bucket=100, break на empty/captcha). ✓_rotate_ip+ 1 retry.gather+Semaphoreчто в одобренном #929 (нет race, asyncio single-thread); Yandex 0-indexed страницы обработаны (range(0, max_pages)).on_bucket→RuntimeError("cancelled")→except RuntimeErrorперед generic →mark_donepartial. Корректный порядок. ✓fetch_around_multi_room/MAX_PAGES) НЕ тронут;test_scraper_proxy.pyобновлён зеркаломyandex_proxy_url→ существующие proxy-тесты не ломаются;__all__+ admin import + config property (зеркало #923) — всё на месте.pytest -k yandex304 passed.🟡 Известное ограничение (не блокер — заведу follow-up)
_extract_total_countпути не верифицированы на живой Yandex-выдаче (state «chunked + evaporates»). Если он всегда вернёт None на проде → каждый бакет идёт fallback'ом без price-split → exhaustive-гарантия деградирует до ~575/room (5 комнатностей ≈ 2875 max), а не полное покрытие. Безопасно (не падает), но «exhaustive» обещание условно. TODO(yandex-total) в коде + operator-note. Завожу issue: сверитьtotalItems=…vspaginate-until-empty fallbackв логах после 1-го prod-прогона.Deploy:
YANDEX_PROXY_URL/YANDEX_PROXY_ROTATE_URLв.env.runtime(fallback наscraper_proxy_urlбез них). Squash-merge + delete branch.Добавлен 2-й коммит — checkpoint/resume (
3bcfb6b), зеркало Cian #932. Refs #930 (Yandex-половина; Cian-половина = #932).Что добавлено поверх exhaustive-load:
yandex_realty.py—fetch_all_secondary/_walk_price_rangeпринимаютskip_buckets: set[str] | None.bucket_key = "{rooms}:{lo}:{hi}". Skip-проверка (probe/split-решение остаются, пропускается только пагинация + on_bucket) применена к обоим leaf-путям Yandex: нормальному параллельному иtotal is Nonepaginate-until-empty fallback. Сигнатура callbackon_bucket(bucket_key, lots).scrape_pipeline.py—run_yandex_full_load(resume_run_id: int | None = None). При resume читаетdone_bucketsиз counters прошлого run (SELECT counters FROM scrape_runs WHERE id = CAST(:rid AS bigint)), сидит ими skip-set._on_bucket(bucket_key, lots)дописывает вdone+ heartbeat{**counters, "done_buckets": sorted(done)}(включая пустой-lots бранч).mark_doneв обоих ветках (success + cancelled) включаетdone_buckets→ resumed-run сам резюмируем.mark_failed— без (как у Cian).admin.py—YandexFullLoadRequest.resume_run_id: int | None, проброшен вrun_yandex_full_load. Без него — full walk с нуля.Тесты: обновлены 2 существующих on_bucket-стаба под 2-арг сигнатуру + 2 новых (skip реально пропускает page-fetch + on_bucket; on_bucket получает
(key, lots)). ruff clean,pytest -k "yandex"→ 67 passed.Ветка теперь:
830657cexhaustive +3bcfb6bcheckpoint (clean fast-forward, verified ancestor). Не запускалось против prod.