
简介这是一套面向计算机相关专业毕业设计场景的知识图谱电影问答系统完整项目采用Python与Neo4j构建适合正在准备毕设、课程设计或期末大作业的学生以及需要项目实战练习的学习者参考。项目经导师指导并获评审99分认可代码完整可运行对新手较为友好。资源包共59个文件约6.28MB以py源码、csv与json数据文件、png与svg图示、js与wxml/wxss小程序前端文件、md说明文档等为主涵盖后端Flask服务、爬虫模块、知识图谱构建与微信小程序前端等部分目录结构清晰便于按模块查阅。目前已有77人学习下载。读者可从中获得一套可直接运行的问答系统实现方案理解电影知识图谱的实体关系设计与查询逻辑参考前后端联调与数据组织方式并借助文档说明快速上手复现与二次修改为毕设答辩与项目实战提供扎实的代码与思路支撑。1. 从一份电影问答毕设说起知识图谱、Python 和 Neo4j 到底怎么串起来很多同学做毕业设计时第一反应是「电影问答系统」听起来简单无非是输入一句「周星驰演过哪些电影」系统返回一个列表。但真正动手才发现如果只用 MySQL 存几张表写几条 LIKE 查询遇到「周星驰和吴孟达合作过哪些电影」这种多跳问题就立刻翻车。这正是知识图谱电影问答系统要解决的核心痛点把电影、演员、导演、类型、评分这些实体以及它们之间的关系用图结构存进 Neo4j再用 Python 做自然语言解析和 Cypher 查询最终返回精准答案。这个方向特别适合计算机毕业设计因为它同时覆盖了数据采集、知识图谱构建、图数据库操作、问答接口开发四个环节工作量饱满且技术栈清晰。Python 负责爬虫、分词、意图识别和 Web 服务Neo4j 负责存储和查询图数据两者通过官方驱动连接。你不需要 GPU不需要大数据集群一台普通笔记本就能跑通全流程。接下来我会按「先建图、再问答、后避坑」的顺序把每个环节的参数和代码都摊开讲清楚。2. 知识图谱构建从电影数据到 Neo4j 节点与关系2.1 为什么选 Neo4j 而不是关系型数据库电影问答的核心是关系查询。比如「张艺谋执导的、评分高于 8 分的电影有哪些」在关系型数据库里需要 JOIN 电影表、导演表、评分表SQL 写起来长且慢。而在 Neo4j 里导演和电影之间就是一条DIRECTED关系查询时直接沿关系遍历语义清晰性能也更好。Neo4j 社区版免费支持 Cypher 查询语言Python 驱动成熟是毕业设计里图数据库的首选。选型时注意Neo4j 4.x 和 5.x 的语法略有差异社区版不支持多数据库只能用一个默认库但这对毕设完全够用。安装时建议用 Neo4j Desktop 或直接下载社区版压缩包配置好dbms.memory.heap.initial_size和dbms.memory.heap.max_size否则导入大量数据时容易内存不足。2.2 用 Python 爬取电影数据并清洗成三元组数据来源可以用豆瓣电影 Top250 或 TMDB 的公开 API。这里以爬取豆瓣为例注意控制请求频率避免被封。爬下来的原始数据是 JSON 或 CSV需要清洗成「实体-关系-实体」的三元组格式。import requests from bs4 import BeautifulSoup import csv import time # 爬取豆瓣Top250电影基本信息 def crawl_douban_top250(): movies [] headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } for start in range(0, 250, 25): url fhttps://movie.douban.com/top250?start{start} resp requests.get(url, headersheaders) soup BeautifulSoup(resp.text, html.parser) for item in soup.find_all(div, class_item): title item.find(span, class_title).text.strip() rating item.find(span, class_rating_num).text.strip() info item.find(div, class_bd).p.get_text(stripTrue) # 提取导演和主演简化处理 director info.split(主演:)[0].replace(导演:, ).strip() movies.append({ title: title, rating: float(rating), director: director }) time.sleep(2) # 控制频率避免触发反爬 return movies # 保存为CSV movies crawl_douban_top250() with open(movies.csv, w, newline, encodingutf-8) as f: writer csv.DictWriter(f, fieldnames[title, rating, director]) writer.writeheader() writer.writerows(movies) print(f共爬取 {len(movies)} 部电影)这段代码的关键参数是time.sleep(2)豆瓣对频繁请求很敏感间隔低于 1 秒很容易返回 403。User-Agent必须伪装成浏览器。爬下来的director字段可能包含多个导演后续需要按「、」或「/」拆分。清洗后的 CSV 就是知识图谱的原始素材。2.3 用 Cypher 批量导入节点和关系有了 CSV接下来用 Python 驱动连接 Neo4j执行 Cypher 语句创建节点和关系。先安装驱动pip install neo4j。然后写导入脚本。from neo4j import GraphDatabase import csv # 连接Neo4j默认地址和密码 driver GraphDatabase.driver(bolt://localhost:7687, auth(neo4j, your_password)) def create_movie_graph(tx, title, rating, director): # 使用MERGE避免重复创建节点 tx.run( MERGE (m:Movie {title: $title}) SET m.rating $rating MERGE (d:Director {name: $director}) MERGE (d)-[:DIRECTED]-(m) , titletitle, ratingrating, directordirector) with driver.session() as session: with open(movies.csv, r, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: session.execute_write( create_movie_graph, row[title], float(row[rating]), row[director] ) # 验证导入结果 result session.run(MATCH (m:Movie) RETURN count(m) AS total) print(电影节点总数:, result.single()[total]) driver.close()这里用MERGE而不是CREATE因为同一部电影可能被多次爬取MERGE会先检查是否存在避免重复节点。execute_write是 Neo4j 4.x 以上推荐的写事务方法。导入完成后可以在 Neo4j Browser 里执行MATCH (d:Director)-[:DIRECTED]-(m:Movie) RETURN d, m LIMIT 25查看图谱效果。如果节点数量对不上检查 CSV 里是否有空行或标题重复。注意Neo4j 社区版默认只允许本地连接如果 Python 脚本和 Neo4j 不在同一台机器需要修改neo4j.conf里的dbms.default_listen_address0.0.0.0但毕设通常本地跑不用改。3. 问答系统实现Python 意图识别加 Cypher 查询模板3.1 用规则匹配做意图识别别一上来就上深度学习很多同学一提到问答系统就想用 BERT 或 LSTM但毕业设计时间有限训练数据也不好找。更务实的做法是先用 jieba 分词加关键词匹配把用户问题分成几类意图比如「查询某导演的电影」「查询某演员参演的电影」「查询某类型的高分电影」。每类意图对应一个 Cypher 查询模板把提取到的实体填进去就行。这样准确率可控调试也方便。意图分类的关键是维护一个同义词词典。比如「主演」「演员」「出演」都指向ACTED_IN关系「导演」「执导」都指向DIRECTED关系。jieba 的自定义词典可以加载这个映射表。3.2 从自然语言到 Cypher 的完整代码链路下面是一个可运行的问答核心模块包含意图识别、实体提取和查询执行。import jieba from neo4j import GraphDatabase # 加载自定义词典提高电影领域分词准确率 jieba.load_userdict(movie_dict.txt) # 每行格式词 词频 词性 driver GraphDatabase.driver(bolt://localhost:7687, auth(neo4j, your_password)) # 意图模板关键词 - Cypher模板 INTENT_TEMPLATES { director_movies: { keywords: [导演, 执导], cypher: MATCH (d:Director {name: $entity})-[:DIRECTED]-(m:Movie) RETURN m.title AS title, m.rating AS rating ORDER BY m.rating DESC }, actor_movies: { keywords: [主演, 演员, 出演], cypher: MATCH (a:Actor {name: $entity})-[:ACTED_IN]-(m:Movie) RETURN m.title AS title, m.rating AS rating ORDER BY m.rating DESC }, high_rating: { keywords: [高分, 评分高, 好看], cypher: MATCH (m:Movie) WHERE m.rating $min_rating RETURN m.title AS title, m.rating AS rating ORDER BY m.rating DESC LIMIT 10 } } def parse_question(question): words jieba.lcut(question) # 匹配意图 for intent, config in INTENT_TEMPLATES.items(): if any(kw in question for kw in config[keywords]): # 提取实体取分词结果中不在关键词里的名词 entity None for w in words: if w not in config[keywords] and len(w) 1: entity w break return intent, config[cypher], entity return None, None, None def answer_question(question): intent, cypher, entity parse_question(question) if not cypher: return 抱歉我还没学会回答这个问题。 with driver.session() as session: if intent high_rating: result session.run(cypher, min_rating8.0) else: result session.run(cypher, entityentity) records [dict(r) for r in result] if not records: return 没有找到相关结果。 return records # 测试 print(answer_question(周星驰主演的电影有哪些)) print(answer_question(评分高的电影推荐))parse_question先用 jieba 分词再遍历意图模板匹配关键词。实体提取逻辑比较粗糙取第一个长度大于 1 且不是关键词的词。实际项目中可以结合命名实体识别NER模型但毕设用规则足够。answer_question根据意图决定传entity还是min_rating参数。查询结果以列表形式返回前端可以渲染成表格。3.3 用 Flask 暴露 HTTP 接口方便前端调用问答核心写好后用 Flask 包一层 REST API前端页面通过 AJAX 请求即可。from flask import Flask, request, jsonify from qa_module import answer_question # 上面的问答模块 app Flask(__name__) app.route(/ask, methods[POST]) def ask(): data request.get_json() question data.get(question, ).strip() if not question: return jsonify({error: 问题不能为空}), 400 answer answer_question(question) return jsonify({question: question, answer: answer}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)启动后用 Postman 或 curl 测试curl -X POST http://localhost:5000/ask -H Content-Type: application/json -d {question:周星驰主演的电影}。返回 JSON 里包含答案列表。前端可以用 Vue 或原生 HTML 写一个简单的搜索框和结果展示区。注意 Flask 的debugTrue只用于开发部署时关掉。提示如果查询结果为空先检查 Neo4j 里是否真的有对应节点。可以在 Neo4j Browser 里手动执行MATCH (a:Actor {name:周星驰}) RETURN a确认实体名称和代码里提取的一致。4. 避坑与排查知识图谱电影问答系统最常见的 5 个翻车点4.1 现象Neo4j 导入数据后节点数量翻倍原因用了CREATE而不是MERGE或者 CSV 里有重复行。解决导入前用 pandas 去重Cypher 里统一用MERGE。如果已经导入重复节点执行MATCH (m:Movie) WITH m.title AS title, collect(m) AS nodes WHERE size(nodes) 1 FOREACH (n IN tail(nodes) | DETACH DELETE n)清理。4.2 现象Python 连接 Neo4j 报错ServiceUnavailable原因Neo4j 服务没启动或者端口不对。默认 Bolt 端口是 7687HTTP 端口是 7474。解决检查 Neo4j 是否在运行neo4j status查看状态。如果改了端口驱动连接字符串也要同步改。密码错误也会报类似错误确认auth里的密码和安装时设置的一致。4.3 现象jieba 分词把「周星驰」切成「周星」「驰」原因默认词典没有电影领域的人名。解决在movie_dict.txt里添加「周星驰 100 n」这样的词条然后jieba.load_userdict加载。词频给高一点确保不被拆开。同样「吴孟达」「张艺谋」等都需要加进去。4.4 现象Cypher 查询返回空结果但数据库里明明有数据原因实体名称大小写不一致或者有多余空格。解决在 Cypher 里用toLower()统一大小写Python 提取实体后.strip()去空格。更稳妥的做法是导入时就统一清洗比如全部转小写存储。4.5 现象Flask 接口返回中文乱码原因Flask 默认 JSON 序列化会转义非 ASCII 字符。解决设置app.config[JSON_AS_ASCII] False或者用jsonify时加ensure_asciiFalse。Flask 2.3 之后用app.json.ensure_ascii False。5. 让问答更聪明多跳查询与结果排序的进阶技巧基础版跑通后可以尝试多跳查询。比如「周星驰和吴孟达合作过哪些电影」这需要先找到两个演员节点再找它们共同连接的 Movie 节点。Cypher 写法如下def find_collaboration(actor1, actor2): cypher MATCH (a1:Actor {name: $actor1})-[:ACTED_IN]-(m:Movie)-[:ACTED_IN]-(a2:Actor {name: $actor2}) RETURN m.title AS title, m.rating AS rating ORDER BY m.rating DESC with driver.session() as session: result session.run(cypher, actor1actor1, actor2actor2) return [dict(r) for r in result]这个查询同时匹配两个演员到同一部电影的关系返回合作作品。参数actor1和actor2从用户问题里提取可以用正则匹配「A和B合作」的模式。另一个进阶点是结果排序。除了按评分降序还可以引入「热度」字段比如电影的评价人数。在 Movie 节点上加votes属性查询时ORDER BY m.rating DESC, m.votes DESC。如果要做推荐可以计算演员之间的合作次数用count(m)作为权重。验证系统效果时准备 20 个测试问题覆盖单跳、多跳、条件过滤三类人工检查返回结果是否正确。准确率低于 80% 就回头检查分词和实体提取。我自己的习惯是每加一个意图模板就先在 Neo4j Browser 里把 Cypher 跑通再写进 Python 代码这样能省下大量调试时间。希望帮到你。本文还有配套的精品资源点击获取