Скрапер: newbuilding-провайдеры без config больше не уходят мимо пула прокси, IMV Авито строит сессию общей фабрикой #3562
6 changed files with 74 additions and 102 deletions
|
|
@ -15,6 +15,7 @@ from __future__ import annotations
|
|||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
# DATABASE_URL required by config before any app import.
|
||||
|
|
@ -28,6 +29,8 @@ from scraper_kit.providers.cian.newbuilding import ( # noqa: E402
|
|||
resolve_cian_zhk_url_via_search,
|
||||
)
|
||||
|
||||
# config обязателен (#2385); на пути с переданной session он не читается.
|
||||
_CONFIG = SimpleNamespace(cian_proxy_url=None)
|
||||
_FIXTURE = Path(__file__).resolve().parents[2] / "fixtures" / "cian_catph_newbuilding_serp.html"
|
||||
|
||||
|
||||
|
|
@ -114,7 +117,7 @@ def test_extract_zhk_url_no_match_returns_none() -> None:
|
|||
async def test_resolve_via_search_happy_path() -> None:
|
||||
"""HTTP 200 + a SERP containing the slug → canonical url; session reused, not closed."""
|
||||
session = _mock_session(status_code=200, text=_serp_fixture())
|
||||
url = await resolve_cian_zhk_url_via_search(48853, session=session)
|
||||
url = await resolve_cian_zhk_url_via_search(48853, config=_CONFIG, session=session)
|
||||
assert url == "https://zhk-parkovyy-kvartal-ekb-i.cian.ru"
|
||||
# caller owns a passed-in session — resolver must NOT close it.
|
||||
session.close.assert_not_awaited()
|
||||
|
|
@ -128,14 +131,14 @@ async def test_resolve_via_search_happy_path() -> None:
|
|||
async def test_resolve_via_search_no_match_returns_none() -> None:
|
||||
"""HTTP 200 but no zhk-slug in the body → None (empty SERP / markup drift)."""
|
||||
session = _mock_session(status_code=200, text="<html>no results</html>")
|
||||
assert await resolve_cian_zhk_url_via_search(999, session=session) is None
|
||||
assert await resolve_cian_zhk_url_via_search(999, config=_CONFIG, session=session) is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_via_search_non_200_returns_none() -> None:
|
||||
"""A non-200 (block / 404) → None without attempting extraction."""
|
||||
session = _mock_session(status_code=429, text="too many requests")
|
||||
assert await resolve_cian_zhk_url_via_search(48853, session=session) is None
|
||||
assert await resolve_cian_zhk_url_via_search(48853, config=_CONFIG, session=session) is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -144,4 +147,4 @@ async def test_resolve_via_search_swallows_request_error() -> None:
|
|||
session = MagicMock()
|
||||
session.get = AsyncMock(side_effect=ConnectionError("proxy refused"))
|
||||
session.close = AsyncMock()
|
||||
assert await resolve_cian_zhk_url_via_search(48853, session=session) is None
|
||||
assert await resolve_cian_zhk_url_via_search(48853, config=_CONFIG, session=session) is None
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ through `build_curl_cffi_session` — so a missing `config=` does NOT raise like
|
|||
(non-proxied) connection (`cian_proxy_url` dropped). That is much harder to notice in
|
||||
prod (no crash, just quietly unproxied traffic that anti-bot can throttle/block), so a
|
||||
caller that forgets `config=` here gets no signal at all.
|
||||
|
||||
#2385: `config` стал обязательным во всех четырёх точках входа. Прежние кейсы
|
||||
`*_without_config` (ждали endpoint=None / proxy_url=None) заменены одним тестом
|
||||
«без config — TypeError, фетчер и сессия не строятся».
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -42,6 +46,7 @@ from scraper_kit.providers.yandex.newbuilding import (
|
|||
|
||||
_TEST_ENDPOINT = "http://test-tradein-browser:9009/fetch"
|
||||
_TEST_PROXY = "http://mobile-proxy.example:8080"
|
||||
_CONFIG = SimpleNamespace(browser_http_endpoint=_TEST_ENDPOINT, use_proxy_pool_browser=False)
|
||||
|
||||
|
||||
def _spy_browser_fetcher(html: str) -> MagicMock:
|
||||
|
|
@ -77,21 +82,6 @@ async def test_cian_fetch_newbuilding_passes_endpoint_from_config(monkeypatch):
|
|||
assert kwargs.get("endpoint") == _TEST_ENDPOINT
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cian_fetch_newbuilding_endpoint_none_without_config(monkeypatch):
|
||||
"""Без config= (backward-compat) endpoint=None передаётся явным kwarg'ом — сигнатура
|
||||
больше не роняет TypeError на отсутствующем обязательном параметре конструктора."""
|
||||
spy = _spy_browser_fetcher("<html></html>")
|
||||
monkeypatch.setattr("scraper_kit.providers.cian.newbuilding.BrowserFetcher", spy)
|
||||
|
||||
await fetch_newbuilding("https://zhk-test-ekb-i.cian.ru/")
|
||||
|
||||
spy.assert_called_once()
|
||||
_, kwargs = spy.call_args
|
||||
assert kwargs.get("source") == "cian"
|
||||
assert kwargs.get("endpoint") is None
|
||||
|
||||
|
||||
# ── yandex.newbuilding.YandexNewbuildingScraper.fetch_jk ─────────────────────
|
||||
|
||||
|
||||
|
|
@ -115,21 +105,6 @@ async def test_yandex_fetch_jk_passes_endpoint_from_config(monkeypatch):
|
|||
assert kwargs.get("endpoint") == _TEST_ENDPOINT
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_yandex_fetch_jk_endpoint_none_without_config(monkeypatch):
|
||||
"""Без config= (backward-compat) endpoint=None передаётся явным kwarg'ом."""
|
||||
spy = _spy_browser_fetcher("<html></html>")
|
||||
monkeypatch.setattr("scraper_kit.providers.yandex.newbuilding.BrowserFetcher", spy)
|
||||
|
||||
scraper = YandexNewbuildingScraper()
|
||||
await scraper.fetch_jk(jk_slug="tatlin", jk_id="1592987")
|
||||
|
||||
spy.assert_called_once()
|
||||
_, kwargs = spy.call_args
|
||||
assert kwargs.get("source") == "yandex"
|
||||
assert kwargs.get("endpoint") is None
|
||||
|
||||
|
||||
# ── yandex.newbuilding.resolve_yandex_jk_slug ────────────────────────────────
|
||||
|
||||
|
||||
|
|
@ -151,20 +126,6 @@ async def test_resolve_yandex_jk_slug_passes_endpoint_from_config(monkeypatch):
|
|||
assert kwargs.get("endpoint") == _TEST_ENDPOINT
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_yandex_jk_slug_endpoint_none_without_config(monkeypatch):
|
||||
"""Без config= (backward-compat) endpoint=None передаётся явным kwarg'ом."""
|
||||
spy = _spy_browser_fetcher("<html></html>")
|
||||
monkeypatch.setattr("scraper_kit.providers.yandex.newbuilding.BrowserFetcher", spy)
|
||||
|
||||
await resolve_yandex_jk_slug("1592987")
|
||||
|
||||
spy.assert_called_once()
|
||||
_, kwargs = spy.call_args
|
||||
assert kwargs.get("source") == "yandex"
|
||||
assert kwargs.get("endpoint") is None
|
||||
|
||||
|
||||
# ── #2860: slug ищется по СТРАНИЦЕ ЖК, а не по фильтру выдачи ────────────────
|
||||
#
|
||||
# Замер 19.08.2026 через браузерный сайдкар прода: `?siteId=<id>` Яндексом
|
||||
|
|
@ -195,9 +156,9 @@ async def test_resolve_yandex_jk_slug_requests_jk_page_not_serp_filter(monkeypat
|
|||
Это и есть суть правки: адрес запроса, а не разбор ответа.
|
||||
"""
|
||||
spy = _spy_browser_fetcher(_JK_PAGE_HTML)
|
||||
monkeypatch.setattr("scraper_kit.providers.yandex.newbuilding.BrowserFetcher", spy)
|
||||
monkeypatch.setattr("scraper_kit.providers._base.BrowserFetcher", spy)
|
||||
|
||||
await resolve_yandex_jk_slug("286394")
|
||||
await resolve_yandex_jk_slug("286394", config=_CONFIG)
|
||||
|
||||
url = spy.return_value.fetch.call_args[0][0]
|
||||
assert "siteId=" not in url, f"фильтр выдачи больше не работает, а запрос идёт туда: {url}"
|
||||
|
|
@ -208,9 +169,9 @@ async def test_resolve_yandex_jk_slug_requests_jk_page_not_serp_filter(monkeypat
|
|||
async def test_resolve_yandex_jk_slug_reads_real_slug_from_page(monkeypatch):
|
||||
"""Со страницы ЖК берётся настоящий slug, а не плейсхолдер из нашего же URL."""
|
||||
spy = _spy_browser_fetcher(_JK_PAGE_HTML)
|
||||
monkeypatch.setattr("scraper_kit.providers.yandex.newbuilding.BrowserFetcher", spy)
|
||||
monkeypatch.setattr("scraper_kit.providers._base.BrowserFetcher", spy)
|
||||
|
||||
assert await resolve_yandex_jk_slug("286394") == "uspenskij"
|
||||
assert await resolve_yandex_jk_slug("286394", config=_CONFIG) == "uspenskij"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -221,9 +182,9 @@ async def test_resolve_yandex_jk_slug_returns_none_on_general_list(monkeypatch):
|
|||
Без привязки к id функция вернула бы «shishkinn» для чужого дома.
|
||||
"""
|
||||
spy = _spy_browser_fetcher(_GENERAL_LIST_HTML)
|
||||
monkeypatch.setattr("scraper_kit.providers.yandex.newbuilding.BrowserFetcher", spy)
|
||||
monkeypatch.setattr("scraper_kit.providers._base.BrowserFetcher", spy)
|
||||
|
||||
assert await resolve_yandex_jk_slug("286394") is None
|
||||
assert await resolve_yandex_jk_slug("286394", config=_CONFIG) is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -232,9 +193,9 @@ async def test_resolve_yandex_jk_slug_does_not_return_placeholder(monkeypatch):
|
|||
spy = _spy_browser_fetcher(
|
||||
'<a href="/ekaterinburg/kupit/novostrojka/zhk-286394/">сама страница</a>'
|
||||
)
|
||||
monkeypatch.setattr("scraper_kit.providers.yandex.newbuilding.BrowserFetcher", spy)
|
||||
monkeypatch.setattr("scraper_kit.providers._base.BrowserFetcher", spy)
|
||||
|
||||
assert await resolve_yandex_jk_slug("286394") is None
|
||||
assert await resolve_yandex_jk_slug("286394", config=_CONFIG) is None
|
||||
|
||||
|
||||
# ── cian.newbuilding.resolve_cian_zhk_url_via_search ─────────────────────────
|
||||
|
|
@ -276,17 +237,33 @@ async def test_resolve_cian_zhk_url_via_search_passes_proxy_from_config(monkeypa
|
|||
assert kwargs.get("proxy_url") == _TEST_PROXY
|
||||
|
||||
|
||||
# ── #2385: config обязателен — без него ни фетчера, ни сессии ────────────────
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_cian_zhk_url_via_search_proxy_none_without_config(monkeypatch):
|
||||
"""Без config= (SILENT footgun): вызов НЕ падает (в отличие от BrowserFetcher
|
||||
assertion выше в этом файле), но proxy_url молча становится None — резолвер уходит
|
||||
на прямое (непроксированное) соединение вместо настроенного мобильного прокси."""
|
||||
spy = _spy_curl_session(_SERP_HTML)
|
||||
monkeypatch.setattr("scraper_kit.providers.cian.newbuilding.build_curl_cffi_session", spy)
|
||||
@pytest.mark.parametrize(
|
||||
"call",
|
||||
[
|
||||
lambda: fetch_newbuilding("https://zhk-test-ekb-i.cian.ru/"),
|
||||
lambda: YandexNewbuildingScraper().fetch_jk(jk_slug="tatlin", jk_id="1592987"),
|
||||
lambda: resolve_yandex_jk_slug("1592987"),
|
||||
lambda: resolve_cian_zhk_url_via_search(48853),
|
||||
],
|
||||
ids=["cian_fetch_newbuilding", "yandex_fetch_jk", "resolve_yandex_slug", "resolve_cian_url"],
|
||||
)
|
||||
async def test_newbuilding_entrypoints_refuse_to_run_without_config(monkeypatch, call):
|
||||
"""Вызов без config= падает TypeError ДО сети: браузерный фетчер и curl-сессия не
|
||||
строятся. Раньше тот же вызов молча уходил на env-узел сайдкара мимо пула (#2767,
|
||||
#3197) — отказ по сигнатуре единственное, что ловит забывчивого вызывающего."""
|
||||
browser_spy = _spy_browser_fetcher("<html></html>")
|
||||
monkeypatch.setattr("scraper_kit.providers._base.BrowserFetcher", browser_spy)
|
||||
session_spy = _spy_curl_session(_SERP_HTML)
|
||||
monkeypatch.setattr(
|
||||
"scraper_kit.providers.cian.newbuilding.build_curl_cffi_session", session_spy
|
||||
)
|
||||
|
||||
url = await resolve_cian_zhk_url_via_search(48853)
|
||||
with pytest.raises(TypeError, match="config"):
|
||||
await call()
|
||||
|
||||
assert url == "https://zhk-parkovyy-kvartal-ekb-i.cian.ru" # "works" — silently unproxied
|
||||
spy.assert_called_once()
|
||||
_, kwargs = spy.call_args
|
||||
assert kwargs.get("proxy_url") is None
|
||||
assert browser_spy.call_count == 0
|
||||
assert session_spy.call_count == 0
|
||||
|
|
|
|||
|
|
@ -199,6 +199,10 @@ async def test_yandex_valuation_session_no_proxies_when_none():
|
|||
# После #972 fetch_newbuilding тянет страницу ЖК через BrowserFetcher (camoufox),
|
||||
# а НЕ через curl_cffi AsyncSession. Прокси применяются на уровне браузер-контейнера,
|
||||
# а не через proxies= kwarg. AsyncSession для page-fetch больше НЕ создаётся.
|
||||
# config обязателен (#2385): фетчер строит фабрика _base.build_browser_fetcher.
|
||||
_NB_CONFIG = SimpleNamespace(
|
||||
browser_http_endpoint="http://tradein-browser:3000", use_proxy_pool_browser=False
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -222,8 +226,8 @@ async def test_cian_newbuilding_own_session_receives_proxies():
|
|||
mock_fetcher.__aenter__ = AsyncMock(return_value=mock_fetcher)
|
||||
mock_fetcher.__aexit__ = AsyncMock(return_value=None)
|
||||
|
||||
with patch.object(cian_newbuilding, "BrowserFetcher", lambda *a, **k: mock_fetcher):
|
||||
await cian_newbuilding.fetch_newbuilding(zhk_url)
|
||||
with patch("scraper_kit.providers._base.BrowserFetcher", lambda *a, **k: mock_fetcher):
|
||||
await cian_newbuilding.fetch_newbuilding(zhk_url, config=_NB_CONFIG)
|
||||
|
||||
# BrowserFetcher.fetch вызван с ЖК-url
|
||||
assert fetch_calls == [zhk_url]
|
||||
|
|
@ -253,8 +257,10 @@ async def test_cian_newbuilding_shared_session_not_recreated():
|
|||
|
||||
sentinel_session = MagicMock(name="external_session")
|
||||
|
||||
with patch.object(cian_newbuilding, "BrowserFetcher", lambda *a, **k: mock_fetcher):
|
||||
await cian_newbuilding.fetch_newbuilding(zhk_url, session=sentinel_session)
|
||||
with patch("scraper_kit.providers._base.BrowserFetcher", lambda *a, **k: mock_fetcher):
|
||||
await cian_newbuilding.fetch_newbuilding(
|
||||
zhk_url, config=_NB_CONFIG, session=sentinel_session
|
||||
)
|
||||
|
||||
# BrowserFetcher.fetch вызван — именно через него тянется страница
|
||||
assert fetch_calls == [zhk_url]
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ def test_yandex_detail_picks_up_delay():
|
|||
|
||||
def test_yandex_newbuilding_picks_up_delay():
|
||||
"""Kit YandexNewbuildingScraper: delay_provider callable вызывается с self.name."""
|
||||
s = YandexNewbuildingScraper(delay_provider=lambda _name: 9.5)
|
||||
s = YandexNewbuildingScraper(config=_KIT_CONFIG, delay_provider=lambda _name: 9.5)
|
||||
assert s.request_delay_sec == 9.5
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import re
|
|||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from scraper_kit.browser_fetcher import BrowserFetcher
|
||||
from scraper_kit.cian_exceptions import CianBlockedError
|
||||
from scraper_kit.cian_state_parser import extract_all_states, extract_state
|
||||
from scraper_kit.providers._base import build_browser_fetcher, build_curl_cffi_session
|
||||
|
|
@ -214,7 +213,7 @@ class NewbuildingEnrichment:
|
|||
async def fetch_newbuilding(
|
||||
zhk_url: str,
|
||||
*,
|
||||
config: ScraperConfig | None = None,
|
||||
config: ScraperConfig,
|
||||
session: AsyncSession | None = None, # kept for backward-compat; unused for page fetch
|
||||
proxy_provider: ProxyProvider | None = None,
|
||||
) -> NewbuildingEnrichment | None:
|
||||
|
|
@ -225,9 +224,8 @@ async def fetch_newbuilding(
|
|||
|
||||
Args:
|
||||
zhk_url: e.g. 'https://zhk-ekaterininskiy-park-ekb-i.cian.ru/'
|
||||
config: ScraperConfig — источник browser_http_endpoint для BrowserFetcher
|
||||
(#2322 fix: раньше не принимался вообще, BrowserFetcher(source="cian")
|
||||
конструировался без endpoint= → TypeError на любом вызове).
|
||||
config: ScraperConfig — источник browser_http_endpoint/use_pool для BrowserFetcher.
|
||||
Обязателен (#2385): без него фетчер строился с endpoint=None мимо пула.
|
||||
session: зарезервирован для обратной совместимости с вызывающими;
|
||||
больше не используется для fetch страницы ЖК. Resolve-функции
|
||||
(resolve_cian_zhk_url_via_search) по-прежнему используют curl_cffi.
|
||||
|
|
@ -254,14 +252,10 @@ async def fetch_newbuilding(
|
|||
# Разметка не менялась: MFE 'newbuilding-card-desktop-frontend'/'initialState'
|
||||
# разобрался ТЕКУЩИМ кодом на всех трёх мобильных узлах. Ломался тракт, не разбор.
|
||||
#
|
||||
# config=None остаётся законным (admin.py debug-роут и
|
||||
# test_cian_fetch_newbuilding_endpoint_none_without_config зовут без config= и
|
||||
# ждут graceful endpoint=None, а не AttributeError), поэтому фабрика применяется
|
||||
# только когда config есть — она требует ScraperConfig mandatory by design.
|
||||
if config is not None:
|
||||
# config обязателен (#2385): запасная ветка `config=None` строила
|
||||
# BrowserFetcher(endpoint=None) мимо фабрики — новый вызывающий без config молча уходил
|
||||
# на env-узел сайдкара, тот самый путь, что стоил 8 суток выше.
|
||||
browser = build_browser_fetcher(config, "cian", proxy_provider=proxy_provider)
|
||||
else:
|
||||
browser = BrowserFetcher(source="cian", endpoint=None) # type: ignore[arg-type]
|
||||
|
||||
# ponytail: lease берётся на ОДИН дом (фетчер живёт внутри этой функции), то есть
|
||||
# прогон из 25 домов даёт до 25 acquire → до 25 релончей camoufox (~8 с каждый).
|
||||
|
|
@ -883,7 +877,7 @@ async def _fetch_zhk_slug(session: AsyncSession, nb_id: int) -> str | None:
|
|||
async def resolve_cian_zhk_url_via_search(
|
||||
nb_id: int,
|
||||
*,
|
||||
config: ScraperConfig | None = None,
|
||||
config: ScraperConfig,
|
||||
session: AsyncSession | None = None,
|
||||
proxy_provider: ProxyProvider | None = None,
|
||||
) -> str | None:
|
||||
|
|
@ -928,7 +922,7 @@ async def resolve_cian_zhk_url_via_search(
|
|||
# Own-session path. Прокси: пул за флагом use_proxy_pool_curl (#2163), иначе env
|
||||
# cian_proxy_url; пусто → прямое подключение (dev/no-op).
|
||||
# curl_proxy_url: mark_banned на CianBlockedError + mark_health + release на выходе.
|
||||
_env = config.cian_proxy_url if config is not None else None
|
||||
_env = config.cian_proxy_url
|
||||
with curl_proxy_url(config, proxy_provider, "cian", env_fallback_url=_env) as _proxy_url:
|
||||
own_session = build_curl_cffi_session(
|
||||
proxy_url=_proxy_url,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ from pydantic import BaseModel, Field
|
|||
from selectolax.parser import HTMLParser, Node
|
||||
|
||||
from scraper_kit.base import BaseScraper
|
||||
from scraper_kit.browser_fetcher import BrowserFetcher
|
||||
from scraper_kit.providers._base import build_browser_fetcher
|
||||
from scraper_kit.proxy_errors import caused_by_no_proxy
|
||||
from scraper_kit.yandex_helpers import (
|
||||
|
|
@ -158,7 +157,7 @@ class YandexNewbuildingScraper(BaseScraper):
|
|||
def __init__(
|
||||
self,
|
||||
*,
|
||||
config: ScraperConfig | None = None,
|
||||
config: ScraperConfig,
|
||||
delay_provider: Callable[[str], float] | None = None,
|
||||
proxy_provider: ProxyProvider | None = None,
|
||||
) -> None:
|
||||
|
|
@ -193,14 +192,10 @@ class YandexNewbuildingScraper(BaseScraper):
|
|||
"""
|
||||
url = f"{self.base_url}/{city}/kupit/novostrojka/{jk_slug}-{jk_id}/"
|
||||
# #3197: через фабрику — endpoint/use_pool/environment из config mandatory
|
||||
# (образец: yandex/serp.py::__aenter__). config=None (dev/офлайн-тесты) —
|
||||
# прежний путь без пула, как в cian/newbuilding.py.
|
||||
if self._config is not None:
|
||||
# (образец: yandex/serp.py::__aenter__). Запасной ветки config=None больше нет (#2385).
|
||||
fetcher_cm = build_browser_fetcher(
|
||||
self._config, "yandex", proxy_provider=self._proxy_provider
|
||||
)
|
||||
else:
|
||||
fetcher_cm = BrowserFetcher(source="yandex", endpoint=None) # type: ignore[arg-type]
|
||||
try:
|
||||
async with fetcher_cm as fetcher:
|
||||
html = await fetcher.fetch(url)
|
||||
|
|
@ -359,7 +354,7 @@ async def resolve_yandex_jk_slug(
|
|||
jk_id: str,
|
||||
city: str = "ekaterinburg",
|
||||
*,
|
||||
config: ScraperConfig | None = None,
|
||||
config: ScraperConfig,
|
||||
proxy_provider: ProxyProvider | None = None,
|
||||
) -> str | None:
|
||||
"""Найти Yandex Realty slug для ЖК по его ext_id (jk_id).
|
||||
|
|
@ -402,10 +397,7 @@ async def resolve_yandex_jk_slug(
|
|||
"""
|
||||
page_url = f"https://realty.yandex.ru/{city}/kupit/novostrojka/{_JK_SLUG_PLACEHOLDER}-{jk_id}/"
|
||||
# #3197: см. fetch_jk — та же проводка пула через фабрику.
|
||||
if config is not None:
|
||||
fetcher_cm = build_browser_fetcher(config, "yandex", proxy_provider=proxy_provider)
|
||||
else:
|
||||
fetcher_cm = BrowserFetcher(source="yandex", endpoint=None) # type: ignore[arg-type]
|
||||
try:
|
||||
async with fetcher_cm as fetcher:
|
||||
html = await fetcher.fetch(page_url)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue