GitHub地址:https://github.com/weijie-he/jinyong
一痹筛、緣起
2018年10月30日莺治,金庸在香港逝世廓鞠,享年94歲。
知道這個(gè)消息之后谣旁,我的情緒很低落床佳,講臺(tái)上老師在講什么仿佛也聽(tīng)不見(jiàn)了,腦海中一直在回想著先生寫(xiě)過(guò)的關(guān)于離別的句子榄审。
程英道:“三妹砌们,你瞧這些白云聚了又散,散了又聚搁进,人生離合浪感,亦復(fù)如斯。你又何必?zé)辣剩俊?她話雖如此說(shuō)影兽,卻也忍不住流下淚來(lái)。
卻聽(tīng)得楊過(guò)朗聲說(shuō)道:“今番良晤匆瓜,豪興不淺赢笨,他日江湖相逢,再當(dāng)杯酒言歡驮吱。咱們就此別過(guò)茧妒。”
金庸先生告訴了我什么是“俠”左冬。作為先生的忠實(shí)讀者桐筏,我覺(jué)得自己該做點(diǎn)什么來(lái)緬懷先生,以我自己的方式拇砰。
正好梅忌,我在學(xué)Spark,便想到了利用Spark Graphx 做金庸小說(shuō)人物關(guān)系分析圖除破。
二牧氮、需求分析
金庸先生給我們留下了什么呢?最著名的無(wú)非是“飛雪連天射白鹿瑰枫,笑書(shū)神俠倚碧鴛”這14本小說(shuō)了踱葛。最容易想到的便是對(duì)這14本書(shū)做一張人物關(guān)系分析圖。但這一來(lái)人物太多光坝,最后畫(huà)出的圖會(huì)很大尸诽;二來(lái)不同書(shū)之間的人物很多也沒(méi)什么關(guān)聯(lián),硬把他們放在同一張圖里并不妥當(dāng)盯另。最終我決定只選取人物聯(lián)系最緊密的“射雕三部曲”(《射雕英雄傳》性含、《神雕俠侶》、《倚天屠龍記》)來(lái)進(jìn)行分析鸳惯。
但是只分析人物又感覺(jué)略顯單薄商蕴。金庸小說(shuō)中還有一些其他的元素叠萍,比如如雷貫耳的稱(chēng)號(hào)(東邪西毒)蛮位、高深莫測(cè)的武功(黯然銷(xiāo)魂掌)碎捺、神兵利器(倚天劍其馏、屠龍刀)拆宛。我想把這些元素也加入到分析之中茂嗓。
同時(shí)還要考慮怎么利用Spark Graphx 的圖計(jì)算功能凶异,做一些有意義的分析改抡。
最終確立了以下需求:
分析人物之間的親密度關(guān)系
找出“專(zhuān)屬昵稱(chēng)”
(很多人物之間的交流并不會(huì)直呼其名伟众,比如黃蓉會(huì)叫郭靖“靖哥哥”理张,我想找出類(lèi)似的“專(zhuān)屬昵稱(chēng)”)
探索小說(shuō)人物中“孤島群體”(即“小圈子”)
有沒(méi)有誰(shuí)經(jīng)常被某種武功/兵器揍
三赫蛇、工作流程
3.1 獲取"射雕三部曲"小說(shuō)原文、人物名冊(cè)雾叭、稱(chēng)號(hào)\武功\武器大全等初始數(shù)據(jù)
? ? 小說(shuō)原文很容易獲取悟耘,人物名冊(cè)、稱(chēng)號(hào)\武功\武器大全 等也可以在網(wǎng)上搜到织狐。
3.2 數(shù)據(jù)預(yù)處理暂幼,將數(shù)據(jù)轉(zhuǎn)換成Graphx需要的格式
? ? Graphx需要的是頂點(diǎn)集和邊集的信息。
? ? 在人物親密度圖中移迫,我將人名旺嬉、昵稱(chēng)作為頂點(diǎn);在人物—武器關(guān)系圖中厨埋,我將人名邪媳、武器、武功作為頂點(diǎn)荡陷。
? 至于邊集信息雨效,是這樣確定的:以原文中每一句話為單位。如果在這句話中废赞,出現(xiàn)了兩個(gè)上述的“頂點(diǎn)”徽龟,則認(rèn)為他們產(chǎn)生了一次聯(lián)系。如果在這句話中唉地,出現(xiàn)了三個(gè)“頂點(diǎn)”据悔,則認(rèn)為他們兩兩之間都有一次聯(lián)系。以此類(lèi)推渣蜗。
處理完的結(jié)果保存在resources文件夾中屠尊。結(jié)果如下所示
?
?
3.3 使用Spark GraphX 生成圖
我想把聯(lián)系的次數(shù)作為邊的權(quán)重旷祸。首先就要統(tǒng)計(jì)同一個(gè)聯(lián)系出現(xiàn)的次數(shù)耕拷。這一步有點(diǎn)像WordCount,由于不想讓一些打醬油的人物出現(xiàn)托享,所以還用了個(gè)filter函數(shù)過(guò)濾骚烧。
/**
* 統(tǒng)計(jì)關(guān)系出現(xiàn)的次數(shù)
* @param sc
* @param path:邊文件
* @param num:關(guān)系數(shù)量閾值
* @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
? }
使用頂點(diǎn)集和邊集構(gòu)建圖
/**
* 構(gòu)建圖
* @param sc
* @param path1:頂點(diǎn)文件
* @param path2:邊文件
* @param num:關(guān)系數(shù)量閾值
*/
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)//起始點(diǎn)ID必須為L(zhǎng)ong浸赫,最后一個(gè)是屬性,可以為任意類(lèi)型
?? }
valgraph_tmp=Graph.fromEdges(edges,1L)
// ?? 經(jīng)過(guò)過(guò)濾后有些頂點(diǎn)是沒(méi)有邊赃绊,所以采用leftOuterJoin將這部分頂點(diǎn)去除
valvertices=graph_tmp.vertices.leftOuterJoin(verticesAll).map(x=>(x._1,x._2._2.getOrElse("")))
valgraph=Graph(vertices,edges)
?
graph
? }
至此既峡,需求中的第一點(diǎn):人物親密度關(guān)系圖已經(jīng)生成。
類(lèi)似的碧查,我們更換一下頂點(diǎn)集和邊集运敢,就可以生成人物——武器\武功的關(guān)系圖,從而找出有沒(méi)有誰(shuí)經(jīng)常被某種武功/兵器揍忠售。
3.4 使用Spark GraphX 處理圖
可以通過(guò)找出度為1或2的點(diǎn)传惠,來(lái)尋找“專(zhuān)屬昵稱(chēng)”。
/**
* 找出度為1或2的點(diǎn)
* @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)
? }
通過(guò)使用內(nèi)置函數(shù)connectedComponents()可以找到小說(shuō)人物中“孤島群體”(即“小圈子”)稻扬。
/**
* 使用連通組件找到孤島人群
* @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")
? }
由于之前我們是每本書(shū)都生成一張圖卦方,最后我們還需要把這幾張圖合并為一張圖。
思路就是先取得所有頂點(diǎn)信息泰佳,去除盼砍,再對(duì)這些頂點(diǎn)重新編號(hào)。再對(duì)這些新生成的點(diǎn)重新構(gòu)建邊逝她。
/**
* 合并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 導(dǎo)出到Gephi
我們可以把圖像按照gexf格式輸出浇坐,然后在Gephi中打開(kāi),就可以進(jìn)行圖形化展示黔宛。
/**
* 輸出為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>"
?
? }
四吗跋、結(jié)果展示與分析
以下圖片的高清完整版可在output/pics中找到
4.1 人物親密度關(guān)系分析
可以看出郭靖和黃蓉的顏色是最深的(聯(lián)系是最緊密的)。這是因?yàn)樗麄冊(cè)凇渡涞瘛泛汀渡竦瘛分卸加泻芏鄳蚍菽选跌宛!渡竦瘛分械哪信餍↓埮蜅钸^(guò)聯(lián)系也很緊密。相比之下《倚天》中的男女主張無(wú)忌和趙敏直接的線就淡的多了积仗。一方面疆拘,這是因?yàn)橼w敏的出場(chǎng)時(shí)間太晚(全書(shū)40章,趙敏在第23章才出場(chǎng))寂曹。另一方面哎迄,張無(wú)忌優(yōu)柔寡斷,情感方面也一直在趙敏和周芷若之間猶豫不決隆圆,導(dǎo)致張無(wú)忌的情感線被周芷若分流了許多漱挚。
4.2 “專(zhuān)屬昵稱(chēng)”分析
?
?
由于我只是篩選出了度為1和2的點(diǎn),但有些點(diǎn)是人名渺氧,而不是昵稱(chēng)旨涝,不必看。
我原來(lái)以為“專(zhuān)屬昵稱(chēng)”只出現(xiàn)在情侶之間侣背,但發(fā)現(xiàn)有兩個(gè)例外白华。
洪七公——靖兒
這兩人情同父子慨默。郭靖自幼喪父,洪七公也沒(méi)有子嗣弧腥。俗話說(shuō)厦取,“一日為師終身為父”,我覺(jué)得這兩個(gè)人不是父子管搪,甚是父子虾攻。所以有這樣的“專(zhuān)屬昵稱(chēng)”也不奇怪。
也許江南七怪也和郭靖情同父子更鲁,但可能是因?yàn)槌霈F(xiàn)的頻率不夠高台谢,所以被過(guò)濾掉了,這張圖上并沒(méi)有出現(xiàn)岁经。
陸無(wú)雙——傻蛋
全書(shū)只有陸無(wú)雙一人可以叫楊過(guò)”傻蛋“朋沮,因?yàn)楫?dāng)初楊過(guò)騙陸無(wú)雙自稱(chēng)傻蛋。
那道姑笑道:“我?guī)讜r(shí)騙過(guò)你了缀壤?喂樊拓,小子,你叫甚么名字塘慕?”楊過(guò)道:“人人都叫我傻蛋筋夏,你不知道么?你叫甚么名字图呢?”那道姑笑道:“傻蛋条篷,你只叫我仙姑就得啦「蛑”
摘錄了一下原文赴叹,發(fā)現(xiàn)短短幾句話,這道姑(陸無(wú)雙)就笑了2次指蚜,足見(jiàn)他們相處的多么愉快乞巧。過(guò)兒一生孤苦,和陸無(wú)雙在一起的日子也算是為數(shù)不多的快樂(lè)時(shí)光摊鸡。我覺(jué)得他們倆很有成為情侶的可能绽媒,只可惜過(guò)兒心里已經(jīng)有了小龍女。最后他們倆結(jié)為了兄妹免猾,也算是一段“有情人終成兄妹”的悲劇故事是辕。
4.3 “孤島人群”分析
?
?
發(fā)現(xiàn)只有3個(gè)“孤島人群”(小團(tuán)體)。
簡(jiǎn)捷和薛公遠(yuǎn)是《倚天屠龍記》中被金花婆婆打傷猎提,找胡青牛治病的人获三。和他們有交集的人確實(shí)很少。
李萍被段天德綁架,很長(zhǎng)一段時(shí)間內(nèi)只有他們兩個(gè)在一起石窑,別人都不知道他們?nèi)チ四摹?/p>
術(shù)赤和察合臺(tái)是成吉思汗的兩個(gè)兒子。和他們有交集的人也很少蚓炬。
這三本書(shū)中涉及到的人物松逊,即使過(guò)濾完,也有將近200號(hào)人肯夏。如果在現(xiàn)實(shí)生活中经宏,200人中應(yīng)該會(huì)有更多的小團(tuán)體,而且也不會(huì)全是2人組驯击,可能有3~5人小團(tuán)體烁兰。
以下是我認(rèn)為可能的兩點(diǎn)原因:
小說(shuō)中,配角是為主角服務(wù)的徊都,一般不會(huì)獨(dú)立于主線人物之外去寫(xiě)小團(tuán)體
即便需要沪斟,寫(xiě)2人也夠了,沒(méi)必要花筆墨寫(xiě)
4.4 人物——武功\兵器分析
主要想看誰(shuí)經(jīng)常被哪種武功\兵器揍暇矫。
無(wú)忌——玄冥神掌
無(wú)忌小時(shí)候就因?yàn)橹辛诵ど裾撇铧c(diǎn)死掉主之,長(zhǎng)大后也經(jīng)常和玄冥二老斗。
郭靖——蛤蟆功
蛤蟆功可以說(shuō)是郭靖發(fā)明的李根,就是因?yàn)樗鄹牧恕毒抨幷娼?jīng)》槽奕,寫(xiě)了本“九陰假經(jīng)”,才讓歐陽(yáng)鋒練成了蛤蟆功房轿。后來(lái)也數(shù)次和歐陽(yáng)鋒的蛤蟆功交手粤攒。《神雕》中小楊過(guò)也學(xué)了點(diǎn)蛤蟆功囱持,被郭靖發(fā)現(xiàn)了夯接,這又產(chǎn)生了一次交集。
楊過(guò)——金輪纷妆、浮塵
這是書(shū)中兩大反派金輪法王和李莫愁的武器钻蹬。
五、后記
? 人人都知道金庸凭需,可大多是通過(guò)影視作品问欠,讀過(guò)原著的人少的可憐。做這個(gè)項(xiàng)目粒蜈,在緬懷先生的同時(shí)顺献,也希望有更多的人能去讀一讀原著,體會(huì)一下先生筆下原汁原味的江湖枯怖。