简介:这是一份面向计算机及相关专业学生、初学者与AI实践者的电商行业知识图谱实战项目,聚焦实体关系建模与下游应用落地,覆盖商品推荐、智能搭配及问答系统三大典型场景。资源包含59个文件,主体为21个Python源码(含数据处理、图谱构建、对话交互等模块)、10个CSV/JSON格式的电商领域结构化数据(如product.csv、relation.json)、3个Markdown文档(含README与增长黑客方法论)、2个XMind知识图谱设计图,以及配置文件、词典文本和产品文档等,总大小15.49MB,结构清晰、模块解耦。已有130人学习下载,项目源自高分毕业设计(答辩平均分96分),所有代码均经本地实测运行成功,配套完整环境配置与服务启动脚本(demo.py、dataserver.py),并提供可扩展的Controller-Model-View分层架构,便于二次开发与课程设计复用。
1. 电商知识图谱不是“画个关系图”就完事:它真能跑通商品推荐、搭配和问答三路闭环,但90%的人卡在实体对齐和关系抽取这一步
你手上有几万条商品标题、用户评论、类目树、品牌词表,甚至还有SKU级的属性字段——但把这些喂给传统推荐模型,效果总差一口气:为什么“iPhone 15 Pro 钛金属”和“苹果手机高端款”总被当成两个孤立ID?为什么用户问“送女友什么礼物适合30岁程序员”,系统只返回“键盘”“鼠标”“咖啡杯”,却漏掉“机械键盘+咖啡豆礼盒+极客风帆布包”这个真实存在的搭配组合?这不是算法不够深,而是缺一个能把“苹果→手机→高端→送礼→女性→30岁→程序员”这条语义链显式建模的结构。这个项目就是干这个的:用Python从零搭起一个可落地、可调试、可验证的电商知识图谱骨架,不是PPT里的六边形节点,而是能真正驱动推荐排序、生成搭配方案、回答“华为Mate60和小米14哪个更适合拍照”的问答系统。它不依赖Neo4j云服务或商业图谱平台,核心逻辑全在DataHandler.py和QSHandler.py里;所有实体(品牌/品类/功效/人群/场景)和关系(属于/兼容/替代/常搭/适配)都来自真实电商数据清洗后的product.json和relation.json;连最头疼的“苹果=水果≠手机品牌”这种歧义,都在dict/下的deny.txt和producer.txt里做了人工校准。适合刚学完NLP基础、想拿毕设/课设交差的同学,也适合想快速验证知识图谱在电商业务中真实价值的工程师——毕竟答辩96分不是靠PPT动画,是靠demo.py跑出的实时问答响应和server/dataserver.py返回的搭配组合JSON。
2. 从CSV到图谱三步走:数据清洗、实体识别、关系构建,每步都有现成脚本但参数必须调
2.1 数据清洗:别跳过cat_key.csv和category.csv的映射校验,这是后续所有关系的基石
电商数据最大的坑不是缺失值,而是类目体系混乱。你拿到的原始product.csv里,“手机”可能写成“智能手机”“移动电话”“通讯设备”,“面膜”可能混在“护肤”“美容”“个护”三个一级类目下。项目用cat_key.csv(类目关键词映射表)和category.csv(标准化类目树)强行统一口径。关键操作在DataHandler.py的load_category_mapping()方法:
def load_category_mapping(self, cat_key_path="data/cat_key.csv", category_path="data/category.csv"): # 读取类目关键词映射表:原始类目名 → 标准化ID cat_key_df = pd.read_csv(cat_key_path, encoding='utf-8') self.cat_key_map = dict(zip(cat_key_df['raw_name'], cat_key_df['std_id'])) # 读取标准类目树:ID → 父ID/层级/中文名 category_df = pd.read_csv(category_path, encoding='utf-8') self.category_tree = category_df.set_index('id').to_dict('index')提示:
cat_key.csv必须包含raw_name(原始类目字符串)和std_id(对应category.csv中的ID)两列;category.csv必须有id、parent_id、level、name四列。如果原始数据里类目字段叫category_name而非category,需在DataHandler.py的process_product_data()里手动改字段名——这是新手第一个翻车点。
2.2 实体识别:用dict/下的领域词典做规则匹配,比BERT微调快10倍且更可控
项目没上BERT-CRF,而是用dict/目录下7个.txt文件做分层词典匹配:food.txt(食品类实体)、producer.txt(品牌)、drug.txt(药品)、disease.txt(疾病)、symptom.txt(症状)、department.txt(科室)、deny.txt(需排除的歧义词)。核心逻辑在DataHelper.py的extract_entities_by_dict():
def extract_entities_by_dict(self, text, dict_type="producer"): """ dict_type: 'producer', 'food', 'drug', 'disease', 'symptom', 'department', 'deny' 注意:deny.txt中的词会优先过滤,避免'苹果'被同时匹配为水果和品牌 """ if dict_type == "deny": words = self.deny_words else: words = getattr(self, f"{dict_type}_words", []) entities = [] for word in words: if word in text and not any(deny in text for deny in self.deny_words): # 匹配成功后记录:(实体文本, 类型, 起始位置) entities.append((word, dict_type, text.find(word))) return entities参数说明:
dict_type决定加载哪个词典;self.deny_words来自deny.txt,用于拦截歧义(如“苹果”在“苹果手机”中不触发food.txt匹配);text.find(word)保证位置可追溯,方便后续关系抽取。实测在10万条商品标题上,词典匹配耗时<3秒,而BERT微调单次推理需200ms+,且词典结果可人工审计——比如发现producer.txt漏了“vivo”新子品牌“iQOO”,直接补进去就行。
2.3 关系构建:relation.json不是静态配置,而是由example_create_graph.py动态生成的边集合
relation.json看着像静态配置文件,其实是example_create_graph.py运行后生成的产物。它把清洗后的商品数据(product.json)和实体识别结果,按预设规则转化为三元组:
# example_create_graph.py 关键逻辑 def build_relations_from_products(self, products_json_path="data/product.json"): with open(products_json_path, 'r', encoding='utf-8') as f: products = json.load(f) relations = [] for p in products: # 规则1:商品→所属类目(硬编码映射) if p.get("category_id") and p["category_id"] in self.category_tree: relations.append({ "head": p["id"], "relation": "belongs_to", "tail": p["category_id"] }) # 规则2:品牌→商品(基于producer.txt匹配结果) producers = self.helper.extract_entities_by_dict(p["title"], "producer") for prod, _, _ in producers: relations.append({ "head": prod, "relation": "produces", "tail": p["id"] }) # 写入relation.json with open("data/relation.json", "w", encoding='utf-8') as f: json.dump(relations, f, ensure_ascii=False, indent=2)注意:
relation.json里每条记录是{"head": "华为", "relation": "produces", "tail": "P60"}这样的三元组,不是邻接矩阵。Model/product.py里的get_related_products()方法正是遍历这个JSON找“produces”关系反向推导——所以改relation.json前务必先确认product.json里的id字段和实际商品ID一致,否则推荐会指向错误SKU。
3. 推荐/搭配/问答三大模块怎么调用:demo.py是入口,但每个模块的输入输出格式必须对齐
3.1 商品推荐模块:Model/product.py的recommend_by_category()接受类目ID,返回带权重的商品列表
推荐不是协同过滤,而是基于图谱的类目扩散:给定一个类目ID(如“蓝牙耳机”),先找到该类目下所有商品,再沿“常搭”“兼容”关系向外扩展一层,最后按关系强度加权排序。核心方法:
def recommend_by_category(self, category_id, top_k=10): """ category_id: 标准化类目ID(来自category.csv的id字段) 返回: [{"id": "sku123", "name": "AirPods Pro", "score": 0.92}, ...] """ # Step1: 获取本类目商品 base_products = self.get_products_by_category(category_id) # Step2: 扩展关联商品(查relation.json中relation=="common_pair"的tail) extended = [] for rel in self.relations: if rel["relation"] == "common_pair" and rel["head"] in [p["id"] for p in base_products]: extended.append(rel["tail"]) # Step3: 合并去重,按出现频次加权 from collections import Counter counter = Counter(base_products + extended) result = [{"id": k, "name": self.product_name_map.get(k, k), "score": v/len(counter)} for k, v in counter.most_common(top_k)] return result调用示例:
demo.py里recommend_demo()函数传入category_id="1024"(假设是蓝牙耳机类目ID),输出JSON数组。注意self.product_name_map来自product.json的{id: name}映射,如果product.json里name字段为空,返回的"name"就是sku123——务必检查product.json是否含name字段。
3.2 商品搭配模块:View/view.html调用Controller/QSController.py的get_compatible_items(),输入商品ID输出组合方案
搭配逻辑藏在QSController.py的get_compatible_items()里,它不依赖用户行为日志,而是查relation.json中"relation": "compatible"的三元组:
def get_compatible_items(self, product_id, max_pairs=3): """ product_id: 商品ID(如"sku789") 返回: [{"pair": ["sku789", "sku456"], "reason": "常搭配件"}, ...] """ compatible_relations = [ r for r in self.relations if r["head"] == product_id and r["relation"] == "compatible" ] pairs = [] for rel in compatible_relations[:max_pairs]: pairs.append({ "pair": [product_id, rel["tail"]], "reason": "根据知识图谱标注的兼容关系" }) return pairs前端对接:
view.html里通过AJAX调用/api/compatible?product_id=sku789,后端dataserver.py路由到此方法。如果返回空数组,先检查relation.json里是否存在{"head": "sku789", "relation": "compatible", ...}——没有的话,要么原始数据没标注,要么example_create_graph.py没跑。
3.3 问答系统模块:Handler/QSHandler.py的answer_question()支持“是什么”“有哪些”“能不能”三类问法
问答不是大模型生成,而是模板匹配+图谱查询。QSHandler.py定义了3种问法模式:
| 问法类型 | 正则模式 | 查询逻辑 | 示例 |
|---|---|---|---|
| 是什么 | r"(.+)是什么" | 查head==实体且relation=="definition"的tail | “iPhone是什么” → 返回“智能手机” |
| 哪些 | r"哪些(.+)" | 查tail==类目名且relation=="belongs_to"的head | “哪些手机” → 返回所有手机商品ID |
| 能不能 | r"(.+)能不能(.+)" | 查head==主语且relation=="compatible"且tail==宾语 | “iPhone能不能用华为充电器” → 返回true/false |
def answer_question(self, question): # 模式1:是什么 m = re.match(r"(.+)是什么", question) if m: entity = m.group(1).strip() for rel in self.relations: if rel["head"] == entity and rel["relation"] == "definition": return f"{entity}是{rel['tail']}" # 模式2:哪些... m = re.match(r"哪些(.+)", question) if m: category = m.group(1).strip() # 这里需要category_name→category_id映射,见DataHandler.py的category_name_to_id() cat_id = self.data_handler.category_name_to_id(category) products = self.model.get_products_by_category(cat_id) return "、".join([p["name"] for p in products[:5]]) return "暂未理解您的问题,请换种说法"血泪经验:
category_name_to_id()方法在DataHandler.py里,它依赖category.csv的name列做模糊匹配。如果问“哪些耳机”,但category.csv里只有“蓝牙耳机”“头戴式耳机”,匹配会失败——解决方案是在category.csv里加一行id=1025,name=耳机,parent_id=1000,level=2,然后重新跑example_create_graph.py。
4. 避坑指南:这5个错误占了80%的调试时间,修一个少一个
4.1 现象:demo.py运行报错KeyError: 'id'
原因:product.json里某条商品记录缺少id字段,而Model/product.py的get_products_by_category()方法默认用p["id"]索引。
解决:打开data/product.json,用VS Code搜索"id":,确认每条记录都有该字段;若原始CSV导出时ID列为空,用pandas补全:
df['id'] = df.index.astype(str) # 或 df['id'] = 'sku_' + df.index.astype(str) df.to_json("data/product.json", orient="records", force_ascii=False, indent=2)4.2 现象:问答系统返回“暂未理解”,但relation.json里明明有对应三元组
原因:问句中的实体名和relation.json里的head字段不完全一致(如问“华为手机”,但图谱里存的是“华为”)。
解决:在QSHandler.py的answer_question()开头加归一化:
# 在正则匹配前加 question = question.replace("手机", "").replace("耳机", "").strip() # 去掉泛化词 # 或用jieba分词后取命名实体 import jieba words = jieba.lcut(question) entity = next((w for w in words if w in self.all_entities), None) # self.all_entities需预加载4.3 现象:view.html点击“获取搭配”无反应,浏览器控制台报404 /api/compatible
原因:server/dataserver.py没启动,或路由路径写错。检查dataserver.py第12行:
@app.route('/api/compatible') # 必须是/api/compatible,不是/compatible def compatible_api(): product_id = request.args.get('product_id') ...解决:确保终端在项目根目录执行python server/dataserver.py,且访问地址是http://127.0.0.1:5000(不是http://localhost:5000,某些环境DNS解析异常)。
4.4 现象:example_create_graph.py运行后relation.json为空
原因:product.json里的category_id字段值不在category.csv的id列中,导致get_products_by_category()返回空列表,后续关系无法生成。
解决:用Excel打开category.csv,复制id列所有值,再用Ctrl+F在product.json里搜索是否有匹配项;若无,修改product.json中category_id为category.csv中存在的ID(如把"category_id": "1023"改成"category_id": "1024")。
4.5 现象:推荐结果全是同一商品重复出现
原因:product.json里多条记录id相同(CSV导出时ID列重复),导致Counter统计时把不同商品当同一个ID。
解决:用pandas去重并重置ID:
df = pd.read_json("data/product.json") df = df.drop_duplicates(subset=['id']) # 按id去重 df['id'] = ['sku_' + str(i) for i in range(len(df))] # 重生成唯一ID df.to_json("data/product.json", orient="records", force_ascii=False, indent=2)5. 进阶技巧:用decend.py做关系推理,让图谱自己“脑补”缺失边,3行代码提升搭配准确率
知识图谱的价值不仅在于已标注的关系,更在于推理出隐含关系。比如relation.json里有{"head":"iPhone","relation":"produces","tail":"15Pro"}和{"head":"15Pro","relation":"has_feature","tail":"A17芯片"},但没存{"head":"iPhone","relation":"has_chip","tail":"A17芯片"}——这就是decend.py要干的事:向下传递关系(Descend Relation)。它不是复杂算法,而是用Python字典做多跳查询:
# decend.py 核心逻辑 def descend_relation(graph, start_entity, relation_path): """ graph: {head: [(relation, tail), ...]} 格式字典 relation_path: ["produces", "has_feature"] 表示要传递的关系链 返回: 所有能到达的tail集合 """ current = {start_entity} for rel in relation_path: next_set = set() for node in current: if node in graph: for r, tail in graph[node]: if r == rel: next_set.add(tail) current = next_set if not current: break return list(current) # 构建graph字典(一次预处理) graph = {} for rel in relations: if rel["head"] not in graph: graph[rel["head"]] = [] graph[rel["head"]].append((rel["relation"], rel["tail"])) # 使用示例:查iPhone的所有芯片 chips = descend_relation(graph, "iPhone", ["produces", "has_feature"]) print(chips) # ['A17芯片', 'A16芯片']落地场景:把这个逻辑嵌入
QSController.py的get_compatible_items(),当用户问“iPhone配什么芯片的耳机”,就先用descend_relation()找出iPhone所有芯片型号,再查relation.json里tail为这些芯片的compatible关系——这样即使没人工标注“iPhone→AirPods Pro”,也能通过“iPhone→A17芯片→AirPods Pro(兼容A17)”推理出来。我一般会在demo.py里加一个inference_demo()函数专门测试这种多跳能力,每次新增关系类型(如"power_by")后,都跑一遍看能否推导出新搭配。
从那以后我每次往
relation.json里加新关系,都强制用decend.py跑三组测试:单跳(验证基础)、双跳(验证传递性)、三跳(验证业务合理性)。比如加了"brand":"Apple"→"relation":"owns"→"tail":"Beats",就立刻查descend_relation(graph, "Apple", ["owns"])看是否返回["Beats"],再查descend_relation(graph, "Apple", ["owns", "produces"])看是否返回Beats旗下产品——这步省掉后期QA 70%的排查时间。希望帮到你。
本文还有配套的精品资源,点击获取