☰
Python+Neo4j构建电影知识图谱问答系统:从建模到答辩全攻略
2026/10/11 11:24:26 网站建设 项目流程

简介:基于知识图谱的电影问答系统是一份面向计算机相关专业毕业设计的完整源码与文档资源,适合正在筹备毕设的学生以及需要项目实战练习的学习者。项目使用Python与Neo4j图数据库构建,涵盖数据预处理、问题分类、问答模板匹配等功能模块,并附带README说明,能够帮助读者快速理解知识图谱问答的实现思路。资源包共44个文件,主要包括7个Python核心脚本、多份CSV数据集、界面样式与交互文件、说明文档及示意图,另含一些运行缓存文件,整体约1.15MB,结构清晰,便于按模块查阅。核心脚本如question_classification.py、process_question.py、data2neo4j.py覆盖数据入库、问题解析与匹配流程,电影、类型、人物等数据文件则支持知识图谱构建与验证。已有105人浏览学习,适合用作毕业设计参考、课程设计或期末大作业,代码完整可运行,可作为高完成度的项目蓝本。

1. 电影问答系统不是玩具:一个知识图谱毕设能练出什么

开题时最常被问到的一句话是:「现在随便一个大模型都能答电影问题,为什么还要做知识图谱问答系统?」我的回答是:大模型给的是「像答案的东西」,知识图谱给的是「能回溯到数据源的答案」。基于 Python 和 Neo4j 的电影问答系统,串起了图数据建模、Cypher 查询、意图识别与接口设计一整条链路,把「从问题到答案」的每一步都变成可控、可查、可答辩的成果。适合计算机、软件工程、数据科学方向做毕设或课设的同学。源码和文档说明只是起点,真正的价值在于你能讲清楚「为什么数据要这样建模」「『导演是谁』是如何变成一条 Cypher 的」。

2. 选型与架构:为什么是 Python + Neo4j,而不是接个大模型 API

先给结论:在毕业设计这个场景里,知识图谱加图数据库的方案,比直接调大模型 API 更容易拿到高完成度,也好答辩。原因不在于模型能力,而在于知识图谱问答的每一步都是确定的:问题解析、实体匹配、图查询、答案格式化,每一层都能被检查、被解释、被演示。

2.1 三种方案对比:关系数据库、图数据库与大模型方案差在哪

动手之前先把候选方案摆在一起比过一轮,你才知道 Neo4j 的价值不在「存数据」,而在「存关系」。

对比维度MySQL 关系表Neo4j 图数据库大模型 API
多跳查询(如「张三和李四合作过哪些电影」)多次 JOIN,SQL 越来越难维护一条 MATCH 路径直接跑完结果不可控,需要额外工程验证
答案可解释性能看 SQL,但业务逻辑散落在代码层Cypher 本身就是路径证据,可回溯无法追溯生成过程
毕业答辩展示度中等,展示的是后端接口高,自带的图可视化界面直观中等,同质化严重
迭代门槛低中,需要学 Cypher 和图建模低,但幻觉和成本问题绕不开

实际写论文时,「多跳查询」是知识图谱方向最好用的卖点。比如「姜文导演的电影里,有哪些是葛优主演的」这类问题,在关系型数据库里要 join 两三张中间表,而在 Neo4j 里只是沿着关系路径走两步的问题:

MATCH (p1:Person {name: "姜文"})-[:DIRECTED]->(m:Movie)<-[:ACTED_IN]-(p2:Person {name: "葛优"}) RETURN m.title

这段 Cypher 本身就是答案的证据链:姜文导演了某部电影,葛优演了某部电影,两者在图上汇聚到同一个 Movie 节点。答辩时把这条路径截图放进论文里,比贴十行 SQL 直观得多。选 Python 而不是 Java 或 Go,是因为 py2neo、官方 neo4j 驱动、以及后面做自然语言处理要用的 jieba、difflib 全都在 Python 生态里,工程实现成本最低。

2.2 系统整体架构:从「导演是谁」到答案的四层流水线

常见做法是把系统拆成四层,每层只做一件事,层与层之间用字典传递数据,不要用字符串连环拼接。

第一层是输入层,负责归一化:把「《 霸王别姬 》导演是谁?」里的空格、全角字符、书名号去掉,得到干净的文本。第二层是理解层,做意图识别和槽位抽取,判断用户问的是导演、演员、上映年份还是评分,并把问题里的电影名或人名抽出来。第三层是查询层,把意图和实体映射成预定义的 Cypher 模板,用参数化查询去 Neo4j 里取数。第四层是答案层,把 Cypher 返回的列表格式化成一句自然语言回答;查不到时还要给出引导话术。

提示:层与层之间传 dict 而不是拼字符串,是后期加日志、加测试、换接口最容易的路径。毕设里 80% 的排错时间都花在「这层到底收到了什么」上,提前把数据结构定清楚,能省掉一半 AO 的功夫。

这一章不会接大模型 API,也不要在这个项目里引入向量检索。倒不是技术不行,而是这个毕设的边界是「知识图谱电影问答」,把精力放在可解释的图查询上,工作量才真正落在你论文的核心章节里。RAG、向量数据库这些方向,留到研究生的课题里做不迟。

2.3 环境准备:Neo4j 版本与 Python 驱动怎么选

去搜索「Neo4j 安装教程」能看到大量旧教程,其中不少还停留在 Neo4j 3.5 时代。装之前先定版本组合,不然「对着教程敲一遍跑不通」会直接劝退一半人。我的建议是按以下三种组合选:

  • 组合 A(最稳妥的毕设组合):Neo4j 4.4 + JDK 11 + py2neo 2021.1。这个组合的网上配套资料最多,py2neo 对 4.x 的兼容性也最好,适合入门。
  • 组合 B(想用最新版):Neo4j 5.x + JDK 17 + 官方 neo4j 驱动。功能新,性能好,但网上大量教程还是 py2neo 老写法,直接照着抄会在连接那一步就翻车。
  • 旧教程里的 Neo4j 3.5:需要配 JDK 8,已经停更多年,除非你是跟着某门只能在 3.5 上跑通的课程走,否则不建议新项目从 3.5 起步。

安装本身不复杂,Community 版压缩包解压即用,不需要安装向导。启动方式如下:

# Linux/macOS 启动 Neo4j bin/neo4j start # 查看状态;HTTP 端口 7474,Bolt 端口 7687 bin/neo4j status

提示:Windows 上运行的是 bin/neo4j.bat。首次启动后打开 http://localhost:7474,默认账号密码都是 neo4j,登录后系统会强制让你改一次密码。

Python 侧最小的连接代码有两种写法。先看 py2neo:

# requirements.txt 里装 py2neo==2021.1 from py2neo import Graph graph = Graph("bolt://localhost:7687", auth=("neo4j", "your_password"), name="neo4j") print(graph.run("RETURN 1 AS ok").evaluate()) # 输出 1

参数说明:bolt://localhost:7687是二进制协议端口,和浏览器用的 7474 是两回事;auth元组第一项是用户名;name="neo4j"指定数据库名,这个参数在 Neo4j 4.x 里必须写,如果写了一个不存在的库名会直接抛异常。

再看官方驱动的写法:

# Neo4j 5.x 推荐用官方驱动 from neo4j import GraphDatabase driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "your_password")) with driver.session() as session: result = session.run("RETURN 1 AS ok") print(result.single()["ok"])

两种驱动在毕设里都够用。我个人倾向 py2neo,因为它操作节点和关系的对象式 API 写起来舒服,而且中文学术资料里用它做例子的比例高,遇到问题好搜。

3. 构建电影知识图谱:数据建模、数据清洗与 Cypher 批量导入

图谱的质量决定问答系统的上限。很多人拿到手就直接往 Neo4j 里灌数据,结果实体重复、类型字段乱七八糟,后面问答系统怎么调都像是摸黑。这一章把建模、清洗、导入三个环节一次说清。

3.1 实体与关系建模:三类节点和三条关系就够撑起一场答辩

先不要急着扩规模。一个能支撑答辩演示的图谱,最小的建模方案是三类节点、三类关系:

  • Movie 节点:属性有 mid、title、year、rating、duration、summary。mid 是全库唯一键,可以用豆瓣 ID、IMDb ID,或者自己生成的自增 ID。
  • Person 节点:属性有 pid、name、birthday。演员和导演共用这一类节点,用关系区分身份。
  • Genre 节点:只保留 name 属性。

三类关系分别是:(Person)-[:DIRECTED]->(Movie)、(Person)-[:ACTED_IN]->(Movie)、(Movie)-[:BELONGS_TO]->(Genre)。其中ACTED_IN关系上可以挂一个role属性,存角色名,这样「葛优在《活着》里演谁」这类问题也能答。

注意:不要为「角色」单独建一类节点。毕设图谱规模一般在几千到一两万节点,这种量级下用「关系加属性」远比「为每个角色建节点」清晰好维护。答辩时被问「为什么这样建模」,回答「属性表示实体属性、关系表示实体联系,角色是关系的属性而不是实体」就是标准答案。

如果想加大工作量,再增加 Award 节点和(Movie)-[:WON]->(Award)关系,或者 User 节点和(User)-[:RATED]->(Movie)关系。但基础版先把三类节点跑通,再考虑扩展。

3.2 数据清洗:编码、去重和别名的三个坑

数据来源一般是两个:自己写爬虫抓公开电影网站,或者整理现成的公开数据集。不管哪条路,拿到的 CSV 几乎都不是能直接导入的形状。清洗这一步至少有三个躲不过去的坑。

第一个坑是编码。Windows 下导出的 CSV 常见 GBK 编码,而 Python 的默认读取是 UTF-8,直接读会报UnicodeDecodeError。第二个坑是脏字段,年份列可能混着「1994年」这种字符串,评分列可能是「9.6/10」。第三个坑是重复,同一部电影在不同来源里有不同译名。

下面这段代码把这三件事一次处理掉:

import pandas as pd import re df = pd.read_csv("movies_raw.csv", encoding="utf-8-sig") # utf-8-sig 自动处理 BOM # 用正则从脏字段里抽出年份和评分 df["year"] = df["year"].astype(str).str.extract(r"(\d{4})").astype(float) df["rating"] = df["rating"].astype(str).str.extract(r"(\d+\.?\d*)").astype(float) # 以 mid 为唯一键去重;没有 mid 时退回 (title, year) 组合键 df = df.drop_duplicates(subset=["mid"], keep="first") df.to_csv("movies_clean.csv", index=False, encoding="utf-8")

逻辑说明:先读原始文件,用utf-8-sig而不是utf-8,是因为带 BOM 的文件读进来后第一列列名会带着\ufeff前缀,后面写 LOAD CSV 时第一列永远匹配不上;str.extract从脏字符串里抽出纯数字;drop_duplicates按唯一键去重,keep="first"保留首次出现的记录。参数说明:astype(str)先统一转字符串再做正则提取,否则某行是空值时extract会直接报错。

别名表是第三步的产物,同时也是第 4 章实体链接的核心数据。维护一张两列的 CSV,一列是标准名,一列是别名,比如「霸王别姬」对应「再见,我的妾」「Farewell My Concubine」,「三傻大闹宝莱坞」对应「三傻」。这张表在清洗阶段就建好,后面问答系统直接复用。

3.3 Cypher 批量导入:先建索引约束,再 MERGE,别用 CREATE

导入节点的顺序有个容易被忽略的原则:先建约束和索引,再导数据。Neo4j 的 MERGE 语句依赖唯一约束来快速判断「这个节点是否存在」,没有约束时 MERGE 等于全库扫描,几千行数据就能让你等到怀疑人生。

先用这段 Cypher 建约束和索引:

CREATE CONSTRAINT movie_mid IF NOT EXISTS FOR (m:Movie) REQUIRE m.mid IS UNIQUE; CREATE CONSTRAINT person_pid IF NOT EXISTS FOR (p:Person) REQUIRE p.pid IS UNIQUE; CREATE INDEX movie_title IF NOT EXISTS FOR (m:Movie) ON (m.title); CREATE INDEX person_name IF NOT EXISTS FOR (p:Person) ON (p.name);

参数说明:IF NOT EXISTS是幂等写法,重复执行不会报错,这对你反复调试导入脚本很重要;REQUIRE ... IS UNIQUE是 Neo4j 4.x 的约束语法,3.5 里对应的写法是ON (m:Movie) ASSERT m.mid IS UNIQUE,版本差异在这里很容易踩坑。

接下来导入电影节点:

LOAD CSV WITH HEADERS FROM 'file:///movies_clean.csv' AS row MERGE (m:Movie {mid: toInteger(row.mid)}) SET m.title = row.title, m.year = toInteger(row.year), m.rating = toFloat(row.rating)

逻辑说明:LOAD CSV WITH HEADERS会把第一行当作列名,后续行通过row.xxx访问;MERGE先按mid查有没有这个节点,没有才创建,重复运行不会造出重复节点;SET用来补属性。toInteger和toFloat是 Cypher 的类型转换函数,CSV 里读出来的值全是字符串,不转换的话year会被存成字符串"1994",后面按年份范围查询时就会出问题。

导入关系时同理:

LOAD CSV WITH HEADERS FROM 'file:///directors.csv' AS row MATCH (p:Person {pid: toInteger(row.pid)}) MATCH (m:Movie {mid: toInteger(row.mid)}) MERGE (p)-[:DIRECTED]->(m)

逻辑说明:先用两个MATCH把两端的节点查出来,再MERGE创建关系。这两步 MATCH 能否快速命中,完全依赖前面建的唯一约束。文件里如果有一行的 pid 在 Person 里不存在,这一行会静默跳过,所以导入后要做一次数量对账,确认关系数和 CSV 行数一致。

提示:LOAD CSV 的文件必须放在 Neo4j 安装目录下的 import 文件夹里,URL 写成file:///movies_clean.csv,注意是三个斜杠。Windows 用户最容易在这里翻车——写成本地盘符路径会直接报「Couldn't load the external resource」。

常见毕设规模做到 5000 部电影、1.5 万名演员就足够演示了。这个量级在 Neo4j 里导入和查询都很轻松,文档里描述数据规模时也好看。

4. 问答流水线核心实现:意图识别、实体链接与 Cypher 生成

图谱建好之后,主战场转移到问答系统本身。这个系统的核心不是机器学习模型,而是一条规则驱动的流水线。别觉得规则低端,在电影问答这个垂直场景里,规则比模型的稳定性和可解释性都高。

4.1 意图识别:用正则模板而不是「训练一个分类模型」

很多毕设为了显得高级,非要在意图识别里上 BERT。实际效果是:在几十条测试样本上,BERT 的正确率未必比 20 条正则模板高,还多了训练、调参、部署一大摊事。电影问答的问法非常有限,先覆盖高频问法再说。

下面是一组可以直接用的意图模板:

import re INTENT_PATTERNS = [ ("director", re.compile(r"(.+?)(?:导演了|执导过|是谁导演的|导演是谁)")), ("actor", re.compile(r"(.+?)(?:的主演|主演了|饰演|演过)")), ("year", re.compile(r"(.+?)(?:哪年|什么时候|年份).*(?:上映|播出)")), ("rating", re.compile(r"(.+?)(?:评分|几分|豆瓣)")), ("genre", re.compile(r"(.+?)(?:是什么类型|什么题材|类型是)")), ] def parse_question(question: str) -> dict: for intent, pattern in INTENT_PATTERNS: m = pattern.search(question) if m: return {"intent": intent, "subject": m.group(1).strip("《》 ")} return {"intent": "unknown", "subject": question}

逻辑说明:每个模板是一个「意图名 + 正则」的元组,按顺序匹配,先命中先返回。group(1)取出来的是用户问题里的实体候选,比如「姜文导演了哪些电影」会取出「姜文」。strip("《》 ")把书名号和空格去掉,这个细节能直接减少「实体查不到」的误报。参数说明:正则里的(?: ... )是非捕获分组,保证group(1)始终是前面的电影名或人名,而不是匹配到的动词。匹配顺序要把「导演是谁」这类放在「执导过」前面,因为后者更长,放后面会被先截胡。

提示:这组模板覆盖常见问法就够了,但别指望它处理「老谋子拍过什么」这种口语化输入。口语化输入交给实体链接层的别名表和编辑距离兜底,不在正则层硬碰。

4.2 实体链接:别名表 + 编辑距离兜底,别迷信精确匹配

意图识别拿到的是「实体候选字符串」,但图谱里的节点名字可能和用户输入不一致。「霸王别姬」在数据里可能存成「再见,我的妾」,这时候就要靠实体链接模块去对齐。

实体链接的逻辑分三层:

from difflib import SequenceMatcher ALIAS = { "霸王别姬": ["再见,我的妾", "Farewell My Concubine"], "三傻大闹宝莱坞": ["三傻"], } def link_entity(subject: str, candidates: list[str]) -> str | None: # 第一层:精确匹配 if subject in candidates: return subject # 第二层:别名表映射 for canonical, aliases in ALIAS.items(): if subject == canonical or subject in aliases: return canonical # 第三层:编辑距离兜底 best = max(candidates, key=lambda c: SequenceMatcher(None, subject, c).ratio()) score = SequenceMatcher(None, subject, best).ratio() return best if score >= 0.6 else None

逻辑说明:精确匹配是最快路径;别名表负责标准名和别名之间的映射;编辑距离兜底处理错别字和简称。candidates从哪来?常见做法是先往 Neo4j 发一条轻量模糊查询,把所有候选名拉回来再做匹配:

MATCH (n) WHERE n.name CONTAINS $kw RETURN n.name

对齐用的是CONTAINS而不是=,保证用户输入一半时也能拉回候选。参数说明:相似度阈值 0.6 是经验值。调低会把「战狼」链到「战狼2」,调高则口语输入容易落空。这个阈值写进论文里当作「参数调优实验」的一部分,答辩时是很好的话题。另外,代码里SequenceMatcher被调用了两次,效率和写法都有优化空间,正式工程里可以把 ratio 存成局部变量,但演示脚本这样写没问题。

4.3 查询生成:把意图和实体拼成参数化 Cypher

实体链接搞定以后,离答案只差一步:生成 Cypher 并执行。这里最容易犯的错误是用 f-string 把用户输入直接拼进查询串。

QUERY_TEMPLATES = { "director": """ MATCH (p:Person {name: $name})-[:DIRECTED]->(m:Movie) RETURN m.title AS title, m.year AS year ORDER BY m.rating DESC LIMIT 10 """, "actor": """ MATCH (p:Person {name: $name})-[:ACTED_IN]->(m:Movie) RETURN m.title AS title, m.rating AS rating ORDER BY m.rating DESC LIMIT 10 """, "year": """ MATCH (m:Movie {title: $title}) RETURN m.year AS year """, "rating": """ MATCH (m:Movie {title: $title}) RETURN m.rating AS rating """, } def run_query(graph, intent: str, entity: str): cypher = QUERY_TEMPLATES[intent] params = {"name": entity, "title": entity} return graph.run(cypher, **params).data()

逻辑说明:每个意图对应一条预编译的 Cypher,模板里的$name和$title是参数占位符,graph.run(cypher, **params)把参数传进查询。ORDER BY m.rating DESC LIMIT 10保证「演过哪些电影」这类问题只返回评分最高的前十条,避免答案列表过长。参数说明:params字典里同时放了name和title两个键,是因为不同模板用了不同的占位符名,而这里的实体可能是人名也可能是电影名,两种模板都能取到值。

永远不要用 f-string 直接拼 Cypher。用户输入里如果带着引号、括号甚至会破坏查询结构,而且一旦用户输入「电影名 + 恶意语句」,拼接式查询在安全上就是裸奔。答辩时主动说「这里用了参数化查询防止注入」,是加分项。

4.4 兜底逻辑:查不到答案也要给出「有效失败」

「查不到」在演示现场太常见了:别名表里没这个电影、用户把「张艺谋导演的」说成「老谋子拍的」、问法不在正则模板里。如果直接返回空列表,演示效果就是「系统坏了」。

常见做法是把失败分三层处理。第一层,实体在图谱里存在,但意图没匹配上,返回「我知道『霸王别姬』,但没听懂你想问它的什么信息」;第二层,实体在图谱里不存在,返回相近的候选名字,比如「你是不是想问:战狼、战狼2、红海行动」;第三层,全部落空,返回一句示例引导,「试试问我:姜文导演了哪些电影?」。

实现上就是在 4.3 的run_query外面套一层分支判断,先查实体是否存在,再根据结果走不同的回答模板。这部分逻辑在论文里对应「系统鲁棒性设计」,在演示现场对应「不冷场」。

5. 避坑排查:连接失败、中文检索不到、导入报错与内存问题的定位思路

这一章把毕设期间最常见的五个坑按「现象 → 原因 → 解决」讲清楚。每一条都是真实翻车现场,按顺序排查能省下大量「玄学调试」时间。

5.1 Neo4j 服务在跑,Python 却连不上

现象:浏览器能打开 7474 端口,但 py2neo 抛ServiceUnavailable或ConnectionError。

原因:最常见的有三个。第一,首次登录后没改默认密码,驱动用旧密码连接被拒;第二,服务虽然启动了,但 Bolt 端口 7687 没监听;第三,py2neo 版本和 Neo4j 版本不匹配,py2neo 2021.1 连 Neo4j 5.x 会出现协议兼容问题。

解决:先跑bin/neo4j status确认服务状态,再用lsof -i:7687(Windows 用netstat -ano | findstr 7687)确认端口监听;用 cypher-shell 改密码:

bin/cypher-shell -u neo4j -p neo4j "ALTER USER neo4j SET PASSWORD 'your_password'"

如果是版本不匹配,直接换官方驱动,别在 py2neo 上死磕。

5.2 中文实体查不到,英文实体正常

现象:问「让子弹飞」,返回空结果;问「Inception」,正常返回。

原因:Neo4j 默认的索引是精确匹配,对中文不像英文那样有「可预期的分词行为」;另一个高频原因是用户输入里带着书名号「《让子弹飞》」,经过意图识别后书名号没被剥干净,和库里的「让子弹飞」对不上。

解决:在输入层统一做归一化,strip("《》 ")去掉书名号和首尾空格,再用unicodedata.normalize("NFKC", text)做全角转半角;查询时优先用CONTAINS而不是=。中文全文检索需要配置专门的 analyzer,毕设阶段一般用不上,别浪费时间在这上面。

5.3 LOAD CSV 报 Couldn't load the external resource

现象:执行 LOAD CSV 语句,Neo4j 直接报错,提示外部资源加载失败。

原因:文件没放进 Neo4j 安装目录下的 import 文件夹;URL 写成file://C:/...少了斜杠;Windows 用户写成反斜杠路径。

解决:把 CSV 统一放到$NEO4J_HOME/import目录下,URL 固定写file:///movies_clean.csv。另外如果 CSV 有 BOM,第一列列名会带着\ufeff,导入时第一列永远匹配不上。解决办法是清洗阶段用encoding="utf-8-sig"另存一份,这一步在第 3 章清洗代码里已经处理过。

5.4 查询越来越慢,导入时内存溢出

现象:几千条数据导入后,每次问答要等 1-2 秒;导入过程中 Neo4j 直接崩掉。

原因:导入时没建唯一约束,导致后续 MATCH 变成全图扫描,这是性能杀手;另一个原因是 Neo4j 默认内存参数配得太保守,数据规模稍大就触发 OOM。

解决:重新核对 3.3 节的约束是否真正生效,用SHOW INDEXES检查索引状态;调内存参数。4.x 在 neo4j.conf 里改这两行:

dbms.memory.heap.initial_size=512m dbms.memory.heap.max_size=1G

5.x 的配置项改成了server.memory.heap.max_size。万级节点 1G 内存足够,十万级节点至少 4G。答辩被问「系统性能如何」时,能说出「索引命中 + 内存参数」这两个优化点,比一句「还行」强太多。

5.5 源码与文档说明对不上,答辩问细节时露怯

现象:代码能跑,但写文档时是按「印象中的系统」写的,函数名、接口对不上;答辩老师翻源码问某个函数是干什么的,答不上来。

原因:大多数毕设是先写代码后补文档,文档独立于代码存在,自然对不上。

解决:整理文档时以「函数签名」为锚点,给每个函数写一行注释,文档里只出现代码里真实存在的函数名。写一张函数清单表,列清楚函数名、入参、返回值、对应的 Cypher、测试问题样例,答辩前自己完整过一遍。这张表也是开题报告和论文「系统设计」章节的素材,一举两得。

6. 让毕设更耐打:从「能跑」到「能答辩」的三层验证

系统能跑只是起点,演示现场不翻车才是及格线。我的习惯是上场前做三层验证。

6.1 演示前十分钟跑一遍 smoke test

写一个小的冒烟测试脚本,把五类意图的固定问题全部跑一遍,断言每个问题都返回非空结果,并打印耗时:

# smoke_test.py —— 演示前必跑 DEMO_CASES = [ "姜文导演了哪些电影", "葛优主演了哪些电影", "霸王别姬哪年上映", "让子弹飞的评分是多少", "流浪地球是什么类型", ] def main(): for question in DEMO_CASES: answer = ask(question) # 对应你系统里的问答入口函数 assert answer, f"[FAIL] {question} 返回为空" print(f"[OK] {question} -> {answer}") if __name__ == "__main__": main()

这里的ask是你系统里的入口函数,冒烟脚本不关心内部实现,只关心「每个类型的问题都有答案」。演示前跑一遍,如果断言挂了,就别进场了。

6.2 给数据一份后悔药

演示现场最怕的不是代码 bug,而是 Neo4j 数据被搞坏。上场前把图谱数据导出一份备份,这是血泪经验换来的习惯:

bin/neo4j-admin dump --database=neo4j --to=backup.dump

如果现场数据异常,花一两分钟恢复即可,不至于对着 Loading 界面干等。我当年有一次就是连接串没改对,演示现场面对空白页面站了两分钟,从那以后任何演示都先跑 smoke、再 dump、然后才进场。数据规模、查询耗时、索引命中这些数据截图提前放进论文,答辩时按图说话,不用临时算。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询