graph_test.py和import_hg.py文件的分析
话接上篇,我们已经研究了
re_web_scraping.py
这份爬虫文件,它的内容是将CVE有关信息从网页上爬取下来存入MongoDB中。今天我们再来研究一下import_hg.py
文件和graph_test.py
文件。最长并且最难的graph_generator4.py
放到下次研究。
graph_test.py源代码
# -*- coding: utf-8 -*-
from PyHugeGraph import PyHugeGraphClient
import json
if __name__ == '__main__':
hg = PyHugeGraphClient.HugeGraphClient("http://127.0.0.1","8080", "hugegraph")
#调用HugeGraphClient这个类,并用它的构造函数完成初始化
print hg.graph
#hug.graph就是上面构造函数输入的'hugegraph'
pk = json.loads(hg.get_graph_allpropertykeys().response)
#获取所有的 PropertyKey
vertexes = hg.get_all_vertelabels().response
#利用该函数获取所有的VertexLabel
vertexes = json.loads(vertexes)
#将vertexes从字符串类型转变为dict对象
for v in vertexes['vertexlabels']: #遍历所有的vertexlabel(即所有顶点的类型)
ret = json.loads(hg.get_vertex_by_condition(label=v['name']).response)
#获取符合条件的顶点,根据label的名字,即得到某一类的所有顶点
for node in ret['vertices']: #根据ID 删除顶点。所以每次运行graph-test.py,hugegraph中的数据都会被删除掉!
hg.delete_vertex_by_id(node['id']).response #delete_vertex_by_id这个函数貌似没有回显
edges = hg.get_all_edgelabels().response #获取所有的EdgeLabels;
edges = json.loads(edges)
ret2 = json.loads(hg.get_edge_by_condition(label='vulnerability').response) #根据条件查询获取边(label为'vulerability'),很奇怪,从之前EdgeLabels返回的结果看,边没有label这个属性;
paths = hg.traverser_all_paths(source='7:A-1', target='7:F-2',direction='OUT', max_depth='10').response #根据起始顶点、目的顶点、方向、边的类型(可选)和最大深度,查找一条最短路径
paths = eval(paths) #本来的paths是字符串,通过eval变成了dict对象
for p in paths['paths']:
print 'path:{}'.format(p['objects'])
PyHugeGraph库
这是一个用于连接hugegraph的库,源代码非常言简意赅,作者是tangjiawei。这里放出[github地址](tanglion/PyHugeGraph: python API for hugegraph database (github.com))。
hg = PyHugeGraphClient.HugeGraphClient("http://127.0.0.1","8080", "hugegraph")
这个语句其实就是利用了这个库中的一个HugeGraphClient
类,并且利用构造函数进行初始化。初始化后我们相当于已经连接上了hugegraph数据库。
然后这个HugeGraphClient类中欲定义了许多函数,就比如我们在这里使用到的get_graph_allpropertykeys
这个函数,这里直接放出库中的源码。
def get_graph_allpropertykeys(self):
"""
获取所有的 PropertyKey
:return:
"""
url = self.host + "/graphs" + "/" + self.graph + "/schema/propertykeys"
response = requests.get(url)
res = Response(response.status_code, response.content)
return res
我们可以看到这个函数的功能是在图数据库中查找所有的PropertyKey。它是通过访问hugegraph后端接口返回的值。原理很简单,但是把每一个方法都写出一个函数工作量可不小,这里向这位作者致敬。
最后返回的值是这样的,这个值我是将返回结果存入一个json文件,然后用vscode格式化后的结果,所以非常清晰。
{
"propertykeys": [
{
"name": "entry",
"data_type": "TEXT",
"aggregate_type": "NONE",
"user_data": {},
"id": 7,
"cardinality": "SINGLE",
"properties": []
},
{
"name": "accessVector",
"data_type": "TEXT",
"aggregate_type": "NONE",
"user_data": {},
"id": 6,
"cardinality": "SINGLE",
"properties": []
},
{
"name": "pre_path",
"data_type": "TEXT",
"aggregate_type": "NONE",
"user_data": {},
"id": 9,
"cardinality": "SINGLE",
"properties": []
},
{
"name": "reachgroup",
"data_type": "TEXT",
"aggregate_type": "NONE",
"user_data": {},
"id": 8,
"cardinality": "SINGLE",
"properties": []
},
{
"name": "vul_port",
"data_type": "TEXT",
"aggregate_type": "NONE",
"user_data": {},
"id": 3,
"cardinality": "SINGLE",
"properties": []
},
{
"name": "type",
"data_type": "TEXT",
"aggregate_type": "NONE",
"user_data": {},
"id": 2,
"cardinality": "SINGLE",
"properties": []
},
{
"name": "accessLevel",
"data_type": "INT",
"aggregate_type": "NONE",
"user_data": {},
"id": 5,
"cardinality": "SINGLE",
"properties": []
},
{
"name": "req_privilege",
"data_type": "INT",
"aggregate_type": "NONE",
"user_data": {},
"id": 4,
"cardinality": "SINGLE",
"properties": []
},
{
"name": "name",
"data_type": "TEXT",
"aggregate_type": "NONE",
"user_data": {},
"id": 1,
"cardinality": "SINGLE",
"properties": []
},
{
"name": "vh_pair",
"data_type": "TEXT",
"aggregate_type": "NONE",
"user_data": {},
"id": 10,
"cardinality": "SINGLE",
"properties": []
}
]
}
在这里我们看可以试试手动在hugegraph的后端接口获取数据,看看是否一致。
我们可以看到,内容是一致的,只是属性的顺序发生了变化,这应该是调用json模块将json字符串和dict对象相互转化时产生的变化。
接下来我们就讲一下json模块
json模块
利用json模块我们可以实现json字符串和dict对象的相互转化。它只要有四个函数dump
、dumps
、load
、loads
。
dump和dumps是将dict对象转变为json字符串。
load和loads是将json字符串转变为dict对象,load非常形象,就是把字符串重新装载成一个对象。
这是dumps使用的实例。
➜ ~ cat test.py
import json
dict1 = {'wwuconix': 'yyds','wcx': 'yyds'}
print('type(dict1):',type(dict1))
dict1 = json.dumps(dict1)
print('type(dict1):',type(dict1))
➜ ~ python3 test.py
type(dict1): <class 'dict'>
type(dict1): <class 'str'>
那dump和dumps有什么区别呢?区别就在dump在使用时需要指定一个文件句柄,用来存放产生的json字符串。
因为我们平常调用json模块的目的可不是简单的在终端中输出,更多的情况是把产生的结果存入json文件中,所以我们可以使用两种方法,分别对应使用dumps和dump。
import json
dict1 = {'wwuconix': 'yyds','wcx': 'yyds'}
with open('test.json','w') as f:
f.write(json.dumps(dict1))
import json
dict1 = {'wwuconix': 'yyds','wcx': 'yyds'}
with open('test.json','w') as f:
json.dump(dict1, f)
使用dump看起来更加简约大气。但是dumps在某些场合(比如我们只想要产生的json的数据,而不想存入文件中)可能就只能使用dumps了。
load和loads类似,不做赘述。
eval的妙用
我们看到这个程序中在最后有这么一句
paths = eval(paths) #本来的paths是字符串,通过eval变成了dict对象
注释也写明了,eval可可以把json字符串转化为dict对象。之前对eval的影响还只停留在eval(‘1+1’)上。没想到它对的字符串的处理可不止如此。
我们先来看一看效果。
➜ ~ cat test.py
import json
dict1 = {'wwuconix': 'yyds','cwj': 'yyds'}
print('type(dict1):',type(dict1))
dict1 = json.dumps(dict1)
print('type(dict1):',type(dict1))
dict1 = eval(dict1)
print('type(dict1):',type(dict1))
➜ ~ python3 test.py
type(dict1): <class 'dict'>
type(dict1): <class 'str'>
type(dict1): <class 'dict'>
可以看到,它有着和json.loads一致的效果。
import_hg.py源代码和分析
# -*- coding: utf-8 -*-
from PyHugeGraph import PyHugeGraphClient
import ast
import json
import csv
def create_graph(hg, jfile, ejfile): #hg是连接了数据库的对象,传入的文件分别是'nodes.json'和'edges.json'
nodes = []
edges = []
with open(jfile, "r") as jf: #从nodes.json中把每一行的顶点经过操作后插入nodes这个列表中。
for l in jf.readlines():
node = l.replace('\n','')
nodes.append(ast.literal_eval(node)) #ast模块将每行取出的字符串变成了dict类型变量
print nodes #返回所有的顶点
print hg.create_multi_vertex(nodes).response #利用该函数创建多个节点, 返回的值是所有被创建的顶点的id
print('-------------')
with open(ejfile, "r") as ejf: #从edges.json中把每一行的边经过操作后插入edges这个列表中。
for l in ejf.readlines():
edge = l.replace('\n', '')
edges.append(ast.literal_eval(edge))
print edges #返回所有的边
print hg.create_multi_edge(edges).response #利用这个函数创建多条边,返回的值是类似于边的名字,但是有所不同
if __name__ == '__main__':
hg = PyHugeGraphClient.HugeGraphClient("http://127.0.0.1","8080", "hugegraph") #连接hugegraph
create_graph(hg, "nodes.json", "edges.json")
print hg.graph
print hg.get_all_graphs().response
这个文件功能非常简单。
我们先来看一下程序中用到的nodes.json
和edges.json
是长什么样子的吧!
这是nodes.json文件的内容,它的每行都以json格式存了一个顶点。然后每行之间是独立的,因为没有逗号。
所以这个程序的目的就是把每一行都都提取出来,然后存入nodes
这个列表中,最后利用PyhugeGraph库中的函数create_multi_vertex
进行创建。
edges.json也类似,把每行的边提取出来放入edges
这个列表中,最后利用函数create_multi_edge
进行创建边。
ast模块
我们看到代码中出现了ast模块
nodes.append(ast.literal_eval(node)) #ast模块将每行取出的字符串变成了dict类型变量
我的注释也标明了,这其实也是将json字符串转为dict对象的方法,之前说过eval
可以实现这个效果,而这个ast.literal_eval的简介如下。
简单点说ast模块就是帮助Python应用来处理抽象的语法解析的。而该模块下的
literal_eval()
函数:则会判断需要计算的内容计算后是不是合法的python类型,如果是则进行运算,否则就不进行运算。比如说上面的计算操作ast.literal_eval(‘1+1’),及危险操作,如果换成了
ast.literal_eval()
,都会拒绝执行。
所以其实就是一个安全版eval
所以总结一下,现在有三种方法可以将json字符串转变为字典对象。
- json.loads
- eval
- ast.literal_eval
经验总结
在研究代码的时候,千万不能硬看。
我们应该对代码进行分割,一步一步来,对每一步的值进行输出查看。
在以直观方式看到结果之后,我们就能更容易理解代码的目的从而获得顿悟。
在获得某些顿悟之后,其他的类似的问题就迎刃而解了。
下次分析的内容
我们看到import_hg.py
文件依赖于两个重要文件nodes.json
和edges.json
。这两个文件是如何产生的呢?下篇文章我们便来分析一下产生这两个文件的重要python文件graph_generator4.py
。
未来可以干的事
-
PyHugeGraph这个库功能非常强大,但是因为作者是在2018年编写的,用的python2版本。所以就导致很多使用这个库的文件都也必须被迫使用python2来进行编写,日后有时间了,可以把这个库改写为python3,以顺应时代潮流。
-
因为nodes.json和edges.json文件的每行是独立的json字符串,导致了import_hg.py在导入数据库的时候需要把每行进行整合,那是不是可以在生成这个文件内容过的时候就直接把每行不独立而只是逗号相隔,组成一个更大的json字符串呢?当然这还要等分析完
graph_generator4.py
这个文件之后,可能有什么不得不这样做的原因。