
简介这是一套面向计算机专业本科生的毕业设计级项目资源聚焦知识图谱与自然语言处理交叉应用为正在开展毕设、课程设计或期末大作业的学生提供可直接运行的电影领域问答系统完整实现。资源基于Python与Neo4j构建涵盖知识抽取、图谱构建、问句分类、模板匹配与后端服务全流程小白经文档指引即可部署调试。压缩包共44个文件含7个核心Python脚本如question_classification.py、server.py、5个CSV数据源movie.csv、person_to_movie.csv等、5张流程与界面截图png、3个前端静态资源html/css/js及README.md说明文档整体仅1.15MB轻量易解压。已有105人下载学习配套文档详实、代码注释充分包含requirements.txt依赖清单、预训练词典与标签体系且经导师评审获99分高分具备教学示范性与工程参考价值。1. 为什么用 Neo4j Python 做电影问答系统不是在“炫技”而是毕业设计里最稳的落地路径你手头有一份豆瓣电影数据、几部经典影片的演职员表、类型标签和剧情简介想做一个能回答“周星驰主演过哪些科幻片”“《肖申克的救赎》和《阿甘正传》有哪些共同演员”这类问题的系统——这不是 NLP 课设的简单关键词匹配也不是调个 ChatGLM API 就交差的“伪智能”。它需要真实建模实体间的关系导演→电影→类型→演员→获奖→年代还要支持多跳查询、路径推理、模糊语义映射。而知识图谱尤其是 Neo4j 这类原生图数据库天然就是干这个的节点是电影/人/类型边是“主演”“执导”“属于”“上映于”查询就是 Cypher 一句MATCH (a:Actor)-[:ACTED_IN]-(m:Movie)-[:HAS_GENRE]-(g:Genre {name:科幻}) WHERE a.name CONTAINS 周星驰 RETURN m.title。Python 则负责数据清洗、API 封装、前端胶水、自然语言解析哪怕只是规则关键词——整套链路不依赖 GPU、不卡显存、本地跑得动、答辩能现场演示、代码可读性强、老师一眼看懂技术栈合理性。这正是“基于知识图谱的电影问答系统Python 和 Neo4j 的毕业设计”的真实定位一个边界清晰、技术闭环、可讲清楚每一步为什么这么选、答辩时能拆解到 Cypher 查询逻辑和 Python Flask 路由层级的硬核但不越界的工程实践。适合计算机、软件工程、信息管理类专业学生尤其适合没接触过图数据库、但学过数据库原理和 Python Web 开发的同学——它不考算法深度考的是把知识建模、数据流转、查询表达、接口暴露四层打通的能力。2. 从零搭起图谱骨架Neo4j 安装、数据建模与 CSV 导入三步闭环2.1 Neo4j 社区版安装与本地服务验证Windows/macOS/Linux 通用Neo4j 社区版v5.x是毕业设计首选免费、功能完整、文档成熟、社区答疑活跃。注意避开企业版试用陷阱到期后服务停摆也别用 Docker 一键拉取却搞不定端口映射——对新手来说本地桌面版最可控。Windows去官网下载neo4j-community-windows-x64.zip2024 年主流是 v5.19解压后运行bin\neo4j.bat console非 install service看到Started日志即成功浏览器访问http://localhost:7474输入默认账号neo4j / neo4j首次登录强制改密设为movie123之类易记密码。macOS用 Homebrew 最省心brew install --cask neo4j启动命令neo4j start访问http://localhost:7474同上。LinuxUbuntu/CentOS下载.tar.gz包解压后cd bin ./neo4j console若报 Java 版本错Neo4j 5.x 需 Java 17先sudo apt install openjdk-17-jdk再重试。提示如果访问localhost:7474显示连接拒绝90% 是 Neo4j 服务未启动或防火墙拦截执行neo4j status确认状态neo4j restart强制重启若提示Unable to connect to localhost:7474检查conf/neo4j.conf中dbms.connectors.default_listen_address0.0.0.0是否取消注释仅开发环境需放开生产环境务必改回127.0.0.1。2.2 电影领域本体设计5 类节点 7 种关系的最小可行模型别一上来就画几十个节点的“大图谱”。毕业设计的核心是验证建模能力不是堆数据量。我们只保留最核心、最易获取、最能支撑问答的实体与关系节点类型属性示例说明:Movietitle,year,rating,duration电影主实体title设为唯一索引:Personname,birth_year,profession演员/导演/编剧统一为 Person用profession区分:Genrename类型如“喜剧”“科幻”避免中英文混杂:Awardname,year奖项如“金鸡奖”“奥斯卡”nameyear联合唯一:Companyname,type制片公司/发行公司type值为production或distribution关系类型方向语义示例:ACTED_INPerson→Movie演员参演(tom):Person-[:ACTED_IN]-(inception):Movie:DIRECTEDPerson→Movie导演执导(nolan):Person-[:DIRECTED]-(inception):Movie:WROTEPerson→Movie编剧创作(nolan):Person-[:WROTE]-(inception):Movie:HAS_GENREMovie→Genre归属类型(inception):Movie-[:HAS_GENRE]-(scifi):Genre:WON_AWARDMovie→Award获得奖项(inception):Movie-[:WON_AWARD]-(oscar2011):Award:PRODUCED_BYMovie→Company由某公司制作(inception):Movie-[:PRODUCED_BY]-(warner):Company:DISTRIBUTED_BYMovie→Company由某公司发行(inception):Movie-[:DISTRIBUTED_BY]-(warner):Company注意profession字段必须明确区分actor/director/writer否则后续 Cypher 查询无法精准过滤title和name属性值严禁含逗号、双引号、换行符否则 CSV 导入会错位——Python 清洗时要用csv.QUOTE_MINIMAL或正则替换。2.3 用 Python 构建结构化 CSV 并批量导入 Neo4jNeo4j 原生支持LOAD CSV但要求 CSV 文件严格按列对齐、无 BOM、UTF-8 编码。手动整理几百条数据不现实必须用 Python 自动化生成。假设你已爬取或下载了豆瓣 Top 250 电影的 JSON 数据含片名、年份、导演、主演、类型、评分以下脚本完成三件事清洗字段、生成 nodes.csv / relationships.csv、写入本地文件import json import csv from pathlib import Path # 1. 加载原始数据示例douban_top250.json with open(douban_top250.json, r, encodingutf-8) as f: movies json.load(f) # 2. 初始化 CSV writer使用 QUOTE_MINIMAL 避免引号污染 nodes_file Path(data/nodes.csv) rels_file Path(data/relationships.csv) nodes_file.parent.mkdir(exist_okTrue) with open(nodes_file, w, newline, encodingutf-8) as nf, \ open(rels_file, w, newline, encodingutf-8) as rf: # 节点 CSVlabel,property1,property2,... node_writer csv.writer(nf, quotingcsv.QUOTE_MINIMAL) node_writer.writerow([label, title, year, rating, duration, name, birth_year, profession, genre_name, award_name, award_year, company_name, company_type]) # 关系 CSVstart_label,start_id,end_label,end_id,type rel_writer csv.writer(rf, quotingcsv.QUOTE_MINIMAL) rel_writer.writerow([start_label, start_id, end_label, end_id, rel_type]) # 3. 遍历电影生成节点与关系行 movie_id_counter 0 person_id_counter 0 for movie in movies: # Movie 节点 movie_id fm{movie_id_counter} node_writer.writerow([Movie, movie[title], movie[year], movie[rating], movie[duration], , , , , , , , ]) movie_id_counter 1 # Genre 节点去重 for genre in movie[genres]: genre_id fg_{genre.replace( , _)} node_writer.writerow([Genre, , , , , , , , genre, , , , ]) rel_writer.writerow([Movie, movie_id, Genre, genre_id, HAS_GENRE]) # Person 节点 ACTED_IN/DIRECTED/WROTE 关系 for director in movie[directors]: person_id fp{person_id_counter} node_writer.writerow([Person, , , , , director, , director, , , , , ]) rel_writer.writerow([Person, person_id, Movie, movie_id, DIRECTED]) person_id_counter 1 for actor in movie[actors][:5]: # 限制每人最多5个主演防数据爆炸 person_id fp{person_id_counter} node_writer.writerow([Person, , , , , actor, , actor, , , , , ]) rel_writer.writerow([Person, person_id, Movie, movie_id, ACTED_IN]) person_id_counter 1 print(✅ CSV 文件生成完毕nodes.csv 和 relationships.csv 已就绪)生成后在 Neo4j Browser 中执行导入命令注意路径用绝对路径Windows 用/C:/xxx/格式// 先创建索引加速查询 CREATE INDEX movie_title_index ON :Movie(title); CREATE INDEX person_name_index ON :Person(name); // 导入 Movie 和 Genre 节点跳过 header 行 LOAD CSV WITH HEADERS FROM file:///nodes.csv AS row WITH row WHERE row.label Movie CREATE (:Movie {title: row.title, year: toInteger(row.year), rating: toFloat(row.rating), duration: toInteger(row.duration)}); LOAD CSV WITH HEADERS FROM file:///nodes.csv AS row WITH row WHERE row.label Genre CREATE (:Genre {name: row.genre_name}); // 导入 Person 节点注意 birth_year 可能为空用 coalesce 处理 LOAD CSV WITH HEADERS FROM file:///nodes.csv AS row WITH row WHERE row.label Person CREATE (:Person {name: row.name, profession: row.profession, birth_year: coalesce(toInteger(row.birth_year), null)}); // 导入关系需确保 start/end 节点已存在 LOAD CSV WITH HEADERS FROM file:///relationships.csv AS row MATCH (a:Person {name: row.start_id}) MATCH (b:Movie {title: row.end_id}) CREATE (a)-[:ACTED_IN]-(b);关键参数说明toInteger()和toFloat()强制类型转换避免数字存成字符串导致排序/比较失败coalesce(..., null)处理缺失出生年份MATCH必须用唯一属性如name或title精准定位节点不能MATCH (n) WHERE n.name ...—— 这会全表扫描大数据量直接超时。3. 让图谱“开口说话”Python 后端封装 Cypher 查询与自然语言接口3.1 用 Neo4j Python Driver 连接数据库并封装基础查询方法neo4j官方驱动pip install neo4j比py2neo更轻量、文档更清晰、错误提示更友好是当前推荐方案。连接池、事务、参数化查询都内置支持无需额外封装from neo4j import GraphDatabase import os class MovieGraph: def __init__(self, uri, user, password): self.driver GraphDatabase.driver(uri, auth(user, password)) def close(self): self.driver.close() def query_acted_in_movies(self, actor_name): 查询某演员参演的所有电影 with self.driver.session() as session: result session.run( MATCH (p:Person {name: $name})-[:ACTED_IN]-(m:Movie) RETURN m.title AS title, m.year AS year, m.rating AS rating ORDER BY m.year DESC, nameactor_name ) return [{title: r[title], year: r[year], rating: r[rating]} for r in result] def query_common_actors(self, movie1, movie2): 查询两部电影的共同演员 with self.driver.session() as session: result session.run( MATCH (m1:Movie {title: $m1})-[:ACTED_IN]-(p:Person)-[:ACTED_IN]-(m2:Movie {title: $m2}) RETURN p.name AS name, m1movie1, m2movie2 ) return [r[name] for r in result] # 使用示例 graph MovieGraph(bolt://localhost:7687, neo4j, movie123) print(graph.query_acted_in_movies(周星驰)) graph.close()注意bolt://localhost:7687是 Neo4j 默认 Bolt 协议地址比 HTTP 更快$name是参数占位符绝不能拼接字符串防 Cypher 注入session.run()返回的是游标对象需用列表推导式或list(result)转为 Python 对象每次查询后session自动关闭但driver需手动close()。3.2 构建 Flask Web API3 个核心问答端点与请求校验Flask 足够轻量50 行代码就能跑通 API且调试方便debugTrue实时重载。重点不是炫技而是让老师能输入 URL 看到 JSON 返回from flask import Flask, request, jsonify from movie_graph import MovieGraph # 上节定义的类 app Flask(__name__) graph MovieGraph(bolt://localhost:7687, neo4j, movie123) app.route(/api/actor-movies, methods[GET]) def get_actor_movies(): actor request.args.get(name) if not actor: return jsonify({error: 缺少 name 参数}), 400 try: results graph.query_acted_in_movies(actor) return jsonify({actor: actor, movies: results}) except Exception as e: return jsonify({error: f查询失败: {str(e)}}), 500 app.route(/api/common-actors, methods[GET]) def get_common_actors(): m1 request.args.get(movie1) m2 request.args.get(movie2) if not (m1 and m2): return jsonify({error: 缺少 movie1 或 movie2 参数}), 400 try: actors graph.query_common_actors(m1, m2) return jsonify({movie1: m1, movie2: m2, common_actors: actors}) except Exception as e: return jsonify({error: f查询失败: {str(e)}}), 500 app.route(/api/movie-genres, methods[GET]) def get_movie_genres(): title request.args.get(title) if not title: return jsonify({error: 缺少 title 参数}), 400 try: with graph.driver.session() as session: result session.run( MATCH (m:Movie {title: $title})-[:HAS_GENRE]-(g:Genre) RETURN g.name AS genre, titletitle ) genres [r[genre] for r in result] return jsonify({movie: title, genres: genres}) except Exception as e: return jsonify({error: f查询失败: {str(e)}}), 500 if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue) # 本地调试用部署时关掉 debug启动后访问http://localhost:5000/api/actor-movies?name周星驰返回标准 JSON{ actor: 周星驰, movies: [ {title: 功夫, year: 2004, rating: 8.8}, {title: 唐伯虎点秋香, year: 1993, rating: 8.7} ] }提示host0.0.0.0允许局域网其他设备访问如手机测试但答辩演示时建议保持127.0.0.1所有 API 都加了try-except和参数校验这是工程规范也是答辩时老师会问“如果用户输错名字怎么办”的答案。3.3 简单 NLP 层用规则关键词映射实现“自然语言→Cypher”毕业设计不必上 BERT用 Python 字符串处理 预定义模板即可覆盖 80% 常见问法。核心思想识别问句中的关键槽位演员名、电影名、关系类型填入 Cypher 模板import re class QASystem: def __init__(self, graph): self.graph graph def parse_question(self, question): 将自然语言问题解析为查询参数 question question.strip() # 模板1X主演过哪些电影 → 提取 X if re.search(r(主演|演过|出演).*?哪些.*?电影, question): actor re.search(r^(.*?)主演, question) or re.search(r^(.*?)演过, question) if actor: return {type: actor_movies, actor: actor.group(1).strip()} # 模板2X和Y有哪些共同演员 → 提取 X, Y if 共同演员 in question or 一起演过 in question: movies re.findall(r《(.*?)》, question) # 支持《阿凡达》《泰坦尼克号》格式 if len(movies) 2: return {type: common_actors, movie1: movies[0], movie2: movies[1]} # 模板3X是什么类型的电影 → 提取 X if re.search(r(什么类型|属于.*?类型), question): movie re.search(r《(.*?)》, question) if movie: return {type: movie_genres, movie: movie.group(1)} return None def answer(self, question): parsed self.parse_question(question) if not parsed: return 抱歉暂不支持该问题格式。请尝试周星驰主演过哪些电影、《阿凡达》和《泰坦尼克号》有哪些共同演员 if parsed[type] actor_movies: return self.graph.query_acted_in_movies(parsed[actor]) elif parsed[type] common_actors: return self.graph.query_common_actors(parsed[movie1], parsed[movie2]) elif parsed[type] movie_genres: with self.graph.driver.session() as session: result session.run( MATCH (m:Movie {title: $title})-[:HAS_GENRE]-(g:Genre) RETURN g.name AS genre, titleparsed[movie] ) return [r[genre] for r in result] return [] # 使用示例 qa QASystem(graph) print(qa.answer(周星驰主演过哪些电影)) print(qa.answer(《阿凡达》和《泰坦尼克号》有哪些共同演员))血泪经验正则re.findall(r《(.*?)》, q)比re.search更鲁棒支持多个书名号中文标点《》必须显式写出不能用\u300a\u300b所有解析函数必须有 fallback 返回“不支持”避免空指针崩溃——这是答辩时演示“故意输错”环节的底气。4. 常见问题排查Neo4j Python 问答系统上线前必踩的 4 个坑4.1 现象CSV 导入后节点数量远少于预期或关系全部丢失原因CSV 文件含 BOM 头Windows 记事本保存时默认添加、字段含未转义的逗号、某列数据含换行符导致行错位。Neo4jLOAD CSV对格式极其敏感一行错全盘崩。解决用 VS Code 或 Notepad 打开 CSV右下角确认编码为UTF-8 without BOM用 Python 脚本预处理df.to_csv(clean.csv, indexFalse, encodingutf-8, quotingcsv.QUOTE_MINIMAL)导入前在 Neo4j Browser 执行LOAD CSV WITH HEADERS FROM file:///nodes.csv AS row RETURN count(*)确认行数是否匹配。4.2 现象Flask API 返回 500 错误日志显示SessionExpired或Connection refused原因Neo4j 服务意外停止如电脑休眠后未重启、Python Driver 连接超时未重连、或graph实例被多线程共享Driver 不是线程安全的。解决在 Flask 路由内每次请求新建 session不要复用全局 driver增加重试机制from neo4j import GraphDatabase import time def safe_query(driver, cypher, params{}): for i in range(3): # 最多重试3次 try: with driver.session() as session: return list(session.run(cypher, params)) except Exception as e: if i 2: raise e time.sleep(0.5)4.3 现象Cypher 查询结果为空但手动MATCH (n) RETURN n LIMIT 10能看到数据原因属性值含不可见字符如全角空格、零宽空格、大小写不匹配周星驰vs周星驰 、或中文标点混用《阿凡达》vs《阿凡达》看似一样实则后者用了全角括号。解决在 Neo4j Browser 执行MATCH (p:Person) WHERE p.name CONTAINS 周 RETURN p.name, size(p.name)查看实际字符长度用trim()和apoc.text.clean()需安装 APOC 插件标准化字符串Python 清洗时加str.strip().replace( , ).replace( , )。4.4 现象问答接口返回正确数据但前端页面显示乱码如“周星驰”变成“周星驼”原因Flask 默认响应头Content-Type: text/html; charsetutf-8但 JSON 接口需显式声明application/json或前端 fetch 未指定responseType: json。解决Flask 路由中强制设置mimetypeapplication/jsonapp.route(/api/xxx) def xxx(): data {...} return jsonify(data) # jsonify 自动设 mimetype但确保 data 是 dict/list # 或手动return Response(json.dumps(data, ensure_asciiFalse), mimetypeapplication/json)注意jsonify()默认ensure_asciiTrue中文变 Unicode加ensure_asciiFalse保证前端看到汉字若用Response必须手动json.dumps(..., ensure_asciiFalse)否则前端需decodeURIComponent处理。5. 答辩加分项3 个让老师眼前一亮的进阶技巧与验证方法5.1 用 Neo4j Bloom 可视化图谱3 分钟讲清“为什么选图数据库”Neo4j Bloom 是官方免费可视化工具随 Neo4j Desktop 自带不用写一行前端代码就能拖拽展示“周星驰→电影→类型→导演→其他演员”的多跳路径。答辩时打开 Bloom输入MATCH (p:Person {name:周星驰})-[*1..3]-(x) RETURN p, x点击“可视化”立刻生成交互式子图。这时你可以说“老师您看传统关系型数据库要 3 张 JOIN 表才能查出这条路径而图数据库一次遍历就完成——这就是知识图谱对关联查询的天然优势。”Bloom 不是炫技而是把抽象的‘关系’具象化让非图数据库背景的老师秒懂价值。导出 PNG 截图放进 PPT比贴 10 行 SQL 更有力。5.2 构建最小测试集验证问答准确率10 个问题 自动化断言毕业设计常被质疑“真能回答问题吗”。与其口头承诺不如用 10 个手工构造的测试用例写个test_qa.py自动跑def test_qa_system(): qa QASystem(graph) test_cases [ (周星驰主演过哪些电影, lambda r: len(r) 0 and 功夫 in [m[title] for m in r]), (《阿凡达》和《泰坦尼克号》有哪些共同演员, lambda r: 莱昂纳多·迪卡普里奥 in r), (《肖申克的救赎》是什么类型的电影, lambda r: 剧情 in r), ] passed 0 for question, validator in test_cases: try: result qa.answer(question) if validator(result): passed 1 print(f✅ {question} - PASS) else: print(f❌ {question} - FAIL, got {result}) except Exception as e: print(f❌ {question} - ERROR {e}) print(f\n 测试通过率: {passed}/{len(test_cases)}) assert passed len(test_cases), 测试未全通过请检查逻辑 if __name__ __main__: test_qa_system()答辩时展示终端运行结果说“我定义了 10 个典型问题全部通过自动化验证准确率 100%——这证明系统不是 Demo而是可稳定工作的模块。”测试即文档比写 2000 字设计说明书更有说服力。5.3 在源码中埋入“可解释性钩子”让每次查询输出 Cypher 语句老师可能追问“你这句自然语言到底生成了什么数据库查询” 如果只能回答“内部逻辑”就露怯了。在QASystem.answer()中加一行日志def answer(self, question): parsed self.parse_question(question) if not parsed: return 不支持 # 关键记录生成的 Cypher if parsed[type] actor_movies: cypher fMATCH (p:Person {{name: {parsed[actor]}}})-[:ACTED_IN]-(m:Movie) RETURN m.title, m.year elif parsed[type] common_actors: cypher fMATCH (m1:Movie {{title: {parsed[movie1]}}})-[:ACTED_IN]-(p:Person)-[:ACTED_IN]-(m2:Movie {{title: {parsed[movie2]}}}) RETURN p.name print(f 问题: {question} → Cypher: {cypher}) # 控制台实时输出 # ... 执行查询答辩演示时打开终端日志窗口输入问题老师亲眼看到“周星驰主演过哪些电影” →MATCH (p:Person {name: 周星驰})-[:ACTED_IN]-(m:Movie) ...技术透明毫无黑匣子感。这招成本为零但信任度拉满。最后说句实在话做这个毕业设计我前后删了 3 版前端界面Vue/React 都太重最终就用 Flask HTML 模板写了 20 行静态页因为答辩核心是证明你能把知识建模、数据流转、查询表达、接口暴露四层打通而不是做个“看起来很炫”的网站。Neo4j 的 Cypher、Python 的 Driver、Flask 的路由、正则的槽位提取——每一环都经得起老师点名深挖。当你能指着代码说清“为什么这里用coalesce”“为什么关系导入要分两步”你就已经赢了。希望帮到你。本文还有配套的精品资源点击获取