py2neo 快速上手

py2neo 快速上手

py2neo简介

neo4j 是目前最流行的图数据库,在建立知识图谱的时候经常用于数据的存储和检索,neo4j 相较mysql 等其他关系型数据库最大的特点就是非常容易查看数据与数据之间的联系,它将所有数据转换成的形式,让使用者能够快速发现蕴含的联系。下面是neo4j 的主要界面和相应的数据图例(射雕英雄传人物知识图谱)。

image-20210219123737557

但是,直接地对neo4j操作,虽然有相应的cypher语句,却并不适合在编程的过程中操作。py2neo就是一个连接了pythonneo4j数据库的库,它不仅让程序本身可以直接执行cypher语句创建图数据库,也提供了更人性化的操作,符合面向对象编程的思想。

neo4j

在进入使用py2neo之前,还是要先安装好neo4j,了解一些基础的cypher语句用法(虽然我自己基本不怎么用它),然而neo4j的介绍不是本文的重点,网上已有大篇幅的文章去讲述怎么使用它。因此,这里我会给出必要的操作步骤(避免再去看冗长的文档)和我个人推荐的博客文章(包含更为详细的说明)。

安装与启动

  • 安装
wget https://neo4j.com/artifact.php?name=neo4j-community-3.4.1-unix.tar.gz
tar -zxvf neo4j-community-3.4.1.tar.gz

安装的环境建议放在linux系统下,这是我个人的爱好也是一个建议(所有部署的服务、数据库等东西都放到linux系统中),不仅仅是安装更加方便,同时也可以避免后续许多的不便,如果你主力系统是windows,那么虚拟机+网络桥接模式是一个不错的选择

  • 启动
cd bin/ 
./neo4j start   # console | start | stop | restart | status

当发现启动成功后,进入http://localhost:7474

py2neo使用

py2neo支持直接执行cypher语句,如果你对cypher语句熟悉的话,将你要执行的操作转换成cypher语句,再让py2neo执行或许是不错的选择,否则,我更推荐你使用py2neo提供的接口

连接数据库

from py2neo import *

NEO4J_URL = 'http://localhost:7474'
NEO4J_USERNAME = 'neo4j'
NEO4J_PASSWORD = '123456'
graph = Graph(NEO4J_URL, username=NEO4J_USERNAME, password=NEO4J_PASSWORD)

节点

创建节点

节点的定义由 Node 实现,要将其添加到 neo4j 数据库中则可通过 createmerge 方法实现,两者都可创建节点,但有些许的不同:

  • create(node) :即便是属性值相同的同一节点,在调用 create 之后也会创建一个新节点
  • merge(node, "node_label", "main_attr") :主要是 main_attr (主属性)一样,就视作同一节点,对改节点做的任何更改在 merge 的时候都会覆盖已有的节点
node0 = Node('Person', name='Alice')  # 直接指定属性

# 通过属性dict创建
person_info = {
    'name':'Jhon'
}    
node1 = Node('Person', **person_info)

graph.create(node0)   # create创建节点,如果不做检查,总是会创建新节点
graph.merge(node1,'Person', 'name')   # 如果节点已存在则覆盖(只要同一key),需要指定primarykey和primarylabel

查找节点

  • 精确查找(根据指定属性)
matcher = NodeMatcher(graph)
matcher.match("Person", name="Alice").first()   # 返回node
描述 后缀 表达式 示例 cypher语句
相等 __exact = matcher.match(“Person”, name__exact=”Kevin Bacon”) MATCH (:Person) WHERE name = “Kevin Bacon” RETURN
不等 __not <> matcher.match(“Person”, name__not=”Rick Astley”) MATCH (:Person) WHERE .name <> “Rick Astley” RETURN _
大于 __gt > matcher.match(“Person”, born__gt=1985) MATCH (:Person) WHERE .born > 1985 RETURN _
大于等于 __gte >= matcher.match(“Person”, born__gte=1965) MATCH (:Person) WHERE .born >= 1965 RETURN _
小于 __lt < matcher.match(“Person”, born__lt=1965) MATCH (:Person) WHERE .born < 1965 RETURN _
小于等于 __lte <= matcher.match(“Person”, born__lte=1965) MATCH (:Person) WHERE .born <= 1965 RETURN _
以…开头 __startswith STARTS WITH matcher.match(“Person”, name__startswith=”Kevin”) MATCH (:Person) WHERE .name STARTS WITH “Kevin” RETURN _
以…结尾 __endswith ENDS WITH matcher.match(“Person”, name__endswith=”Smith”) MATCH (:Person) WHERE .name ENDS WITH “Smith” RETURN _
包含 __contains CONTAINS matcher.match(“Person”, name__contains=”James”) MATCH (:Person) HWERE .name CONTAINS “James” RETURN _
  • 模糊查找(根据正则表达式)
list(matcher.match("Person").where("_.name =~ 'K.*'"))  # _指代节点,~代表采用正则表达式
  • 查找结果排序与限制
list(matcher.match("Person").where("_.name =~ 'K.*'").order_by("_.name").limit(3))
  • 查找结果个数
len(matcher.match("Person").where("_.name =~ 'K.*'"))
  • 跳过前n个查找结果
list(matcher.match("Person").where("_.name =~ 'K.*'").skip(3))

节点属性与标签

  • 属性操作
node[key] = value   # 给节点属性赋值
del node[key]   # 删除节点属性
len(node)   # 节点属性的个数
dict(node)  # 返回字典,包括了该节点的所有属性
node.identity   # 返回节点id
  • 标签操作
node.labels     # 返回节点的所有标签
labelA in node.labels   # 如果节点具有标签labelA,返回True
node.labels.add(labelB)     # 给节点增加标签labelB
node.labels.discard(labelC)     # 删除节点标签labelC
node.labels.remove(labelC)  # 同上,但是如果labelC不存在的话会返回ValueError
node.labels.clear()     # 清除节点所有标签
node.labels.update(manylabels)  # 从可迭代对象manylabels中给节点增加多个标签

关系

创建关系

properties = {
    'year' : 1999
}
ab = Relationship(node_a,'knows', node_b, )  # 后面还可以传入关系的属性字典
aa = Relationship(node_a,'like')

ab['time'] = '2019/01/01'  # 给关系添加属性

查找关系

  • 查找所有指定类型的关系
matcher=RelationshipMatcher(graph)
list(matcher.match(r_type='KNOWS'))
  • 查找指定起始节点或终点的关系
relation_matcher = RelationshipMatcher(graph)
result = relation_matcher.match((node,), '父亲').first()  # 注意这里的node需要先按照nodematcher.match得到

(node,) 也可以是{node}

关系属性

relationshipA == relationshipB
relationshipA != relationshipB  # 判断两个关系是否相等,但是和节点不同,这里只要起始、终止节点和关系类型相同,就判定两个关系相等
del relationship[key]   # 删除属性
len(relationship)   # 返回属性个数
dict(relationship)  # 返回字典,包括所有属性
type(relationship)  # 返回关系的类型
relationship.nodes   # 返回关系中的所有节点

OGM

from py2neo.ogm import GraphObject, RelatedTo, RelatedFrom
class Movie(GraphObject):
    __primarykey__ = "title"

    title = Property()
    tag_line = Property("tagline")
    released = Property()

    actors = RelatedFrom("Person", "ACTED_IN")
    directors = RelatedFrom("Person", "DIRECTED")
    producers = RelatedFrom("Person", "PRODUCED")


class Person(GraphObject):
    __primarykey__ = "name"

    name = Property()  # Property(key='姓名', default='Tom')
    born = Property()

    acted_in = RelatedTo(Movie)  # RelatedTo(Movie,relationship_type='ACTED_IN'), 类名引号可用可不用
    directed = RelatedTo(Movie)
    produced = RelatedTo(Movie)

查找节点

Person.match(graph, "Alice").first()
list(Person.match(graph).where("_.name =~ 'A.*'"))

属性&操作

alice = Person()
alice.name = "Alice Smith"
graph.push(alice)  # 不需要create/merge

创建关系

cp = Person()
cp.name = 'cp'
m1 = Movie()
m1.title = '煎饼侠'
cp.acted_in.add(m1)  # 添加cp->acted_in ->m1, add有参数,可用于传入关系的属性,直接传入字典或**dict 
# cp.acted_in.remove(m1)  # 移除关系
graph.push(cp)
graph.push(m1)

# add
def add(self, obj, properties=None, **kwproperties):
    """ Add or update a related object.

    :param obj: the :py:class:`.Model` to relate
    :param properties: dictionary of properties to attach to the relationship (optional)
    :param kwproperties: additional keyword properties (optional)
    """

RelatedTo是主动的,RelatedFrom是被动的,因此只需要添加RelatedTo关系(cp.acted_in.add(m1)),就可用查询相应m1.actors