Compare commits
No commits in common. "ba17c868d4a6d6ed584cf889ec8baf53134047cf" and "deb6517bd5df2098e257f8ed71a14cd62c74b780" have entirely different histories.
ba17c868d4
...
deb6517bd5
2 changed files with 6 additions and 103 deletions
|
|
@ -71,16 +71,9 @@ source_id) считает сам кит (`ScrapedLot.compute_dedup_hash`), це
|
||||||
курсор идёт по `id` вью, так что порядок и полнота обхода от прогона к прогону
|
курсор идёт по `id` вью, так что порядок и полнота обхода от прогона к прогону
|
||||||
одинаковы.
|
одинаковы.
|
||||||
|
|
||||||
Отбор Москвы (source=yandex) стоит ноль вызовов: адрес приходит полным и
|
|
||||||
нормализованным («Россия, Москва, Коробейников переулок, 1»), регион читается
|
|
||||||
вторым компонентом. Замер по 21 393 карточкам первого прохода — ровно два
|
|
||||||
значения, «Москва» 10 610 и «Московская область» 10 783. Координаты у Яндекса
|
|
||||||
заполнены у 100% карточек, поэтому ни геокод, ни `geocode_missing` ему не нужны.
|
|
||||||
|
|
||||||
Запуск:
|
Запуск:
|
||||||
python -m app.tasks.msk_raw_import --dry-run
|
python -m app.tasks.msk_raw_import --dry-run
|
||||||
python -m app.tasks.msk_raw_import --limit 500
|
python -m app.tasks.msk_raw_import --limit 500
|
||||||
python -m app.tasks.msk_raw_import --source yandex
|
|
||||||
python -m app.tasks.msk_raw_import --source avito --geocode --geocode-limit 9000
|
python -m app.tasks.msk_raw_import --source avito --geocode --geocode-limit 9000
|
||||||
python -m app.tasks.msk_raw_import --source avito --allow-unfiltered # аварийный
|
python -m app.tasks.msk_raw_import --source avito --allow-unfiltered # аварийный
|
||||||
"""
|
"""
|
||||||
|
|
@ -159,7 +152,6 @@ _ARTIFACT_RE = re.compile(r"Екатеринбург", re.IGNORECASE)
|
||||||
SOURCE_VIEWS = {
|
SOURCE_VIEWS = {
|
||||||
"cian": "msk_raw.cian_latest",
|
"cian": "msk_raw.cian_latest",
|
||||||
"avito": "msk_raw.avito_latest",
|
"avito": "msk_raw.avito_latest",
|
||||||
"yandex": "msk_raw.yandex_latest",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_PAGE_SQL = """
|
_PAGE_SQL = """
|
||||||
|
|
@ -468,31 +460,6 @@ def is_moscow_address(address: str | None) -> bool:
|
||||||
return _MOSCOW_OKRUG_RE.match(address.strip()) is not None
|
return _MOSCOW_OKRUG_RE.match(address.strip()) is not None
|
||||||
|
|
||||||
|
|
||||||
def is_moscow_yandex_address(address: str | None) -> bool:
|
|
||||||
"""У Яндекса регион — второй компонент полного адреса.
|
|
||||||
|
|
||||||
Адрес приходит нормализованным и с городом: «Россия, Москва, Коробейников
|
|
||||||
переулок, 1». Замер по 21 393 карточкам первого прохода: во втором
|
|
||||||
компоненте ровно ДВА значения — «Москва» 10 610 и «Московская область»
|
|
||||||
10 783, третьего не встречается. Поэтому ни префикса округа (как у Циана),
|
|
||||||
ни внешнего геокода (как у Авито) источнику не нужно: разделение 77 и 50
|
|
||||||
читается из самой карточки и стоит ноль вызовов.
|
|
||||||
|
|
||||||
Новая Москва отдельным значением НЕ приходит — Троицк и Зеленоград Яндекс
|
|
||||||
кладёт под «Москва», что совпадает с кодом региона 77.
|
|
||||||
"""
|
|
||||||
parts = [part.strip() for part in (address or "").split(",")]
|
|
||||||
return len(parts) > 1 and parts[1] == "Москва"
|
|
||||||
|
|
||||||
|
|
||||||
# Источники, у которых город виден в самой карточке. Ключ отсутствует —
|
|
||||||
# источник про город молчит, и без пред-геокода писать его нельзя (avito).
|
|
||||||
CITY_FILTERS = {
|
|
||||||
"cian": is_moscow_address,
|
|
||||||
"yandex": is_moscow_yandex_address,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def recompute_listing_segment(payload: dict) -> str:
|
def recompute_listing_segment(payload: dict) -> str:
|
||||||
"""Заново считаем сегмент: 'novostroyki' только при продаже застройщиком.
|
"""Заново считаем сегмент: 'novostroyki' только при продаже застройщиком.
|
||||||
|
|
||||||
|
|
@ -551,15 +518,12 @@ def import_msk_raw(
|
||||||
matcher = RealMatcherAdapter()
|
matcher = RealMatcherAdapter()
|
||||||
budget = GeocodeBudget(remaining=max(0, geocode_limit))
|
budget = GeocodeBudget(remaining=max(0, geocode_limit))
|
||||||
|
|
||||||
# Источник, который сам говорит про город: у Циана это префикс округа, у
|
filter_by_okrug = source == "cian"
|
||||||
# Яндекса — второй компонент полного адреса. Авито не говорит ничего, ему
|
if geocode and filter_by_okrug:
|
||||||
# нужен пред-геокод, поэтому его в словаре нет.
|
# У Циана город опознаётся префиксом округа даром и без ошибок —
|
||||||
city_filter = CITY_FILTERS.get(source)
|
# тратить на него внешнюю квоту незачем.
|
||||||
if geocode and city_filter is not None:
|
|
||||||
# Город опознаётся даром и без ошибок — тратить на него внешнюю квоту
|
|
||||||
# незачем.
|
|
||||||
raise SystemExit(f"source={source}: --geocode нужен только для avito")
|
raise SystemExit(f"source={source}: --geocode нужен только для avito")
|
||||||
if city_filter is None and not geocode:
|
if not filter_by_okrug and not geocode:
|
||||||
# Без пред-геокода у Авито по-прежнему нечем отделить Москву от области:
|
# Без пред-геокода у Авито по-прежнему нечем отделить Москву от области:
|
||||||
# ни города в адресе, ни координат. Пишем только по явному разрешению.
|
# ни города в адресе, ни координат. Пишем только по явному разрешению.
|
||||||
if not (dry_run or allow_unfiltered):
|
if not (dry_run or allow_unfiltered):
|
||||||
|
|
@ -587,7 +551,7 @@ def import_msk_raw(
|
||||||
if is_artifact_address(address):
|
if is_artifact_address(address):
|
||||||
counters.skipped_artifact += 1
|
counters.skipped_artifact += 1
|
||||||
continue
|
continue
|
||||||
if city_filter is not None and not city_filter(address):
|
if filter_by_okrug and not is_moscow_address(address):
|
||||||
counters.skipped_not_moscow += 1
|
counters.skipped_not_moscow += 1
|
||||||
continue
|
continue
|
||||||
if geocode:
|
if geocode:
|
||||||
|
|
|
||||||
|
|
@ -400,64 +400,3 @@ def test_dry_run_survives_missing_geocode_cache(monkeypatch: pytest.MonkeyPatch)
|
||||||
|
|
||||||
assert db.cache_reads == 0
|
assert db.cache_reads == 0
|
||||||
assert list(points.values()) == [point]
|
assert list(points.values()) == [point]
|
||||||
|
|
||||||
|
|
||||||
# ── Яндекс: город из второго компонента адреса ───────────────────────────────
|
|
||||||
|
|
||||||
|
|
||||||
def _yandex(**over: Any) -> dict[str, Any]:
|
|
||||||
base = _payload(
|
|
||||||
source="yandex",
|
|
||||||
source_url="https://realty.yandex.ru/offer/1234567890123456789/",
|
|
||||||
source_id="1234567890123456789",
|
|
||||||
address="Россия, Москва, Коробейников переулок, 1",
|
|
||||||
lat=55.737312,
|
|
||||||
lon=37.601006,
|
|
||||||
)
|
|
||||||
base.update(over)
|
|
||||||
return base
|
|
||||||
|
|
||||||
|
|
||||||
def test_yandex_moscow_is_recognised_without_geocode() -> None:
|
|
||||||
assert mod.is_moscow_yandex_address("Россия, Москва, Коробейников переулок, 1")
|
|
||||||
assert mod.is_moscow_yandex_address("Россия, Москва, Зеленоград, корпус 1130")
|
|
||||||
|
|
||||||
|
|
||||||
def test_yandex_oblast_is_not_moscow() -> None:
|
|
||||||
"""Ровно те два значения, что встречаются в корпусе; область писать нельзя."""
|
|
||||||
oblast = "Россия, Московская область, Химки, Ивановская улица, 5"
|
|
||||||
assert not mod.is_moscow_yandex_address(oblast)
|
|
||||||
assert not mod.is_moscow_yandex_address("Москва, Коробейников переулок, 1") # без страны
|
|
||||||
assert not mod.is_moscow_yandex_address("")
|
|
||||||
assert not mod.is_moscow_yandex_address(None)
|
|
||||||
|
|
||||||
|
|
||||||
def test_yandex_oblast_rows_do_not_reach_the_writer(captured: list[dict[str, Any]]) -> None:
|
|
||||||
rows = [
|
|
||||||
{"id": 1, "payload": _yandex(source_id="1")},
|
|
||||||
{
|
|
||||||
"id": 2,
|
|
||||||
"payload": _yandex(
|
|
||||||
source_id="2", address="Россия, Московская область, Химки, Ивановская улица, 5"
|
|
||||||
),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
counters = mod.import_msk_raw(_FakeSession(rows), source="yandex", batch_size=10)
|
|
||||||
assert [lot.source_id for lot in captured[0]["lots"]] == ["1"]
|
|
||||||
assert counters.skipped_not_moscow == 1
|
|
||||||
|
|
||||||
|
|
||||||
def test_yandex_keeps_its_own_coordinates(captured: list[dict[str, Any]]) -> None:
|
|
||||||
"""Координаты у Яндекса свои — геокод ему не нужен, geom появляется сразу."""
|
|
||||||
rows = [{"id": 1, "payload": _yandex()}]
|
|
||||||
mod.import_msk_raw(_FakeSession(rows), source="yandex", batch_size=10)
|
|
||||||
lot = captured[0]["lots"][0]
|
|
||||||
assert (lot.lat, lot.lon) == (55.737312, 37.601006)
|
|
||||||
|
|
||||||
|
|
||||||
def test_geocode_is_refused_for_yandex(captured: list[dict[str, Any]]) -> None:
|
|
||||||
"""Квота нужна только Авито: у Яндекса город и точка есть даром."""
|
|
||||||
with pytest.raises(SystemExit):
|
|
||||||
mod.import_msk_raw(
|
|
||||||
_FakeSession([{"id": 1, "payload": _yandex()}]), source="yandex", geocode=True
|
|
||||||
)
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue