简介这是一套面向Python初学者与知识图谱入门者的实战项目资源基于Flask框架构建三国演义人物关系可视化及智能问答系统解决古典文学数据结构化表达、关系挖掘与交互式查询等典型NLPWeb应用问题。资源包共364个文件含12个核心Python脚本实现图谱构建、Flask后端路由与问答逻辑、4个HTML前端页面、11个CSS与8个JS文件基于Bootstrap、DataTables及Nifty UI组件实现响应式关系图谱渲染与表格展示以及306张人物关系截图与结构化CSV/JSON数据集整体压缩包仅8.45MB轻量易部署。目前已有217人学习下载配套提供完整部署文档、环境配置说明及清晰目录结构所有代码经实测可在Python 3.7环境下一键运行无需修改主逻辑替换数据即可迁移至其他名著或领域知识图谱项目是理解知识图谱构建、Flask前后端交互与可视化集成的优质教学范例。1. 项目缘起从“三国演义”到“知识图谱”的实践之路最近在整理个人项目库时翻出了一个几年前做的老项目一个基于Flask和知识图谱的《三国演义》人物关系可视化及问答系统。当时做这个项目一方面是出于对三国历史的兴趣想用一种更直观的方式理清那些错综复杂的人物关系另一方面也是想找一个具体的场景把当时刚火起来的“知识图谱”和“问答系统”技术落地实践一下。没想到这个项目后来成了我面试、分享和教学中的一个经典案例因为它麻雀虽小五脏俱全涵盖了从前端可视化、后端API、到知识抽取、存储和智能问答的完整链路。这个项目的核心目标很简单给你一本《三国演义》的文本系统能自动抽取出里面的人物、地点、事件以及他们之间的关系构建成一个结构化的知识网络。然后你可以通过一个网页像看地图一样浏览这个人物关系网络或者直接输入自然语言问题比如“刘备和诸葛亮是什么关系”、“赤壁之战有哪些人物参与”系统能给出准确的答案。听起来是不是有点像给小说装了个“大脑”这正是知识图谱的魅力所在。今天我就把这个项目的完整实现思路、技术细节、踩过的坑以及部署经验毫无保留地分享出来。无论你是想学习Flask全栈开发、入门知识图谱还是对NLP和信息抽取感兴趣这个项目都能给你提供一个非常清晰的实战路径。我们用的技术栈很经典Python作为主力语言Flask搭建轻量级Web框架Neo4j作为图数据库存储知识图谱再配合一些NLP库进行文本处理。下面我们就一层层拆解这个系统是怎么搭建起来的。2. 核心架构设计如何让小说“活”起来一个系统首先要解决的是“怎么干”的问题。我们不能一上来就写代码得先想清楚数据怎么来、怎么存、怎么用。对于这个三国人物关系系统我设计了一个典型的三层架构数据层、服务层和应用层。这个架构清晰地将数据处理、业务逻辑和用户交互分离开使得每一层都可以独立开发和优化。2.1 数据流转的全景图整个系统的生命线始于一本《三国演义》的TXT文本。我们的目标是让机器读懂它并提炼出有价值的知识。这个过程可以分解为以下几个核心步骤原始文本输入系统读入《三国演义》的纯文本文件。这是最原始、非结构化的数据源。知识抽取这是最核心、也最具挑战性的一步。我们需要通过自然语言处理技术从文本中识别出实体如“刘备”、“诸葛亮”、“赤壁”、属性如“字玄德”、“号卧龙”和关系如“刘备-三顾茅庐-诸葛亮”、“曹操-败于-赤壁之战”。这一步通常需要结合规则如人物称谓列表、统计模型如命名实体识别NER和依存句法分析。知识存储抽取出来的结构化知识不能散乱地放着我们需要一个专门为“关系”设计的数据仓库——图数据库。这里我选择了Neo4j因为它用“节点”和“边”来存储实体和关系非常直观查询关系密集型数据的速度也极快。例如“人物”是一个节点类型“事件”是另一个节点类型“参与”就是连接它们的边。服务封装存储好的知识不能直接给前端用我们需要一个中间层来提供标准化的访问接口。这就是Flask后端的工作。它会提供两类主要API一类用于图查询比如“获取与关羽相关的所有人物和关系”返回前端可视化所需的节点和边数据另一类用于问答接收用户的自然语言问题将其解析成对知识图谱的查询即Cypher查询语句执行后返回答案。应用呈现最后通过一个Web界面将一切呈现给用户。前端使用ECharts或类似的可视化库来绘制知识图谱节点可以拖拽点击节点可以展开详细信息。同时提供一个简单的输入框用于问答交互。这个流程构成了一个从非结构化文本到结构化知识再到智能应用的数据闭环。理解了这一点我们再看具体的技术选型就豁然开朗了。2.2 技术栈选型背后的“为什么”当时技术选型时每个选择都不是随意的背后都有具体的考量Python这是自然语言处理和数据分析领域的“普通话”。有极其丰富的库支持如Jieba, HanLP, LTP用于中文处理SpaCy, NLTK的生态也有借鉴意义社区活跃快速原型开发能力极强。对于这样一个涉及大量文本处理的项目Python是不二之选。Flask为什么是Flask而不是Django这个项目后端API的逻辑相对单纯主要是接收请求、查询数据库、返回JSON。Django固然强大但其“大而全”的框架带来了不必要的复杂度和学习成本。Flask的微框架特性让我们可以“按需装配”从零开始搭建一个清晰、轻量的RESTful API服务更专注于核心业务逻辑。这对于理解和掌控Web后端的工作机制非常有帮助。Neo4j这是图数据库领域的代表。存储人物关系用传统的关系型数据库如MySQL不是不行但当你需要查询“刘备的二哥的结拜兄弟的对手是谁”这种多层关系时SQL语句会变得非常复杂且低效。而Neo4j的查询语言Cypher是为图遍历而生的写起来直观执行效率也高。它的社区版完全免费对于学习和中小项目足够了。ECharts前端可视化库的选择很多如D3.js功能强大但学习曲线陡峭。ECharts是百度开源的一个纯JavaScript图表库配置项驱动文档丰富特别是其关系图类型几乎是为知识图谱可视化量身定做的。几行配置就能生成一个可交互的力导向图大大降低了前端开发的门槛。HanLP / Jieba对于中文文本处理分词和实体识别是基础。Jieba分词速度快基础分词效果好而HanLP则提供了更丰富的功能如词性标注、命名实体识别NER、依存句法分析等。在项目中我结合了两者用Jieba进行快速分词和词性标注作为初筛对于复杂句式和人名、地名识别则调用HanLP的NER接口以达到精度和速度的平衡。这个技术栈组合在保证功能实现的前提下最大限度地降低了各环节的学习和开发成本让开发者能把精力集中在“知识图谱构建”和“问答逻辑”这两个核心创新点上。3. 从文本到图谱知识抽取的实战细节有了架构设计我们就要面对第一个硬骨头如何让机器从文言文和白话文夹杂的《三国演义》中准确地找出人物和关系这一步是知识图谱的基石基石不稳后面的大楼再漂亮也是空中楼阁。3.1 实体识别谁是“刘玄德”谁是“诸葛孔明”中文人名识别本身就有难度加上古文中人物常有字、号、官职、尊称等多种称谓比如“刘备”可能被称为“刘玄德”、“玄德”、“刘皇叔”、“先主”。这给实体识别带来了巨大挑战。我采用的是一种“词典匹配规则修正模型辅助”的组合策略。首先构建一个核心人物词典。这是最直接有效的方法。我从公开的三国资料中整理了一份包含主要人物及其常见称谓的列表。例如刘备: 刘玄德, 玄德公, 皇叔, 先主 诸葛亮: 孔明, 卧龙, 武乡侯 曹操: 孟德, 曹阿瞒, 魏武帝, 丞相这个词典作为识别的第一道防线能快速抓取文本中明确出现这些名字的地方。但是词典无法覆盖所有情况比如“云长”需要联系上下文才知道是指“关羽”。这时就需要规则和上下文分析。我制定了一些启发式规则当出现“字XX”结构时如“关羽字云长”则“云长”是“关羽”的别名。当出现“号XX”结构时如“诸葛亮号卧龙”则“卧龙”是“诸葛亮”的别名。通过句法分析识别“刘备谓关张曰”这样的结构可以推断“关张”是“关羽”和“张飞”的合称。对于更复杂或词典未覆盖的情况我引入了预训练模型进行命名实体识别。可以使用HanLP或LTP等工具提供的NER模型。虽然通用模型对古文识别效果会打折扣但我们可以用前面词典识别出的结果作为“种子”对模型进行微调或者将模型结果与词典、规则结果进行投票集成显著提升了召回率和准确率。一个具体的代码片段示例如下展示了结合Jieba分词和简单规则进行初步实体提取的思路import jieba import jieba.posseg as pseg # 加载自定义人物词典 jieba.load_userdict(data/sanguo_characters.txt) def extract_entities_from_sentence(sentence): 从单个句子中初步提取人物实体 words pseg.cut(sentence) # 进行分词和词性标注 entities [] for word, flag in words: # 规则1nr词性人名且长度大于1的很可能是人物 if flag nr and len(word) 1: entities.append((PERSON, word)) # 规则2自定义词典中的词即使被误分词性也加入 # 这里假设我们已经将人物名加入了jieba词典所以大部分能被正确切出 # 更复杂的规则可以在这里添加比如识别“X将军”、“X王”等模式 return entities # 示例 sentence 却说玄德访孔明两次不遇欲再往访之。 print(extract_entities_from_sentence(sentence)) # 输出可能包含(PERSON, 玄德), (PERSON, 孔明)这只是万里长征第一步接下来要把这些零散的实体连接成有意义的关系。3.2 关系抽取如何发现“三顾茅庐”和“赤壁之战”关系抽取比实体识别更难因为关系类型多样表达方式灵活。我主要采用了基于依存句法分析和模式匹配的方法。首先对句子进行依存句法分析。这能帮助我们理解句子中词语之间的语法修饰关系比如主谓宾。例如分析“刘备三顾茅庐请诸葛亮”这句话我们可以得到“刘备”是动作“顾”的主语“茅庐”是宾语“诸葛亮”可能是“请”的宾语。这为我们提取“刘备-拜访-诸葛亮”这样的关系提供了结构基础。然后定义一系列关系模式。针对《三国演义》中常见的关系类型我预先定义了一些模式模板亲属关系[人物A] 之 [亲属词] [人物B]-A (亲属关系) B。如“刘备之弟关羽”。隶属关系[人物A] [投靠/效力于/跟随] [人物B]-A (效力于) B。如“赵云投靠刘备”。敌对关系[人物A] [与] [人物B] [交战/为敌]-A (敌对) B。如“曹操与袁绍交战”。事件参与关系[于] [事件] [中][人物A] [动作]-A (参与) 事件。如“于赤壁之战中周瑜火烧曹军”。这些模式可以写成正则表达式或者利用句法分析的结果树进行匹配。当句子结构匹配某个模式时我们就抽取出对应的实体和关系类型存入知识库。对于更复杂的事件和关系如“赤壁之战”它涉及多个参与方曹操、孙权、刘备、周瑜、诸葛亮、多个子事件蒋干盗书、草船借箭、火烧连营这就需要更复杂的篇章级分析可能涉及事件抽取和共指消解确定“他”、“其”指代谁。在这个项目中我对这类复杂事件做了简化处理主要是识别事件名和核心参与人物建立“人物-参与-事件”的关系更深层的事件逻辑则没有展开这可以作为项目的一个进阶方向。3.3 知识存储Neo4j图数据库建模与Cypher查询抽取出来的三元组头实体关系尾实体需要持久化存储。我选择Neo4j因为它用图的方式思考数据非常契合我们的场景。数据模型设计很简单节点代表实体。我主要创建了两种标签的节点Person人物和Event事件。每个节点有属性如Person节点有name标准名、alias别名列表、description简介等。关系代表实体间的联系。关系是有方向的并且有类型。例如(:Person {name:刘备})-[:FRIEND]-(:Person {name:关羽})(:Person {name:周瑜})-[:PARTICIPATE_IN]-(:Event {name:赤壁之战})。将数据导入Neo4j需要使用Cypher查询语言。下面是一个示例的Python脚本片段展示了如何将一条抽取到的关系存入Neo4jfrom neo4j import GraphDatabase class Neo4jHandler: def __init__(self, uri, user, password): self.driver GraphDatabase.driver(uri, auth(user, password)) def close(self): self.driver.close() def create_person(self, name, aliasNone): with self.driver.session() as session: # 使用MERGE如果节点不存在则创建存在则不做操作避免重复 session.run(MERGE (p:Person {name: $name}) SET p.alias $alias RETURN p, namename, aliasalias) def create_relationship(self, person1_name, relation_type, person2_name): with self.driver.session() as session: # 为两个人物创建关系 session.run(MATCH (a:Person {name: $p1_name}) MATCH (b:Person {name: $p2_name}) MERGE (a)-[r:RELATION {type: $rel_type}]-(b) RETURN r, p1_nameperson1_name, p2_nameperson2_name, rel_typerelation_type) # 使用示例 handler Neo4jHandler(bolt://localhost:7687, neo4j, password) handler.create_person(刘备, [刘玄德, 先主]) handler.create_person(诸葛亮, [孔明, 卧龙]) handler.create_relationship(刘备, EMPLOYS, 诸葛亮)在项目中我们需要编写一个完整的data_pipeline.py脚本它负责读取原始文本调用前面提到的实体和关系抽取函数然后将得到的三元组批量导入Neo4j。这个过程可能需要处理几十万字的文本因此要考虑批处理、去重和错误处理。4. 后端引擎Flask API的设计与实现知识图谱构建好后我们需要一个桥梁让前端能方便地访问和查询这些数据。这就是Flask后端的任务。它的设计核心是提供一组清晰、高效的RESTful API。4.1 API端点规划我设计了两个核心的API端点分别服务于可视化和问答两个功能/api/graph(GET)图谱数据获取接口。这个接口接收前端传来的参数例如一个中心人物的名字或者需要展开的层级深度然后查询Neo4j返回符合要求的节点和边数据格式通常是前端可视化库如ECharts能直接消费的JSON。请求示例GET /api/graph?center刘备depth2功能查询与“刘备”直接相关depth1以及通过一度关系相连的人物depth2的所有节点和关系。后端逻辑将参数转化为Cypher查询。例如对于上述请求Cypher查询可能是MATCH path (p:Person {name:刘备})-[*1..2]-(related) UNWIND nodes(path) as n UNWIND relationships(path) as r RETURN collect(DISTINCT n) as nodes, collect(DISTINCT r) as links然后将查询到的Neo4j节点和关系对象转换成前端需要的格式比如每个节点包含id,name,category等属性每条边包含source,target,name关系类型等属性。/api/qa(POST)智能问答接口。这个接口接收用户输入的自然语言问题通过自然语言理解模块将其解析成Cypher查询执行查询后将结果组织成自然语言答案返回。请求示例POST /api/qa {“question”: “刘备和诸葛亮是什么关系”}功能理解问题意图查询知识图谱并返回答案。后端逻辑这是后端最复杂的部分。它需要包含一个“问答引擎”其工作流程是 a.问题解析对用户问题进行分词、实体识别识别出“刘备”、“诸葛亮”并判断问题类型是问关系、问属性、还是问事件参与方等。这里可以用规则模板匹配也可以训练简单的分类模型。 b.查询生成根据解析出的意图和实体组装Cypher查询语句。例如对于“A和B是什么关系”生成类似MATCH (a:Person {name:‘刘备’})-[r]-(b:Person {name:‘诸葛亮’}) RETURN type(r)的查询。 c.查询执行与答案生成执行Cypher查询获取结果如‘EMPLOYS’。然后将结果填充到预设的答案模板中生成自然语言答案如“刘备和诸葛亮是君臣关系刘备是诸葛亮的君主。”4.2 Flask应用结构一个清晰的Flask应用结构有助于维护。我的项目目录通常如下所示sanguo-kg-qa/ ├── app.py # Flask应用主入口 ├── config.py # 配置文件数据库连接等 ├── requirements.txt # 项目依赖 ├── knowledge_graph/ │ ├── __init__.py │ ├── neo4j_db.py # Neo4j数据库操作封装类 │ └── qa_engine.py # 问答引擎核心逻辑 ├── static/ # 静态文件CSS, JS, 图片 │ └── js/ │ └── main.js # 前端主要逻辑 ├── templates/ # Jinja2模板 │ └── index.html # 主页面 └── data_pipeline/ # 知识抽取和构建管道独立脚本 ├── __init__.py ├── text_processor.py ├── entity_extractor.py └── relation_extractor.py在app.py中核心的路由和视图函数如下from flask import Flask, render_template, request, jsonify from knowledge_graph.neo4j_db import Neo4jHandler from knowledge_graph.qa_engine import QAEngine app Flask(__name__) app.config.from_object(config) # 初始化数据库和问答引擎 db_handler Neo4jHandler(app.config[NEO4J_URI], app.config[NEO4J_USER], app.config[NEO4J_PASSWORD]) qa_engine QAEngine(db_handler) app.route(/) def index(): 渲染主页面 return render_template(index.html) app.route(/api/graph, methods[GET]) def get_graph_data(): 获取图谱数据API center_person request.args.get(center, 曹操) # 默认中心人物 depth int(request.args.get(depth, 1)) # 默认深度为1 try: # 调用数据库处理函数获取节点和边数据 nodes, links db_handler.get_subgraph(center_person, depth) return jsonify({nodes: nodes, links: links, status: success}) except Exception as e: return jsonify({status: error, message: str(e)}), 500 app.route(/api/qa, methods[POST]) def answer_question(): 问答API data request.get_json() question data.get(question, ) if not question: return jsonify({status: error, answer: 问题不能为空}), 400 try: # 调用问答引擎 answer qa_engine.answer(question) return jsonify({status: success, answer: answer}) except Exception as e: return jsonify({status: error, answer: f处理问题时出错{str(e)}}), 500 if __name__ __main__: app.run(debugTrue)这样的结构将Web路由、业务逻辑和数据访问分离开代码清晰易于测试和扩展。5. 前端呈现让知识图谱“动”起来后端提供了数据接口前端的工作就是把这些数据美观、交互式地呈现出来。我们的前端主要是一个单页面应用包含两大区域一个可交互的知识图谱可视化区域和一个问答聊天区域。5.1 基于ECharts的可视化实现ECharts的graph类型非常适合做知识图谱可视化。其核心配置包括series[0].type: ‘graph’series[0].layout: ‘force’使用力导向布局让节点自动寻找合适位置形成清晰的网络结构。series[0].data: 对应节点数组每个节点可以设置name,category,symbolSize大小,itemStyle颜色等。series[0].links: 对应边数组每个边设置source,target,name关系类型,lineStyle等。一个简化的前端JavaScript代码示例如下// 基于获取到的数据初始化ECharts实例 function initGraph(centerPerson 曹操) { // 1. 通过API获取数据 fetch(/api/graph?center${centerPerson}depth2) .then(response response.json()) .then(data { if (data.status success) { // 2. 处理数据适配ECharts格式 const nodes data.nodes.map(node ({ id: node.id, name: node.properties.name, category: node.labels[0], // 如Person symbolSize: calculateSize(node), // 根据关联度计算节点大小 ... })); const links data.links.map(link ({ source: link.startNode, target: link.endNode, name: link.type, ... })); // 3. 配置图表选项 const option { title: { text: 三国人物关系图谱 - 中心: ${centerPerson} }, tooltip: {}, legend: { data: [人物, 事件] }, series: [{ type: graph, layout: force, force: { repulsion: 100, edgeLength: 150 }, roam: true, // 允许拖拽缩放 label: { show: true, position: right }, edgeLabel: { show: true, formatter: {c} }, data: nodes, links: links, categories: [{ name: 人物 }, { name: 事件 }], ... }] }; // 4. 设置选项并渲染 myChart.setOption(option); } }); } // 点击节点事件以该节点为中心重新查询并渲染图谱 myChart.on(click, function(params) { if (params.dataType node) { initGraph(params.data.name); } });通过这样的设置我们就得到了一个可拖拽、缩放、点击交互的动态知识图谱。节点之间的力作用会让关系紧密的人物聚集在一起关系疏远的则被推开非常直观。5.2 问答交互界面设计问答区域相对简单就是一个输入框和一个发送按钮下方是一个消息列表用于展示问答历史。div classqa-panel h3智能问答/h3 div classmessage-list idmessageList !-- 问答历史会动态添加到这里 -- /div div classinput-area input typetext idquestionInput placeholder请输入关于三国人物关系的问题如刘备和诸葛亮是什么关系 button onclickaskQuestion()发送/button /div /divJavaScript处理发送逻辑function askQuestion() { const input document.getElementById(questionInput); const question input.value.trim(); if (!question) return; // 在消息列表中添加用户问题 addMessage(user, question); input.value ; // 发送到后端API fetch(/api/qa, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ question: question }) }) .then(response response.json()) .then(data { const answer data.status success ? data.answer : 抱歉暂时无法回答${data.answer}; // 在消息列表中添加系统回答 addMessage(system, answer); }); }这样一个完整的、前后端分离的Web应用就搭建起来了。用户可以在可视化图上探索也可以通过自然语言直接提问。6. 部署上线让项目从本地跑到云端开发完成只是第一步让项目能在服务器上稳定运行才能算是一个完整的作品。部署一个Flask应用有多种方式这里我分享两种最常用的传统WSGI服务器部署和容器化部署。6.1 使用Gunicorn Nginx 部署传统方式这是Python Web应用非常经典的部署组合。Gunicorn是一个WSGI HTTP服务器负责运行我们的Flask应用Nginx是一个高性能的Web服务器和反向代理负责处理静态文件、负载均衡和将外部请求转发给Gunicorn。部署步骤服务器准备准备一台Linux服务器如Ubuntu安装Python3、pip、虚拟环境工具。上传代码将项目代码上传到服务器例如/var/www/sanguo-kg目录。创建虚拟环境并安装依赖cd /var/www/sanguo-kg python3 -m venv venv source venv/bin/activate pip install -r requirements.txtrequirements.txt文件需要包含所有依赖如Flask,neo4j,jieba,hanlp,gunicorn等。配置Gunicorn创建一个Gunicorn配置文件gunicorn_config.pybind 127.0.0.1:8000 # Gunicorn监听本机8000端口 workers 2 # 工作进程数通常为CPU核心数*21 worker_class sync # 工作模式对于I/O密集型可用gevent使用Systemd管理Gunicorn服务创建服务文件/etc/systemd/system/sanguo-kg.service[Unit] DescriptionGunicorn instance for Sanguo KG QA System Afternetwork.target [Service] Userwww-data # 运行用户 Groupwww-data WorkingDirectory/var/www/sanguo-kg EnvironmentPATH/var/www/sanguo-kg/venv/bin ExecStart/var/www/sanguo-kg/venv/bin/gunicorn --workers 2 --bind 127.0.0.1:8000 app:app --config gunicorn_config.py [Install] WantedBymulti-user.target然后启动服务sudo systemctl start sanguo-kg sudo systemctl enable sanguo-kg # 设置开机自启配置Nginx反向代理编辑Nginx站点配置文件如/etc/nginx/sites-available/sanguo-kgserver { listen 80; server_name your_domain.com; # 你的域名或服务器IP location / { proxy_pass http://127.0.0.1:8000; # 转发给Gunicorn proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 可选让Nginx直接处理静态文件效率更高 location /static { alias /var/www/sanguo-kg/static; } }启用配置并重启Nginxsudo ln -s /etc/nginx/sites-available/sanguo-kg /etc/nginx/sites-enabled/ sudo nginx -t # 测试配置 sudo systemctl restart nginx配置Neo4j确保服务器上的Neo4j数据库已启动并在Flask的config.py中正确配置连接信息注意将localhost改为服务器内网IP或127.0.0.1。至此通过访问你的服务器IP或域名就能看到运行中的三国知识图谱系统了。6.2 使用Docker容器化部署现代方式容器化部署更利于环境隔离和迁移。我们需要编写Dockerfile和docker-compose.yml文件。Dockerfile(用于构建Flask应用镜像):# 使用官方Python轻量级镜像 FROM python:3.9-slim # 设置工作目录 WORKDIR /app # 复制依赖文件并安装 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 复制应用代码 COPY . . # 暴露端口 EXPOSE 5000 # 定义启动命令 CMD [gunicorn, --bind, 0.0.0.0:5000, app:app]docker-compose.yml(编排应用和数据库):version: 3.8 services: neo4j: image: neo4j:4.4-community container_name: sanguo-neo4j ports: - 7474:7474 # Neo4j浏览器端口 - 7687:7687 # Bolt协议端口 environment: - NEO4J_AUTHneo4j/your_strong_password # 设置密码 volumes: - neo4j_data:/data - neo4j_logs:/logs networks: - sanguo-network web: build: . container_name: sanguo-web ports: - 80:5000 # 将宿主机的80端口映射到容器的5000端口 depends_on: - neo4j environment: - NEO4J_URIbolt://neo4j:7687 # 注意这里用服务名‘neo4j’ - NEO4J_USERneo4j - NEO4J_PASSWORDyour_strong_password networks: - sanguo-network volumes: neo4j_data: neo4j_logs: networks: sanguo-network: driver: bridge部署时只需在服务器上安装Docker和Docker Compose将项目文件包含Dockerfile和docker-compose.yml上传然后运行一条命令docker-compose up -dDocker会自动拉取镜像、构建应用、创建网络和卷并启动所有服务。这种方式极大简化了部署流程并且保证了开发、测试、生产环境的一致性。7. 踩坑实录与进阶思考做这个项目的过程中遇到了不少坑也引发了对未来优化的思考。这里分享几个关键点希望能帮你少走弯路。坑1中文分词与人名识别精度最初的版本只用Jieba默认分词结果“诸葛亮孔明”会被切成“诸葛/亮孔/明”完全无法识别。解决方案是必须加载自定义词典并且结合HanLP等更强大的工具进行实体识别。对于古文还可以考虑使用在古文语料上微调过的模型或者利用《三国志》等正史资料构建更全的别名库。坑2Neo4j查询性能当图谱关系达到上万条时一些复杂的多层关系查询可能会变慢。优化方法包括为经常查询的属性如Person.name创建索引CREATE INDEX ON :Person(name)。在查询时使用PROFILE或EXPLAIN查看执行计划优化Cypher语句避免全图扫描。对于固定模式的复杂查询可以考虑使用Neo4j的“存储过程”或“用户自定义函数”进行封装优化。坑3问答引擎的泛化能力基于规则模板的问答引擎对于训练集内的问题模式效果很好但一旦用户换一种问法如“谁三顾茅庐” vs “三顾茅庐的是谁”可能就无法匹配。进阶方向是引入更先进的NLP技术意图识别与槽位填充使用模型如BERT来理解问题意图并抽取关键实体而不是依赖硬编码的规则。语义解析将自然语言问题直接解析成知识图谱查询语言如Cypher的中间表示这是一个更有挑战性但也更通用的方向。检索增强生成结合当前热门的RAG技术。将知识图谱中的三元组转化为文本片段与问题一起输入大语言模型让LLM来生成答案。这样可以处理更开放、更复杂的问题。坑4前端大量节点渲染卡顿当一次性渲染数百个节点和边时ECharts力导向图可能会出现卡顿。优化建议默认只加载1-2度关系提供“展开更多”的按钮进行懒加载。对节点进行聚类例如将同一阵营的人物先聚合为一个超级节点点击后再展开。考虑使用WebGL渲染的图形库如G6、Three.js来应对更大规模的数据可视化。这个项目虽然以“三国”为背景但其技术框架是通用的。你可以很容易地将数据源换成《红楼梦》、《权力的游戏》甚至公司内部的组织架构文档和项目报告构建出不同领域的知识图谱和问答系统。从“项目驱动学习”的角度看它是一条贯穿了数据获取、数据处理、数据存储、后端开发、前端交互和算法应用的完整学习路径价值远不止于一个简单的Demo。本文还有配套的精品资源点击获取
