本文最后更新于 2025年12月3日。
太棒了!既然您已经安装好了 Neo4j,我们现在就通过一个最小可行性产品 (MVP),把圣经中的“亚伯拉罕家族迁移”作为案例,完整实现从数据录入到网页可视化的全过程。
我们将采用 Python (Flask) 做后端 API,Vis.js 做前端可视化(这是最适合展示复杂关系图的库)。
📂 项目文件结构
请在电脑上创建一个文件夹 BibleGraphMVP,结构如下:
Plaintext
BibleGraphMVP/
├── app.py # Python 后端代码
├── templates/
│ └── index.html # 前端网页代码
└── data/ # 存放 CSV 数据文件
├── people.csv
├── places.csv
├── events.csv
└── relationships.csv
第一步:准备数据 (CSV)
为了演示,我们手动创建一小部分数据。请在 data 文件夹下创建以下 4 个 CSV 文件。
1. people.csv (人物)
Code snippet
id,name,name_cn
p1,Abraham,亚伯拉罕
p2,Sarah,撒拉
p3,Isaac,以撒
2. places.csv (地点 – 含坐标)
Code snippet
id,name,lat,long
l1,Ur,30.96,46.10
l2,Haran,36.86,39.02
l3,Canaan,31.50,34.80
3. events.csv (事件 – 含时间)
Code snippet
id,title,year
e1,Call of Abram,-2091
e2,Birth of Isaac,-2066
- relationships.csv (关系 – 核心)
这里定义了人与人、人与事件、事件与地点的关系。
Code snippet
source,target,type
p1,p2,SPOUSE # 亚伯拉罕 配偶 撒拉
p1,p3,FATHER_OF # 亚伯拉罕 父亲 以撒
p2,p3,MOTHER_OF # 撒拉 母亲 以撒
p1,e1,PARTICIPATED_IN # 亚伯拉罕 参与 呼召事件
e1,l1,STARTED_AT # 呼召事件 开始于 吾珥
e1,l2,MOVED_TO # 呼召事件 迁移到 哈兰
p3,e2,SUBJECT_OF # 以撒 是 出生事件的主角
e2,l3,HAPPENED_AT # 出生事件 发生于 迦南
⚠️ 重要操作: 将这 4 个 CSV 文件复制到您 Neo4j 安装目录下的
import文件夹中(例如C:\Users\...\.Neo4jDesktop\relate-data\dbmss\...\import)。这是 Neo4j 默认读取文件的位置。
第二步:数据导入 (Cypher)
打开 Neo4j Browser (http://localhost:7474),依次运行以下 Cypher 语句将数据加载进数据库:
Cypher
// 1. 加载人物
LOAD CSV WITH HEADERS FROM 'file:///people.csv' AS row
MERGE (p:Person {id: row.id})
SET p.name = row.name, p.name_cn = row.name_cn;
// 2. 加载地点
LOAD CSV WITH HEADERS FROM 'file:///places.csv' AS row
MERGE (l:Location {id: row.id})
SET l.name = row.name, l.lat = toFloat(row.lat), l.long = toFloat(row.long);
// 3. 加载事件
LOAD CSV WITH HEADERS FROM 'file:///events.csv' AS row
MERGE (e:Event {id: row.id})
SET e.title = row.title, e.year = toInteger(row.year);
// 4. 加载关系 (动态匹配)
LOAD CSV WITH HEADERS FROM 'file:///relationships.csv' AS row
MATCH (s {id: row.source})
MATCH (t {id: row.target})
CALL apoc.create.relationship(s, row.type, {}, t) YIELD rel
RETURN count(rel);
(注:如果第4步报错提示找不到 apoc,可以用标准的 MERGE 语句代替,但为了MVP简单,我们假设您能分别处理,或者手动写几条 MERGE 语句。最简单的标准写法如下:)
替代第4步(标准写法,无插件):
Cypher
LOAD CSV WITH HEADERS FROM 'file:///relationships.csv' AS row
MATCH (s {id: row.source})
MATCH (t {id: row.target})
FOREACH (ignoreMe IN CASE WHEN row.type = 'SPOUSE' THEN [1] ELSE [] END | MERGE (s)-[:SPOUSE]->(t))
FOREACH (ignoreMe IN CASE WHEN row.type = 'FATHER_OF' THEN [1] ELSE [] END | MERGE (s)-[:FATHER_OF]->(t))
FOREACH (ignoreMe IN CASE WHEN row.type = 'MOTHER_OF' THEN [1] ELSE [] END | MERGE (s)-[:MOTHER_OF]->(t))
FOREACH (ignoreMe IN CASE WHEN row.type = 'PARTICIPATED_IN' THEN [1] ELSE [] END | MERGE (s)-[:PARTICIPATED_IN]->(t))
FOREACH (ignoreMe IN CASE WHEN row.type = 'STARTED_AT' THEN [1] ELSE [] END | MERGE (s)-[:STARTED_AT]->(t))
FOREACH (ignoreMe IN CASE WHEN row.type = 'MOVED_TO' THEN [1] ELSE [] END | MERGE (s)-[:MOVED_TO]->(t))
FOREACH (ignoreMe IN CASE WHEN row.type = 'SUBJECT_OF' THEN [1] ELSE [] END | MERGE (s)-[:SUBJECT_OF]->(t))
FOREACH (ignoreMe IN CASE WHEN row.type = 'HAPPENED_AT' THEN [1] ELSE [] END | MERGE (s)-[:HAPPENED_AT]->(t));
第三步:编写后端 (Python Flask)
安装依赖:
Bash
pip install flask neo4j
创建 app.py:
Python
from flask import Flask, render_template, jsonify
from neo4j import GraphDatabase
app = Flask(__name__)
# 配置 Neo4j 连接 (修改为你的密码)
URI = "bolt://localhost:7687"
AUTH = ("neo4j", "your_password")
driver = GraphDatabase.driver(URI, auth=AUTH)
def get_graph_data():
query = """
MATCH (n)-[r]->(m)
RETURN n, r, m
LIMIT 100
"""
nodes = {}
edges = []
with driver.session() as session:
result = session.run(query)
for record in result:
# 处理源节点
n_node = record["n"]
n_id = n_node.get("id") or n_node.element_id
# 根据标签设置显示名称
label = list(n_node.labels)[0]
if label == "Person": label_prop = n_node.get("name_cn")
elif label == "Event": label_prop = f"{n_node.get('title')} ({n_node.get('year')})"
else: label_prop = n_node.get("name")
# 使用简单的颜色区分节点类型
color = "#97C2FC" # 默认蓝
if label == "Person": color = "#FFD700" # 金色
if label == "Event": color = "#FF6347" # 红色
if label == "Location": color = "#90EE90" # 绿色
nodes[n_id] = {
"id": n_id,
"label": label_prop,
"group": label,
"color": color,
"title": str(dict(n_node)) # 鼠标悬停显示详细信息
}
# 处理目标节点 (同上逻辑,简化处理)
m_node = record["m"]
m_id = m_node.get("id") or m_node.element_id
m_label = list(m_node.labels)[0]
if m_label == "Person": m_show = m_node.get("name_cn")
elif m_label == "Event": m_show = f"{m_node.get('title')} ({m_node.get('year')})"
else: m_show = m_node.get("name")
m_color = "#97C2FC"
if m_label == "Person": m_color = "#FFD700"
if m_label == "Event": m_color = "#FF6347"
if m_label == "Location": m_color = "#90EE90"
nodes[m_id] = {
"id": m_id,
"label": m_show,
"group": m_label,
"color": m_color,
"title": str(dict(m_node))
}
# 处理关系
edges.append({
"from": n_id,
"to": m_id,
"label": record["r"].type,
"arrows": "to"
})
return {"nodes": list(nodes.values()), "edges": edges}
@app.route('/')
def index():
return render_template('index.html')
@app.route('/api/data')
def data():
graph_data = get_graph_data()
return jsonify(graph_data)
if __name__ == '__main__':
app.run(debug=True, port=5000)
第四步:编写前端 (HTML + Vis.js)
创建 templates/index.html:
HTML
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>圣经关系可视化 MVP</title>
<script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
<style>
body { font-family: sans-serif; background-color: #f0f0f0; }
#network {
width: 100%;
height: 800px;
border: 1px solid lightgray;
background-color: white;
}
.legend { margin: 10px; }
span.dot { height: 10px; width: 10px; border-radius: 50%; display: inline-block; }
</style>
</head>
<body>
<h1>圣经人物、事件、地点关系图谱</h1>
<div class="legend">
<span class="dot" style="background-color: #FFD700;"></span> 人物
<span class="dot" style="background-color: #FF6347;"></span> 事件 (含时间)
<span class="dot" style="background-color: #90EE90;"></span> 地点
</div>
<div id="network">加载中...</div>
<script type="text/javascript">
// 从后端 API 获取数据
fetch('/api/data')
.then(response => response.json())
.then(data => {
var container = document.getElementById('network');
var options = {
nodes: {
shape: 'dot',
size: 20,
font: { size: 14 }
},
edges: {
font: { size: 12, align: 'middle' },
color: 'gray',
smooth: { type: 'continuous' }
},
physics: {
stabilization: false,
barnesHut: { gravitationalConstant: -8000 }
}
};
// 初始化网络图
var network = new vis.Network(container, data, options);
})
.catch(error => console.error('Error:', error));
</script>
</body>
</html>
第五步:运行项目 🚀
-
启动后端: 在终端中运行
python app.py。 -
访问网页: 打开浏览器访问
http://127.0.0.1:5000。
🎉 您将看到什么?
您会看到一个交互式的网络图:
-
黄色节点是亚伯拉罕、撒拉和以撒。
-
红色节点是“呼召”和“出生”事件,上面标注了年份(如 -2091)。
-
绿色节点是吾珥、哈兰和迦南。
-
连线清晰地展示了:“亚伯拉罕” -> 参与了 -> “呼召事件” -> 发生于 -> “吾珥”。
这就完成了一个最基础的 MVP!您可以随时往 CSV 里添加更多数据,重新运行 Cypher 导入语句,刷新网页即可看到扩充后的图谱。
验证关系是否创建成功
MATCH ()-[r]->() RETURN count(r);