fix encar duplicates

This commit is contained in:
qananasikq
2026-04-17 14:50:39 +03:00
parent 9d6c19be90
commit 9a0431e731

View File

@@ -2,6 +2,7 @@ import json
import logging import logging
import re import re
import time import time
from collections import Counter
from concurrent.futures import ThreadPoolExecutor, as_completed from concurrent.futures import ThreadPoolExecutor, as_completed
from dataclasses import dataclass, field, replace from dataclasses import dataclass, field, replace
from pathlib import Path from pathlib import Path
@@ -41,6 +42,7 @@ BATCH_VEHICLES_CHUNK_SIZE = 20
ENCAR_DETAIL_URL_TEMPLATE = "https://www.encar.com/dc/dc_cardetailview.do?carid={vehicle_id}" ENCAR_DETAIL_URL_TEMPLATE = "https://www.encar.com/dc/dc_cardetailview.do?carid={vehicle_id}"
ENCAR_IMAGE_BASE = "https://ci.encar.com" ENCAR_IMAGE_BASE = "https://ci.encar.com"
VEHICLE_ID_RE = re.compile(r"(?:carid|vehicleId)=?(\d+)") VEHICLE_ID_RE = re.compile(r"(?:carid|vehicleId)=?(\d+)")
PHOTO_VEHICLE_ID_RE = re.compile(r"/(\d+)_\d+\.(?:jpg|jpeg|png|webp)$", re.IGNORECASE)
# Максимальный номер фото для пробинга и допустимые промахи подряд # Максимальный номер фото для пробинга и допустимые промахи подряд
PHOTO_PROBE_MAX = 50 PHOTO_PROBE_MAX = 50
@@ -613,6 +615,42 @@ class EncarScraper:
return True return True
def _canonical_vehicle_id_from_images(self, record: CarRecord) -> str | None:
current_vehicle_id = self._extract_vehicle_id(record.origin_url or "")
if not current_vehicle_id:
current_vehicle_id = record.origin_id.split(":", 1)[-1] if ":" in record.origin_id else record.origin_id
image_vehicle_ids: list[str] = []
for image in record.images:
url = image.fullres_image or ""
match = PHOTO_VEHICLE_ID_RE.search(url)
if match:
image_vehicle_ids.append(match.group(1))
if not image_vehicle_ids:
return None
canonical_vehicle_id, occurrences = Counter(image_vehicle_ids).most_common(1)[0]
if canonical_vehicle_id == current_vehicle_id:
return None
# Канонизируем только если mismatch подтверждается минимум двумя фото.
if occurrences < 2:
return None
return canonical_vehicle_id
def _apply_canonical_vehicle_id(self, record: CarRecord) -> bool:
canonical_vehicle_id = self._canonical_vehicle_id_from_images(record)
if not canonical_vehicle_id:
return False
record.parser_id = f"encar:{canonical_vehicle_id}"
record.origin_id = f"encar:{canonical_vehicle_id}"
record.origin_url = ENCAR_DETAIL_URL_TEMPLATE.format(vehicle_id=canonical_vehicle_id)
record.slug = self.car_mapper._build_slug(record.brand, record.model, canonical_vehicle_id)
return True
def collect_listing( def collect_listing(
self, self,
limit: int | None = None, limit: int | None = None,
@@ -1130,6 +1168,7 @@ class EncarScraper:
failed_ids: list[str] = [] failed_ids: list[str] = []
skipped_brands = 0 skipped_brands = 0
skipped_runtime = 0 skipped_runtime = 0
canonicalized_records = 0
# Фаза 1: маппинг без пробинга + фильтр брендов # Фаза 1: маппинг без пробинга + фильтр брендов
for item in items: for item in items:
@@ -1151,7 +1190,6 @@ class EncarScraper:
records.append(record) records.append(record)
probe_tasks.append((len(records) - 1, item)) probe_tasks.append((len(records) - 1, item))
all_origin_ids.add(record.origin_id)
except Exception as exc: except Exception as exc:
logger.warning("Failed to map vehicle %s: %s", vehicle_id, exc) logger.warning("Failed to map vehicle %s: %s", vehicle_id, exc)
failed += 1 failed += 1
@@ -1171,6 +1209,30 @@ class EncarScraper:
# Без пробинга: берём только фото из API (как reference-проект) # Без пробинга: берём только фото из API (как reference-проект)
self._apply_api_photos(records, probe_tasks) self._apply_api_photos(records, probe_tasks)
if records:
for record in records:
if self._apply_canonical_vehicle_id(record):
canonicalized_records += 1
deduped_records: list[CarRecord] = []
seen_origin_ids: set[str] = set()
collapsed_duplicates = 0
for record in records:
if record.origin_id in seen_origin_ids:
collapsed_duplicates += 1
continue
seen_origin_ids.add(record.origin_id)
deduped_records.append(record)
all_origin_ids.add(record.origin_id)
records = deduped_records
if canonicalized_records or collapsed_duplicates:
logger.info(
"Canonicalized %d Encar records by photo vehicle id and collapsed %d duplicates in batch",
canonicalized_records,
collapsed_duplicates,
)
# Фаза 3: upsert в БД # Фаза 3: upsert в БД
if records: if records:
try: try: