资讯动态

电商知识图谱实战:从数据清洗到推荐问答闭环

发布时间:2026/10/26 3:37:36 来源:尧图企业网站定制
简介这是一份面向计算机及相关专业学生、初学者与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_pathdata/cat_key.csv, category_pathdata/category.csv): # 读取类目关键词映射表原始类目名 → 标准化ID cat_key_df pd.read_csv(cat_key_path, encodingutf-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, encodingutf-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_typeproducer): 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_pathdata/product.json): with open(products_json_path, r, encodingutf-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, encodingutf-8) as f: json.dump(relations, f, ensure_asciiFalse, indent2)注意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_k10): 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中relationcommon_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_id1024假设是蓝牙耳机类目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_pairs3): 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_idsku789后端dataserver.py路由到此方法。如果返回空数组先检查relation.json里是否存在{head: sku789, relation: compatible, ...}——没有的话要么原始数据没标注要么example_create_graph.py没跑。3.3 问答系统模块Handler/QSHandler.py的answer_question()支持“是什么”“有哪些”“能不能”三类问法问答不是大模型生成而是模板匹配图谱查询。QSHandler.py定义了3种问法模式问法类型正则模式查询逻辑示例是什么r(.)是什么查head实体且relationdefinition的tail“iPhone是什么” → 返回“智能手机”哪些r哪些(.)查tail类目名且relationbelongs_to的head“哪些手机” → 返回所有手机商品ID能不能r(.)能不能(.)查head主语且relationcompatible且tail宾语“iPhone能不能用华为充电器” → 返回true/falsedef 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里加一行id1025,name耳机,parent_id1000,level2然后重新跑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, orientrecords, force_asciiFalse, indent2)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列所有值再用CtrlF在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去重并重置IDdf 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, orientrecords, force_asciiFalse, indent2)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%的排查时间。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑