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

您所在的位置:网站首页 momoland中文名 [📌Demo]使用结构化信息构建知识图谱

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

#[📌Demo]使用结构化信息构建知识图谱| 来源: 网络整理| 查看: 265

「这是我参与11月更文挑战的第1天,活动详情查看:2021最后一次更文挑战」

文档作者:ArmorHTK

开源资料:www.openkg.cn/dataset/202…

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

简介

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

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

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

所以本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…

效果展示

2020年中国普通高等学校图谱可视化 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; } 2020年中国普通高等学校知识图谱 图形 文字 $(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("" + names[i] + ""); } // 相互作用力,定义鼠标拖拽时的效果 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('

' + key + '' + info[name][key] + '

'); } 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('

' + key + '' + info[name][key] + '

'); } 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; }); }); 复制代码 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/ ,查看自己的图谱吧

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



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3