import re, html import pandas as pd from typing import Dict, Any BR_RE = re.compile(r'<\s*br\s*/?>', flags=re.I) TAG_RE = re.compile(r'<[^>]+>') def clean_html(text: str) -> str: if not isinstance(text, str): return "" #
-> \n, bỏ tags, unescape text = BR_RE.sub("\n", text) text = TAG_RE.sub("", text) text = html.unescape(text) # dọn khoảng trắng, gạch đầu dòng lines = [re.sub(r'^\s*[-•–]\s*', '', ln.strip()) for ln in text.splitlines()] # bỏ dòng trống lines = [ln for ln in lines if ln] return "\n".join(lines) # --------- PARSER CHO SPECS ---------- def parse_product_specs(raw: str) -> Dict[str, Any]: text = clean_html(raw) specs: Dict[str, Any] = {} # Tách theo dòng dạng "Khóa: Giá trị" for ln in text.splitlines(): if ":" in ln: k, v = ln.split(":", 1) k = k.strip().lower() v = v.strip() specs[k] = v else: # Dòng không có dấu :, giữ lại để regex sau nhận dạng specs.setdefault("_free", []).append(ln) # Ánh xạ các khóa thường gặp sang tên chuẩn alias = { "công nghệ màn hình": "screen_tech", "độ phân giải": "resolution", "kích thước màn hình": "screen_size", "hệ điều hành": "os", "vi xử lý": "chipset", "bộ nhớ trong": "storage", "ram": "ram", "mạng di động": "network", "số khe sim": "sim", "dung lượng pin": "battery", } norm: Dict[str, Any] = {} for k, v in list(specs.items()): if k in alias: norm[alias[k]] = v # ---- Regex nhận dạng nếu thiếu khóa/không chuẩn ---- all_text = "\n".join([*text.splitlines(), *(specs.get("_free", []))]).lower() # screen size m = re.search(r'(\d+(?:\.\d+)?)\s*(inch|")', all_text) if m and "screen_size" not in norm: norm["screen_size"] = m.group(1) + " inch" # resolution m = re.search(r'(\d{3,4})\s*[x×]\s*(\d{3,4})', all_text) if m and "resolution" not in norm: norm["resolution"] = f"{m.group(1)}×{m.group(2)}" # RAM (lấy số lớn nhất trong dòng có 'ram' hoặc toàn văn) m = re.search(r'ram[^0-9]*(\d+)\s*gb', all_text) or re.search(r'(\d+)\s*\+\s*(\d+)\s*gb\s*ram', all_text) if m and "ram" not in norm: if len(m.groups()) == 2: norm["ram"] = f"{max(int(m.group(1)), int(m.group(2)))}GB" else: norm["ram"] = f"{m.group(1)}GB" # storage m = re.search(r'(?:bộ nhớ trong|rom)[^0-9]*(\d{2,4})\s*gb', all_text) if m and "storage" not in norm: norm["storage"] = f"{m.group(1)}GB" # battery m = re.search(r'(\d{3,5})\s*mAh', all_text, flags=re.I) if m and "battery" not in norm: norm["battery"] = f"{m.group(1)} mAh" # chipset (nếu có “unisoc|snapdragon|helio|dimensity|exynos|kirin” trên dòng “vi xử lý”) if "chipset" not in norm: m = re.search(r'(unisoc|snapdragon|helio|dimensity|exynos|kirin)[^\n]*', all_text, flags=re.I) if m: norm["chipset"] = m.group(0).strip() # OS if "os" not in norm: m = re.search(r'android\s*\d+\s*(?:[a-z0-9 .-]*)', all_text, flags=re.I) if m: norm["os"] = m.group(0).strip() # sim m = re.search(r'(\d+)\s*(?:nano\s*)?sim', all_text) if m and "sim" not in norm: norm["sim"] = f"{m.group(1)} SIM" # 5G flag norm["has_5g"] = bool(re.search(r'\b5g\b', all_text)) and not bool(re.search(r'không\s+hỗ\s+trợ\s+5g', all_text)) return norm # ---- Làm sạch promotion & build text cho embedding ---- def clean_promotion(raw: str) -> str: return clean_html(raw) def build_embedding_text(row: pd.Series) -> str: parts = [str(row.get("title", ""))] if row.get("product_specs_clean"): parts.append(row["product_specs_clean"]) if row.get("product_promotion_clean"): parts.append(row["product_promotion_clean"]) return " | ".join([p for p in parts if p]).strip() # ======= TÍCH HỢP VÀO DataProcessor ======= def process_dataframe(df: pd.DataFrame) -> pd.DataFrame: df = df.copy() # Làm sạch text df["product_specs_clean"] = df["product_specs"].map(clean_html) df["product_promotion_clean"] = df.get("product_promotion", "").map(clean_promotion) # Parse specs có cấu trúc parsed = df["product_specs_clean"].map(parse_product_specs) df["screen_size"] = parsed.map(lambda d: d.get("screen_size")) df["resolution"] = parsed.map(lambda d: d.get("resolution")) df["chipset"] = parsed.map(lambda d: d.get("chipset")) df["ram"] = parsed.map(lambda d: d.get("ram")) df["storage"] = parsed.map(lambda d: d.get("storage")) df["battery"] = parsed.map(lambda d: d.get("battery")) df["sim"] = parsed.map(lambda d: d.get("sim")) df["os"] = parsed.map(lambda d: d.get("os")) df["has_5g"] = parsed.map(lambda d: d.get("has_5g")) # Trường text để embed (đủ ngữ nghĩa) df["embedding_text"] = df.apply(build_embedding_text, axis=1) return df if __name__ == "__main__": # Ví dụ sử dụng df = pd.read_excel("data.xlsx") df_processed = process_dataframe(df) print(df_processed['embedding_text'].head(3)) df_processed.to_excel("data_processed.xlsx", index=False)