资讯动态

中医药知识图谱问答系统:Django与Neo4j实战指南

发布时间:2026/10/1 2:34:56 来源:尧图企业网站定制
简介这份资源是面向高校计算机相关专业学生的毕业设计完整项目包主题为基于Django与Neo4j的中医药知识图谱与智能问答平台。项目难度适中适合用作毕业设计、期末大作业或课程设计评审得分达到98分内容经助教老师审定源码已在本地编译验证可运行下载后可直接部署调试。压缩包共约2000个文件整体39.04MB以1059个Python源码文件为核心辅以739个编译缓存文件、66个JavaScript与34个CSS前端资源另有HTML模板、JSON配置、SQLite数据库及说明文档等覆盖后端逻辑、图谱构建、问答交互与前端展示各环节。目前已有138人学习关注。读者可获得一套结构完整、可运行的中医药知识图谱问答系统源码并配套文档说明便于理解Django与Neo4j的整合方式、知识图谱数据组织与智能问答实现思路为毕业设计选题、答辩准备与二次开发提供可靠参考。1. 中医药知识图谱问答系统从 Django 到 Neo4j 的完整落地路径中医药领域的知识有个特点实体类型多、关系复杂、术语还带大量别名。光靠 MySQL 存几张表做多跳推理查询时 SQL 会写得让人怀疑人生。这个毕业设计资源包的核心思路是用 Neo4j 存图结构、Django 做 Web 层、Python 写数据清洗和问答逻辑把「中药材—方剂—症状—功效」这些实体和关系组织成一张可查询的知识网络。它适合正在做知识图谱方向毕设的同学也适合想快速搭一个图谱问答 Demo 的后端开发者。资源包里包含源码和文档说明拿到手能直接跑起来看效果再按自己的数据替换。下面我从环境搭建、数据建模、问答实现到避坑把这条链路拆开讲清楚。2. 环境搭建与项目结构Django 和 Neo4j 怎么接上2.1 为什么选 Django Neo4j 而不是纯 MySQL中医药知识图谱的查询模式决定了存储选型。如果只是查「当归有哪些功效」一张关联表就够了。但实际问答场景里经常出现「含有当归且用于血虚证的方剂有哪些」这类需要两跳甚至三跳的查询。用 SQL 写就是多次 JOIN表一多性能断崖式下跌而且每加一种关系就要改表结构。Neo4j 的图模型天然适合这种场景节点表示实体边表示关系查询用 Cypher 语句描述路径模式加新关系类型不需要改 schema。Django 在这里的角色是 Web 框架负责用户请求路由、模板渲染和调用 Python 层的图谱查询逻辑。两者通过 Python 的 neo4j 官方驱动连接不经过 Django ORM。常见做法是 Django 只管业务逻辑和页面Neo4j 只管图数据中间用一个graph_service.py做封装。这样职责清晰调试时也容易定位问题出在哪一层。2.2 Neo4j 安装与 Django 项目初始化Neo4j 社区版就够用毕设场景不需要企业版。安装方式根据系统不同有差异Windows 下下载安装包解压后运行bin\neo4j.bat consolemacOS 用 Homebrew 装更方便Linux 下解压后配置conf/neo4j.conf再启动。# macOS 下通过 Homebrew 安装 Neo4j 社区版 brew install neo4j # 启动服务 neo4j start # 首次启动后需要设置初始密码默认用户名 neo4j # 浏览器打开 http://localhost:7474 进入 Neo4j Browser 修改密码启动后在 Neo4j Browser 里执行一条测试语句确认连通// 创建一个测试节点并查询 CREATE (n:TestNode {name: 测试}) RETURN nDjango 侧的项目初始化# 创建虚拟环境 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 安装依赖 pip install django neo4j jieba # 创建 Django 项目 django-admin startproject tcm_kg cd tcm_kg # 创建应用 python manage.py startapp qa依赖里neo4j是官方 Python 驱动jieba用于中文分词后面问答模块会用到。Django 版本建议 3.2 以上Python 3.8 以上太老的版本在异步和模板语法上会有兼容问题。2.3 Django 连接 Neo4j 的配置方式Django 的settings.py里加 Neo4j 连接参数# settings.py NEO4J_CONFIG { uri: bolt://localhost:7687, user: neo4j, password: 你的密码, }然后在qa应用下建一个graph_service.py# qa/graph_service.py from neo4j import GraphDatabase from django.conf import settings class GraphService: def __init__(self): config settings.NEO4J_CONFIG self.driver GraphDatabase.driver( config[uri], auth(config[user], config[password]) ) def close(self): self.driver.close() def run_query(self, cypher, paramsNone): 执行 Cypher 查询并返回结果列表 with self.driver.session() as session: result session.run(cypher, params or {}) return [record.data() for record in result] # 单例避免每次请求都创建连接 graph_service GraphService()这里用单例模式持有 driver因为 Neo4j 的 driver 本身是线程安全的连接池不需要每次请求都新建。run_query统一封装了 session 的创建和结果转换返回的是字典列表方便 Django 视图层直接序列化成 JSON。注意Neo4j 的 bolt 协议默认端口是 7687HTTP 端口是 7474。Django 连接用的是 bolt不是浏览器那个地址。3. 中医药知识图谱的数据建模与导入3.1 实体类型与关系设计中医药知识图谱的核心实体类型包括中药材、方剂、症状、功效、证型、脏腑。关系类型包括方剂-包含-中药材、中药材-具有功效-功效、方剂-主治-症状、症状-属于-证型等。这个资源包里的数据模型大致是这样的结构实体类型标签名关键属性中药材Herbname, alias, nature, flavor方剂Formulaname, source, usage症状Symptomname, description功效Effectname证型Syndromename关系设计上Formula-[:CONTAINS]-Herb是最核心的一条边问答系统里大量查询都围绕它展开。Herb-[:HAS_EFFECT]-Effect和Formula-[:TREATS]-Symptom是另外两条高频路径。建模时有个容易忽略的点中药材的别名。比如「当归」又叫「干归」「人参」又叫「棒槌」。如果不在图谱里处理别名用户搜「干归」就查不到任何结果。常见做法是给 Herb 节点加一个alias数组属性查询时用WHERE 干归 IN h.alias OR h.name 干归来匹配。3.2 用 Python 脚本批量导入 CSV 数据假设你手头有整理好的 CSV 文件格式如下# herbs.csv name,alias,nature,flavor 当归,干归,秦归,温,甘,辛 人参,棒槌,神草,平,甘,微苦 # formulas.csv name,source,usage 四物汤,太平惠民和剂局方,水煎服 补中益气汤,脾胃论,水煎服 # formula_herb.csv formula_name,herb_name 四物汤,当归 四物汤,川芎 补中益气汤,人参导入脚本# scripts/import_data.py import csv from neo4j import GraphDatabase driver GraphDatabase.driver(bolt://localhost:7687, auth(neo4j, 你的密码)) def import_herbs(tx, csv_path): with open(csv_path, r, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: tx.run( MERGE (h:Herb {name: $name}) SET h.alias split($alias, ,), h.nature $nature, h.flavor $flavor, namerow[name], aliasrow[alias], naturerow[nature], flavorrow[flavor] ) def import_formula_relations(tx, csv_path): with open(csv_path, r, encodingutf-8) as f: reader csv.DictReader(f) for row in reader: tx.run( MATCH (f:Formula {name: $formula}) MATCH (h:Herb {name: $herb}) MERGE (f)-[:CONTAINS]-(h), formularow[formula_name], herbrow[herb_name] ) with driver.session() as session: session.execute_write(import_herbs, data/herbs.csv) session.execute_write(import_formula_relations, data/formula_herb.csv) driver.close() print(导入完成)脚本用MERGE而不是CREATE这样重复执行不会产生重复节点。execute_write是 neo4j 驱动 5.x 的写法自动处理事务提交和重试。如果你的驱动版本是 4.x需要改成session.write_transaction。导入完成后在 Neo4j Browser 里验证// 查看所有实体数量 MATCH (h:Herb) RETURN count(h) AS herb_count MATCH (f:Formula) RETURN count(f) AS formula_count // 查看一条完整路径 MATCH (f:Formula)-[:CONTAINS]-(h:Herb) RETURN f.name, h.name LIMIT 103.3 数据清洗中的几个实际问题原始中医药数据往往来自不同来源格式不统一。常见问题包括同一味药在不同文件里写法不同「炙甘草」和「甘草炙」、方剂名称带括号注释、CSV 编码不是 UTF-8。处理方式是在导入前加一层清洗脚本用正则统一格式。比如去掉括号注释、把全角逗号转半角、统一「炙」字的位置。这一步看起来琐碎但不做的话后面查询会大量漏结果。另一个实际问题是 Neo4j 社区版的内存配置。默认配置下导入几千条数据没问题但中医药图谱如果做到几万节点需要改conf/neo4j.conf里的dbms.memory.heap.max_size和dbms.memory.pagecache.size。一般 4GB 堆内存加 2GB 页缓存能撑住十万级节点。4. 智能问答模块从自然语言到 Cypher 查询4.1 问句解析的基本流程问答模块要做的事情是用户输入「当归有什么功效」系统返回「补血活血、调经止痛」。中间需要把自然语言转成 Cypher 查询。流程分三步分词和实体识别、意图判断、模板匹配生成 Cypher。这个资源包里用的是基于规则的方式没有上深度学习模型好处是可控、可解释、不需要训练数据。分词用 jieba实体识别靠预先构建的词典匹配。意图判断用关键词规则出现「功效」「作用」就归为查功效出现「方剂」「汤」就归为查方剂出现「症状」「治」就归为查主治。4.2 基于规则的问句到 Cypher 转换# qa/qa_engine.py import jieba from qa.graph_service import graph_service # 加载自定义词典确保中医药术语不被切碎 jieba.load_userdict(data/tcm_dict.txt) # 意图关键词映射 INTENT_RULES { effect: [功效, 作用, 有什么效果], formula: [方剂, 方, 汤, 包含哪些药], symptom: [治, 主治, 症状, 用于], } def detect_intent(question): for intent, keywords in INTENT_RULES.items(): if any(kw in question for kw in keywords): return intent return None def extract_entity(question): 从问句中提取中医药实体名称 words jieba.lcut(question) # 从图谱中查询匹配的实体 for word in words: if len(word) 2: continue result graph_service.run_query( MATCH (h:Herb) WHERE h.name $name OR $name IN h.alias RETURN h.name AS name, {name: word} ) if result: return result[0][name] return None def answer(question): intent detect_intent(question) entity extract_entity(question) if not entity: return 未识别到相关的中医药实体请换个说法试试。 if not intent: return 暂时无法理解这个问题可以试试问「XX的功效」或「XX方剂包含哪些药」。 if intent effect: cypher MATCH (h:Herb {name: $name})-[:HAS_EFFECT]-(e:Effect) RETURN e.name AS effect elif intent formula: cypher MATCH (f:Formula)-[:CONTAINS]-(h:Herb {name: $name}) RETURN f.name AS formula elif intent symptom: cypher MATCH (f:Formula)-[:TREATS]-(s:Symptom) WHERE EXISTS { (f)-[:CONTAINS]-(:Herb {name: $name}) } RETURN DISTINCT s.name AS symptom else: return 暂不支持该类型的问题。 results graph_service.run_query(cypher, {name: entity}) if not results: return f图谱中暂时没有关于「{entity}」的相关记录。 values [list(r.values())[0] for r in results] return f{entity}的{功效 if intent effect else 相关方剂 if intent formula else 主治症状}{、.join(values)}这段代码的核心逻辑是先判断用户问的是什么类型的问题再提取问题中的实体名称最后根据意图选择对应的 Cypher 模板。extract_entity里遍历分词结果去图谱里匹配匹配到就返回标准名称这样即使用户输入的是别名也能正确查询。symptom意图的 Cypher 用了一个子查询EXISTS { ... }先找到包含目标药材的方剂再查这些方剂主治的症状。这是两跳查询用 SQL 写需要嵌套子查询Cypher 里表达更直观。4.3 Django 视图与前端交互视图层把问答引擎包成一个 POST 接口# qa/views.py import json from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from qa.qa_engine import answer csrf_exempt def ask(request): if request.method ! POST: return JsonResponse({error: 仅支持 POST}, status405) data json.loads(request.body) question data.get(question, ).strip() if not question: return JsonResponse({error: 问题不能为空}, status400) reply answer(question) return JsonResponse({question: question, answer: reply})前端页面用一个简单的表单加 fetch 请求就能跑通。资源包里应该带了模板文件放到templates/目录下在settings.py里配置好TEMPLATES的DIRS路径即可。提示Django 的 CSRF 中间件对 POST 请求有校验开发阶段可以用csrf_exempt跳过上线前记得换成正常的 CSRF token 方式。5. 避坑与常见问题排查5.1 Neo4j 连接报错「Unable to connect」现象Django 启动后调用图谱查询接口报neo4j.exceptions.ServiceUnavailable。原因Neo4j 服务没启动或者 bolt 端口被防火墙拦了。另一个常见原因是 Neo4j 4.x 之后默认只监听 localhost如果 Django 跑在容器里就连不上。解决先确认neo4j status显示 running再用telnet localhost 7687测试端口通不通。容器场景下改neo4j.conf里的dbms.default_listen_address0.0.0.0然后重启服务。5.2 Cypher 查询返回空结果但数据明明存在现象在 Neo4j Browser 里能查到的数据Django 里查出来是空列表。原因最常见的是参数传递时类型不匹配。比如节点属性存的是字符串传参传了整数。另一个原因是中文编码问题CSV 导入时用了 GBK 编码存进去的数据带乱码查询时用 UTF-8 匹配不上。解决在 Neo4j Browser 里用完全相同的 Cypher 和参数跑一遍确认数据本身没问题。然后检查 Python 侧传参的类型用type()打印确认。编码问题在导入脚本里统一指定encodingutf-8。5.3 jieba 分词把中医药术语切碎现象用户输入「四物汤」jieba 切成「四物」「汤」导致实体识别失败。原因jieba 默认词典不含中医药专业术语按通用语料切分。解决建一个自定义词典文件每行一个术语格式为「词语 词频 词性」词频给高一点确保不被切开。然后在代码里用jieba.load_userdict()加载。资源包里如果有tcm_dict.txt就直接用没有的话从图谱的实体名称导出生成一份。5.4 Django 模板里静态文件加载失败现象页面样式丢失控制台报 404。原因settings.py里STATIC_URL配了但STATICFILES_DIRS没配或者模板里{% load static %}没写。解决确认INSTALLED_APPS里有django.contrib.staticfilesSTATICFILES_DIRS指向项目的 static 目录模板开头加{% load static %}引用时用{% static css/style.css %}这种写法。5.5 Neo4j 导入大量数据时内存溢出现象导入脚本跑到一半报OutOfMemoryErrorNeo4j 服务崩溃。原因默认堆内存太小大批量写入时事务日志和索引占满内存。解决改conf/neo4j.conf里的dbms.memory.heap.max_size4G和dbms.memory.pagecache.size2G。导入脚本里每 1000 条提交一次事务不要把所有数据放在一个事务里。用session.execute_write分批调用。6. 进阶技巧用 APOC 插件做路径查询和问答增强基础版的问答只能处理一跳查询比如「当归的功效」。但用户经常会问「当归和川芎能一起治什么」这需要先找到两味药的共同方剂再查方剂主治的症状是三跳查询。手写 Cypher 不是不行但路径模式一复杂就容易写错。APOC 插件提供了路径查询和图算法的封装能省不少事。安装 APOC 就是把对应的 jar 包放到 Neo4j 的plugins目录下然后在neo4j.conf里加一行dbms.security.procedures.unrestrictedapoc.*重启服务。版本要和 Neo4j 版本对应4.4 的 Neo4j 就下 4.4 的 APOC。装好之后查两味药的共同方剂和主治可以这样写// 查找两味药材的共同方剂及其主治症状 MATCH (h1:Herb {name: 当归})-[:CONTAINS]-(f:Formula)-[:CONTAINS]-(h2:Herb {name: 川芎}) MATCH (f)-[:TREATS]-(s:Symptom) RETURN f.name AS 方剂, collect(DISTINCT s.name) AS 主治症状这个查询的模式是当归和川芎同时出现在某个方剂中然后查这个方剂主治什么。collect(DISTINCT s.name)把同一方剂的多个症状聚合成列表返回。APOC 的apoc.path.expandConfig更适合做不确定跳数的路径查询// 从当归出发查找 2 到 3 跳内的所有关联实体 MATCH (start:Herb {name: 当归}) CALL apoc.path.expandConfig(start, { minLevel: 2, maxLevel: 3, relationshipFilter: CONTAINS|HAS_EFFECT|TREATS }) YIELD path RETURN path LIMIT 20relationshipFilter用竖线分隔多种关系类型minLevel和maxLevel控制跳数范围。这个在探索性查询里很好用比如用户问「当归相关的所有信息」你可以用这个把两跳内的实体都拉出来展示。问答引擎里集成 APOC 的方式是加一个「关联查询」意图。当用户问「XX和XX有什么关系」时走 APOC 路径查询把返回的路径转成自然语言描述。路径转文本的逻辑大概是遍历路径上的节点和关系按「A 通过 关系 连接到 B」的模板拼接。还有一个实用技巧是用 Neo4j 的全文索引做模糊匹配。中医药名称经常有错别字或者简写精确匹配容易漏。建索引的语句CREATE FULLTEXT INDEX herbNameIndex FOR (h:Herb) ON EACH [h.name, h.alias]查询时用CALL db.index.fulltext.queryNodes(herbNameIndex, 当归)就能做模糊匹配对用户输入容错更好。我自己的习惯是每次改完 Cypher 查询先在 Neo4j Browser 里用EXPLAIN看执行计划确认没有全表扫描再放到代码里。中医药图谱节点不多的时候感觉不出来数据量上去之后一个没走索引的查询能把响应时间从毫秒级拉到秒级。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑