py2neo的简单使用(二)
1、连接Neo4j数据库
要通过python来操作Neo4j,首先需要安装py2neo,可以直接使用pip安装。
pip install py2neo
在完成安装之后,打开pycharm,调用py2neo即可,
from py2neo import Graph,Node,Relationship
graph = Graph("http://localhost:7474", username="neo4j", password=pwd)
后来访问数据库的方式换了(随着py2neo版本的升级就是这么不兼容哈)
graph = Graph(“”, auth=(user, password))
2、节点的建立
节点的建立要用到py2neo.Node,建立节点的时候要定义它的节点类型(label)以及一个基本属性(property,包括property_key和property_value)。以下代码为建立了两个测试节点。
node_1 = Node("Person",name = "test_node_1") # 注意:这里不要加“label=”,否则label会以属性的形式展示,而非标签
node_2 = Node("Person",name = "test_node_2")
neo_graph.create(node_1)
neo_graph.create(node_2)
3、节点间关系的建立
节点间的关系(Relationship)是有向的,所以在建立关系的时候,必须定义一个起始节点和一个结束节点。值得注意的是,起始节点可以和结束节点是同一个点,这时候的关系就是这个点指向它自己。
node_1_call_node_2 = Relationship(node_1,'CALL',node_2)
node_1_call_node_2['count'] = 1 # 该关系的属性
node_2_call_node_1 = Relationship(node_2,'CALL',node_1)
node_2_call_node_1['count'] = 2
graph.create(node_1_call_node_2)
graph.create(node_2_call_node_1)
注意:如果建立关系的时候,起始节点或者结束节点不存在,则在建立关系的同时建立这个节点
4、节点/关系属性值的更新(graph.push())
以关系建立里的 node_1_call_node_2 为例,让它的count加1,再更新到图数据库里面,使用push函数来进行更新即可。
node_1_call_node_2['count']+=1
graph.push(node_1_call_node_2)
5、通过属性值来查找节点(find,find_one)
通过find和find_one函数,可以根据类型和属性、属性值来查找节点和关系。
示例如下:
find_code_1 = graph.find_one(
label="Person",
property_key="name",
property_value="test_node_1"
)
print find_code_1['name']
find和find_one的区别在于:
find_one的返回结果是一个具体的节点/关系,可以直接查看它的属性和值。如果没有这个节点/关系,返回None。
find返回的结果是一个游标cursors,必须要通过循环取到所找到的所有节点/关系。
6、通过节点/关系查找相关联的节点/关系(match,match_one)
如果已经确定了一个节点或者关系,想找到和它相关的关系和节点,就可以使用match和match_one。
find_relationship = graph.match_one(start_node=find_code_1,end_node=find_code_3,bidirectional=False)
print(find_relationship)
如以上代码所示,match和match_one的参数包括start_node,Relationship,end_node中的至少一个。bidirectional参数的意义是指关系是否可以双向。如果为False,则起始节点必须为start_node,结束节点必须为end_node。如果有Relationship参数,则一定按照Relationship对应的方向。如果为True,则不需要关心方向问题,会把两个方向的数据都返回。
match_relation = graph.match(start_node=find_code_1,bidirectional=True)
for i in match_relation:
print i
i['count']+=1
graph.push(i)
如以上代码所示,查找和find_code_1相关的关系。
match里面的参数只写了start_node,bidirectional的值为True,则不会考虑方向问题,返回的是以find_code_1为起始节点和结束节点的所有关联关系。
如果,bidirectional的值为False,则只会返回以find_code_1为起始节点的所有关联关系。
7、使用NodeMatcher查询节点
首先创建一个NodeMatcher对象,用match来指明要匹配哪种label的节点,用where来表示筛选条件(有两种方法)。需要注意的是,匹配成功返回的是NodeMatcher的对象,要转化成Node对象,可以用first取出符合条件的第一个节点,或者转化成节点的list。
>>>node_matcher = NodeMatcher(graph)
>>>node = node_matcher.match("Person").where(age=20).first()
>>>node
Node('Person', age=20, name='李李', work='脚本之家')
>>>nodes = list(node_matcher.match("Person").where(age=35))
>>>nodes
[Node('Person', age=35, name='王王', work='脚本之家')]
where条件有两种写法,一种是把要匹配的属性和值写成key=value的形式,例如上面where(age=20),这种写法只能按照值是否完全一致来匹配,不能按照值的大小来筛选,如果写成下面这样是会报错的:
node = node_matcher.match("Person").where(age>20).first() # 错误
想要按照值的大小筛选或者做一些字符串的模糊匹配,可以把条件表达式写成一个字符串,整体放在where语句中,在这个字符串中,可以用 _ 来代指匹配到的节点。下面两个例子,第一个是匹配work属性为“月亮XX”模式的Person节点,另一个是匹配age大于20的Person节点。
>>>node = node_matcher.match("Person").where("_.work =~ '月亮.*'").first()
>>>node
Node('Person', 'Teacher', age=45, name='赵赵', work='月亮中学')
>>>nodes = list(node_matcher.match("Person").where("_.age > 20"))
>>>nodes
[Node('Person', age=35, name='王王', work='脚本之家'),
Node('Person', age=30, name='张张', work='脚本之家'),
Node('Person', 'Teacher', age=45, name='赵赵', work='月亮中学')]
>>>node = node_matcher.match("Person").where("_.work =~ '月亮.*'").first()
>>>node
Node('Person', 'Teacher', age=45, name='赵赵', work='月亮中学')
>>>nodes = list(node_matcher.match("Person").where("_.age > 20"))
>>>nodes
[Node('Person', age=35, name='王王', work='脚本之家'),
Node('Person', age=30, name='张张', work='脚本之家'),
Node('Person', 'Teacher', age=45, name='赵赵', work='月亮中学')]
将NodeMatcher返回的结果转化为Node数据类型或者Node的list之后,访问其中的属性也就十分简单了,如上面最后一例的结果,访问其中第一个节点的name属性:
>>>nodes[0]['name']
'王王'
8、 使用RelationshipMatcher查询关系
RelationshipMatcher的match方法有三个及以上参数:
第一个参数是节点的序列或者set,可以为None,为None表示任意节点均可;
第二个参数是关系的类型,可以为None,为None表示任意类型的关系均可;
第三个参数开始是要匹配的属性,写成key=value的形式。
match方法的返回值是RelationshipMatcher类型,需要通过first转化成Relationship数据结构,或者转化为list。
列1:比如想要查询“李李”节点的所有关系。先查询出节点,再查询节点的关系,r_type=None表示任意类型的关系均可。返回的关系包括到访、同事。
>>>node1 = node_matcher.match("Person").where(name='李李').first()
>>>relationship = list(relationship_matcher.match([node1], r_type=None))
>>>relationship
[到访(Node('Person', age=20, name='李李', work='脚本之家'), Node('Location', name='禄口机场'), date='2021/7/16', stay_hours=1),
同事(Node('Person', age=20, name='李李', work='脚本之家'), Node('Person', age=30, name='张张', work='脚本之家')),
同事(Node('Person', age=20, name='李李', work='脚本之家'), Node('Person', age=35, name='王王', work='脚本之家'))]
例2:查询“李李”和“张张”的关系,两个节点的顺序表示了要匹配的关系的方向。所以在整个图中“李李”和“张张”节点之间的同事关系是双向的,但是查询结果只给出了从“张张”节点到“李李”节点的一条关系。
>>>node1 = node_matcher.match("Person").where(name='李李').first()
>>>node2 = node_matcher.match("Person").where(name='张张').first()
>>>relationship = list(relationship_matcher.match((node2,node1), r_type=None))
>>>relationship
[同事(Node('Person', age=30, name='张张', work='脚本之家'), Node('Person', age=20, name='李李', work='脚本之家'))]
例3:询图中某一类关系,第一个参数为None,第二个参数r_type指定关系类型,这里查询了图中所有的同事关系。
>>>relationship = list(relationship_matcher.match(None, r_type='同事'))
>>>relationship
[同事(Node('Person', age=20, name='李李', work='脚本之家'), Node('Person', age=30, name='张张', work='脚本之家')),
同事(Node('Person', age=20, name='李李', work='脚本之家'), Node('Person', age=35, name='王王', work='脚本之家')),
同事(Node('Person', age=35, name='王王', work='脚本之家'), Node('Person', age=20, name='李李', work='脚本之家')),
同事(Node('Person', age=30, name='张张', work='脚本之家'), Node('Person', age=20, name='李李', work='脚本之家'))]```
例4: 在查询关系时按照属性的值筛选,可以将该属性写为key=value的形式作为match方法的第三个参数。这里,查询图中的到访关系,并且stay_hours属性为1。
>>>relationship = list(relationship_matcher.match(None, r_type='到访', stay_hours=1))
>>>relationship
[到访(Node('Person', age=20, name='李李', work='脚本之家'), Node('Location', name='禄口机场'), date='2021/8/24', stay_hours=1)]
虽然Py2neo的手册上没有写,但其实RelationshipMatcher也可以接上where方法,按照属性的值筛选关系。上面这个例子也可以写作下面这种形式,效果是一样的。
relationship = list(relationship_matcher.match(None, r_type='到访').where(stay_hours=1))
同样,在where方法中也可以写一个字符串表达式,实现按值大小来筛选关系。例如要筛选出所有到访关系,且stay_hours>=1的关系时,可以这样写:
>>>relationship = list(relationship_matcher.match(None, r_type='到访').where("_.stay_hours>=1"))
>>>relationship
[到访(Node('Person', age=20, name='李李', work='脚本之家'), Node('Location', name='禄口机场'), date='2021/8/24', stay_hours=1),
到访(Node('Person', age=20, name='刘刘', work='地球电子商务公司'), Node('Location', name='禄口机场'), date='2021/8/24', stay_hours=4)]
如何访问返回的结果中的各个属性呢,Relationship其实是包含了一对起止节点:start_node和end_node,包含了关系的类型,而关系的属性是以字典形式存在的,可以用get方法来获取属性的值。
获取关系的起止节点:
>>>print(relationship[0].start_node['name'])
>>>print(relationship[0].end_node['name'])
李李
禄口机场
获取关系的类型的文本字符串
>>>print(relationship[0])
>>>print(type(relationship[0]).__name__)
(李李)-[:到访 {date: '2021/8/24', stay_hours: 1}]->(禄口机场)
到访
获取关系中的属性和值
>>>print(relationship[0].keys())
>>>print(relationship[0].values())
>>>print(relationship[0].get('date'))
dict_keys(['date', 'stay_hours'])
dict_values(['2021/8/24', 1])
2021/8/24
9、判断节点是否存在
org = graph.nodes.match("ORG", name='国务院').first()
if org!=None:
# 证明已经建立了这个节点
10、直接执行Cypher语句(run)
NodeMatcher和RelationshipMatcher能够表达的匹配条件相对简单,更加复杂的查询还是需要用Cypher语句来表达。Py2neo本身支持执行Cypher语句的执行,可以将复杂的查询写成Cypher语句,通过graph.run方法查询,返回的结果可以转化为pandas.DataFrame或者pandas.Series对象,从而和其他数据分析工具无缝衔接。
可以利用run方法来执行Cypher语句,返回的是游标cursors,cursors必须通过遍历来展示结果
find_rela = graph.run("match (n:Person{name:'test_node_1'}) return n")
for i in find_rela :
print i
例如:要查询Person节点,并满足work属性为“脚本之家”。Cypher语句中可以使用WHERE接条件表达式,使用AS将返回的属性改名,返回多个属性时,用xxx AS x, yyy AS y。graph.run方法之后再接to_data_frame()可以将返回的数据变成pandas的DataFrame对象,并且用AS改过的属性名即为DataFrame中的列名。
cypher_ = "MATCH (n:Person) \
WHERE n.work='脚本之家' \
RETURN n.name AS name, n.age AS age "
df = graph.run(cypher_).to_data_frame() # pd.DataFrame
例2:查询一个已知节点和其他哪些节点有关系,有什么样的关系。Cypher语言查询关系时用 < 或者 > 表示方向,这里需要返回type(r),直接返回r的话结果里是空值。
>>>cypher_ = "MATCH (n:Person)-[r]->(m:Person) \
WHERE n.name='李李' \
RETURN type(r) AS type,m.name AS name"
>>>df = graph.run(cypher_).to_data_frame() # pd.DataFrame
例3:Cypher语言还可以查询路径,因为不确定返回的路径数量,所以最好先将结果转化为pandas.Series,再遍历访问其中每条路径的节点和关系。
这里查询的是“赵赵”节点和“王王”节点之间的关系路径,关系指定为同事或邻居,关系不超过4层。
>>>cypher_ = "MATCH path=(m:Person)-[:同事|邻居*1..4]->(n:Person) \
WHERE m.name='赵赵' AND n.name='王王' \
RETURN path"
>>>s = graph.run(cypher_).to_series()
>>>print(len(s))
>>>s[0]
Path(Node('Person', 'Teacher', age=45, name='赵赵', work='月亮中学'),
邻居(Node('Person', 'Teacher', age=45, name='赵赵', work='月亮中学'),
Node('Person', age=30, name='张张', work='脚本之家')),
同事(Node('Person', age=30, name='张张', work='脚本之家'),
Node('Person', age=20, name='李李', work='脚本之家')),
同事(Node('Person', age=20, name='李李', work='脚本之家'),
Node('Person', age=35, name='王王', work='脚本之家')))
这里查询到的关系路径数量仅有1条。从上图的结果中也可以看出来,Path是一个比较复杂的结构,Path中的节点和关系分别用nodes和relationships表示,并且是按照路径上节点和关系的顺序分别存放的。这里给出一段示例代码,对每一个路径都做了直接打印path数据结构和自己组织路径文本。
for path in s:
# 直接打印path
print(path)
# 获取路径中的节点和关系
nodes = path.nodes
relationshis = path.relationships
# 自己组织路径文本
path_text = ""
for n,r in zip(nodes, relationshis):
# 每次加入一个节点和一个关系的类型
path_text += "{} - {} - ".format(n['name'], type(r).__name__)
# 别忘了最后一个节点
path_text += nodes[-1]['name'] + '\n'
print(path_text)
运行这段代码得的结果如下所示,上面一行是直接打印路径的结果,下面一行是自己组织文本得到的结果。
(赵赵)-[:邻居 {}]->(张张)-[:同事 {}]->(李李)-[:同事 {}]->(王王)
赵赵 - 邻居 - 张张 - 同事 - 李李 - 同事 - 王王
11、通过graph.schema查询图中节点和关系有哪些类型
查看节点的类型用graph.schema.node_labels,查看关系的类型用graph.schema.relationship_types,它们的返回值类型都是frozenset,是不能增删元素的集合。
>>>graph.schema.node_labels
frozenset({'Location', 'Person', 'Teacher'})
>>>graph.schema.relationship_types
frozenset({'到访', '包含', '同事', '学生', '老师', '邻居'})