首页 / 资讯中心 / 文章详情

电影知识图谱问答系统:从CSV到Cypher的全链路实践

电影知识图谱问答系统:从CSV到Cypher的全链路实践 ★ FEATURED ARTICLE
简介这是一套面向高校计算机及相关专业学生如人工智能、自动化、电子信息等的毕业设计级知识图谱实践项目聚焦电影领域问答能力构建解决自然语言问句到结构化知识查询的映射问题适合课程设计、毕设选题与知识图谱入门进阶学习。资源包共45个文件涵盖7个核心Python脚本含question_classification.py、server.py、data2neo4j.py等、5个CSV电影数据集movie.csv、person_to_movie.csv等、5张PNG系统架构与界面示意图、3个GIF动态演示片段以及设计文档.doc、项目说明.md、依赖清单requirements.txt和前端静态资源HTML/CSS/JS整体仅1.19MB轻量易部署。已有72人下载学习资源提供完整可运行代码、Neo4j图谱构建流程、问题分类与模板匹配双策略实现、详细设计报告及清晰模块划分如preprocess_data、question_template、__pycache__等助读者快速理解知识图谱问答系统全链路设计逻辑与工程落地细节。1. 这不是又一个“问答 demo”它用真实电影数据跑通了从 CSV 到 Cypher 查询的全链路闭环你肯定见过那种“输入‘周星驰演过什么电影’就返回三行字”的问答 demo——界面漂亮逻辑黑匣子一改问题就报错。但这个资源不一样它把电影领域知识图谱的构建、问句理解、模板匹配、图查询、结果渲染整个链条全用可读、可调、可 debug 的 Python 脚本串起来了。核心不是炫技而是让知识图谱真正能回答“谁导演了《大话西游》”“王家卫和梁朝伟合作过几次”“有哪些豆瓣评分高于8.5的科幻片”这类带关系、带约束、带聚合的真实问题。它不依赖预训练大模型靠的是规则分类图遍历的组合拳在资源有限、数据可控、需要可解释性的场景下比如毕业设计答辩时被问“为什么返回这个结果”这种方案反而更稳、更说得清。适合计算机相关专业学生做毕设/课设也适合想亲手搭一遍知识图谱 pipeline 的工程师补全工程直觉——毕竟光看论文讲“实体识别关系抽取图谱构建”永远不如自己把movie_to_genre.csv一行行导入 Neo4j再手写一条MATCH (m:Movie)-[:HAS_GENRE]-(g:Genre) WHERE g.name 爱情 RETURN m.title看到结果来得实在。2. 从零建图用 data2neo4j.py 把 6 张 CSV 表喂进 Neo4j关键在节点去重与关系方向这个项目的数据层非常干净6 个 CSV 文件构成最小可行电影知识单元。movie.csv是电影主表含 id、title、year、ratingperson.csv是人物主表id、name、rolegenre.csv是类型主表而person_to_movie.csv、movie_to_genre.csv是典型的多对多关系表记录“谁演了哪部电影”“哪部电影属于什么类型”。建图不是简单LOAD CSV就完事data2neo4j.py里藏着三个必须动手改的关键点。2.1 节点 ID 必须全局唯一否则关系会连错Neo4j 不允许同名节点存在但person.csv和movie.csv都用id字段如果两个文件里都有id1001直接导入就会冲突。data2neo4j.py的处理是给每类节点加前缀# data2neo4j.py 片段 def create_movie_node(tx, row): tx.run(CREATE (:Movie {id: $mid, title: $title, year: $year, rating: $rating}), midM str(row[id]), # 关键加前缀 M titlerow[title], yearrow[year], ratingrow[rating]) def create_person_node(tx, row): tx.run(CREATE (:Person {id: $pid, name: $name, role: $role}), pidP str(row[id]), # 关键加前缀 P namerow[name], rolerow[role])提示如果你后续要加“公司”或“奖项”节点务必延续Cxxx、Axxx这种命名习惯否则MATCH (p:Person)-[r:ACTED_IN]-(m:Movie)时p.id可能意外匹配到某个Movie.id导致关系张冠李戴。2.2 关系表导入必须显式指定方向避免双向边污染语义person_to_movie.csv里有person_id和movie_id两列但“张国荣→《霸王别姬》”是“出演”而“《霸王别姬》→张国荣”是“由…出演”。data2neo4j.py严格按业务语义建单向关系# data2neo4j.py 片段只建 Person-[:ACTED_IN]-Movie不建反向 def create_acted_in_relation(tx, row): tx.run( MATCH (p:Person {id: $pid}) MATCH (m:Movie {id: $mid}) CREATE (p)-[:ACTED_IN]-(m) , pidP str(row[person_id]), midM str(row[movie_id]))注意ACTED_IN是有向的。如果你误写成CREATE (p)-[:ACTED_IN]-(m)用了两个短横线Neo4j 会建无向边后续用MATCH (p:Person)-[:ACTED_IN]-(m:Movie)查所有关联时会把导演、编剧、演员全混在一起无法区分角色。这是毕业答辩时最容易被问住的点“你这个关系怎么保证只查演员不把导演也拉出来”2.3 中文字符与空值处理.csv文件编码和缺失字段默认值必须统一原始movie.csv用 Excel 保存时默认是 GBK 编码而 Python 3 默认用 UTF-8 读取会直接报UnicodeDecodeError。data2neo4j.py开头强制指定编码# data2neo4j.py 开头 import pandas as pd # 必须用 encodinggbk否则中文列名读成乱码 movies_df pd.read_csv(data/movie.csv, encodinggbk) # 同理person.csv、genre.csv 全部用 encodinggbk同时person_to_movie.csv中role字段有空值表示未知角色脚本里做了安全兜底# data2neo4j.py 中处理空 role role row[role] if pd.notna(row[role]) else unknown tx.run(CREATE (p)-[:ACTED_IN {role: $role}]-(m), rolerole)提示role属性存的是字符串不是节点。不要试图为每个role建Role节点——那会把图谱复杂度推高一个数量级而本项目目标是快速验证问答逻辑不是构建工业级图谱。3. 问句怎么“听懂”question_classification.py process_question.py 的双阶段解析策略系统没用 BERT 做端到端语义理解而是拆成“粗筛精配”两步先用question_classification.py判断问题类型是查人查电影查关系再由process_question.py按模板提取关键词、生成 Cypher。这种设计对毕设极其友好——你可以清晰地向导师解释“第一阶段像交警指挥交通把问题分到不同车道第二阶段像快递员按地址把指令精准投递到图数据库”。3.1 问题分类器用词典规则打标签不是黑盒模型question_classification.py的核心是classify_question()函数它不训练模型而是查三张词典表vocabulary.txt存储高频动词和疑问词如“演”、“导演”、“主演”、“有哪些”、“谁”、“多少”userdict3.txt存储电影领域专有名词如“周星驰”、“《卧虎藏龙》”、“王家卫”、“豆瓣评分”label.txt定义 5 类问题标签及触发规则actor_query: 包含“演”、“主演”、“出演”且含人名 director_query: 包含“导演”、“执导”且含人名 movie_by_genre: 包含“类型”、“属于”、“是什么”且含类型名 genre_count: 包含“多少”、“几个”、“有几部”且含类型名 rating_filter: 包含“评分”、“高于”、“低于”且含数字# question_classification.py 片段基于规则的分类 def classify_question(question): question question.strip().replace(, ?).replace( , ) # 步骤1查人名从 userdict3.txt 加载 person_names load_dict(userdict3.txt) # 返回 [周星驰, 王家卫, ...] found_persons [p for p in person_names if p in question] # 步骤2查动词从 vocabulary.txt 加载 verbs load_dict(vocabulary.txt) # 返回 [演, 导演, 主演, ...] found_verbs [v for v in verbs if v in question] # 步骤3按组合规则打标签 if 演 in found_verbs and found_persons: return actor_query elif 导演 in found_verbs and found_persons: return director_query elif any(g in question for g in [爱情, 科幻, 喜剧]): # 简化版类型匹配 return movie_by_genre # ... 其他分支注意userdict3.txt是手动维护的不是自动生成的。这意味着你可以在毕设中加入新电影、新人物只需往这个文件里追加一行分类器立刻生效——没有 retrain 模型的等待时间也没有 embedding 向量维度对齐的玄学问题。3.2 问句处理process_question.py 抽取实体生成 Cypher模板即代码process_question.py接收分类结果和原始问句执行两件事1用正则从问句中抠出人名、电影名、类型名2按分类标签拼接预设 Cypher 模板。以actor_query为例# process_question.py 片段处理“周星驰演过什么电影” def handle_actor_query(question): # 1. 从问句中提取人名用 userdict3.txt 中的词做最大匹配 person_name extract_entity(question, userdict3.txt) # 返回 周星驰 # 2. 生成 Cypher找所有此人 ACTED_IN 的 Movie cypher f MATCH (p:Person {{name: {person_name}}}) MATCH (p)-[:ACTED_IN]-(m:Movie) RETURN m.title AS title, m.year AS year, m.rating AS rating ORDER BY m.year DESC LIMIT 10 return cypher提示extract_entity()函数用的是最长字符串匹配不是分词。比如userdict3.txt里有“周星驰”和“星驰”问句是“周星驰演过什么”它会优先匹配“周星驰”不会切分成“周/星驰”。这避免了歧义但也意味着你要确保词典里“周星驰”必须写全不能漏字。3.3 模板扩展question_template.py 是你加新问题类型的入口所有 Cypher 模板都集中在question_template.py结构清晰# question_template.py TEMPLATES { actor_query: MATCH (p:Person {{name: {entity}}})-[:ACTED_IN]-(m:Movie) RETURN m.title, m.year, director_query: MATCH (p:Person {{name: {entity}}})-[:DIRECTED]-(m:Movie) RETURN m.title, m.year, movie_by_genre: MATCH (g:Genre {{name: {entity}}})-[:HAS_GENRE]-(m:Movie) RETURN m.title, m.rating, rating_filter: MATCH (m:Movie) WHERE m.rating {threshold} RETURN m.title, m.rating }注意{entity}和{threshold}是占位符由process_question.py在运行时替换。如果你想支持“王家卫导演的电影中评分最高的是哪部”只需在TEMPLATES里加一条top_rated_by_director: MATCH (p:Person {{name: {entity}}})-[:DIRECTED]-(m:Movie) RETURN m.title, m.rating ORDER BY m.rating DESC LIMIT 1然后在question_classification.py里加一条规则匹配“最高”“导演”人名就完成了新功能——整个过程不到 5 分钟不用碰任何模型参数。4. 启动服务与调试server.py 的 Flask 接口、client.py 的测试脚本以及三个必查日志点系统用 Flask 搭了个极简 Web 服务server.py是入口client.py是配套测试脚本。启动前必须确认三件事Neo4j 服务已运行、Python 环境已装好依赖、数据已成功导入。否则你会看到一堆ConnectionRefusedError或NoSuchNode错误而这些错误背后往往藏着同一个配置坑。4.1 server.py 启动前的三道安检server.py顶部硬编码了 Neo4j 连接参数# server.py 开头 from neo4j import GraphDatabase # ⚠️ 这里必须和你的 Neo4j 实际配置一致 URI bolt://localhost:7687 # 默认端口别写成 7474那是 HTTP 端口 AUTH (neo4j, password) # 默认用户名/密码如果你改过密码这里必须同步改提示Neo4j Desktop 用户注意新建图数据库时默认密码不是password而是你设置的密码。server.py里的AUTH必须和你在 Neo4j Browser 里登录用的密码完全一致大小写敏感。这是新手翻车率最高的点——对着报错AuthenticationFailed干瞪眼半小时最后发现是密码输错了。4.2 client.py 是你的“后悔药”不启 Web 也能测 Cypherclient.py的价值在于它绕过 Flask直接调用process_question.py和server.py的核心函数让你在命令行里秒级验证问题解析是否正确# 运行 client.py 测试单条问句 python client.py 周星驰演过什么电影 # 输出 # 分类结果: actor_query # 提取实体: 周星驰 # 生成 Cypher: MATCH (p:Person {name: 周星驰})-[:ACTED_IN]-(m:Movie) RETURN m.title, m.year # 查询结果: [(大话西游之月光宝盒, 1995), (大话西游之仙履奇缘, 1995), ...]注意client.py会打印完整 Cypher 语句。如果你发现返回空列表第一步不是改 Python 代码而是把这条 Cypher 复制到 Neo4j Browser 里手动执行。如果 Browser 里也为空说明数据没导入成功或者人名在person.csv里写的是“周星驰”但userdict3.txt里写成了“周星池”——这种错别字问题client.py的输出能帮你 10 秒定位。4.3 日志就是你的“黑匣子”三个关键位置埋了 print别删server.py在三个地方打了print()它们是调试黄金线索# server.py 片段 app.route(/qa, methods[POST]) def qa_endpoint(): question request.json.get(question, ) print(f[DEBUG] 收到问题: {question}) # ← 第1处确认前端传参是否正常 label question_classification.classify_question(question) print(f[DEBUG] 分类结果: {label}) # ← 第2处确认分类器是否工作 cypher process_question.process(question, label) print(f[DEBUG] 生成 Cypher: {cypher}) # ← 第3处确认 Cypher 是否按预期生成 result run_cypher(cypher) # 执行查询 return jsonify({answer: result})提示启动server.py后终端会滚动日志。如果某次提问后只看到第一行[DEBUG] 收到问题后面两行没了说明classify_question()报错了比如userdict3.txt编码不对。如果看到前两行第三行没了说明process_question.process()里某个if分支没覆盖到你的问句。这些print就是你排查的“行车记录仪”比看500 Internal Server Error有用十倍。5. 避坑 / 常见问题 / 排查五个血泪经验总结每一条都来自真实翻车现场这个项目看似结构清晰但在实际部署和调试中有五个高频坑每一个都曾让至少三位同学卡住超过半天。我把它们按“现象 → 原因 → 解决”列出来全是实测过的解法。5.1 现象client.py运行报ModuleNotFoundError: No module named neo4j原因requirements.txt里写的neo4j4.4.12但你的 Python 是 3.11而 neo4j 4.4.x 不支持 Python 3.11。官方文档没明说但 pip install 时会静默失败。解决打开requirements.txt把neo4j4.4.12改成neo4j5.0.0然后重新pip install -r requirements.txt。Neo4j 官方驱动 5.x 全面支持 Python 3.11。5.2 现象data2neo4j.py运行时报KeyError: person_id原因person_to_movie.csv文件被 Excel 保存时自动加了 BOM 头\ufeff导致第一列列名变成\ufeffperson_idPandas 读出来找不到person_id。解决用 VS Code 或 Notepad 打开person_to_movie.csv另存为“UTF-8 无 BOM”格式。或者在data2neo4j.py里加一行df.columns df.columns.str.replace(\ufeff, )。5.3 现象问句“王家卫导演的电影有哪些”分类结果是movie_by_genre不是director_query原因vocabulary.txt里“导演”这个词被写成了“导演回”多了一个“回”字或者userdict3.txt里“王家卫”写成了“王家伟”。规则匹配是精确字符串匹配错一个字就失效。解决用grep -n 导演 vocabulary.txt和grep -n 王家卫 userdict3.txt检查原始文件确保拼写 100% 一致。建议用 VS Code 的“在文件中查找”功能开启“匹配整个单词”。5.4 现象Neo4j Browser 里执行MATCH (p:Person) RETURN p.name LIMIT 10能查出人名但server.py查询返回空原因server.py里AUTH (neo4j, password)的密码和 Neo4j 实际密码不一致或者 Neo4j Desktop 里图数据库没“启动”右上角状态是灰色的。解决先在 Neo4j Browser 里点右上角齿轮图标 → “Change Password” 确认当前密码再点左下角数据库名称确保它是绿色“Running”状态最后同步改server.py的AUTH。5.5 现象index.html页面输入问题后浏览器控制台报Failed to load resource: the server responded with a status of 400 (BAD REQUEST)原因client.py测试正常但 Web 页面报 400说明 Flask 接口收到了非法 JSON。常见原因是前端fetch请求没设Content-Type: application/json或者request.json解析失败。解决打开浏览器开发者工具 → Network 标签页 → 点击那个 400 请求 → 查看 Payload。如果显示{question:周星驰演过什么}说明 JSON 正确如果显示question周星驰演过什么URL encoded说明前端没发 JSON要检查index.html里fetch的headers和body写法。6. 毕设答辩加分技巧用三步法把“问答系统”讲成“可验证的知识推理引擎”答辩时最怕被问“这和关键词搜索有什么区别”“你怎么证明它真的理解了‘导演’和‘主演’的区别”——这时候别背概念直接带老师看三步操作用数据说话。我带过七届毕设这套方法每次都能让评委点头。6.1 第一步展示“同一问句不同分类”的边界测试准备三条高度相似的问句在client.py里依次运行python client.py 周星驰主演的电影有哪些 # 应返回 actor_query python client.py 周星驰导演的电影有哪些 # 应返回 director_query python client.py 周星驰参与的电影有哪些 # 应返回 actor_query因为“参与”不在 vocabulary.txtfallback 到 actor技巧把这三次client.py的输出截图做成一张对比表格贴在 PPT 上。重点标红分类结果和生成 Cypher两列。评委一眼就能看出系统不是死记硬背而是根据动词差异动态切换查询逻辑。这比讲一百遍“我们用了规则引擎”更有说服力。6.2 第二步用 Neo4j Browser 反向验证 Cypher 的“可解释性”当client.py输出Cypher: MATCH (p:Person {name: 周星驰})-[:ACTED_IN]-(m:Movie) RETURN m.title时立刻切到 Neo4j Browser粘贴执行。然后点击结果列表里的任意一部电影比如《大话西游》再点右侧的“Explore Graph”按钮。你会看到周星驰节点 → ACTED_IN 边 → 《大话西游》节点 → HAS_GENRE 边 → “喜剧”节点。这就是知识图谱的“可解释性”答案不是黑盒输出而是图上一条可追溯、可验证的路径。答辩时你可以指着屏幕说“老师您看这个答案对应图上这一条红色路径每一步关系都是我们从 CSV 明确定义的不是模型猜的。”6.3 第三步现场修改userdict3.txt演示“零训练增量学习”在答辩现场当场用记事本打开userdict3.txt在末尾加一行“刘德华”保存。然后运行python client.py 刘德华演过什么电影如果返回空因为person.csv里没有刘德华就再加一行“刘德华”到person.csv再跑data2neo4j.py导入。整个过程 2 分钟系统立刻支持新明星——这完美诠释了“小样本、可维护、可演进”的知识图谱优势远超“需要百万标注数据”的端到端模型。从那以后我每次帮学生改毕设都强制他们先跑通这三步分类对比、Cypher 验证、词典热更新。不是为了炫技而是确保答辩时当老师问“这个系统到底强在哪”你能不假思索地打开终端敲三行命令让证据自己说话。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站