Add vehicle color parsing
This commit is contained in:
@@ -420,11 +420,20 @@ class EncarMapper:
|
||||
return self._clean_base_model_name(translated)
|
||||
|
||||
def _translate_color(self, color_raw: str) -> str:
|
||||
value = self._as_str(color_raw)
|
||||
value = re.sub(r"\s+", "", self._as_str(color_raw))
|
||||
if not value:
|
||||
return "other"
|
||||
return COLOR_TRANSLATIONS.get(value, self._translate_text(value).lower() or "other")
|
||||
|
||||
def apply_batch_color(self, record: CarRecord, vehicle_data: dict[str, Any]) -> None:
|
||||
spec = vehicle_data.get("spec")
|
||||
if not isinstance(spec, dict):
|
||||
return
|
||||
|
||||
color_raw = self._as_str(spec.get("colorName") or spec.get("customColor") or "")
|
||||
if color_raw:
|
||||
record.color = self._translate_color(color_raw)
|
||||
|
||||
def _to_float(self, value: Any) -> float | None:
|
||||
if value is None:
|
||||
return None
|
||||
@@ -1500,24 +1509,33 @@ class EncarScraper:
|
||||
for i in range(0, len(tasks), BATCH_VEHICLES_CHUNK_SIZE):
|
||||
chunks.append(tasks[i:i + BATCH_VEHICLES_CHUNK_SIZE])
|
||||
|
||||
def _fetch_chunk(chunk: list[tuple[int, str]]) -> list[tuple[int, list[ImageRecord]]]:
|
||||
def _fetch_chunk(chunk: list[tuple[int, str]]) -> list[tuple[int, list[ImageRecord], dict[str, Any]]]:
|
||||
ids_str = ",".join(vid for _, vid in chunk)
|
||||
path = f"/v1/readside/vehicles?vehicleIds={ids_str}&include=PHOTOS"
|
||||
for attempt in range(2):
|
||||
path = f"/v1/readside/vehicles?vehicleIds={ids_str}&include=SPEC,PHOTOS"
|
||||
for attempt in range(3):
|
||||
try:
|
||||
resp = pool.request("GET", path, timeout=15, retries=False)
|
||||
if resp.status == 200:
|
||||
if not resp.data:
|
||||
return [(ri, []) for ri, _ in chunk]
|
||||
return [(ri, [], {}) for ri, _ in chunk]
|
||||
vehicles = json.loads(resp.data.decode("utf-8", errors="ignore"))
|
||||
# Маппим по vehicleId, а не по индексу — API может пропускать удалённые ID
|
||||
id_to_rec: dict[str, int] = {vid: ri for ri, vid in chunk}
|
||||
result: list[tuple[int, list[ImageRecord]]] = []
|
||||
for vehicle_data in vehicles:
|
||||
result: list[tuple[int, list[ImageRecord], dict[str, Any]]] = []
|
||||
matched_record_indexes: set[int] = set()
|
||||
for vehicle_position, vehicle_data in enumerate(vehicles):
|
||||
vid = str(vehicle_data.get("vehicleId") or vehicle_data.get("id") or "")
|
||||
rec_idx = id_to_rec.get(vid)
|
||||
rec_idx = None
|
||||
if vehicle_position < len(chunk):
|
||||
positional_rec_idx = chunk[vehicle_position][0]
|
||||
if positional_rec_idx not in matched_record_indexes:
|
||||
rec_idx = positional_rec_idx
|
||||
if rec_idx is None:
|
||||
rec_idx = id_to_rec.get(vid)
|
||||
if rec_idx is None:
|
||||
continue
|
||||
if rec_idx in matched_record_indexes:
|
||||
continue
|
||||
matched_record_indexes.add(rec_idx)
|
||||
photos_raw = vehicle_data.get("photos") or []
|
||||
images: list[ImageRecord] = []
|
||||
seen_paths: set[str] = set()
|
||||
@@ -1535,28 +1553,28 @@ class EncarScraper:
|
||||
order_index=order,
|
||||
))
|
||||
images.sort(key=lambda x: x.order_index)
|
||||
result.append((rec_idx, images))
|
||||
result.append((rec_idx, images, vehicle_data))
|
||||
return result
|
||||
if resp.status in (429, 503):
|
||||
time.sleep(1 + attempt * 2)
|
||||
continue
|
||||
return [(ri, []) for ri, _ in chunk]
|
||||
return [(ri, [], {}) for ri, _ in chunk]
|
||||
except json.JSONDecodeError:
|
||||
return [(ri, []) for ri, _ in chunk]
|
||||
return [(ri, [], {}) for ri, _ in chunk]
|
||||
except Exception as exc:
|
||||
logger.debug("Batch photo chunk error (attempt %d): %s", attempt + 1, exc)
|
||||
if attempt < 1:
|
||||
if attempt < 2:
|
||||
time.sleep(1)
|
||||
return [(ri, []) for ri, _ in chunk]
|
||||
return [(ri, [], {}) for ri, _ in chunk]
|
||||
|
||||
results: dict[int, list[ImageRecord]] = {}
|
||||
results: dict[int, tuple[list[ImageRecord], dict[str, Any]]] = {}
|
||||
with ThreadPoolExecutor(max_workers=30) as executor:
|
||||
futures = {executor.submit(_fetch_chunk, ch): ch for ch in chunks}
|
||||
for future in as_completed(futures):
|
||||
try:
|
||||
for rec_idx, images in future.result():
|
||||
if images:
|
||||
results[rec_idx] = images
|
||||
for rec_idx, images, vehicle_data in future.result():
|
||||
if vehicle_data:
|
||||
results[rec_idx] = (images, vehicle_data)
|
||||
except Exception as exc:
|
||||
logger.warning("Batch photo future failed: %s", exc)
|
||||
|
||||
@@ -1565,7 +1583,10 @@ class EncarScraper:
|
||||
fallback_count = 0
|
||||
for rec_idx, item in probe_tasks:
|
||||
if rec_idx in results:
|
||||
records[rec_idx].images = results[rec_idx]
|
||||
images, vehicle_data = results[rec_idx]
|
||||
if images:
|
||||
records[rec_idx].images = images
|
||||
self.car_mapper.apply_batch_color(records[rec_idx], vehicle_data)
|
||||
api_ok += 1
|
||||
else:
|
||||
fallback_count += 1
|
||||
|
||||
Reference in New Issue
Block a user