V10.584 強化 PChome Nick 比對窄門
All checks were successful
CD Pipeline / deploy (push) Successful in 1m6s
All checks were successful
CD Pipeline / deploy (push) Successful in 1m6s
This commit is contained in:
@@ -526,6 +526,7 @@ FOCUSED_IDENTITY_VARIANT_REVIEW_BYPASS_REASONS = {
|
||||
"ts6_lubricant_100g_3pack",
|
||||
"ts6_peach_bright_gel_45g_3pack",
|
||||
"ts6_white_wash_peach_gel_kit",
|
||||
"ts6_cooling_clean_mousse_100g",
|
||||
"vaseline_baby_jelly_368g_3pack",
|
||||
}
|
||||
|
||||
@@ -601,7 +602,10 @@ FOCUSED_IDENTITY_TOTAL_PRICE_REASONS = {
|
||||
"ts6_lubricant_100g_3pack",
|
||||
"ts6_peach_bright_gel_45g_3pack",
|
||||
"ts6_white_wash_peach_gel_kit",
|
||||
"ts6_cooling_clean_mousse_100g",
|
||||
"vaseline_baby_jelly_368g_3pack",
|
||||
"nivea_creme_100ml",
|
||||
"schick_womens_sensitive_blade_3pack",
|
||||
}
|
||||
|
||||
SEARCH_BROAD_ANCHORS = {
|
||||
@@ -1665,6 +1669,15 @@ def _has_paulas_choice_body_lotion_210ml_2pack_alignment(
|
||||
return has_two_pack(left) and has_two_pack(right)
|
||||
|
||||
|
||||
def _has_nivea_creme_100ml_alignment(left: ProductIdentity, right: ProductIdentity) -> bool:
|
||||
brand_tokens = left.brand_tokens | right.brand_tokens
|
||||
if not ({"nivea", "妮維雅"} & brand_tokens):
|
||||
return False
|
||||
if not _has_shared_volume(left, right, 100):
|
||||
return False
|
||||
return all("妮維雅霜" in item.searchable_name and "隨身版" in item.searchable_name for item in (left, right))
|
||||
|
||||
|
||||
def _has_refill_pack(identity: ProductIdentity) -> bool:
|
||||
text = identity.normalized_name
|
||||
return bool(
|
||||
@@ -2332,8 +2345,11 @@ def score_marketplace_match(
|
||||
if brand_reason:
|
||||
reasons.append(brand_reason)
|
||||
reasons.extend(spec_reasons)
|
||||
if left.product_type and right.product_type and left.product_type != right.product_type:
|
||||
nivea_creme_100ml_alignment = _has_nivea_creme_100ml_alignment(left, right)
|
||||
if left.product_type and right.product_type and left.product_type != right.product_type and not nivea_creme_100ml_alignment:
|
||||
reasons.append("type_conflict")
|
||||
if nivea_creme_100ml_alignment:
|
||||
reasons.append("nivea_creme_100ml_type_alignment")
|
||||
model_line_conflict = _has_model_line_conflict(left, right)
|
||||
if model_line_conflict:
|
||||
reasons.append("model_line_conflict")
|
||||
@@ -2548,7 +2564,7 @@ def score_marketplace_match(
|
||||
hard_veto = True
|
||||
if chinese_name_score < 0.16 and token_score < 0.72:
|
||||
hard_veto = True
|
||||
if left.product_type and right.product_type and left.product_type != right.product_type:
|
||||
if left.product_type and right.product_type and left.product_type != right.product_type and not nivea_creme_100ml_alignment:
|
||||
hard_veto = True
|
||||
if sun_protection_line_conflict:
|
||||
hard_veto = True
|
||||
@@ -4562,6 +4578,14 @@ def _has_focused_low_score_exact_identity_line(left: ProductIdentity, right: Pro
|
||||
and _has_exact_count_alignment(left, right)
|
||||
):
|
||||
return "ts6_white_mousse_180g_3pack"
|
||||
if (
|
||||
{"ts6", "護一生"} & (left.brand_tokens & right.brand_tokens)
|
||||
and "沁涼潔淨慕斯" in left_text
|
||||
and "沁涼潔淨慕斯" in right_text
|
||||
and _has_shared_weight(left, right, 100)
|
||||
and _has_exact_count_alignment(left, right)
|
||||
):
|
||||
return "ts6_cooling_clean_mousse_100g"
|
||||
if (
|
||||
{"ts6", "護一生"} & (left.brand_tokens & right.brand_tokens)
|
||||
and "蜜愛潤滑液" in left_text
|
||||
@@ -4659,6 +4683,15 @@ def _has_focused_low_score_exact_identity_line(left: ProductIdentity, right: Pro
|
||||
and _has_shared_volume(left, right, 473)
|
||||
):
|
||||
return "cetaphil_long_lotion_473ml"
|
||||
if (
|
||||
{"nivea", "妮維雅"} & (left.brand_tokens & right.brand_tokens)
|
||||
and "妮維雅霜" in left_text
|
||||
and "妮維雅霜" in right_text
|
||||
and "隨身版" in left_text
|
||||
and "隨身版" in right_text
|
||||
and _has_shared_volume(left, right, 100)
|
||||
):
|
||||
return "nivea_creme_100ml"
|
||||
if (
|
||||
"nailmatic" in (left.brand_tokens & right.brand_tokens)
|
||||
and "小精靈" in left_text
|
||||
@@ -4801,6 +4834,17 @@ def _has_focused_low_score_exact_identity_line(left: ProductIdentity, right: Pro
|
||||
and "唇頰兩用" in pair_text
|
||||
):
|
||||
return "benefit_lip_tint"
|
||||
if (
|
||||
{"schick", "舒適牌"} & (left.brand_tokens & right.brand_tokens)
|
||||
and "舒綺" in left_text
|
||||
and "舒綺" in right_text
|
||||
and "除毛刀片" in left_text
|
||||
and "除毛刀片" in right_text
|
||||
and "敏感肌" in left_text
|
||||
and "敏感肌" in right_text
|
||||
and _has_shared_count(left, right, 3, "入")
|
||||
):
|
||||
return "schick_womens_sensitive_blade_3pack"
|
||||
if (
|
||||
{"herb24", "草本"} & (left.brand_tokens & right.brand_tokens)
|
||||
and "晨霧純精油擴香儀ii" in left_text
|
||||
|
||||
@@ -11,6 +11,7 @@ API 參考:
|
||||
- 圖片 URL: https://img.pchome.com.tw/cs{Pic.B}
|
||||
"""
|
||||
|
||||
import html
|
||||
import re
|
||||
import time
|
||||
import logging
|
||||
@@ -49,14 +50,34 @@ class PChomeProduct:
|
||||
return data
|
||||
|
||||
|
||||
def _clean_subtitle_text(value: str) -> str:
|
||||
"""Normalize PChome Nick text before using it as identity evidence."""
|
||||
text = html.unescape(str(value or ""))
|
||||
text = re.sub(r"<\s*br\s*/?\s*>", " ", text, flags=re.I)
|
||||
text = re.sub(r"<[^>]+>", " ", text)
|
||||
text = re.sub(r"[★☆][^★☆]{0,80}[★☆]", " ", text)
|
||||
return re.sub(r"\s+", " ", text).strip()
|
||||
|
||||
|
||||
def _compact_identity_text(value: str) -> str:
|
||||
return re.sub(r"[^0-9a-zA-Z\u4e00-\u9fff]+", "", str(value or "").lower())
|
||||
|
||||
|
||||
def _build_match_name(name: str, subtitle: str) -> str:
|
||||
"""Build an identity-rich title without duplicating the PChome display name."""
|
||||
display_name = str(name or '').strip()
|
||||
nick = str(subtitle or '').strip()
|
||||
nick = _clean_subtitle_text(subtitle)
|
||||
if not nick or nick == display_name:
|
||||
return display_name
|
||||
if display_name and nick.startswith(display_name):
|
||||
display_compact = _compact_identity_text(display_name)
|
||||
nick_compact = _compact_identity_text(nick)
|
||||
if display_name and (
|
||||
nick.startswith(display_name)
|
||||
or (display_compact and nick_compact.startswith(display_compact))
|
||||
):
|
||||
return nick
|
||||
if display_compact and display_compact == nick_compact:
|
||||
return display_name
|
||||
return f"{display_name} {nick}".strip()
|
||||
|
||||
|
||||
@@ -348,7 +369,7 @@ class PChomeCrawler:
|
||||
image_url = f"{self.IMAGE_BASE_URL}{pic_path}" if pic_path else ''
|
||||
|
||||
name = data.get('Name', '') or ''
|
||||
subtitle = data.get('Nick', '') or ''
|
||||
subtitle = _clean_subtitle_text(data.get('Nick', '') or '')
|
||||
|
||||
return PChomeProduct(
|
||||
product_id=product_id,
|
||||
|
||||
Reference in New Issue
Block a user