使用Spark Graphx 探索你不知道的金庸武侠世界

GitHub地址:https://github.com/weijie-he/jinyong

一、缘起

2018年10月30日,金庸在香港逝世,享年94岁。

知道这个消息之后,我的情绪很低落,讲台上老师在讲什么仿佛也听不见了,脑海中一直在回想着先生写过的关于离别的句子。

程英道:“三妹,你瞧这些白云聚了又散,散了又聚,人生离合,亦复如斯。你又何必烦恼?” 她话虽如此说,却也忍不住流下泪来。

却听得杨过朗声说道:“今番良晤,豪兴不浅,他日江湖相逢,再当杯酒言欢。咱们就此别过。”

金庸先生告诉了我什么是“侠”。作为先生的忠实读者,我觉得自己该做点什么来缅怀先生,以我自己的方式。

正好,我在学Spark,便想到了利用Spark Graphx 做金庸小说人物关系分析图。

二、需求分析

金庸先生给我们留下了什么呢?最著名的无非是“飞雪连天射白鹿,笑书神侠倚碧鸳”这14本小说了。最容易想到的便是对这14本书做一张人物关系分析图。但这一来人物太多,最后画出的图会很大;二来不同书之间的人物很多也没什么关联,硬把他们放在同一张图里并不妥当。最终我决定只选取人物联系最紧密的“射雕三部曲”(《射雕英雄传》、《神雕侠侣》、《倚天屠龙记》)来进行分析。

但是只分析人物又感觉略显单薄。金庸小说中还有一些其他的元素,比如如雷贯耳的称号(东邪西毒)、高深莫测的武功(黯然销魂掌)、神兵利器(倚天剑、屠龙刀)。我想把这些元素也加入到分析之中。

同时还要考虑怎么利用Spark Graphx 的图计算功能,做一些有意义的分析。

最终确立了以下需求:

分析人物之间的亲密度关系

找出“专属昵称”

(很多人物之间的交流并不会直呼其名,比如黄蓉会叫郭靖“靖哥哥”,我想找出类似的“专属昵称”)

探索小说人物中“孤岛群体”(即“小圈子”)

有没有谁经常被某种武功/兵器揍

三、工作流程

3.1 获取"射雕三部曲"小说原文、人物名册、称号\武功\武器大全等初始数据

  ​ 小说原文很容易获取,人物名册、称号\武功\武器大全 等也可以在网上搜到。

3.2 数据预处理,将数据转换成Graphx需要的格式

  ​ Graphx需要的是顶点集和边集的信息。

  ​ 在人物亲密度图中,我将人名、昵称作为顶点;在人物—武器关系图中,我将人名、武器、武功作为顶点。

  至于边集信息,是这样确定的:以原文中每一句话为单位。如果在这句话中,出现了两个上述的“顶点”,则认为他们产生了一次联系。如果在这句话中,出现了三个“顶点”,则认为他们两两之间都有一次联系。以此类推。

处理完的结果保存在resources文件夹中。结果如下所示



3.3 使用Spark GraphX 生成图

我想把联系的次数作为边的权重。首先就要统计同一个联系出现的次数。这一步有点像WordCount,由于不想让一些打酱油的人物出现,所以还用了个filter函数过滤。

/**

* 统计关系出现的次数

* @param sc

* @param path:边文件

* @param num:关系数量阈值

* @return

*/

defedgeCount(sc:SparkContext,path:String,num:Int)={

valtextFile=sc.textFile(path)

valcounts=textFile.map(word=>(word,1))

.reduceByKey(_+_).filter(_._2>num)

//    counts.collect().foreach(println)

counts

  }

使用顶点集和边集构建图

/**

* 构建图

* @param sc

* @param path1:顶点文件

* @param path2:边文件

* @param num:关系数量阈值

*/

defcreatGraph(sc:SparkContext,path1:String,path2:String,num:Int)={

valhero=sc.textFile(path1)

valcounts=edgeCount(sc,path2,num)

valverticesAll=hero.map{line=>

valfields=line.split('')

(fields(0).toLong,fields(1))

   }

valedges=counts.map{line=>

valfields=line._1.split(" ")

Edge(fields(0).toLong,fields(1).toLong,line._2)//起始点ID必须为Long,最后一个是属性,可以为任意类型

   }

valgraph_tmp=Graph.fromEdges(edges,1L)

//    经过过滤后有些顶点是没有边,所以采用leftOuterJoin将这部分顶点去除

valvertices=graph_tmp.vertices.leftOuterJoin(verticesAll).map(x=>(x._1,x._2._2.getOrElse("")))

valgraph=Graph(vertices,edges)

graph

  }

至此,需求中的第一点:人物亲密度关系图已经生成。

类似的,我们更换一下顶点集和边集,就可以生成人物——武器\武功的关系图,从而找出有没有谁经常被某种武功/兵器揍。

3.4 使用Spark GraphX 处理图

可以通过找出度为1或2的点,来寻找“专属昵称”。

/**

* 找出度为1或2的点

* @param g

* @tparam VD

* @tparam ED

* @return

*/

defminDegrees[VD,ED](g:GraphOps[VD,ED])={

//    g.degrees.filter(_._2<3).map(_._1).collect().mkString("\n")

g.degrees.filter(_._2<3).map(_._1).collect().map(a=>a.toInt)

  }

通过使用内置函数connectedComponents()可以找到小说人物中“孤岛群体”(即“小圈子”)。

/**

* 使用连通组件找到孤岛人群

* @param g

* @tparam VD

* @tparam ED

* @return

*/

defisolate[VD,ED](g:GraphOps[VD,ED])={

g.connectedComponents.vertices.map(_.swap).groupByKey().map(_._2).collect().mkString("\n")

  }

由于之前我们是每本书都生成一张图,最后我们还需要把这几张图合并为一张图。

思路就是先取得所有顶点信息,去除,再对这些顶点重新编号。再对这些新生成的点重新构建边。

/**

* 合并2张图

* @param g1

* @param g2

* @return

*/

defmergeGraphs(g1:Graph[String,Int],g2:Graph[String,Int])={

valv=g1.vertices.map(_._2).union(g2.vertices.map(_._2)).distinct().zipWithIndex()

defedgeWithNewVid(g:Graph[String,Int])={

g.triplets.map(et=>(et.srcAttr,(et.attr,et.dstAttr)))

.join(v)

.map(x=>(x._2._1._2,(x._2._2,x._2._1._1)))

.join(v)

.map(x=>newEdge(x._2._1._1,x._2._2,x._2._1._2))

   }

defreduceEdge(g3:Graph[String,Int],g4:Graph[String,Int])={

edgeWithNewVid(g3).union(edgeWithNewVid(g4)).

map(e=>((e.dstId,e.srcId),e.attr)).

reduceByKey(_+_).

map(e=>Edge(e._1._1,e._1._2,e._2))

   }

Graph(v.map(_.swap),reduceEdge(g1,g2))

  }

3.5 导出到Gephi

我们可以把图像按照gexf格式输出,然后在Gephi中打开,就可以进行图形化展示。

/**

* 输出为gexf格式

* @param g:图

* @tparam VD

* @tparam ED

* @return

*/

deftoGexf[VD,ED](g:Graph[VD,ED])={

"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"+

"<gexf xmlns=\"http://www.gexf.net/1.2draft\" version=\"1.2\">\n"+

" <graph mode=\"static\" defaultedgetype=\"directed\">\n  "+

"<nodes>\n "+

g.vertices.map(v=>"  <node id=\""+v._1+"\" label=\""+v._2+"\" />\n").collect().mkString+

"</nodes>\n  "+

"<edges>\n"+

g.edges.map(e=>"  <edge source=\""+e.srcId+"\" target=\""+e.dstId+"\" weight=\""+e.attr+"\"/>\n").

collect().mkString+

"</edges>\n        </graph>\n      </gexf>"

  }

四、结果展示与分析

以下图片的高清完整版可在output/pics中找到

4.1 人物亲密度关系分析

可以看出郭靖和黄蓉的颜色是最深的(联系是最紧密的)。这是因为他们在《射雕》和《神雕》中都有很多戏份。《神雕》中的男女主小龙女和杨过联系也很紧密。相比之下《倚天》中的男女主张无忌和赵敏直接的线就淡的多了。一方面,这是因为赵敏的出场时间太晚(全书40章,赵敏在第23章才出场)。另一方面,张无忌优柔寡断,情感方面也一直在赵敏和周芷若之间犹豫不决,导致张无忌的情感线被周芷若分流了许多。

4.2 “专属昵称”分析

由于我只是筛选出了度为1和2的点,但有些点是人名,而不是昵称,不必看。

我原来以为“专属昵称”只出现在情侣之间,但发现有两个例外。

洪七公——靖儿

这两人情同父子。郭靖自幼丧父,洪七公也没有子嗣。俗话说,“一日为师终身为父”,我觉得这两个人不是父子,甚是父子。所以有这样的“专属昵称”也不奇怪。

也许江南七怪也和郭靖情同父子,但可能是因为出现的频率不够高,所以被过滤掉了,这张图上并没有出现。

陆无双——傻蛋

全书只有陆无双一人可以叫杨过”傻蛋“,因为当初杨过骗陆无双自称傻蛋。

那道姑道:“我几时骗过你了?喂,小子,你叫甚么名字?”杨过道:“人人都叫我傻蛋,你不知道么?你叫甚么名字?”那道姑道:“傻蛋,你只叫我仙姑就得啦。”

摘录了一下原文,发现短短几句话,这道姑(陆无双)就笑了2次,足见他们相处的多么愉快。过儿一生孤苦,和陆无双在一起的日子也算是为数不多的快乐时光。我觉得他们俩很有成为情侣的可能,只可惜过儿心里已经有了小龙女。最后他们俩结为了兄妹,也算是一段“有情人终成兄妹”的悲剧故事。

4.3 “孤岛人群”分析

发现只有3个“孤岛人群”(小团体)。

简捷和薛公远是《倚天屠龙记》中被金花婆婆打伤,找胡青牛治病的人。和他们有交集的人确实很少。

李萍被段天德绑架,很长一段时间内只有他们两个在一起,别人都不知道他们去了哪。

术赤和察合台是成吉思汗的两个儿子。和他们有交集的人也很少。

这三本书中涉及到的人物,即使过滤完,也有将近200号人。如果在现实生活中,200人中应该会有更多的小团体,而且也不会全是2人组,可能有3~5人小团体。

以下是我认为可能的两点原因:

小说中,配角是为主角服务的,一般不会独立于主线人物之外去写小团体

即便需要,写2人也够了,没必要花笔墨写

4.4 人物——武功\兵器分析

主要想看谁经常被哪种武功\兵器揍。

无忌——玄冥神掌

无忌小时候就因为中了玄冥神掌差点死掉,长大后也经常和玄冥二老斗。

郭靖——蛤蟆功

蛤蟆功可以说是郭靖发明的,就是因为他篡改了《九阴真经》,写了本“九阴假经”,才让欧阳锋练成了蛤蟆功。后来也数次和欧阳锋的蛤蟆功交手。《神雕》中小杨过也学了点蛤蟆功,被郭靖发现了,这又产生了一次交集。

杨过——金轮、浮尘

这是书中两大反派金轮法王和李莫愁的武器。

五、后记

  人人都知道金庸,可大多是通过影视作品,读过原著的人少的可怜。做这个项目,在缅怀先生的同时,也希望有更多的人能去读一读原著,体会一下先生笔下原汁原味的江湖。

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