Python使用py2neo操作图数据库neo4j的方法详解


Posted in Python onJanuary 13, 2020

本文实例讲述了Python使用py2neo操作图数据库neo4j的方法。分享给大家供大家参考,具体如下:

1、概念

图:数据结构中的图由节点和其之间的边组成。节点表示一个实体,边表示实体之间的联系。

图数据库:以图的结构存储管理数据的数据库。其中一些数据库将原生的图结构经过优化后直接存储,即原生图存储。还有一些图数据库将图数据序列化后保存到关系型或其他数据库中。

之所以使用图数据库存储数据是因为它在处理实体之间存在复杂关系的数据具有很大的优势。使用传统的关系型数据库在处理数据之间的关系时其实很不方便。例如查询选修一个课程的同学时需要join两个表,查询选修某个课程的同学还选修什么课程,这就需要两次join操作,当涉及到十分复杂的关系以及庞大的数据量时,关系型数据库效率十分低下。而通过图存储,可以通过节点之间的边十分便捷地查询到结果。

图模型:

Python使用py2neo操作图数据库neo4j的方法详解

节点(Node)是主要的数据元素,表示一个实体。

属性(Properties)用于描述实体的特征,以键值对的方式表示,其中键是字符串,可以对属性创建索引和约束。

关系(Relationships)表示实体之间的联系,关系具有方向,实体之间可以有多个关系,关系也可以具有属性

标签(Label)用于将实体分类,一个实体可以具有多个标签,对标签进行索引可以加速查找

2、Neo4j

Neo4j是目前最流行的图数据库,它采用原生图存储,在windows中下载安装访问如下地址https://neo4j.com/download/community-edition/。在Linux下通过如下命令下载解压

curl -O http://dist.neo4j.org/neo4j-community-3.4.5-unix.tar.gz
tar -axvf neo4j-community-3.4.5-unix.tar.gz

修改配置文件conf/neo4j.conf

# 修改第22行load csv时l路径,在前面加个#,可从任意路径读取文件
#dbms.directories.import=import
# 修改35行和36行,设置JVM初始堆内存和JVM最大堆内存
# 生产环境给的JVM最大堆内存越大越好,但是要小于机器的物理内存
dbms.memory.heap.initial_size=5g
dbms.memory.heap.max_size=10g
# 修改46行,可以认为这个是缓存,如果机器配置高,这个越大越好
dbms.memory.pagecache.size=10g
# 修改54行,去掉改行的#,可以远程通过ip访问neo4j数据库
dbms.connectors.default_listen_address=0.0.0.0
# 默认 bolt端口是7687,http端口是7474,https关口是7473,不修改下面3项也可以
# 修改71行,去掉#,设置http端口为7687,端口可以自定义,只要不和其他端口冲突就行
#dbms.connector.bolt.listen_address=:7687
# 修改75行,去掉#,设置http端口为7474,端口可以自定义,只要不和其他端口冲突就行
dbms.connector.http.listen_address=:7474
# 修改79行,去掉#,设置http端口为7473,端口可以自定义,只要不和其他端口冲突就行
dbms.connector.https.listen_address=:7473
# 去掉#,允许从远程url来load csv
dbms.security.allow_csv_import_from_file_urls=true
# 修改250行,去掉#,设置neo4j-shell端口,端口可以自定义,只要不和其他端口冲突就行
dbms.shell.port=1337
# 修改254行,设置neo4j可读可写
dbms.read_only=false

在bin目录下执行 ./neo4j start,启动服务,在浏览器http://服务器ip地址:7474/browser/可以看到neo4j的可视化界面

3、py2neo

py2neo是一个社区第三方库,通过它可以更为便捷地使用python来操作neo4j

安装py2neo:pip install py2neo,我安装的版本是4.3.0

3.1、Node与Relationship

创建节点和它们之间的关系,注意在使用下面的py2neo相关类之前首先需要import导入:

# 引入库
from py2neo import Node, Relationship
# 创建节点a、b并定义其标签为Person,属性name
a = Node("Person", name="Alice",height=166)
b = Node("Person", name="Bob")
# 节点添加标签
a.add_label('Female')
# 创建ab之间的关系
ab = Relationship(a, "KNOWS", b)
# 输出节点之间的关系:(Alice)-[:KNOWS]->(Bob)
print(ab)

Node 和 Relationship 都继承了 PropertyDict 类,类似于python的dictionary,可以通过如下方式对 Node 或 Relationship 进行属性赋值和访问

# 节点和关系添加、修改属性
a['age']=20
ab['time']='2019/09/03'
# 删除属性
del a['age']
# 打印属性
print(a[name])
# 设置默认属性,如果没有赋值,使用默认值,否则设置的新值覆盖默认值
a.setdefault('sex','unknown')
# 更新属性
a.update(age=22, sex='female')
ab.update(time='2019/09/03')

3.2、Subgraph

由节点和关系组成的集合就是子图,通过关系运算符求交集&、并集|、差集-、对称差集^

subgraph.labels返回子图中所有标签集合,keys()返回所有属性集合,nodes返回所有节点集,relationships返回所有关系集

# 构建一个子图
s = a | b | ab
# 对图中的所有节点集合进行遍历
for item in s.nodes:
  print('s的节点:', item)

通常将图中的所有节点和关系构成一个子图后再统一写入数据库,与多次写入单个节点相比效率更高

# 连接neo4j数据库,输入地址、用户名、密码
graph = Graph('http://localhost:7474', username='neo4j', password='123456')
# 将节点和关系通过关系运算符合并为一个子图,再写入数据库
s=a | b | ab
graph.create(s)

3.3、Walkable

walkable是在子图subgraph的基础上增加了遍历信息的对象,通过它可以便捷地遍历图数据库。

通过+号将关系连接起来就构成了一个walkable对象。通过walk()函数对其进行遍历,可以利用 start_node、end_node、nodes、relationships属性来获取起始 Node、终止 Node、所有 Node 和 Relationship

# 组合成一个walkable对象w
w = ab + bc + ac
# 对w进行遍历
for item in walk(w):
  print(item)
# 访问w的初始、终止节点
print('起始节点:', w.start_node, ' 终止节点:', w.end_node)
# 访问w的所有节点、关系列表
print('节点列表:', w.nodes)
print('关系列表:', w.relationships)

运行结果为:

(:Person {age: 20, name: 'Bob'})
(Bob)-[:KNOWS {}]->(Alice)
(:Person {age: 21, name: 'Alice'})
(Alice)-[:LIKES {}]->(Mike)
(:Person {name: 'Mike'})
(Bob)-[:KNOWS {}]->(Mike)
(:Person {age: 20, name: 'Bob'})
起始节点: (:Person {age: 22, name: 'Bob', sex: 'female'})  终止节点: (:Person {age: 22, name: 'Bob', sex: 'female'})
节点列表: ((:Person {age: 22, name: 'Bob', sex: 'female'}), (:Person {age: 21, name: 'Alice'}), (:Person {name: 'Mike'}), (:Person {age: 22, name: 'Bob', sex: 'female'}))
关系列表: ((Bob)-[:KNOWS {time: '2019/09/03'}]->(Alice), (Alice)-[:LIKES {}]->(Mike), (Bob)-[:KNOWS {}]->(Mike))

3.4、Graph

py2neo通过graph对象操作neo4j数据库,目前的neo4j只支持一个数据库定义一张图

通过Graph的初始化函数完成对数据库的连接并创建一个graph对象

graph.create()可以将子图写入数据库,也可以一次只写入一个节点或关系

graph.delete()删除指定子图,graph.delete_all()删除所有子图

graph.seperate()删除指定关系

# 初始化连接neo4j数据库,参数依次为url、用户名、密码
graph = Graph('http://localhost:7474', username='neo4j', password='123456')
# 写入子图w
graph.create(w)
# 删除子图w
graph.delete(w)
# 删除所有图
graph.delete_all()
# 删除关系rel
graph.separate(rel)

graph.match(nodes=None, r_type=None, limit=None)查找符合条件的关系,第一个参数为节点集合或者集合(起始节点,终止节点),如果省略代表所有节点。第二个参数为关系的属性,第三个为返回结果的数量。也可以使用match_one()代替,返回一条结果。例如查找所有节点a认识的人:

# 查找所有以a为起点,并且属性为KNOWS的关系
res = graph.match((a, ), r_type="KNOWS")
# 打印关系的终止节点,即为a所有认识的人
for rel in res:
  print(rel.end_node["name"])

使用graph.nodes.match()查找指定节点,可以使用first()、where()、order_by()等函数对查找做高级限制

还可以通过节点或关系的id查找

# 查找标签为Person,属性name="Alice"的节点,并返回第一个结果
graph.nodes.match("Person", name="Alice").first()
# 查找所有标签为Person,name以B开头的节点,并将结果按照age字段排序
res = graph.nodes.match("Person").where("_.name =~ 'B.*'").order_by('_.age')
for node in res:
  print(node['name'])
# 查找id为4的节点
t_node = graph.nodes[4]
# 查找id为196的关系
rel = graph.relationships[196]

通过Graph对象进行Cypher操作并处理返回结果

graph.evaluate()执行一个Cypher语句并返回结果的第一条数据

# 执行Cypher语句并返回结果集的第一条数据
res = graph.evaluate('MATCH (p:Person) return p')
# 输出:(_3:Person {age: 20, name: 'Bob'})
print(res)

graph.run()执行Cypher语句并返回结果数据流的游标Cursor,通过forward()方法不断向前移动游标可以向前切换结果集的每条记录Record对象

# 查询(p1)-[k]->(p2),并返回所有节点和关系
gql="MATCH (p1:Person)-[k:KNOWS]->(p2:Person) RETURN *"
cursor=graph.run(gql)
# 循环向前移动游标
while cursor.forward():
  # 获取并打印当前的结果集
  record=cursor.current
  print(record)

打印的每条Record记录对象如下所示,可以看到其中的元素是key=value的集合,通过方法get(key)可以取出具体元素。通过方法items(keys)可以将记录中指定的key按(key,value)元组的形式返回

<Record k=(xiaowang)-[:KNOWS {}]->(xiaozhang) p1=(_96:Person {name: 'xiaowang'}) p2=(_97:Person {name: 'xiaozhang'})>
  record = cursor.current
  print('通过get返回:', record.get('k'))
  for (key, value) in record.items('p1', 'p2'):
    print('通过items返回元组:', key, ':', value)
# 运行结果如下
'''
通过get返回: (xiaowang)-[:KNOWS {}]->(xiaozhang)
通过items返回元组: p1 : (_92:Person {name: 'xiaowang'})
通过items返回元组: p2 : (_93:Person {name: 'xiaozhang'})
'''

还可以将graph.run()返回的结果通过data()方法转化为字典列表,所有结果整体上是一个列表,其中每一条结果是字典的格式,其查询与结果如下,可以通过访问列表与字典的方式获取数据:

# 查询(p1)-[k]->(p2),并返回所有节点和关系
gql = "MATCH (p1:Person)-[k:KNOWS]->(p2:Person) RETURN *"
res = graph.run(gql).data()
print(res)
#结果如下:
'''
[{'k': (xiaowang)-[:KNOWS {}]->(xiaozhang), 
 'p1': (_196:Person {name: 'xiaowang'}), 
 'p2': (_197:Person {name: 'xiaozhang'})}, 
{'k': (xiaozhang)-[:KNOWS {}]->(xiaozhao), 
 'p1': (_197:Person {name: 'xiaozhang'}),
 'p2': (_198:Person {name: 'xiaozhao'})},
{'k': (xiaozhao)-[:KNOWS {}]->(xiaoli),
 'p1': (_198:Person {name: 'xiaozhao'}),
 'p2': (_199:Person {name: 'xiaoli'})}
]
'''

通过graph.run().to_subgraph()方法将返回的结果转化为SubGraph对象,接着按之前操作SubGraph对象的方法取得节点对象,这里的节点对象Node可以直接按照之前的Node操作

# 查询(p1)-[k]->(p2),并返回所有节点和关系
gql = "MATCH (p1:Person)-[k:KNOWS]->(p2:Person) RETURN *"
sub_graph = graph.run(gql).to_subgraph()
# 获取子图中所有节点对象并打印
nodes=sub_graph.nodes
for node in nodes:
  print(node)
# 输出的节点对象如下:
'''
(_101:Person {name: 'xiaozhang'})
(_100:Person {name: 'xiaowang'})
(_103:Person {name: 'xiaoli'})
(_102:Person {name: 'xiaozhao'})
'''

3.5、OGM

Object-Graph Mapping将图数据库中的节点映射为python对象,通过对象的方式对节点进行访问和操作。

将图中的每种标签定义为一个python类,其继承自GraphObject,注意使用前先import。在定义时可以指定数据类的主键,并定义类的属性Property()、标签Label()、关系RelatedTo()/RelatedFrom。

from py2neo.ogm import GraphObject, Property, RelatedTo, RelatedFrom, Label
class Person(GraphObject):
  # 定义主键
  __primarykey__ = 'name'
  # 定义类的属性
  name=Property()
  age=Property()
  # 定义类的标签
  student=Label()
  # 定义Person指向的关系
  knows=RelatedTo('Person','KNOWS')
  # 定义指向Person的关系
  known=RelatedFrom('Person','KNOWN')

通过类方法wrap()可以将一个普通节点转化为类的对象。

类方法match(graph,primary_key)可以在graph中查找主键值为primary_key的节点

可以直接通过类构造方法创建一个对象,并直接访问对象的属性及方法,并通过关系方法add()添加关系

类的标签是一个bool值,默认为False,将其修改为True,即可为对象添加标签

# 将节点c转化为OGM类型
c=Person.wrap(c)
print(c.name)
# 查找Person类中主键(name)为Alice的节点
ali=Person.match(graph,'Alice').first()
# 创建一个新的Person对象并对其属性赋值
new_person = Person()
new_person.name = 'Durant'
new_person.age = 28
# 标签值默认为False
print(new_person.student)
# 修改bool值为True,为对象添加student标签
new_person.student=True
# 将修改后的图写入数据库
graph.push(ali)

在定义节点类时还可以定义其相关的关系,例如通过RelatedTo()定义从该节点指出的关系,RelatedFrom()定义指向该节点的关系。通过对象调用关系的对应的方法完成节点周围的关系操作,例如add()添加关系,clear()清除节点所有的关系,get()获取关系属性,remove()清楚指定的关系,update()更新关系

class Person(GraphObject):
  # 定义Person指向的关系
  knows=RelatedTo('Person','KNOWS')
  # 定义指向Person的关系
  known=RelatedFrom('Person','KNOWN')
# 新建一个从ali指向new_person的关系
ali.knows.add(new_person)
# 清除ali节点所有的know关系
ali.knows.clear()
# 清除ali节点指向new_person的那个know关系
ali.knows.remove(new_person)
# 更新ali指向new_person关系的属性值
ali.knows.update(new_person,year=5)
# 获取ali指向new_person关系的属性year的值
ali.knows.get(new_person,'year')

通过图对象也可以调用match方法对节点、关系进行匹配

# 获取第一个主键name名为Alice的Person对象
ali = Person.match(graph, 'Alice').first()
# 获取所有name以B开头的Person对象
Person.match(graph).where("_.name =~ 'B.*'")

也可以通过图graph对节点对象进行操作:

# 更新图中ali节点的相关数据
graph.push(ali)
# 用图中的信息来更新ali节点
graph.pull(ali)
# 删除图中的ali对象节点
graph.delete(ali)

希望本文所述对大家Python程序设计有所帮助。

Python 相关文章推荐
Python备份Mysql脚本
Aug 11 Python
Python实现list反转实例汇总
Nov 11 Python
python 同时运行多个程序的实例
Jan 07 Python
解决Python3 被PHP程序调用执行返回乱码的问题
Feb 16 Python
Python中时间datetime的处理与转换用法总结
Feb 18 Python
Python神奇的内置函数locals的实例讲解
Feb 22 Python
python实现抠图给证件照换背景源码
Aug 20 Python
Pycharm创建项目时如何自动添加头部信息
Nov 14 Python
解决python虚拟环境切换无效的问题
Apr 30 Python
Python图像阈值化处理及算法比对实例解析
Jun 19 Python
python编写实现抽奖器
Sep 10 Python
Python常用base64 md5 aes des crc32加密解密方法汇总
Nov 06 Python
Python模块_PyLibTiff读取tif文件的实例
Jan 13 #Python
python多线程实现代码(模拟银行服务操作流程)
Jan 13 #Python
Python timeit模块的使用实践
Jan 13 #Python
Python 列表的清空方式
Jan 13 #Python
Python SSL证书验证问题解决方案
Jan 13 #Python
python清空命令行方式
Jan 13 #Python
Pytorch GPU显存充足却显示out of memory的解决方式
Jan 13 #Python
You might like
PHP 在5.1.* 和5.2.*之间 PDO数据库操作中的不同之处小结
2012/03/07 PHP
深入file_get_contents函数抓取内容失败的原因分析
2013/06/25 PHP
PHP中copy on write写时复制机制介绍
2014/05/13 PHP
PHP PDO fetch 模式各种参数的输出结果一览
2015/01/07 PHP
linux中cd命令使用详解
2015/01/08 PHP
php如何连接sql server
2015/10/16 PHP
PHP实现的随机IP函数【国内IP段】
2016/07/20 PHP
thinkphp隐藏index.php/home并允许访问其他模块的实现方法
2016/10/13 PHP
再谈ie和firefox下的document.all属性
2009/10/21 Javascript
JavaScript随机排序(随即出牌)
2010/09/17 Javascript
jQuery插件jRumble实现网页元素抖动
2015/06/05 Javascript
BootStrap中Tab页签切换实例代码
2016/05/30 Javascript
NODE.JS跨域问题的完美解决方案
2016/10/20 Javascript
JavaScript队列、优先队列与循环队列
2016/11/14 Javascript
Ajax的概述与实现过程
2016/11/18 Javascript
Bootstrap实现提示框和弹出框效果
2017/01/11 Javascript
js实现无缝滚动图(可控制当前滚动的方向)
2017/02/22 Javascript
JavaScript订单操作小程序完整版
2017/06/23 Javascript
nodejs基础之常用工具模块util用法分析
2018/12/26 NodeJs
JS访问对象两种方式区别解析
2020/08/29 Javascript
python基础教程之简单入门说明(变量和控制语言使用方法)
2014/03/25 Python
零基础写python爬虫之爬虫的定义及URL构成
2014/11/04 Python
Python制作微信好友背景墙教程(附完整代码)
2019/07/17 Python
Python正则表达式如何匹配中文
2020/05/27 Python
英文版银行求职信
2013/10/09 职场文书
单位未婚证明范本
2014/01/18 职场文书
工程招投标邀请书
2014/01/30 职场文书
运动会稿件100字
2014/02/21 职场文书
资源工程专业毕业生求职信
2014/02/27 职场文书
公务员平时考核实施方案
2014/03/11 职场文书
任命书模板
2014/06/04 职场文书
开展批评与自我批评发言稿
2014/10/16 职场文书
2014年妇联工作总结
2014/11/21 职场文书
2015年消费者权益日活动总结
2015/02/09 职场文书
二十年同学聚会致辞
2015/07/28 职场文书
java如何实现socket连接方法封装
2021/09/25 Java/Android