之前遇到一个比较头疼的场景:一批电商商品链接因为活动下架、店铺调整等原因失效了,但历史运营记录、采购清单和竞品分析还需要继续使用其中的关键参数。比如标题里写得很清楚的“可拆洗布艺沙发床”“奶油风”“1.55米”“母婴A类”“0甲醛雪尼尔”“羽绒版”,这些信息如果只躺在失效链接里,后续做选品复盘和价格对比时就等于数据丢失。本文就用这个场景,带大家从零实现一个“商品标题结构化解析与数据分析”的小工具,既能处理失效商品信息,也能作为数据清洗、规则解析、SQLite 存储的入门练手项目。
这套方案基于 Python 实现,核心思路是:把非结构化的商品标题转换成结构化的字段数据,再落到 SQLite 里做查询和统计。全文包含完整代码、样例数据、运行结果和常见问题排查,适合对 Python 数据处理、正则表达式和轻量级存储感兴趣的同学,拿来就能改、就能跑。
1. 背景与核心概念
1.1 什么是商品标题结构化解析
商品标题本质上是一条非结构化文本,但里面包含的信息密度很高。以本文的原始标题为例:
【已失效】狄普雾屿 可拆洗布艺沙发床 2026新款 奶油风 1.55米 母婴A类 0甲醛雪尼尔 羽绒版这条标题里可以拆出以下信息:
| 信息维度 | 标题原文 | 结构化字段 |
|---|---|---|
| 品牌 | 狄普雾屿 | brand |
| 商品类型 | 布艺沙发床 | product_type |
| 功能特性 | 可拆洗 | feature |
| 年份 | 2026新款 | year |
| 风格 | 奶油风 | style |
| 尺寸 | 1.55米 | size_cm |
| 安全等级 | 母婴A类 | safety_level |
| 环保声明 | 0甲醛 | eco_flag |
| 面料材质 | 雪尼尔 | material |
| 填充物 | 羽绒版 | filling |
结构化解析,就是通过正则表达式、关键词词典和规则引擎,把上面这种“挤在一起”的标题文本,拆成一个一个可查询的字段。这样后续的统计、筛选、对比、可视化才有的放矢。
1.2 链接失效后的数据场景
“【已失效】”这个前缀,在日常数据工作中很常见。当一个商品链接失效后,我们仍然可能需要这些历史数据来做:
- 采购复盘:去年采购的沙发床,面料和填充物分别是什么,有没有“母婴A类”标识。
- 价格趋势:虽然链接失效了,但历史快照数据里可能还有价格记录,需要和标题参数关联。
- 选品参考:同类目的热销品在风格、尺寸、材质上有哪些共性。
- 合规检查:检查商品是否存在“0甲醛”等宣称,后续需要看到检测报告。
如果没有一套解析工具,面对几百条、几千条标题文本,靠人工整理效率太低,而且容易出错。解析工具的意义,就是把这种重复劳动自动化。
1.3 为什么选择 Python 和 SQLite
Python 在处理文本方面有天然优势:正则表达式库re、数据分析库pandas、以及海量的文本处理生态,都能快速上手。SQLite 则是一个非常轻量的嵌入式数据库,不需要单独安装数据库服务,一个文件就能完成建表、查询、统计,非常适合这种小规模的数据处理任务。
用这套组合,不需要部署 Hadoop,也不需要引入大型数仓,一台普通电脑就能完成从标题解析到数据统计的全流程。
2. 环境准备与项目结构
2.1 运行环境说明
本文示例使用 Python 3 环境。版本需要根据你的项目实际情况调整,本文示例以常见环境为例,重点演示配置思路。建议使用 Python 3.9 及以上版本,但即使版本略低,只要包含re、sqlite3、csv这些标准库,代码也能正常运行。
需要安装的第三方库:
pip install pandaspandas主要用于最终的数据读取和统计分析。如果不方便安装,也可以使用 Python 自带的csv模块完成大部分功能,本文会先以标准库为主,最后给出 pandas 的进阶用法。
2.2 项目目录结构
建议创建如下目录结构:
product-parser/ ├── data/ │ ├── sample_data.csv # 样例数据 │ └── parsed_products.db # SQLite 数据库文件(运行后生成) ├── parser/ │ ├── __init__.py │ ├── title_parser.py # 标题解析核心逻辑 │ ├── storage.py # SQLite 存储逻辑 │ └── analyzer.py # 统计与分析逻辑 ├── main.py # 主程序入口 └── requirements.txt # 依赖清单如果只是本地练习,不建包结构也可以,直接写一个main.py也能运行。但为了让代码更清晰,本文会按模块拆分。
2.3 样例数据准备
在data/sample_data.csv中写入如下样例数据。这些数据是我为演示构造的示例,不代表真实商品,也不涉及真实商家信息:
id,title,source_url,collected_at 1,【已失效】狄普雾屿 可拆洗布艺沙发床 2026新款 奶油风 1.55米 母婴A类 0甲醛雪尼尔 羽绒版,https://example.com/item/1,2025-01-15 10:00:00 2,北欧简约实木沙发 双人位 2.2米 科技布 防猫抓 可拆洗,https://example.com/item/2,2025-01-15 10:05:00 3,现代轻奢真皮沙发 三人位 2.8米 头层牛皮 意式极简,https://example.com/item/3,2025-01-16 09:30:00 4,日式榻榻米沙发椅 单人位 80cm 棉麻面料 可折叠,https://example.com/item/4,2025-01-16 11:20:00 5,奶油风布艺沙发床 小户型 155cm 母婴A类 雪尼尔 羽绒版 可拆洗,https://example.com/item/5,2025-01-17 14:00:00 6,简约现代科技布沙发 三人位 2.6米 防泼水 高回弹海绵,https://example.com/item/6,2025-01-18 16:30:00 7,意式极简真皮沙发 大平层 3.2米 头层牛皮 乳胶填充,https://example.com/item/7,2025-01-19 10:15:00 8,奶油风可拆洗沙发 小户型 1.8米 母婴A类 科技布 0甲醛,https://example.com/item/8,2025-01-20 13:45:003. 数据建模与规则设计
3.1 数据库字段设计
在 SQLite 中,我们设计一张product_info表,用来存储解析后的商品信息:
CREATE TABLE IF NOT EXISTS product_info ( id INTEGER PRIMARY KEY AUTOINCREMENT, raw_id INTEGER, title TEXT, brand TEXT, product_type TEXT, style TEXT, size_cm REAL, safety_level TEXT, eco_flag INTEGER, material TEXT, filling TEXT, washable INTEGER, year TEXT, source_url TEXT, collected_at TEXT, parse_time TEXT, UNIQUE(raw_id) );字段说明:
| 字段名 | 含义 | 示例 |
|---|---|---|
| raw_id | 原始数据编号 | 1 |
| title | 原始标题 | 可拆洗布艺沙发床... |
| brand | 品牌 | 狄普雾屿 |
| product_type | 商品类型 | 沙发床 |
| style | 风格 | 奶油风 |
| size_cm | 归一化后的尺寸(厘米) | 155.0 |
| safety_level | 安全等级 | 母婴A类 |
| eco_flag | 是否宣称环保 | 1 |
| material | 面料材质 | 雪尼尔 |
| filling | 填充物 | 羽绒 |
| washable | 是否可拆洗 | 1 |
| year | 年份/新款标识 | 2026 |
| source_url | 来源链接 | https://example.com/item/1 |
| collected_at | 采集时间 | 2025-01-15 10:00:00 |
| parse_time | 解析时间 | 2025-01-21 09:00:00 |
这里把“0甲醛”处理成eco_flag布尔字段,而不是直接存文本,是为了方便后续统计“环保宣称占比”。要注意的是,“0甲醛”更多是商家宣传用语,真正是否达标要看检测报告。在工程处理时,我们只做信息提取,不做质量鉴定。
3.2 规则词典设计
规则解析的关键是建立关键词词典。词典可以维护成 Python 字典,也可以放在外置 JSON 文件中。本文为了演示简单,先放在代码里。
# parser/dicts.py BRAND_DICT = ["狄普雾屿", "林氏木业", "全友", "顾家家居", "喜临门", "慕思"] STYLE_DICT = { "奶油风": ["奶油", "奶油风"], "现代简约": ["现代", "简约"], "北欧": ["北欧"], "日式": ["日式", "榻榻米"], "意式极简": ["意式", "极简"], "轻奢": ["轻奢"] } MATERIAL_DICT = ["雪尼尔", "科技布", "棉麻", "头层牛皮", "真皮", "绒布", "亚麻"] FILLING_DICT = ["羽绒", "乳胶", "高回弹海绵", "海绵", "记忆棉"] FEATURE_DICT = { "可拆洗": ["可拆洗", "拆洗"], "可折叠": ["可折叠", "折叠"], "防猫抓": ["防猫抓"], "防泼水": ["防泼水", "防水"], "多功能": ["多功能"] } SAFETY_DICT = { "母婴A类": ["母婴A类", "A类", "母婴"], "B类": ["B类"], "C类": ["C类"] }为什么要把词典放到单独模块?因为真实业务中,词典会随着数据增加而持续迭代。比如今天发现了一个新面料“灯芯绒”,只需要在MATERIAL_DICT里加一个词,不需要改解析逻辑。这种设计能降低后期维护成本。
4. 核心代码实现
4.1 标题清洗
解析前要先做标题清洗,主要处理以下几类噪声:
- 移除“【已失效】”这类状态标记。
- 统一全角/半角符号和空格。
- 去掉多余的特殊字符。
# parser/title_parser.py import re def clean_title(title: str) -> str: """清洗原始标题,返回去除失效标记和多余符号的文本。""" if not title: return "" # 去除【】里的状态标记,如【已失效】 title = re.sub(r"【.*?】", "", title) # 去除方括号和括号 title = re.sub(r"[\[\]()()]", " ", title) # 将全角空格转半角,多个空格合并为一个 title = title.replace("\u3000", " ").replace(" ", " ") title = re.sub(r"\s+", " ", title).strip() return title这段代码里有两个值得注意的点:
re.sub(r"【.*?】", "", title)使用了非贪婪匹配.*?,会精准删除“【已失效】”这类括号内容。如果写成.*,可能因为贪婪匹配误删标题后面的部分。re.sub(r"\s+", " ", title)将多个连续空白字符合并为单个空格,方便后续按空格分词。
4.2 解析尺寸字段
尺寸是商品参数中非常重要的数值字段,写法非常多,例如1.55米、155cm、1.5m。需要统一归一化处理,方便后续按范围筛选。
def extract_size(text: str) -> float: """提取尺寸并统一转为厘米。""" # 匹配数字+单位,单位支持 米、m、cm、厘米 pattern = re.compile(r"(\d+(?:\.\d+)?)\s*(米|m|cm|厘米)", re.IGNORECASE) match = pattern.search(text) if not match: return None value = float(match.group(1)) unit = match.group(2).lower() if unit.startswith("cm"): return value elif unit == "米" or unit == "m": return value * 100 return None示例运行结果:
extract_size("1.55米") # 155.0 extract_size("155cm") # 155.0 extract_size("80cm") # 80.04.3 品牌与风格解析
品牌解析可以通过词典匹配实现,也可以结合标题开头的位置信息。中文商品标题通常会把品牌放在最前面,但也不绝对,所以本文用词典匹配加遍历方式实现。
def extract_brand(text: str, brand_dict: list) -> str: """从标题中匹配品牌词。""" for brand in brand_dict: if brand in text: return brand return None风格解析类似:
def extract_style(text: str, style_dict: dict) -> str: """从标题中匹配风格关键词。""" for style_name, keywords in style_dict.items(): for kw in keywords: if kw in text: return style_name return None4.4 材质与填充物解析
材质和填充物解析逻辑类似,都是关键词匹配。需要注意标题中可能出现多个材质词,但为了样例简单,本示例只返回第一个匹配到的结果。实际项目中,可以改为返回列表。
def extract_by_keywords(text: str, keywords: list) -> str: """通用关键词匹配,返回第一个命中的关键词。""" for kw in keywords: if kw in text: return kw return None4.5 安全等级与环保标识解析
安全等级匹配需要注意词序,“母婴A类”包含“A类”和“母婴”两个关键词,所以匹配时要把更长的词放在前面,避免先匹配到“A类”导致结果不完整。
def extract_safety_level(text: str, safety_dict: dict) -> str: """解析安全等级。""" for level, keywords in safety_dict.items(): for kw in keywords: if kw in text: return level return None def extract_eco_flag(text: str) -> int: """判断标题中是否包含环保宣称。""" if "0甲醛" in text or "零甲醛" in text or "无甲醛" in text or "环保" in text: return 1 return 04.6 合并成一个解析器类
把上面的方法统一封装成一个ProductTitleParser类,方便复用。
# parser/title_parser.py import re from datetime import datetime class ProductTitleParser: def __init__(self): from parser.dicts import ( BRAND_DICT, STYLE_DICT, MATERIAL_DICT, FILLING_DICT, FEATURE_DICT, SAFETY_DICT ) self.brand_dict = BRAND_DICT self.style_dict = STYLE_DICT self.material_dict = MATERIAL_DICT self.filling_dict = FILLING_DICT self.feature_dict = FEATURE_DICT self.safety_dict = SAFETY_DICT def clean_title(self, title: str) -> str: """清洗标题""" if not title: return "" title = re.sub(r"【.*?】", "", title) title = re.sub(r"[\[\]()()]", " ", title) title = title.replace("\u3000", " ").replace(" ", " ") title = re.sub(r"\s+", " ", title).strip() return title def extract_size(self, text: str) -> float: """提取尺寸并转为厘米""" pattern = re.compile(r"(\d+(?:\.\d+)?)\s*(米|m|cm|厘米)", re.IGNORECASE) match = pattern.search(text) if not match: return None value = float(match.group(1)) unit = match.group(2).lower() if unit.startswith("cm"): return value elif unit == "米" or unit == "m": return value * 100 return None def extract_brand(self, text: str) -> str: for brand in self.brand_dict: if brand in text: return brand return None def extract_style(self, text: str) -> str: for style_name, keywords in self.style_dict.items(): for kw in keywords: if kw in text: return style_name return None def extract_material(self, text: str) -> str: for kw in self.material_dict: if kw in text: return kw return None def extract_filling(self, text: str) -> str: for kw in self.filling_dict: if kw in text: return kw return None def extract_feature(self, text: str) -> str: for feature_name, keywords in self.feature_dict.items(): for kw in keywords: if kw in text: return feature_name return None def extract_safety_level(self, text: str) -> str: for level, keywords in self.safety_dict.items(): for kw in keywords: if kw in text: return level return None def extract_eco_flag(self, text: str) -> int: if "0甲醛" in text or "零甲醛" in text or "无甲醛" in text or "环保" in text: return 1 return 0 def extract_year(self, text: str) -> str: pattern = re.compile(r"(20\d{2})\s*新款?") match = pattern.search(text) if match: return match.group(1) return None def parse(self, title: str) -> dict: clean = self.clean_title(title) result = { "cleaned_title": clean, "brand": self.extract_brand(clean), "product_type": "沙发" if "沙发" in clean else None, "style": self.extract_style(clean), "size_cm": self.extract_size(clean), "safety_level": self.extract_safety_level(clean), "eco_flag": self.extract_eco_flag(clean), "material": self.extract_material(clean), "filling": self.extract_filling(clean), "feature": self.extract_feature(clean), "year": self.extract_year(clean), } return result这里有一个细节:product_type暂时用了最简单的字符串包含判断。真实项目里可能需要更细的品类识别,比如“沙发床”“沙发椅”“单椅”,可以单独维护一个品类词典,后续扩展。
5. 完整实战案例
5.1 编写存储模块
接下来编写 SQLite 存储模块,负责建表、插入和去重。
# parser/storage.py import sqlite3 from datetime import datetime class SQLiteStorage: def __init__(self, db_path: str): self.db_path = db_path self.conn = sqlite3.connect(db_path) self.create_table() def create_table(self): sql = """ CREATE TABLE IF NOT EXISTS product_info ( id INTEGER PRIMARY KEY AUTOINCREMENT, raw_id INTEGER, title TEXT, cleaned_title TEXT, brand TEXT, product_type TEXT, style TEXT, size_cm REAL, safety_level TEXT, eco_flag INTEGER, material TEXT, filling TEXT, feature TEXT, year TEXT, washable INTEGER, source_url TEXT, collected_at TEXT, parse_time TEXT, UNIQUE(raw_id) ); """ self.conn.execute(sql) self.conn.commit() def insert_product(self, data: dict) -> bool: """插入一条解析结果。如果 raw_id 已存在则跳过。""" try: self.conn.execute( """ INSERT OR IGNORE INTO product_info ( raw_id, title, cleaned_title, brand, product_type, style, size_cm, safety_level, eco_flag, material, filling, feature, year, washable, source_url, collected_at, parse_time ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( data.get("raw_id"), data.get("title"), data.get("cleaned_title"), data.get("brand"), data.get("product_type"), data.get("style"), data.get("size_cm"), data.get("safety_level"), data.get("eco_flag"), data.get("material"), data.get("filling"), data.get("feature"), data.get("year"), data.get("washable", 0), data.get("source_url"), data.get("collected_at"), datetime.now().strftime("%Y-%m-%d %H:%M:%S"), ), ) self.conn.commit() return True except Exception as e: print(f"插入失败: {e}") return False def query_all(self, limit: int = 100): cursor = self.conn.execute( "SELECT * FROM product_info ORDER BY raw_id LIMIT ?", (limit,) ) columns = [desc[0] for desc in cursor.description] rows = cursor.fetchall() return [dict(zip(columns, row)) for row in rows] def close(self): self.conn.close()使用INSERT OR IGNORE是保证数据不重复的关键。结合UNIQUE(raw_id)约束,即使同一份数据重复跑多次,也不会产生重复记录。
5.2 编写读取与解析主流程
# main.py import csv import os from parser.title_parser import ProductTitleParser from parser.storage import SQLiteStorage def read_csv(file_path: str): """读取 CSV 文件,返回字典列表。""" rows = [] with open(file_path, mode="r", encoding="utf-8-sig") as f: reader = csv.DictReader(f) for row in reader: rows.append(row) return rows def process_sample(): csv_path = "data/sample_data.csv" db_path = "data/parsed_products.db" parser = ProductTitleParser() storage = SQLiteStorage(db_path) if not os.path.exists(csv_path): print(f"找不到样例数据: {csv_path}") return rows = read_csv(csv_path) success_count = 0 for row in rows: raw_title = row.get("title", "") parse_result = parser.parse(raw_title) parse_result["raw_id"] = int(row.get("id", 0)) parse_result["title"] = raw_title parse_result["source_url"] = row.get("source_url", "") parse_result["collected_at"] = row.get("collected_at", "") # 单独处理可拆洗字段 parse_result["washable"] = 1 if "可拆洗" in raw_title or "拆洗" in raw_title else 0 ok = storage.insert_product(parse_result) if ok: success_count += 1 print(f"解析 [{raw_id}] -> {parse_result}") print(f"共处理 {len(rows)} 条,成功入库 {success_count} 条。") storage.close() if __name__ == "__main__": process_sample()这里注意 CSV 编码问题。用utf-8-sig读取可以兼容带 BOM 头的文件,避免第一列字段名出现\ufeff前缀的问题。
5.3 编写统计分析模块
数据入库后,可以做几个基础统计:风格分布、材质分布、尺寸区间分布、环保宣称占比、可拆洗占比。
# parser/analyzer.py import csv from collections import Counter def analyze_from_storage(storage) -> dict: rows = storage.query_all(limit=1000) if not rows: return {} result = {} result["total"] = len(rows) # 风格分布 style_counter = Counter() for r in rows: if r.get("style"): style_counter[r["style"]] += 1 else: style_counter["未识别"] += 1 result["style_dist"] = dict(style_counter) # 材质分布 material_counter = Counter() for r in rows: if r.get("material"): material_counter[r["material"]] += 1 else: material_counter["未识别"] += 1 result["material_dist"] = dict(material_counter) # 尺寸区间分布 size_buckets = {"<1米": 0, "1-2米": 0, "2-3米": 0, ">3米": 0, "未知": 0} for r in rows: size_cm = r.get("size_cm") if size_cm is None: size_buckets["未知"] += 1 elif size_cm < 100: size_buckets["<1米"] += 1 elif size_cm < 200: size_buckets["1-2米"] += 1 elif size_cm < 300: size_buckets["2-3米"] += 1 else: size_buckets[">3米"] += 1 result["size_dist"] = size_buckets # 环保宣称占比 eco_count = sum(1 for r in rows if r.get("eco_flag") == 1) result["eco_ratio"] = round(eco_count / len(rows) * 100, 2) # 可拆洗占比 washable_count = sum(1 for r in rows if r.get("washable") == 1) result["washable_ratio"] = round(washable_count / len(rows) * 100, 2) return result def export_to_csv(result: dict, output_path: str): """将统计结果导出为 CSV。""" with open(output_path, mode="w", encoding="utf-8-sig", newline="") as f: writer = csv.writer(f) writer.writerow(["指标", "值"]) for key, value in result.items(): if isinstance(value, dict): for sub_key, sub_value in value.items(): writer.writerow([f"{key}_{sub_key}", sub_value]) else: writer.writerow([key, value])再在main.py中增加调用:
def analyze_and_export(db_path="data/parsed_products.db", output_path="data/analysis_result.csv"): storage = SQLiteStorage(db_path) result = analyze_from_storage(storage) export_to_csv(result, output_path) print("统计结果已导出:", output_path) for k, v in result.items(): print(k, "->", v) storage.close()5.4 运行与验证
在项目根目录执行:
python main.py预期输出大致如下:
解析 [1] -> {'cleaned_title': '狄普雾屿 可拆洗布艺沙发床 2026新款 奶油风 1.55米 母婴A类 0甲醛雪尼尔 羽绒版', ...} 解析 [2] -> {'cleaned_title': '北欧简约实木沙发 双人位 2.2米 科技布 防猫抓 可拆洗', ...} ... 共处理 8 条,成功入库 8 条。 统计结果已导出: data/analysis_result.csv 风格分布 -> {'奶油风': 3, '现代简约': 1, '北欧': 1, '日式': 1, '意式极简': 1, '未识别': 1} 材质分布 -> {'雪尼尔': 2, '科技布': 3, '头层牛皮': 2, '棉麻': 1} ...如果你想要更直观的统计,这里可以用 pandas 进一步处理:
import pandas as pd df = pd.read_csv("data/analysis_result.csv") print(df.head())实际项目中,可以把统计结果接入报表系统,或者用 matplotlib 画成柱状图、饼图。
6. 常见问题与排查思路
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 正则无法匹配中文 | Python 源码文件编码问题,或正则中包含不兼容字符 | 统一使用 UTF-8 保存源码文件,字符串前加r前缀 |
| 尺寸字段解析为 None | 标题里没有标准单位,例如“1米5” | 增加“x米x”“1米5”等写法支持,或人工补录 |
CSV 读取后第一列有\ufeff | 文件带 BOM 头 | 使用encoding="utf-8-sig"读取 |
| 多次运行后数据重复 | 没有唯一约束或插入逻辑 | 使用INSERT OR IGNORE,并在表上建立UNIQUE(raw_id) |
| 品牌词识别不准 | 品牌不在词典中 | 迭代补充词典,或结合首个空格前的词作为候选品牌 |
| “母婴A类”被识别成“A类” | 词典顺序不当 | 把长词、更具体的词放在前面优先匹配 |
| 标题中“已失效”影响解析 | 清洗步骤未生效 | 检查re.sub(r"【.*?】", "", title)是否在解析前执行 |
| SQLite 数据库无法写入 | 目录不存在或权限不足 | 确认data/目录存在,检查文件权限 |
排查顺序也很重要。当解析结果不准确时,建议按以下步骤来:
- 先打印清洗后的标题,确认基础文本是否正常。
- 再单独测试某个解析函数,例如只测尺寸正则。
- 最后再组合多个字段,定位是词典问题还是正则问题。
7. 最佳实践与工程建议
7.1 词典外置化
在真实项目中,品牌、材质、风格这些词典会频繁更新,建议不要硬编码在.py文件里,而是放到 JSON 或 YAML 配置文件中。例如:
{ "brand": ["狄普雾屿", "林氏木业", "全友"], "material": ["雪尼尔", "科技布", "棉麻"] }解析器启动时读取配置文件,业务人员可以直接维护词典,不用改代码。
7.2 异常处理与日志
解析逻辑虽然简单,但真实标题往往千奇百怪。建议在解析入口加统一异常捕获,并输出日志:
import logging logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") try: result = parser.parse(title) except Exception as e: logging.error(f"解析失败: {title}, 错误: {e}")这样即使某条数据解析出错,也不会让整个任务中断。
7.3 数据合规与边界
在处理电商数据时,需要注意几个边界:
- 只处理公开数据或已经获得授权的数据,不绕过平台反爬机制。
- 不采集非公开的订单信息、用户隐私数据。
- 不利用解析结果进行恶意比价、批量下单、恶意举报等违反平台规则的行为。
- “0甲醛”“母婴A类”等词,在业务上最好与检测报告字段绑定,避免单纯依据标题做质量判断。
这不是形式要求,而是工程实践中必须遵守的底线。数据工作做久了就会发现,数据链路的合规性直接影响整个项目的可持续性。
7.4 从规则解析到智能解析
规则解析的优势是可控、可解释、性能高,但缺点也很明显:新词层出不穷,词典维护成本高。当数据量增大到一定程度,可以考虑引入 NLP 技术,例如:
- 分词工具:jieba 分词后,再结合词性标注提取品牌、材质、风格。
- 命名实体识别:训练一个专门的商品属性识别模型。
- 大模型辅助:利用 LLM 从标题中抽取结构化字段,再人工校验。
推荐顺序是:先用规则解析跑通流程,积累标注数据;当规则维护成本超过阈值时,再考虑引入模型,形成“规则 + 模型 + 人工审核”的混合链路。
7.5 增量更新与幂等设计
如果每天都会采集新的商品数据,建议在设计表结构时增加collected_at和parse_time两个时间字段,并建立唯一索引。解析任务支持重跑,重复执行不会产生脏数据。这是数据工程里最基本的幂等性原则。
8. 总结与下一步方向
本文从一个【已失效】的商品标题出发,做了一套完整的数据解析项目:通过正则与词典实现商品标题结构化,把解析结果存储到 SQLite,并完成基础统计和 CSV 导出。整个过程中涉及的关键技术点包括文本清洗、正则匹配、词典设计、数据库幂等写入、统计分析和异常排查,掌握了这些,后续处理其他品类的商品标题或者类似的非结构化文本,思路都是相通的。
如果你想把项目继续深入,可以尝试以下几个方向:
- 把词典迁移到外部 JSON 文件,做成可配置的规则引擎。
- 增加多字段返回,例如让材质返回多个命中结果,而不是只返回第一个。
- 接入 jieba 分词,对比规则解析与分词解析的准确率差异。
- 将统计结果用 Flask 做一个简单的本地展示页面,或者用 matplotlib 生成可视化报表。
实际项目中,优先关注数据质量和词典维护成本。规则解析不是银弹,但随着词典不断迭代,它能覆盖 80% 以上的常见标题。剩下的疑难杂症,再结合人工标注和模型辅助来处理。
如果这篇文章对你理解商品信息解析有帮助,可以收藏备用,后面遇到类似需求时直接照着改造。