Python使用py2neo操作圖資料庫neo4j的方法詳解
本文例項講述了Python使用py2neo操作圖資料庫neo4j的方法。分享給大家供大家參考,具體如下:
1、概念
圖:資料結構中的圖由節點和其之間的邊組成。節點表示一個實體,邊表示實體之間的聯絡。
圖資料庫:以圖的結構儲存管理資料的資料庫。其中一些資料庫將原生的圖結構經過優化後直接儲存,即原生圖儲存。還有一些圖資料庫將圖資料序列化後儲存到關係型或其他資料庫中。
之所以使用圖資料庫儲存資料是因為它在處理實體之間存在複雜關係的資料具有很大的優勢。使用傳統的關係型資料庫在處理資料之間的關係時其實很不方便。例如查詢選修一個課程的同學時需要join兩個表,查詢選修某個課程的同學還選修什麼課程,這就需要兩次join操作,當涉及到十分複雜的關係以及龐大的資料量時,關係型資料庫效率十分低下。而通過圖儲存,可以通過節點之間的邊十分便捷地查詢到結果。
圖模型:
節點(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,sex: 'female'})
節點列表: ((:Person {age: 22,sex: 'female'}),(:Person {age: 21,name: 'Alice'}),(:Person {name: 'Mike'}),(:Person {age: 22,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',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','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數學運算技巧總結》、《Python資料結構與演算法教程》、《Python函式使用技巧總結》、《Python字串操作技巧彙總》、《Python入門與進階經典教程》及《Python檔案與目錄操作技巧彙總》
希望本文所述對大家Python程式設計有所幫助。