阅读 232

Demo 使用结构化信息构建知识图谱

内含国内前一百名高校信息;本文通过爬虫获取我国所有院校的结构化数据进行扩充,形成高校知识图谱。

简介

构造知识图谱是一个复杂的系统工程。其构造和实现方法并不唯一,尚未存在固定的范式。

在算法上不考虑知识图谱的实体抽取、关系抽取、知识消融和嵌入算法。

在数据上不考虑非结构化数据,仅通过百度百科爬取半结构化的数据进行数据源获取。

所以本Demo是假设在已具备数据质量优良的前提下,把数据从Neo4j或其他的图数据库中释放出来,进行web端的可视化展示,并据此开发一些基本功能或下游任务。

参考openKG的开源项目,进行一定程度的修改和适配。

1.数据获取和清洗

  • 从政府公开信息获得 全国普通高等学校名单 文件获取点我

  • 截至2020年6月30日,全国高等学校共计3005所,其中:普通高等学校2740所,含本科院校1258所、高职(专科)院校1482所;

1.1 清洗高校名单数据

  • 在清洗之前,先打开excel文件删除头两行,再用python代码进行清洗

import numpy as np import pandas as pd # 用pandas 打开 df = pd.read_excel("全国高等学校名单.xls") # 先把备注中所有的Nan全部替换为公办 df["备注"].fillna("公办",inplace=True) # 删除含Nan的空行 df.dropna(axis=0,inplace=True) # 学校表示码转换数据类型 成int64 df["学校标识码"] = df["学校标识码"].astype(np.int64) # 保存文件 df.to_csv("School_List_2020.csv",index=False) # 看看长啥样 print(df.shape) df.head() 复制代码

????OK!得到一个干净的csv文件,接下来进行爬虫。

1.2 爬虫获取json数据

可以看到高校的百度百科的构成是 https://baike.baidu.com/item/ + "高校名称" 

读取csv文件中的学校名称拼接url得道urls列表

df = pd.read_csv("School_List_2020.csv") urls = [] for i in df["学校名称"]:     url = "https://baike.baidu.com/item/" + str(i)     urls.append(url) 复制代码

抓取以下六个标签【'中文名'、 '英文名'、 '简称'、'创办时间'、'类型'、 '主管部门'】

爬虫完整代码:

import requests import json import time from tqdm import tqdm import numpy as np import pandas as pd from bs4 import BeautifulSoup # 计时 def run_time(start_time):     current_time = time.strftime("%Y-%m-%d %H:%M:%S",time.localtime())     print(f"当前时间:{current_time}")     print("耗时:%.3f sec" %(time.time()-start_time)) # get url 并获取网页内容 def url_open(url):     headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36'}     r = requests.get(url, headers = headers)     return r  # get the school list def school_list(filename):     schools = []     df = pd.read_csv(filename)     for i in df["学校名称"]:         schools.append(i)     return schools if __name__ == "__main__":     school = school_list("School_List_2020.csv")     # print(school)     result_data = []     start_time = time.time()     for index in tqdm(school):         url = 'https://baike.baidu.com/item/' + index          print(url)         data = url_open(url)         soup = BeautifulSoup(data.content, 'html.parser', from_encoding='utf-8')         name_data = []         value_data = []         name_node = soup.find_all('dt', class_='basicInfo-item name')         # print(name_node)         for i in range(len(name_node)):             name_data.append(name_node[i].get_text().replace('\xa0', ''))             # name_data.append(name_node[i].get_text())             # print(name_data)         value_node = soup.find_all('dd', class_='basicInfo-item value')         for i in range(len(value_node)):             value_data.append(value_node[i].get_text().replace('\n', ''))             # print(type(value_node[i].get_text().replace('\n', '')))             # print(value_node[i].get_text().replace('\n', ''))             # print(value_data)             # print(type(value_data))         result = {'中文名': '无信息', '英文名': '无信息', '简称':'无信息','创办时间': '无信息', '类型': '综合', '主管部门': '无信息'}         for i in range(len(name_data)):             if name_data[i] == '中文名':                 result['中文名'] = value_data[i]             if name_data[i] in ['英文名','外文名']:                 result['英文名'] = value_data[i]             if name_data[i] == '简称':                 result['简称'] = value_data[i]             if name_data[i] == '创办时间':                 result['创办时间'] = value_data[i]             if name_data[i] == '类型':                 result['类型'] = value_data[i]             if name_data[i] == '主管部门':                 result['主管部门'] = value_data[i]         result_data.append({'中文名': result['中文名'], '英文名': result['英文名'], '简称': result['简称'], '创办时间': result['创办时间'], '类型': result['类型'], '主管部门': result['主管部门']})         # print('reading the website...')         # print(result_data)     fw = open('all.json', 'w', encoding='utf-8')     fw.write(json.dumps(result_data, ensure_ascii=False))     fw.close()     print('complete!')     run_time(start_time) 复制代码

  • 预计等候15分钟左右

1.3 json数据清洗

  • json数据清洗分为两步骤:特征提取、linknode构造。

  • 特征提取:主要是把节点属性提取为一个一个的txt文本,方便后续构造node-link-node的三元组形式。

import json  with open('./spider/all.json', 'r', encoding='utf-8') as fr:     str_data = fr.read()     full_data = json.loads(str_data) # json 解码     fw1 = open('./dataprocess/Name.txt', 'w', encoding='utf-8')    # 名称list     fw2 = open('./dataprocess/English.txt', 'w', encoding='utf-8') # 英文名list     fw3 = open('./dataprocess/Abbr.txt', 'w', encoding='utf-8')    # 简称list     fw4 = open('./dataprocess/Time.txt', 'w', encoding='utf-8')    # 创办时间list     fw5 = open('./dataprocess/Type.txt', 'w', encoding='utf-8')    # 类型list     fw6 = open('./dataprocess/Admin.txt', 'w', encoding='utf-8')   # 主管部门list          for i in range(len(full_data)):         # 傻瓜式遍历         for key, value in full_data[i].items():             if key == '中文名':                 fw1.write("{'中文名': '" + value +"'}\n")             if key == '英文名':                 fw2.write("{'英文名': '" + value +"'}\n")             if key == '简称':                 fw3.write("{'简称': '" + value +"'}\n")             if key == '创办时间':                 # fw4.write("{'创办时间': '" + value[0:4] +"年'}\n")                 fw4.write("{'创办时间': '" + value +"'}\n")             if key == '类型':                 fw5.write("{'类型': '" + value +"'}\n")             if key == '主管部门':                 fw6.write("{'主管部门': '" + value +"'}\n") fw1.close() fw2.close() fw3.close() fw4.close() fw5.close() fw6.close() 复制代码

  • linknode构造

import json  import csv  nodes = [] links = [] name_list = [] english_list = [] abbr_list = [] time_list = [] type_list = [] admin_list = [] # english2_list = [] # time2_list = [] # abbr2_list = [] # central node nodes.append({'id': '大学', 'class': 'university', 'group': 0, 'size': 22}) # type node fr = open('./dataprocess/Type.txt', 'r', encoding='utf-8') for line in fr.readlines():     tmp = line.strip('\n')     for key, value in eval(tmp).items():         if value not in type_list:             type_list.append(value)             nodes.append({'id': value, 'class': 'type', 'group': 5,  'size': 18})             links.append({'source': '大学', 'target': value, 'value': 3})             links.append({'source': value, 'target': '大学', 'value': 3}) fr.close() # english node fr = open('./dataprocess/English.txt', 'r', encoding='utf-8') for line in fr.readlines():     tmp = line.strip('\n')     for key, value in eval(tmp).items():         if value not in english_list:             english_list.append(value)             nodes.append({'id': value, 'class': 'english', 'group': 2,  'size': 15}) fr.close() # abbr node fr = open('./dataprocess/Abbr.txt', 'r', encoding='utf-8') for line in fr.readlines():     tmp = line.strip('\n')     for key, value in eval(tmp).items():         if value not in abbr_list:             abbr_list.append(value)             nodes.append({'id': value, 'class': 'abbr', 'group': 3,  'size': 15}) fr.close() # time node fr = open('./dataprocess/Time.txt', 'r', encoding='utf-8') for line in fr.readlines():     tmp = line.strip('\n')     for key, value in eval(tmp).items():         if value not in time_list:             time_list.append(value)             nodes.append({'id': value, 'class': 'time', 'group': 4,  'size': 11}) fr.close() # admin node fr = open('./dataprocess/Admin.txt', 'r', encoding='utf-8') for line in fr.readlines():     tmp = line.strip('\n')     for key, value in eval(tmp).items():         if value not in admin_list:             admin_list.append(value)             nodes.append({'id': value, 'class': 'admin', 'group': 6,  'size': 11}) fr.close() with open('./spider/all.json', 'r', encoding='utf-8') as fr:     str_data = fr.read()     full_data = json.loads(str_data)     for i in range(len(full_data)):         # for key, value in full_data[i].items():         # name node         nodes.append({'id': full_data[i]['中文名'], 'class': 'names', 'group': 1, 'size': 20})         links.append({'source': full_data[i]['类型'], 'target': full_data[i]['中文名'], 'value': 3})         links.append({'source': full_data[i]['中文名'], 'target': full_data[i]['类型'], 'value': 3})         # english node         links.append({'source': full_data[i]['中文名'], 'target': full_data[i]['英文名'], 'value': 3})         links.append({'source': full_data[i]['英文名'], 'target': full_data[i]['中文名'], 'value': 3})         # abbr node         links.append({'source': full_data[i]['中文名'], 'target': full_data[i]['简称'], 'value': 3})         links.append({'source': full_data[i]['简称'], 'target': full_data[i]['中文名'], 'value': 3})         # time node         links.append({'source': full_data[i]['简称'], 'target': full_data[i]['创办时间'], 'value': 3})         links.append({'source': full_data[i]['创办时间'], 'target': full_data[i]['简称'], 'value': 3})         # admin node         links.append({'source': full_data[i]['简称'], 'target': full_data[i]['主管部门'], 'value': 3})         links.append({'source': full_data[i]['主管部门'], 'target': full_data[i]['简称'], 'value': 3}) fw = open('./nodes.json', 'w', encoding='utf-8') fw.write(json.dumps({'nodes': nodes, 'links': links}, ensure_ascii=False)) fw.close() 复制代码

经过一系列处理得到了含node和link信息的 nodes.json 接下来我们通过D3.js进行图谱可视化生成。

2.生成图谱

图谱生成可以用Echarts或者D3.js,Echarts简单易上手但定制不够灵活,D3.js灵活可定制但难上手。根据参考资料,先用D3.js实现整体图谱的展示,并做适当修改美化。

  • 关于力项导图可参考:blog.csdn.net/tengxing007…

效果展示

<!DOCTYPE html> <html> <head>     <meta charset="UTF-8"/>     <meta http-equiv="X-UA-Compatible" content="IE=edge">     <meta name="viewport" content="width=device-width, initial-scale=1">     <title>2020年中国普通高等学校图谱可视化</title>     <meta name="description" content=""/>     <meta name="keywords" content=""/>     <meta name="author" content=""/>     <link rel="shortcut icon" href="">     <script src="http://cdn.bootcss.com/jquery/2.1.4/jquery.min.js"></script>     <link href="http://cdn.bootcss.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet">     <script src="http://cdn.bootcss.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>     <script src="https://cdn.staticfile.org/echarts/4.3.0/echarts.min.js"></script> </head> <style>     body {         background-color: #333333;         padding: 30px 40px;         text-align: center;         font-family: OpenSans-Light, PingFang SC, Hiragino Sans GB, Microsoft Yahei, Microsoft Jhenghei, sans-serif;     }     .links line {         stroke: rgb(240, 240, 240);         stroke-opacity: 0.8;     }     .links line.inactive {         /*display: none !important;*/         stroke-opacity: 0;     }     .nodes circle {         stroke: #fff;         stroke-width: 1.5px;     }     .nodes circle:hover {         cursor: pointer;     }     .nodes circle.inactive {         display: none !important;     }     .texts text {         display: none;     }     .texts text:hover {         cursor: pointer;     }     .texts text.inactive {         display: none !important;     }     #indicator {         position: absolute;         left: 45px;         bottom: 50px;         text-align: left;         color: #f2f2f2;         font-size: 20px;     }     #indicator > div {         margin-bottom: 4px;     }     #indicator span {         display: inline-block;         width: 30px;         height: 14px;         position: relative;         top: 2px;         margin-right: 8px;     }     #mode {         position: absolute;         top: 60px;         left: 45px;     }     #mode span {         display: inline-block;         border: 1px solid #fff;         color: #fff;         padding: 6px 10px;         border-radius: 4px;         font-size: 14px;         transition: color, background-color .3s;         -o-transition: color, background-color .3s;         -ms-transition: color, background-color .3s;         -moz-transition: color, background-color .3s;         -webkit-transition: color, background-color .3s;     }     #mode span.active, #mode span:hover {         background-color: #fff;         color: #333;         cursor: pointer;     }     #info {         position: absolute;         bottom: 40px;         right: 30px;         text-align: right;         width: 270px;     }     #info p {         color: #fff;         font-size: 12px;         margin-bottom: 5px;         margin-top: 0px;     }     #info p span {         color: #888;         margin-right: 10px;     }     #search input {         position: absolute;         top: 100px;         left: 45px;         color: #000;         border: none;         outline: none;         box-shadow: none;         width: 160px;         background-color: #FFF;     }     #svg2 g.row:hover {         stroke-width: 1px;         stroke: #fff;     } </style> <body>     <h1 style="color: #fff;font-size: 32px;text-align: left;margin-left:40px;">2020年中国普通高等学校知识图谱</h1>     <div style="text-align: center;position: relative;">         <svg width="1600" height="1200" style="margin-left: 0px;margin-bottom: 0px;" id="svg1"></svg>         <div id="indicator"></div>         <div id="mode">             <span class="active" style="border-top-right-radius: 0;border-bottom-right-radius: 0; ">图形</span>             <span style="border-top-left-radius: 0;border-bottom-left-radius: 0; position: relative;left: -5px;">文字</span>         </div>         <div id="search">             <input type="text" class="form-control">         </div>     </div>     <div style="text-align: center;position: relative;"></div>     <div id="info">         <h4></h4>     </div>     <!-- <div id="main" style="width: 600px;height:400px;"></div> --> </body> <script src="https://d3js.org/d3.v4.min.js"></script> <script>     $(document).ready(function () {         var svg = d3.select("#svg1"), width = svg.attr('width'), height = svg.attr('height');         var names = ['大学', '中文名','英文名','简称','创办时间','类型','主管部门'];         var colors = ['#bd0404','#b7d28d', '#b8f1ed', '#ca635f', '#5153ee','#836FFF', '#f0b631'];           // 图注         for (var i = 0; i < names.length; i++) {             $('#indicator').append("<div><span style='background-color: " + colors[i] + "'></span>" + names[i] + "</div>");         }         // 相互作用力,定义鼠标拖拽时的效果         var simulation = d3.forceSimulation()             //速度衰减因子,相当于摩擦力,0是无摩擦,1是冻结             .velocityDecay(0.6)             // α衰变,借用粒子的放射性的概念,指力的模拟经过一定次数后会逐渐停止;             // 数值范围也是0-1,如果设为1,经过300次迭代后,模拟就会停止;这里我们设为0,会一直进行模拟。             .alphaDecay(0)             //连线间的斥力             .force("link", d3.forceLink().id(function (d) {                 return d.id;             }))             //斥力             .force("charge", d3.forceManyBody())             //中心力             .force("center", d3.forceCenter(width / 2, height / 2));         //导图设置         var graph;         d3.json("nodes.json", function (error, data) {             if (error) throw error;             graph = data;             console.log(graph);             var link = svg.append("g").attr("class", "links").selectAll("line").data(graph.links).enter().append("line").attr("stroke-width", function (d) {                 return 1;             });             var node = svg.append("g").attr("class", "nodes").selectAll("circle").data(graph.nodes).enter().append('circle').attr('r', function (d) {                 return d.size;              }).attr("fill", function (d) {                 return colors[d.group];             }).attr("stroke", "none").attr("name", function (d) {                 return d.id;             }).call(d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended));             var text =                 svg.append("g").attr("class", "texts").selectAll("text").data(graph.nodes).enter().append('text').attr("font-size", function (d) {                     return d.size;                 }).attr("fill", function (d) {                     return colors[d.group];                 }).attr("name", function (d) {                     return d.id;                 }).text(function (d) {                     return d.id;                 }).attr("text-anchor", 'middle').call(d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended));             var data = svg.append("g").attr("class", "datas").selectAll("text").data(graph.nodes).enter();             node.append("title").text(function (d) {                 return d.id;             });             print = node.append("title").text(function (d) {                 return d.id;             });             print.enter().append("text").style("text-anchor", "middle").text(function (d) {                 return d.name;             });             simulation                 .nodes(graph.nodes)                 .on("tick", ticked);             simulation.force("link")                 .links(graph.links);             //tick函数的作用:由于力导向图是不断运动的,每一时刻都在发生更新,因此,必须不断更新节点和连线的位置。             //迭代力项导图位置             function ticked() {                 link                     .attr("x1", function (d) {                         return d.source.x;                     })                     .attr("y1", function (d) {                         return d.source.y;                     })                     .attr("x2", function (d) {                         return d.target.x;                     })                     .attr("y2", function (d) {                         return d.target.y;                     });                 node                     .attr("cx", function (d) {                         return d.x;                     })                     .attr("cy", function (d) {                         return d.y;                     });                 text.attr('transform', function (d) {                     return 'translate(' + d.x + ',' + (d.y + d.size / 2) + ')';                 });             }         });         //激活导图函数         var dragging = false;         // 起始位置         function dragstarted(d) {             if (!d3.event.active) simulation.alphaTarget(0.6).restart();             d.fx = d.x;             d.fy = d.y;             dragging = true;         }         // 画图         function dragged(d) {             d.fx = d3.event.x;             d.fy = d3.event.y;         }         // 结束位置 alphaTarget也代表衰减因子,如果设置为1迭代位置后定死位置         function dragended(d) {             if (!d3.event.active) simulation.alphaTarget(0);             d.fx = null;             d.fy = null;             dragging = false;         }         // 图像/文字 按钮         $('#mode span').click(function (event) {             $('#mode span').removeClass('active');             $(this).addClass('active');             if ($(this).text() == '图形') {                 $('.texts text').hide();                 $('.nodes circle').show();             }             else {                 $('.texts text').show();                 $('.nodes circle').show();             }         });         $('#svg1').on('mouseenter', '.nodes circle', function (event) {             if (!dragging) {                 var name = $(this).attr('name');                 $('#info h4').css('color', $(this).attr('fill')).text(name);                 $('#info p').remove();                 console.log(info[name]);                 for (var key in info[name]) {                     if (typeof(info[name][key]) == 'object') {                         continue;                     }                     if (key == 'url' || key == 'title' || key == 'name' || key == 'edited' || key == 'created' || key == 'homeworld') {                         continue;                     }                     $('#info').append('<p><span>' + key + '</span>' + info[name][key] + '</p>');                 }                 d3.select("#svg1 .nodes").selectAll('circle').attr('class', function (d) {                     if (d.id == name) {                         return '';                     }                     for (var i = 0; i < graph.links.length; i++) {                         if (graph.links[i]['source'].id == name && graph.links[i]['target'].id == d.id) {                             return '';                         }                         if (graph.links[i]['target'].id == name && graph.links[i]['source'].id == d.id) {                             return '';                         }                     }                     return 'inactive';                 });                 d3.select("#svg1 .links").selectAll('line').attr('class', function (d) {                     if (d.source.id == name || d.target.id == name) {                         return '';                     } else {                         return 'inactive';                     }                 });             }         });         $('#svg1').on('mouseleave', '.nodes circle', function (event) {             if (!dragging) {                 d3.select('#svg1 .nodes').selectAll('circle').attr('class', '');                 d3.select('#svg1 .links').selectAll('line').attr('class', '');             }         });         $('#svg1').on('mouseenter', '.texts text', function (event) {             if (!dragging) {                 var name = $(this).attr('name');                 $('#info h4').css('color', $(this).attr('fill')).text(name);                 $('#info p').remove();                 for (var key in info[name]) {                     if (typeof(info[name][key]) == 'object') {                         continue;                     }                     if (key == 'url' || key == 'title' || key == 'name' || key == 'edited' || key == 'created' || key == 'homeworld') {                         continue;                     }                     $('#info').append('<p><span>' + key + '</span>' + info[name][key] + '</p>');                 }                 d3.select('#svg1 .texts').selectAll('text').attr('class', function (d) {                     if (d.id == name) {                         return '';                     }                     for (var i = 0; i < graph.links.length; i++) {                         if (graph.links[i]['source'].id == name && graph.links[i]['target'].id == d.id) {                             return '';                         }                         if (graph.links[i]['target'].id == name && graph.links[i]['source'].id == d.id) {                             return '';                         }                     }                     return 'inactive';                 });                 d3.select("#svg1 .links").selectAll('line').attr('class', function (d) {                     if (d.source.id == name || d.target.id == name) {                         return '';                     } else {                         return 'inactive';                     }                 });             }         });                  $('#svg1').on('mouseleave', '.texts text', function (event) {             if (!dragging) {                 d3.select('#svg1 .texts').selectAll('text').attr('class', '');                 d3.select('#svg1 .links').selectAll('line').attr('class', '');             }         });         $('#search input').keyup(function (event) {             if ($(this).val() == '') {                 d3.select('#svg1 .texts').selectAll('text').attr('class', '');                 d3.select('#svg1 .nodes').selectAll('circle').attr('class', '');                 d3.select('#svg1 .links').selectAll('line').attr('class', '');             }             else {                 var name = $(this).val();                 d3.select('#svg1 .nodes').selectAll('circle').attr('class', function (d) {                     if (d.id.toLowerCase().indexOf(name.toLowerCase()) >= 0) {                         return '';                     } else {                         return 'inactive';                     }                 });                 d3.select('#svg1 .texts').selectAll('text').attr('class', function (d) {                     if (d.id.toLowerCase().indexOf(name.toLowerCase()) >= 0) {                         return '';                     } else {                         return 'inactive';                     }                 });                 d3.select("#svg1 .links").selectAll('line').attr('class', function (d) {                     return 'inactive';                 });             }         });         var info;         d3.json("all.json", function (error, data) {             info = data;         });     }); </script> </html> 复制代码

3.本地部署

当前目录下的结构如下图:

   all.json │  creatNodeLink.ipynb │  getFeature.ipynb │  index.html │  nodes.json │  School_list_2020.csv │  Spider_school.ipynb │  全国高等学校名单.xls │ └─dataprocess         Abbr.txt         Admin.txt         English.txt         Name.txt         Time.txt         Type.txt 复制代码

  • 移动到当前index.html目录下,在目录下进入cmd命令,输入以下代码,快速假设本地服务器。

  • python3一行代码搞定服务器

python -m http.server 8000 复制代码

打开浏览器 http://localhost:8000/ ,查看自己的图谱吧

本项目仅为简单的将半结构化数据转化为知识图谱的可视化实现,技术略显粗糙,数据挖掘和自然语言处理入门萌新,请多多指教~


作者:ArmorHTK
链接:https://juejin.cn/post/7025424422026625054


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐