手把手教你搭建一个中式菜谱知识图谱可视化系统

中式菜谱知识图谱

今天分享一个自己从数据爬取到d3可视化的中式菜谱知识图谱可视化系统的搭建流程。
Github项目地址:https://github.com/ngl567/CookBook-KG
访问可视化系统GithubPage地址:https://ngl567.github.io/CookBook-KG/

1、系统功能

本项目开发的系统名称为AI Food Time,中文名为爱食光。通过收集网上完全公开的有关中式菜谱的数据,经过数据清洗和分析,转换为知识图谱的存储结构,并提供可视化展示和模糊搜索等功能,为热爱美食与烹饪的人们提供方便快捷的中式菜谱服务,并以知识图谱的形式直观显示出不同菜品的关系及所用原料,在生活中具有很大的实际应用需求,包括:

  • 一类菜品的不同具体做法,例如水煮鱼包括麻辣水煮鱼、小清新版水煮鱼和家常版水煮鱼等;
  • 通过菜品与食材的关联关系,可以查询家中现有食材可以烹饪哪些菜品;
  • 可以直接显示出每种菜品所需主料,辅料,配料及其具体数量和烹饪方法,与网上的一些菜谱网页相比更加简单直观;
  • 可视化能够对各种菜品及关联关系有一个全局的认识,并能够显示每种菜品对应的图片;
  • 搜索菜谱关键词,显示出和关键词相关的各类菜品信息。

2、先来看看效果

实体间关联关系及实体信息显示

实体间关联关系及实体信息显示

不同类型实体开关显示

不同类型实体开关显示

搜索功能展示

搜索功能展示

怎么样,是不是觉得还有那么点意思。在可视化系统中,同一类实体用相同颜色的节点表示,鼠标位于某个节点上方时显示其相关联的其它实体和之间的关系名称;具有同一类实体显示开关节点显示模式转换,并支持搜索功能;同时,我们对每种菜品的信息栏中显示菜品对应的成品图片,并进行了实体对齐,消除了食品原料中比如“蒜”和“大蒜”这类的冗余信息。

这里展示的是mini版系统,包含10大类,50种菜品之间的关联关系,包括菜品制作的各种食材制作步骤,建议用电脑浏览器打开,如需体验可直接进入Github Page(如果打开没有可视化显示,请尝试多刷新几次)访问入口

3、系统实现流程

3.1 数据爬取

所有菜谱的数据都是从完全公开的网页上爬取的,这些网页上的数据是以半结构化知识呈现的,如图所示:


网页上的数据

使用XPath可以很轻松地从网页上将这些半结构化知识爬取出来,先以树形结构存储菜谱及属性数据:

                       菜品大类
                         |_具体的精品特色菜
                                 |_主料
                                 |_辅料
                                 |_配料
                                 |_制作步骤

接着,对于树形结构存储的数据,以三元组的格式:菜品大类-属于-具体的精品特色菜,精品特色菜-主料-主料名,精品特色菜-配料-配料名,精品特色菜-辅料-辅料名,精品特色菜-制作步骤-制作步骤列表表示所有数据。用于可视化的数据分为三元组组成的关系图结构数据vizdata.json和实体属性组成的数据entities_items.json。

三元组组成的关系图结构数据vizdata.json存储的是字典数据,“links”键对应的是所有头实体-关系-尾实体组成的三元组,“nodes”设定了节点的类型、名称和大小等属性。

    "links": [                                                                           
        {
            "relation": "选材",
            "source": "山楂红烧肉",
            "target": "五花肉",
            "value": 3
        },

        {
            "relation": "选材",
            "source": "山楂红烧肉",
            "target": "山楂",
            "value": 3
        },
        ...
    ]

    "nodes": [
        {
            "class": "菜品大类",
            "group": "0",
            "id": "红烧肉",
            "size": "16"
        },
        {
            "class": "精品特色菜",
            "group": "1",
            "id": "家庭版水煮活鱼",
            "size": "10"
        },
        ...
    ]

实体属性组成的entities_items.json文件存储所有实体属性的字典数据,是以”菜品“为一级索引,对应的主料、辅料、配料和制作步骤分别为二级索引,以及属性值组成的列表,如下所示:

"桂香红烧肉": {    

"主料": [    
"五花肉: 两条"    
],    
"辅料": [    
"葱: 一根",    
"姜: 两片",    
"桂皮: 一块",    
"香叶: 两片"    
],    
"配料": [    
"老抽: 一汤匙",    
"生抽: 两汤匙",    
"冰糖: 适量",    
"料酒: 两汤匙"    
],    
"特色": [    
"口味: 咸甜",    
"工艺: 烧",    
"耗时: 一小时",    
"难度: 简单"    
],    
"制作步骤": [    
"1: 五花肉放入沸水中,煮至断生,洗净",    
"2: 准备材料",    
"3: 五花肉切块",    
"4: 取砂锅,热锅冷油,加入葱姜,桂皮,香叶爆香。放入五花肉,加高汤。",    
"5: 加入冰糖,料酒,生抽和老抽,加盖小火煮一个小时,开盖收汁。"    
]    
},... 

3.2 D3可视化

D3是基于数据的文档操作javascript库,D3能够把数据和HTML、SVG、CSS结合起来,创造出可交互的数据图表。我分别用D3知识图谱力导向图和Neo4j分别构建了知识图谱可视化系统,D3在可视化方面具有更好的展示和灵活性,因此选用D3进行知识图谱的可视化。

对于上面得到的关系图数据vizdata.json和实体属性数据entities_itmes.json两个文件可以存储在自己的github项目中,因为D3可视化只支持从web服务读取json数据。由于字数限制,本文给出D3可视化的几个主要模块。

首先,需要设定可视化的样式,具体可以看github中的代码。然后,需要从json文件中读取关系图数据:

var graph;    
d3.json("https://raw.githubusercontent.com/ngl567/CookBook-KG/master/visualization
/vizdata_mimini_aglin.json", function(error, data) 

用vizdata.json中的links数据去驱动两个节点之间的边的线宽:

//边上的文字(实体之间的关系) 
var linktext = svg.append('g')    
.attr("class", "linetexts")    
.selectAll("text")    
.data(graph.links)    
.enter()    
.append("text")    
.style("display","block")    
.style("color","red")    
.text(function(d){    
return d.relation;    
});    

用vizdata.json中的links数据去驱动两个节点之间的边的线宽:

//边上的文字(实体之间的关系)                                                             
var linktext = svg.append('g')    
.attr("class", "linetexts")    
.selectAll("text")    
.data(graph.links)    
.enter()    
.append("text")    
.style("display","block")    
.style("color","red")    
.text(function(d){    
return d.relation;    
});  

添加所有的节点,并对每个节点按照不同的类型设置节点颜色:

// 添加所有的node                                                                           
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()             // 绑定d3的拖动函数    
.on("start", dragstarted) // 拖动开始    
.on("drag", dragged)      // 拖动进行    
.on("end", dragended));   // 拖动结束

通过点击圆点和文字两种方式表示节点切换不同的模式:

// 处理模式点击后的事件(这些元素页面上本来有)                                          
$('#mode span').click(function(event) {    
// 把mode里面所有span的active全部去掉    
// 把被点击的这个设置为active    
$('#mode span').removeClass('active')    
$(this).addClass('active')    
if ($(this).text() == 'Circles') {    
// 隐藏所有文本里面的svg元素    
// 把node里面的显示出来    
$('.texts text').hide();    
$('.nodes circle').show();    
}    
else {    
$('.texts text').show();    
$('.nodes circle').hide ();    
}    
});

不同类型的实体有一个开关,决定一类实体节点是否显示:

// 处理开关1点击后的事件(这些元素页面上本来有)                                       
$('#switch1 span').click(function(event) {    
// 把mode里面所有span的active全部去掉    
// 把被点击的这个设置为active    
$('#switch1 span').removeClass('active')    
$(this).addClass('active')    
if ($(this).text() == 'On') {    
sw1 = true;    
d3.select('#svg1 .nodes').selectAll('circle').attr('class', function(d){    
// 当前选中类型实体显示    
if (d.group == 0 && sw1 == true) {    
return '';    
}    
else if (d.group == 1 && sw2 == true){    
return '';    
}    
else if (d.group == 2 && sw3 == true){    
return '';    
}    
else{    
return 'inactive'    
}    
});  

当鼠标悬浮在某个实体节点上方时,实体的属性信息都能够显示出来,如果是精品特色菜类的实体,菜品图片等信息都能显示:

// 增加各个菜品的图片    
if (typeof(info[name]) != "undefined") {    
//avatar_ID = info[name]['ID'][0]    
//if(outlier_avatar_ID.indexOf(avatar_ID) != -1) {    
//    avatar_ID = avatar_ID + '0'    
//}    
if ('主料' in info[name]){    
$('#info').append('<p>' + '<img src="https://raw.githubusercontent.com/ngl567/CookBook-KG
/master/visualization/recipe_photo/' + name + '.jpg" />' + '</p>');    
}    
}    
for (var key in info[name]) {    
value = info[name][key];    
var flag_none = false;    
for (var item in value) {    
if (value[item] == null || value[item] == 'N/A' || value[item] == '') {    
flag_none = true;    
break;    
}    
}     
if (flag_none == true) {              // 排除为空的属性值    
continue;    
}    

$('#info').append('<p><span>' + key + '</span></p>');    
var item_info = '';    
count = 0    
for (var food_item in info[name][key]){    
if (count == 0){    
item_info = item_info + info[name][key][food_item];    
}    
else{    
item_info = item_info + "&nbsp;||&nbsp;" + info[name][key][food_item];    
}    
count = count + 1;    
}    
$('#info').append('<p>' + item_info + '</p>');   
} 

设置搜索功能,按照搜索框中的关键词显示所有匹配到关键词的所有节点:

// 搜索框    
$('#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', '');;    
d3.select("#svg1 .linetexts").selectAll('text').attr('fill-opacity', 0);    
}    
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';    
});    
d3.select("#svg1 .linetexts").selectAll('text').attr('fill-opacity', 0);    
}    
});        

具体一些比如页面设计的代码具体可以看github中的代码,如果有任何问题也可以与我交流讨论,希望这个工作可能帮助大家一起来做点有意思的小项目。

同时,如果对我们的文章感兴趣,欢迎关注”人工智能遇上知识图谱“微信公众号,也欢迎关注知乎专栏“人工智能遇上知识图谱“,我们会力求为您呈上人工智能和知识图谱领域的精彩内容。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343