fix(tradein/domclick): свип ходил на QRATOR без кук и вис на PoW каждым запросом (#3264) #3265
4 changed files with 378 additions and 5 deletions
|
|
@ -3,8 +3,12 @@
|
|||
`build_product_handlers(ctx)` собирает реестр НЕ-sweep source'ов, тело которых
|
||||
осталось в `app` (rosreestr_dkp / sber_index / deactivate_stale_* / *_backfill /
|
||||
cadastral_geo_match / house_* / proxy_healthcheck / …). Kit-native sweep-оркестраторы
|
||||
(avito/yandex/cian/domclick city/full-load) регистрируются самим `build_registry`
|
||||
через `_default_kit_handlers` — здесь их НЕТ.
|
||||
(avito/yandex/cian/domclick full-load) регистрируются самим `build_registry` через
|
||||
`_default_kit_handlers` — здесь их НЕТ. Исключение: `domclick_city_sweep` (#3264) —
|
||||
kit-native тело осталось прежним (`run_domclick_city_sweep`), но здесь переопределён
|
||||
Handler, который ДО вызова читает БД за куки сессии (kit не имеет права на app.*/БД,
|
||||
см. докстринг `_job_domclick_city_sweep` ниже); `build_registry` явно допускает такое
|
||||
переопределение продуктовым ключом.
|
||||
|
||||
Дизайн-инвариант: НЕ дублируем логику. Каждый `job` переиспользует существующую боевую
|
||||
джоб-функцию (`app.services.scheduler.import_rosreestr_dkp`, `app.tasks.*`,
|
||||
|
|
@ -32,6 +36,9 @@ from scraper_kit.orchestration.scheduler import (
|
|||
from scraper_kit.orchestration.scheduler import (
|
||||
_defer_next_run_at as kit_defer_next_run_at,
|
||||
)
|
||||
from scraper_kit.orchestration.scheduler import (
|
||||
_pick_resume as kit_pick_resume,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from scraper_kit.orchestration.scheduler import SchedulerContext
|
||||
|
|
@ -419,6 +426,52 @@ async def _job_domclick_detail_backfill(
|
|||
await run_domclick_detail_backfill(db, run_id=run_id, params=params)
|
||||
|
||||
|
||||
# ── domclick_city_sweep — override kit-native с инъекцией куки сессии (#3264) ─
|
||||
# Kit-native `_job_domclick_city_sweep` (scraper_kit.orchestration.scheduler) ходит на
|
||||
# bff-search-web.domclick.ru БЕЗ кук: kit не имеет права импортировать app.* / читать БД
|
||||
# (strangler-инвариант #2133). Каждый запрос свипа поэтому был обязан решать QRATOR PoW
|
||||
# с нуля — прод 30.08 (прогон 5330): 9/9 запросов зависли на challenge, 0 листингов.
|
||||
# `domclick_detail_backfill` (добор карточек) уже решает эту задачу инъекцией того же
|
||||
# снимка (domclick_session.load_session) — здесь тот же приём для свипа.
|
||||
#
|
||||
# Регистрируем ЗДЕСЬ (app-side) под тем же ключом "domclick_city_sweep", а не правим
|
||||
# kit: build_registry явно допускает переопределение kit-native продуктовым Handler'ом
|
||||
# (см. докстринг build_registry — "последнее слово за продуктом"), а строгий запрет на
|
||||
# app.* внутри kit при этом не нарушается — БД читает только этот модуль.
|
||||
async def _job_domclick_city_sweep(
|
||||
db: Session, run_id: int, params: dict[str, Any], ctx: SchedulerContext
|
||||
) -> None:
|
||||
from scraper_kit.orchestration.pipeline import run_domclick_city_sweep
|
||||
|
||||
from app.services import domclick_session as domclick_session_svc
|
||||
|
||||
# None — сессии в БД нет либо протухла (load_session сам фильтрует
|
||||
# expires_at_estimate > NOW()). Это НЕ авария: свип продолжает работать без
|
||||
# инъекции, ровно как до #3264, только логируем факт один раз для видимости.
|
||||
cookies = domclick_session_svc.load_session(db)
|
||||
if cookies is None:
|
||||
logger.info(
|
||||
"domclick_city_sweep run_id=%d: куки Sber ID сессии отсутствуют/протухли — "
|
||||
"свип идёт без инъекции (см. #3264)",
|
||||
run_id,
|
||||
)
|
||||
|
||||
await run_domclick_city_sweep(
|
||||
db,
|
||||
run_id=run_id,
|
||||
config=ctx.config,
|
||||
matcher=ctx.matcher,
|
||||
shutdown_requested=ctx.shutdown_requested,
|
||||
proxy_provider=ctx.proxy_provider,
|
||||
city_id=int(params.get("city_id", 4)),
|
||||
rooms=params.get("rooms"),
|
||||
pages=int(params.get("pages_per_anchor", 5)),
|
||||
request_delay_sec=float(params.get("request_delay_sec", 6.0)),
|
||||
resume_run_id=kit_pick_resume(db, run_id),
|
||||
cookies=cookies,
|
||||
)
|
||||
|
||||
|
||||
# ── house_coords_from_listings — sync set-based UPDATE в executor (#2771) ─────
|
||||
async def _job_house_coords_from_listings(
|
||||
db: Session, run_id: int, params: dict[str, Any], ctx: SchedulerContext
|
||||
|
|
@ -709,6 +762,8 @@ def build_product_handlers(ctx: SchedulerContext) -> dict[str, Handler]:
|
|||
"domclick_detail_backfill": Handler(
|
||||
_job_domclick_detail_backfill, "domclick_detail_backfill"
|
||||
),
|
||||
# Override kit-native (#3264) — инъекция куки сессии, см. докстринг job'а выше.
|
||||
"domclick_city_sweep": Handler(_job_domclick_city_sweep, "domclick_city_sweep"),
|
||||
"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"),
|
||||
"house_imv_backfill": Handler(_job_house_imv_backfill, "house_imv_backfill"),
|
||||
|
|
|
|||
297
tradein-mvp/backend/tests/test_3264_domclick_sweep_cookies.py
Normal file
297
tradein-mvp/backend/tests/test_3264_domclick_sweep_cookies.py
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
"""#3264: свип DomClick (`domclick_city_sweep`) ходил на bff-search-web.domclick.ru БЕЗ
|
||||
кук сессии и был обязан решать QRATOR PoW с нуля на каждый запрос — прод 30.08 (прогон
|
||||
5330): 9/9 запросов зависли на challenge, 0 листингов. `domclick_detail_backfill` (добор
|
||||
карточек) уже решал эту задачу инъекцией снимка `domclick_session.load_session` — этот
|
||||
файл проверяет, что тот же снимок теперь доезжает и до свипа:
|
||||
|
||||
1. DomClickScraper(cookies=...) кладёт снимок в self._cookies и пробрасывает его в
|
||||
ОБА fetcher.fetch() (count-probe и paginate-leaf).
|
||||
2. run_domclick_city_sweep(cookies=...) пробрасывает их в DomClickScraper(...).
|
||||
3. product_handlers._job_domclick_city_sweep (app-side override kit-native, см. его
|
||||
докстринг) грузит куки через domclick_session.load_session(db) и не падает, когда
|
||||
сессии в БД нет/протухла (load_session вернул None) — свип в этом случае работает
|
||||
как раньше, без инъекции.
|
||||
|
||||
Не проверяется (вне периметра): реальный обход QRATOR, browser_fetcher.fetch()
|
||||
внутренности (уже покрыты test_domclick_detail.py), ротация прокси/корзин (#2854/#2670).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")
|
||||
|
||||
import pytest
|
||||
from scraper_kit.providers.domclick.serp import DomClickScraper
|
||||
|
||||
PFX = "scraper_kit.orchestration.pipeline"
|
||||
|
||||
|
||||
def _config() -> SimpleNamespace:
|
||||
return SimpleNamespace(
|
||||
scraper_fetch_mode="curl_cffi",
|
||||
browser_http_endpoint="http://browser.test/fetch",
|
||||
scraper_proxy_url=None,
|
||||
avito_proxy_max_rotations=0,
|
||||
avito_serp_ok_not_banned=True,
|
||||
avito_proxy_rotate_settle_s=0.0,
|
||||
proxy_rotate_attempts=1,
|
||||
proxy_rotate_attempt_timeout_s=1.0,
|
||||
cian_proxy_max_rotations=0,
|
||||
yandex_proxy_max_rotations=0,
|
||||
scraper_skip_seen_today=False,
|
||||
cian_full_load_per_fetch_timeout_s=0.0,
|
||||
)
|
||||
|
||||
|
||||
# ── 1. DomClickScraper: cookies → self._cookies → оба fetcher.fetch() ────────────────
|
||||
|
||||
|
||||
def test_domclick_scraper_stores_cookies() -> None:
|
||||
snapshot = {"sbjs": "x", "dvc": "y"}
|
||||
scraper = DomClickScraper(_config(), cookies=snapshot)
|
||||
assert scraper._cookies == snapshot
|
||||
|
||||
|
||||
def test_domclick_scraper_defaults_cookies_to_none() -> None:
|
||||
scraper = DomClickScraper(_config())
|
||||
assert scraper._cookies is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_count_passes_cookies_to_fetch() -> None:
|
||||
snapshot = {"sbjs": "x"}
|
||||
scraper = DomClickScraper(_config(), cookies=snapshot)
|
||||
fetcher = MagicMock()
|
||||
fetcher.fetch = AsyncMock(return_value='{"result": {"snippetsCount": 3}}')
|
||||
n = await scraper._count(fetcher, "st", None, None)
|
||||
assert n == 3
|
||||
assert fetcher.fetch.await_args.kwargs["cookies"] == snapshot
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_count_passes_none_cookies_without_error() -> None:
|
||||
"""Cookies отсутствуют/протухли — не авария, fetch() зовётся с cookies=None."""
|
||||
scraper = DomClickScraper(_config(), cookies=None)
|
||||
fetcher = MagicMock()
|
||||
fetcher.fetch = AsyncMock(return_value='{"result": {"snippetsCount": 0}}')
|
||||
await scraper._count(fetcher, "st", None, None)
|
||||
assert fetcher.fetch.await_args.kwargs["cookies"] is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_paginate_passes_cookies_to_fetch() -> None:
|
||||
snapshot = {"sbjs": "x"}
|
||||
scraper = DomClickScraper(_config(), cookies=snapshot)
|
||||
fetcher = MagicMock()
|
||||
fetcher.fetch = AsyncMock(return_value='{"result": {"items": []}}')
|
||||
out: list[object] = []
|
||||
await scraper._paginate(fetcher, "st", None, None, set(), out, pages=1)
|
||||
assert fetcher.fetch.await_args.kwargs["cookies"] == snapshot
|
||||
|
||||
|
||||
# ── 2. run_domclick_city_sweep: cookies → DomClickScraper(...) ───────────────────────
|
||||
|
||||
def _stub_runs() -> MagicMock:
|
||||
"""runs-двойник: is_cancelled=False (иначе sweep выходит ДО создания scraper'а)."""
|
||||
m = MagicMock()
|
||||
m.is_cancelled = MagicMock(return_value=False)
|
||||
return m
|
||||
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_domclick_city_sweep_passes_cookies_to_scraper() -> None:
|
||||
from scraper_kit.orchestration.pipeline import run_domclick_city_sweep
|
||||
from scraper_kit.providers.domclick.serp import ROOM_BUCKETS
|
||||
|
||||
snapshot = {"sbjs": "x"}
|
||||
scraper = MagicMock()
|
||||
scraper.__aenter__ = AsyncMock(return_value=scraper)
|
||||
scraper.__aexit__ = AsyncMock(return_value=None)
|
||||
scraper.fetch_city = AsyncMock(return_value=[])
|
||||
scraper.blocked = False
|
||||
scraper.geo_filtered = 0
|
||||
scraper.fetch_errors = 0
|
||||
scraper.buckets_completed = len(ROOM_BUCKETS)
|
||||
scraper.buckets_total = len(ROOM_BUCKETS)
|
||||
ctor = MagicMock(return_value=scraper)
|
||||
|
||||
db = MagicMock()
|
||||
with (
|
||||
patch(f"{PFX}.DomClickScraper", ctor),
|
||||
patch(f"{PFX}.save_listings", MagicMock(return_value=(0, 0))),
|
||||
patch(f"{PFX}.runs", _stub_runs()),
|
||||
):
|
||||
await run_domclick_city_sweep(
|
||||
db,
|
||||
config=_config(),
|
||||
matcher=MagicMock(),
|
||||
run_id=1,
|
||||
city_id=4,
|
||||
pages=1,
|
||||
request_delay_sec=0.0,
|
||||
cookies=snapshot,
|
||||
)
|
||||
|
||||
assert ctor.call_args.kwargs["cookies"] == snapshot
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_domclick_city_sweep_none_cookies_does_not_break() -> None:
|
||||
"""cookies=None (дефолт) — прежнее поведение, свип не падает."""
|
||||
from scraper_kit.orchestration.pipeline import run_domclick_city_sweep
|
||||
from scraper_kit.providers.domclick.serp import ROOM_BUCKETS
|
||||
|
||||
scraper = MagicMock()
|
||||
scraper.__aenter__ = AsyncMock(return_value=scraper)
|
||||
scraper.__aexit__ = AsyncMock(return_value=None)
|
||||
scraper.fetch_city = AsyncMock(return_value=[])
|
||||
scraper.blocked = False
|
||||
scraper.geo_filtered = 0
|
||||
scraper.fetch_errors = 0
|
||||
scraper.buckets_completed = len(ROOM_BUCKETS)
|
||||
scraper.buckets_total = len(ROOM_BUCKETS)
|
||||
ctor = MagicMock(return_value=scraper)
|
||||
|
||||
db = MagicMock()
|
||||
with (
|
||||
patch(f"{PFX}.DomClickScraper", ctor),
|
||||
patch(f"{PFX}.save_listings", MagicMock(return_value=(0, 0))),
|
||||
patch(f"{PFX}.runs", _stub_runs()),
|
||||
):
|
||||
counters = await run_domclick_city_sweep(
|
||||
db,
|
||||
config=_config(),
|
||||
matcher=MagicMock(),
|
||||
run_id=1,
|
||||
city_id=4,
|
||||
pages=1,
|
||||
request_delay_sec=0.0,
|
||||
)
|
||||
|
||||
assert ctor.call_args.kwargs["cookies"] is None
|
||||
assert counters.errors_count == 0
|
||||
|
||||
|
||||
# ── 3. product_handlers._job_domclick_city_sweep: БД → cookies → sweep ───────────────
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_job_domclick_city_sweep_loads_and_passes_cookies() -> None:
|
||||
from app.services.product_handlers import _job_domclick_city_sweep
|
||||
|
||||
snapshot = {"sbjs": "x"}
|
||||
db = MagicMock()
|
||||
ctx = MagicMock(proxy_provider=None, shutdown_requested=lambda: False)
|
||||
|
||||
with (
|
||||
patch(
|
||||
"app.services.domclick_session.load_session",
|
||||
MagicMock(return_value=snapshot),
|
||||
),
|
||||
patch(
|
||||
"scraper_kit.orchestration.pipeline.run_domclick_city_sweep",
|
||||
AsyncMock(),
|
||||
) as run_mock,
|
||||
patch("app.services.product_handlers.kit_pick_resume", MagicMock(return_value=None)),
|
||||
):
|
||||
await _job_domclick_city_sweep(db, 1, {}, ctx)
|
||||
|
||||
assert run_mock.await_args.kwargs["cookies"] == snapshot
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_job_domclick_city_sweep_none_session_is_not_fatal() -> None:
|
||||
"""load_session вернул None (нет/протухла) — свип идёт БЕЗ инъекции, не падает."""
|
||||
from app.services.product_handlers import _job_domclick_city_sweep
|
||||
|
||||
db = MagicMock()
|
||||
ctx = MagicMock(proxy_provider=None, shutdown_requested=lambda: False)
|
||||
|
||||
with (
|
||||
patch("app.services.domclick_session.load_session", MagicMock(return_value=None)),
|
||||
patch(
|
||||
"scraper_kit.orchestration.pipeline.run_domclick_city_sweep",
|
||||
AsyncMock(),
|
||||
) as run_mock,
|
||||
patch("app.services.product_handlers.kit_pick_resume", MagicMock(return_value=None)),
|
||||
):
|
||||
await _job_domclick_city_sweep(db, 1, {}, ctx)
|
||||
|
||||
assert run_mock.await_args.kwargs["cookies"] is None
|
||||
|
||||
|
||||
def test_domclick_city_sweep_registered_as_product_override() -> None:
|
||||
"""build_product_handlers() переопределяет kit-native domclick_city_sweep (#3264)."""
|
||||
from app.services.product_handlers import _job_domclick_city_sweep, build_product_handlers
|
||||
|
||||
handlers = build_product_handlers(ctx=None) # type: ignore[arg-type]
|
||||
assert handlers["domclick_city_sweep"].job is _job_domclick_city_sweep
|
||||
|
||||
|
||||
# ── 4. Дрейф override'а от kit-native (#3264) ────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_product_override_matches_kit_native_call_except_cookies() -> None:
|
||||
"""Продуктовый override зовёт sweep ТЕМИ ЖЕ аргументами, что kit-native, плюс cookies.
|
||||
|
||||
Override (app.services.product_handlers) не оборачивает kit-джобу, а повторяет её
|
||||
вызов целиком — иначе пришлось бы тащить чтение БД внутрь kit, что запрещено
|
||||
strangler-инвариантом #2133. Цена такого решения — дрейф: параметр, добавленный в
|
||||
kit-native `_job_domclick_city_sweep`, в override не появится, и свип по расписанию
|
||||
молча поедет со старым набором. Этот тест — единственное, что такой дрейф ловит:
|
||||
оба джоба зовутся с одинаковыми db/run_id/params/ctx, и наборы kwargs сравниваются.
|
||||
|
||||
Расхождение допускается ровно одно — `cookies`, ради которого override и заведён.
|
||||
"""
|
||||
from scraper_kit.orchestration.scheduler import (
|
||||
_job_domclick_city_sweep as kit_job,
|
||||
)
|
||||
|
||||
from app.services.product_handlers import _job_domclick_city_sweep as product_job
|
||||
|
||||
params = {"city_id": 4, "pages_per_anchor": 7, "request_delay_sec": 3.0}
|
||||
|
||||
# Патчить sweep надо В ПРОСТРАНСTВЕ ИМЁН ВЫЗЫВАЮЩЕГО: kit-джоба связала имя
|
||||
# `run_domclick_city_sweep` на импорте модуля, продуктовая импортирует его внутри
|
||||
# тела. Патч по одному только `pipeline` kit-джобу не перехватит — она позовёт
|
||||
# настоящий свип и уйдёт в сеть.
|
||||
# db и ctx общие на оба захвата: иначе сравнение спотыкается не о дрейф, а о то,
|
||||
# что ctx.config — разные MagicMock-объекты в двух вызовах.
|
||||
db = MagicMock()
|
||||
ctx = MagicMock(proxy_provider=None, shutdown_requested=lambda: False)
|
||||
|
||||
async def _capture(job, patch_sweep_in: str, patch_resume_in: str) -> dict:
|
||||
with (
|
||||
patch("app.services.domclick_session.load_session", MagicMock(return_value=None)),
|
||||
patch(patch_sweep_in, AsyncMock()) as run_mock,
|
||||
patch(patch_resume_in, MagicMock(return_value=None)),
|
||||
):
|
||||
await job(db, 1, params, ctx)
|
||||
return run_mock.await_args.kwargs
|
||||
|
||||
kit_kwargs = await _capture(
|
||||
kit_job,
|
||||
"scraper_kit.orchestration.scheduler.run_domclick_city_sweep",
|
||||
"scraper_kit.orchestration.scheduler._pick_resume",
|
||||
)
|
||||
product_kwargs = await _capture(
|
||||
product_job,
|
||||
f"{PFX}.run_domclick_city_sweep",
|
||||
"app.services.product_handlers.kit_pick_resume",
|
||||
)
|
||||
|
||||
assert set(product_kwargs) - set(kit_kwargs) == {"cookies"}, (
|
||||
"override приобрёл аргументы сверх kit-native помимо cookies"
|
||||
)
|
||||
assert not set(kit_kwargs) - set(product_kwargs), (
|
||||
"kit-native получил аргумент, которого нет в override — свип по расписанию "
|
||||
"поедет без него (дрейф #3264)"
|
||||
)
|
||||
for key, kit_value in kit_kwargs.items():
|
||||
assert product_kwargs[key] == kit_value, f"override разошёлся с kit-native по {key!r}"
|
||||
|
|
@ -4289,6 +4289,7 @@ async def run_domclick_city_sweep(
|
|||
request_delay_sec: float | None = None,
|
||||
region_code: int = DEFAULT_REGION_CODE,
|
||||
resume_run_id: int | None = None,
|
||||
cookies: dict[str, str] | None = None,
|
||||
) -> DomClickCitySweepCounters:
|
||||
"""DomClick citywide sweep через BFF JSON API.
|
||||
|
||||
|
|
@ -4310,6 +4311,13 @@ async def run_domclick_city_sweep(
|
|||
прогон не доделал работу). Ноль лотов с fetch-ошибками, но без блока → mark_failed.
|
||||
Иначе mark_done.
|
||||
|
||||
cookies (#3264): снимок Sber ID сессии (dict cookie_name→value), инжектируется
|
||||
снаружи strangler-путём (#2133) — kit сам не читает app.services.domclick_session /
|
||||
БД. Без него sweep ходит на bff-search-web.domclick.ru БЕЗ кук и обязан решать
|
||||
QRATOR PoW с нуля на каждый запрос (прод 30.08, прогон 5330 — 9/9 запросов
|
||||
зависли на challenge). None (дефолт, сессии в БД нет/протухла) — прежнее
|
||||
поведение, без инъекции.
|
||||
|
||||
Возвращает DomClickCitySweepCounters.
|
||||
"""
|
||||
_resolved_delay = request_delay_sec if request_delay_sec is not None else 6.0
|
||||
|
|
@ -4376,7 +4384,9 @@ async def run_domclick_city_sweep(
|
|||
async def _domclick_phase() -> None:
|
||||
"""Единственная citywide-фаза: fetch_city + save."""
|
||||
nonlocal lots
|
||||
async with DomClickScraper(config, proxy_provider=proxy_provider) as _scraper:
|
||||
async with DomClickScraper(
|
||||
config, proxy_provider=proxy_provider, cookies=cookies
|
||||
) as _scraper:
|
||||
_scraper_ref.append(_scraper)
|
||||
if request_delay_sec is not None:
|
||||
_scraper.request_delay_sec = _resolved_delay
|
||||
|
|
|
|||
|
|
@ -253,6 +253,7 @@ class DomClickScraper(BaseScraper):
|
|||
*,
|
||||
delay_provider: Callable[[str], float] | None = None,
|
||||
proxy_provider: ProxyProvider | None = None,
|
||||
cookies: dict[str, str] | None = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
# Strangler-инжекция (#2133): config даёт browser_http_endpoint для
|
||||
|
|
@ -263,6 +264,16 @@ class DomClickScraper(BaseScraper):
|
|||
# ЕДИНСТВЕННЫМ sweep-источником без пула (#2160 P4 wiring gap) и потому ходил
|
||||
# через статический SCRAPER_PROXY_URL сайдкара.
|
||||
self._proxy_provider = proxy_provider
|
||||
# #3264: снимок cookies авторизованной test-аккаунт сессии (Sber ID), тот же
|
||||
# приём, что уже работает в detail.py fetch_detail(). Свип раньше ходил на
|
||||
# bff-search-web.domclick.ru БЕЗ кук и был обязан решать QRATOR PoW с нуля на
|
||||
# каждый запрос — прод 30.08 (прогон 5330) показал 9/9 запросов зависших на
|
||||
# PoW-челлендже. Strangler-инжекция (#2133): kit не читает app.* / БД сам,
|
||||
# снимок приходит параметром снаружи (см. run_domclick_city_sweep →
|
||||
# scheduler._job_domclick_city_sweep, который грузит его через
|
||||
# app.services.domclick_session.load_session). None (дефолт) — сессии в БД
|
||||
# нет/протухла, свип работает как раньше, без инъекции.
|
||||
self._cookies = cookies
|
||||
if delay_provider is not None:
|
||||
self.request_delay_sec = delay_provider(self.name)
|
||||
self.parse_failures: int = 0
|
||||
|
|
@ -471,7 +482,7 @@ class DomClickScraper(BaseScraper):
|
|||
"""
|
||||
url = _build_count_url(rooms, price_gte, price_lte)
|
||||
logger.debug("domklik: count url=%s", url)
|
||||
html = await fetcher.fetch(url)
|
||||
html = await fetcher.fetch(url, cookies=self._cookies) # #3264: снимок Sber ID сессии, обходит QRATOR PoW
|
||||
try:
|
||||
data = _extract_json(html)
|
||||
except DomClickBlockedError:
|
||||
|
|
@ -560,7 +571,7 @@ class DomClickScraper(BaseScraper):
|
|||
break
|
||||
url = _build_offers_url(rooms, price_gte, price_lte, offset)
|
||||
logger.debug("domklik: offers url=%s", url)
|
||||
html = await fetcher.fetch(url)
|
||||
html = await fetcher.fetch(url, cookies=self._cookies) # #3264: снимок Sber ID сессии, обходит QRATOR PoW
|
||||
try:
|
||||
data = _extract_json(html)
|
||||
except DomClickBlockedError:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue