上級 | Python を使用して金庸の小説の世界を探索する 20,000 語

リリース: 2023-08-09 17:26:03
転載
1373 人が閲覧しました


今日は、Xiao Ming の学生が Python を使用して作成したオリジナルの作品を共有します。ジンヨンが書いた世界を探検してみませんか?

娯楽と学習のために Python で小説を読んでみませんか。

#関係する知識ポイントは次のとおりです:

  1. ##従来の小説 Web サイトのアイデアをクロールする
  2. ##基本的な pandas データの並べ替え
  3. ##lxml xpath を使用したアプリケーション スキル
  4. #通常のパターン マッチング
  5. カウンターワード頻度統計
  6. pyechartsデータ視覚化
  7. stylecloud ワード クラウド チャート
  8. ##gensim.models.Word2Vecの使用
  9. ##scipy.cluster.hierarchy 階層クラスタリング
  10. #この記事は、従来のマッチング論理分析遷移から始まります。単語ベクトルの機械学習や総合的なテキスト分析など、学ぶ価値のある情報が満載です。

    金庸の小説集

    かつては金庸の小説のウェブサイトがたくさんありましたが、そのほとんどが現在アクセスできなくなっています。ファンの皆さん、新しいウェブサイトが常に登場しています。最近、Baidu 経由で今でもアクセスできる金庸の小説の Web サイトを見つけました: aHR0cDovL2ppbnlvbmcxMjMuY29tLw==

    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    しかし、収集したデータはすでに準備しました。データを直接ダウンロードして、この章の内容をスキップすることもできます。

    データソースダウンロードアドレス: https://gitcode.net/as604049322/blog_data

    小説作品ごとdate

    以下では、まず、これら 15 作品の名前、作成年、および対応するリンクを取得します。開発者ツールからは、各行に多くの a タグがあることがわかります。必要なノードの特徴は、後続の隣接ノードの後に​​作成日の文字列が続くことです:

    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    その後、すべての a タグを調べて、次の隣接ノードのコンテンツが日付形式に準拠しているかどうかを判断できます。最終的な完全なダウンロード コードは次のとおりです:
    import requests
    from lxml import etree
    import pandas as pd
    import re
    
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36",
        "Accept-Language": "zh-CN,zh;q=0.9"
    }
    res = requests.get(base_url, headers=headers)
    res.encoding = res.apparent_encoding
    html = etree.HTML(res.text)
    a_tags = html.xpath("//div[@class='jianjie']/p/a")
    data = []
    for a_tag in a_tags:
        m_obj = re.search("\((\d{4}(?:—\d{4})?年)\)", a_tag.tail)
        if m_obj:
            data.append((a_tag.text, m_obj.group(1), a_tag.attrib["href"]))
    data = pd.DataFrame(data, columns=["名称", "创作时间", "网址"])
    ログイン後にコピー

    作成日順に並べて表示できます :

    data.sort_values("创作时间", ignore_index=True, inplace=True)
    data
    ログイン後にコピー
    #白马暁西风1961/baimaxiaoxifeng/イーティアン トゥロン ジ1961/yitiantulongji/元鸯刀1961/元陽島/##1963-1966/tianlongbabu/連城居1963/連城居/#夏克興##シャオアオジャンフー 鹿と大釜

    章节页下载与顺序校正

    下面看看章节页节点的分布情况,以《雪山飞狐》为例:

    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    同时可以看到部分小说的节点出现了倒序的情况,我们需要在识别出倒序时将其正序,完整代码:
    from urllib.parse import urljoin
    
    
    def getTitleAndUrl(url):
        url = urljoin(base_url, url)
        data = []
        res = requests.get(url, headers=headers)
        res.encoding = res.apparent_encoding
        html = etree.HTML(res.text)
        reverse, last_num = False, None
        for i, a_tag in enumerate(html.xpath("//dl[@class='cat_box']/dd/a")):
            data.append([re.sub("\s+", " ", a_tag.text), a_tag.attrib["href"]])
            nums = re.findall("第(\d+)章", a_tag.text)
            if nums:
                if last_num and int(nums[0]) < last_num:
                    reverse = True
                last_num = int(nums[0])
        # 顺序校正并删除后记之后的内容
        if reverse:
            data.reverse()
        return data
    ログイン後にコピー

    测试一下:

    title2url = getTitleAndUrl(data.query("名称==&#39;雪山飞狐&#39;").网址.iat[0])
    title2url
    ログイン後にコピー
    [[&#39;第01章&#39;, &#39;/xueshanfeihu/488.html&#39;],
     [&#39;第02章&#39;, &#39;/xueshanfeihu/489.html&#39;],
     [&#39;第03章&#39;, &#39;/xueshanfeihu/490.html&#39;],
     [&#39;第04章&#39;, &#39;/xueshanfeihu/491.html&#39;],
     [&#39;第05章&#39;, &#39;/xueshanfeihu/492.html&#39;],
     [&#39;第06章&#39;, &#39;/xueshanfeihu/493.html&#39;],
     [&#39;第07章&#39;, &#39;/xueshanfeihu/494.html&#39;],
     [&#39;第08章&#39;, &#39;/xueshanfeihu/495.html&#39;],
     [&#39;第09章&#39;, &#39;/xueshanfeihu/496.html&#39;],
     [&#39;第10章&#39;, &#39;/xueshanfeihu/497.html&#39;],
     [&#39;后记&#39;, &#39;/xueshanfeihu/498.html&#39;]]
    ログイン後にコピー

    可以看到章节已经顺利的正序排列。

    每部小说的下载

    小说每一章的详细页最后一行的数据我们不需要:

    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    下载每章内容的代码:
    def download_page_content(url):
        res = requests.get(url, headers=headers)
        res.encoding = res.apparent_encoding
        html = etree.HTML(res.text)
        content = "\n".join(html.xpath("//div[@class=&#39;entry&#39;]/p/text()")[:-1])
        return content
    ログイン後にコピー

    然后我们就可以批量下载全部小说了:

    import os
    
    def download_one_novel(filename, url):
        "下载单部小说"
        title2url = getTitleAndUrl(url)
        print("创建文件:", filename)
        for title, url in title2url:
            with open(filename, "a", encoding="u8") as f:
                f.write(title)
                f.write("\n\n")
                print("下载:", title)
                content = download_page_content(url)
                f.write(content)
                f.write("\n\n")
    
    
    os.makedirs("novels", exist_ok=True)
    for row in data.itertuples():
        filename = f"novels/{row.名称}.txt"
        os.remove(filename)
        download_one_novel(filename, row.网址)
    ログイン後にコピー

    人物、武功和门派数据整理

    为了更好分析金庸小说,我们还需要采集金庸小说的人物、武功和门派,个人并没有找到还可以访问相关数据的网站,于是自行收集整理了相关数据:

    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    相关数据都以如下格式存储,例如金庸小说的人物:
    小说1
    人物1 人物2 ……
    小说2
    人物1 人物2 ……
    小说3
    人物1 人物2 ……
    ログイン後にコピー

    武功:

    小说1
    武功1 武功2 ……
    小说2
    武功1 武功2 ……
    小说3
    武功1 武功2 ……
    ログイン後にコピー

    数据源下载地址:https://gitcode.net/as604049322/blog_data

    高频分析

    定义一个加载小说的方法:

    def load_novel(novel):
        with open(f&#39;novels/{novel}.txt&#39;, encoding="u8") as f:
            return f.read()
    ログイン後にコピー

    主角分析

    首先我们加载人物数据:

    with open(&#39;data/names.txt&#39;,encoding="utf-8") as f:
        data = [line.rstrip() for line in f]
    novels = data[::2]
    names = data[1::2]
    novel_names = {k: v.split() for k, v in zip(novels, names)}
    del novels, names, data
    ログイン後にコピー

    可以预览一下天龙八部中的人物:

    print(",".join(novel_names[&#39;天龙八部&#39;][:20]))
    ログイン後にコピー
    刀白凤,丁春秋,马夫人,马五德,小翠,于光豪,巴天石,不平道人,邓百川,风波恶,甘宝宝,公冶乾,木婉清,包不同,天狼子,太皇太后,王语嫣,乌老大,无崖子,云岛主
    ログイン後にコピー

    下面我们寻找一下每部小说的主角,统计每个人物的出场 次数,显然次数越多主角光环越强,下面我们看看每部小说,出现次数最多的前十个人物:

    from collections import Counter
    
    
    def find_main_charecters(novel, num=10, content=None):
        if content is None:
            content = load_novel(novel)
        count = Counter()
        for name in novel_names[novel]:
            count[name] = content.count(name)
        return count.most_common(num)
    
    
    for novel in novel_names:
        print(novel, dict(find_main_charecters(novel, 10)))
    ログイン後にコピー
    书剑恩仇录 {&#39;陈家洛&#39;: 2095, &#39;张召重&#39;: 760, &#39;徐天宏&#39;: 685, &#39;霍青桐&#39;: 650, &#39;余鱼同&#39;: 605, &#39;文泰来&#39;: 601, &#39;骆冰&#39;: 594, &#39;周绮&#39;: 556, &#39;李沅芷&#39;: 521, &#39;陆菲青&#39;: 486}
    碧血剑 {&#39;袁承志&#39;: 3028, &#39;何铁手&#39;: 306, &#39;温青&#39;: 254, &#39;阿九&#39;: 215, &#39;洪胜海&#39;: 200, &#39;焦宛儿&#39;: 197, &#39;皇太极&#39;: 183, &#39;崔秋山&#39;: 180, &#39;穆人清&#39;: 171, &#39;闵子华&#39;: 163}
    射雕英雄传 {&#39;郭靖&#39;: 5009, &#39;黄蓉&#39;: 3650, &#39;洪七公&#39;: 1041, &#39;黄药师&#39;: 868, &#39;周伯通&#39;: 654, &#39;欧阳克&#39;: 611, &#39;丘处机&#39;: 606, &#39;梅超风&#39;: 480, &#39;杨康&#39;: 439, &#39;柯镇恶&#39;: 431}
    神雕侠侣 {&#39;杨过&#39;: 5991, &#39;小龙女&#39;: 2133, &#39;郭靖&#39;: 1431, &#39;黄蓉&#39;: 1428, &#39;李莫愁&#39;: 1016, &#39;郭芙&#39;: 850, &#39;郭襄&#39;: 778, &#39;陆无双&#39;: 575, &#39;周伯通&#39;: 555, &#39;赵志敬&#39;: 482}
    雪山飞狐 {&#39;胡斐&#39;: 230, &#39;曹云奇&#39;: 228, &#39;宝树&#39;: 225, &#39;苗若兰&#39;: 217, &#39;胡一刀&#39;: 207, &#39;苗人凤&#39;: 129, &#39;刘元鹤&#39;: 107, &#39;陶子安&#39;: 107, &#39;田青文&#39;: 103, &#39;范帮主&#39;: 83}
    飞狐外传 {&#39;胡斐&#39;: 2761, &#39;程灵素&#39;: 765, &#39;袁紫衣&#39;: 425, &#39;苗人凤&#39;: 405, &#39;马春花&#39;: 331, &#39;福康安&#39;: 287, &#39;赵半山&#39;: 287, &#39;田归农&#39;: 227, &#39;徐铮&#39;: 217, &#39;商宝震&#39;: 217}
    白马啸西风 {&#39;李文秀&#39;: 441, &#39;苏普&#39;: 270, &#39;阿曼&#39;: 164, &#39;苏鲁克&#39;: 147, &#39;陈达海&#39;: 106, &#39;车尔库&#39;: 99, &#39;李三&#39;: 31, &#39;丁同&#39;: 29, &#39;霍元龙&#39;: 23, &#39;桑斯&#39;: 22}
    倚天屠龙记 {&#39;张无忌&#39;: 4665, &#39;赵敏&#39;: 1250, &#39;谢逊&#39;: 1211, &#39;张翠山&#39;: 1146, &#39;周芷若&#39;: 825, &#39;殷素素&#39;: 550, &#39;杨逍&#39;: 514, &#39;张三丰&#39;: 451, &#39;灭绝师太&#39;: 431, &#39;小昭&#39;: 346}
    鸳鸯刀 {&#39;萧中慧&#39;: 103, &#39;袁冠南&#39;: 82, &#39;卓天雄&#39;: 76, &#39;周威信&#39;: 74, &#39;林玉龙&#39;: 52, &#39;任飞燕&#39;: 51, &#39;萧半和&#39;: 48, &#39;盖一鸣&#39;: 45, &#39;逍遥子&#39;: 28, &#39;常长风&#39;: 19}
    天龙八部 {&#39;段誉&#39;: 3372, &#39;萧峰&#39;: 1786, &#39;虚竹&#39;: 1636, &#39;阿紫&#39;: 1150, &#39;乔峰&#39;: 1131, &#39;阿朱&#39;: 986, &#39;慕容复&#39;: 925, &#39;王语嫣&#39;: 859, &#39;段正淳&#39;: 757, &#39;木婉清&#39;: 734}
    连城诀 {&#39;狄云&#39;: 1433, &#39;水笙&#39;: 439, &#39;戚芳&#39;: 390, &#39;丁典&#39;: 364, &#39;万震山&#39;: 332, &#39;万圭&#39;: 288, &#39;花铁干&#39;: 256, &#39;吴坎&#39;: 155, &#39;血刀老祖&#39;: 144, &#39;戚长发&#39;: 117}
    侠客行 {&#39;石破天&#39;: 1804, &#39;石清&#39;: 611, &#39;丁珰&#39;: 446, &#39;白万剑&#39;: 446, &#39;丁不四&#39;: 343, &#39;谢烟客&#39;: 337, &#39;闵柔&#39;: 327, &#39;贝海石&#39;: 257, &#39;丁不三&#39;: 217, &#39;白自在&#39;: 199}
    笑傲江湖 {&#39;令狐冲&#39;: 5838, &#39;岳不群&#39;: 1184, &#39;林平之&#39;: 926, &#39;岳灵珊&#39;: 919, &#39;仪琳&#39;: 729, &#39;田伯光&#39;: 708, &#39;任我行&#39;: 525, &#39;向问天&#39;: 513, &#39;左冷禅&#39;: 473, &#39;方证&#39;: 415}
    鹿鼎记 {&#39;韦小宝&#39;: 9731, &#39;吴三桂&#39;: 949, &#39;双儿&#39;: 691, &#39;鳌拜&#39;: 479, &#39;陈近南&#39;: 472, &#39;方怡&#39;: 422, &#39;茅十八&#39;: 400, &#39;小桂子&#39;: 355, &#39;施琅&#39;: 296, &#39;吴应熊&#39;: 290}
    越女剑 {&#39;范蠡&#39;: 121, &#39;阿青&#39;: 64, &#39;勾践&#39;: 47, &#39;薛烛&#39;: 29, &#39;西施&#39;: 26, &#39;文种&#39;: 23, &#39;风胡子&#39;: 7}
    ログイン後にコピー

    上述结果用文本展示了每部小说的前5个主角,但是不够直观,下面我用pyecharts的树图展示一下:

    from pyecharts import options as opts
    from pyecharts.charts import Tree
    
    data = []
    for novel in novel_kungfus:
        tmp = []
        data.append({"name": novel, "children": tmp})
        for name, count in find_main_kungfus(novel, 5):
            tmp.append({"name": name, "value": count})
    c = (
        TreeMap()
        .add("", data, levels=[
            opts.TreeMapLevelsOpts(),
            opts.TreeMapLevelsOpts(
                color_saturation=[0.3, 0.6],
                treemap_itemstyle_opts=opts.TreeMapItemStyleOpts(
                    border_color_saturation=0.7, gap_width=5, border_width=10
                ),
                upper_label_opts=opts.LabelOpts(
                    is_show=True, position=&#39;insideTopLeft&#39;, vertical_align=&#39;top&#39;
                )
            ),
        ])
        .set_global_opts(title_opts=opts.TitleOpts(title="金庸小说主角"))
    )
    c.render_notebook()
    ログイン後にコピー
    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    显然,《神雕侠侣》中的杨过和小龙女,《天龙八部》中的萧(乔)峰,段誉,虚竹,《射雕英雄传》的郭靖和黄蓉,《倚天屠龙记》的张无忌和赵敏 都是主角光环最强的角色。

    武功分析

    使用上述相同的方法,分析各种武功的出现频次,首先加载武功数据:

    with open(&#39;data/kungfu.txt&#39;, encoding="utf-8") as f:
        data = [line.rstrip() for line in f]
    novels = data[::2]
    kungfus = data[1::2]
    novel_kungfus = {k: v.split() for k, v in zip(novels, kungfus)}
    del novels, kungfus, data
    ログイン後にコピー

    定义计数方法:

    def find_main_kungfus(novel, num=10, content=None):
        if content is None:
            content = load_novel(novel)
        count = Counter()
        for name in novel_kungfus[novel]:
            count[name] = content.count(name)
        return count.most_common(num)
    
    
    for novel in novel_kungfus:
        print(novel, dict(find_main_kungfus(novel, 10)))
    ログイン後にコピー
    书剑恩仇录 {&#39;芙蓉金针&#39;: 16, &#39;柔云剑术&#39;: 15, &#39;百花错拳&#39;: 13, &#39;追魂夺命剑&#39;: 12, &#39;三分剑术&#39;: 12, &#39;八卦刀&#39;: 10, &#39;铁琵琶手&#39;: 9, &#39;无极玄功拳&#39;: 9, &#39;甩手箭&#39;: 7, &#39;黑沙掌&#39;: 7}
    侠客行 {&#39;雪山剑法&#39;: 46, &#39;金乌刀法&#39;: 33, &#39;碧针清掌&#39;: 8, &#39;五行六合掌&#39;: 8, &#39;梅花拳&#39;: 8, &#39;罗汉伏魔神功&#39;: 3, &#39;无妄神功&#39;: 1, &#39;神倒鬼跌三连环&#39;: 1, &#39;上清快剑&#39;: 1, &#39;黑煞掌&#39;: 1}
    倚天屠龙记 {&#39;七伤拳&#39;: 98, &#39;乾坤大挪移&#39;: 93, &#39;九阳真经&#39;: 46, &#39;玄冥神掌&#39;: 43, &#39;龙爪手&#39;: 24, &#39;金刚伏魔圈&#39;: 21, &#39;千蛛万毒手&#39;: 18, &#39;幻阴指&#39;: 17, &#39;寒冰绵掌&#39;: 16, &#39;真武七截阵&#39;: 10}
    天龙八部 {&#39;六脉神剑&#39;: 148, &#39;生死符&#39;: 124, &#39;凌波微步&#39;: 77, &#39;化功大法&#39;: 52, &#39;北冥神功&#39;: 36, &#39;般若掌&#39;: 36, &#39;火焰刀&#39;: 34, &#39;小无相功&#39;: 28, &#39;天山六阳掌&#39;: 25, &#39;大金刚拳&#39;: 24}
    射雕英雄传 {&#39;九阴真经&#39;: 191, &#39;铁掌&#39;: 169, &#39;降龙十八掌&#39;: 92, &#39;打狗棒法&#39;: 47, &#39;蛤蟆功&#39;: 39, &#39;空明拳&#39;: 25, &#39;一阳指&#39;: 22, &#39;先天功&#39;: 14, &#39;双手互搏&#39;: 13, &#39;杨家枪法&#39;: 13}
    碧血剑 {&#39;伏虎掌&#39;: 30, &#39;混元功&#39;: 23, &#39;两仪剑法&#39;: 21, &#39;神行百变&#39;: 18, &#39;蝎尾鞭&#39;: 12, &#39;破玉拳&#39;: 9, &#39;金蛇剑法&#39;: 5, &#39;软红蛛索&#39;: 4, &#39;混元掌&#39;: 4, &#39;斩蛟拳&#39;: 4}
    神雕侠侣 {&#39;玉女素心剑法&#39;: 25, &#39;黯然销魂掌&#39;: 19, &#39;五毒神掌&#39;: 18, &#39;龙象般若功&#39;: 12, &#39;玉箫剑法&#39;: 10, &#39;七星聚会&#39;: 8, &#39;美女拳法&#39;: 8, &#39;天罗地网势&#39;: 7, &#39;上天梯&#39;: 5, &#39;三无三不手&#39;: 4}
    笑傲江湖 {&#39;辟邪剑法&#39;: 160, &#39;独孤九剑&#39;: 80, &#39;吸星大法&#39;: 67, &#39;紫霞神功&#39;: 36, &#39;易筋经&#39;: 33, &#39;嵩山剑法&#39;: 33, &#39;华山剑法&#39;: 30, &#39;玉女剑十九式&#39;: 20, &#39;恒山剑法&#39;: 20, &#39;无双无对,宁氏一剑&#39;: 13}
    连城诀 {&#39;连城剑法&#39;: 29, &#39;神照经&#39;: 23, &#39;六合拳&#39;: 4}
    雪山飞狐 {&#39;胡家刀法&#39;: 8, &#39;苗家剑法&#39;: 7, &#39;龙爪擒拿手&#39;: 5, &#39;追命毒龙锥&#39;: 2, &#39;大擒拿手&#39;: 2, &#39;飞天神行&#39;: 1}
    飞狐外传 {&#39;西岳华拳&#39;: 26, &#39;八极拳&#39;: 20, &#39;八仙剑法&#39;: 8, &#39;四象步&#39;: 6, &#39;燕青拳&#39;: 5, &#39;赤尻连拳&#39;: 5, &#39;一路华拳&#39;: 4, &#39;金刚拳&#39;: 3, &#39;毒砂掌&#39;: 3, &#39;四门刀法&#39;: 2}
    鸳鸯刀 {&#39;夫妻刀法&#39;: 17, &#39;呼延十八鞭&#39;: 6, &#39;震天三十掌&#39;: 1}
    鹿鼎记 {&#39;化骨绵掌&#39;: 24, &#39;拈花擒拿手&#39;: 12, &#39;大慈大悲千叶手&#39;: 11, &#39;沐家拳&#39;: 11, &#39;八卦游龙掌&#39;: 10, &#39;少林长拳&#39;: 7, &#39;金刚护体神功&#39;: 5, &#39;波罗蜜手&#39;: 4, &#39;散花掌&#39;: 3, &#39;金刚神掌&#39;: 2}
    ログイン後にコピー

    每部小说频次前5的武功可视化:

    from pyecharts import options as opts
    from pyecharts.charts import Tree
    
    data = []
    for novel in novel_kungfus:
        tmp = []
        data.append({"name": novel, "children": tmp})
        for name, count in find_main_kungfus(novel, 5):
            tmp.append({"name": name, "value": count})
    c = (
        TreeMap()
        .add("", data, levels=[
            opts.TreeMapLevelsOpts(),
            opts.TreeMapLevelsOpts(
                color_saturation=[0.3, 0.6],
                treemap_itemstyle_opts=opts.TreeMapItemStyleOpts(
                    border_color_saturation=0.7, gap_width=5, border_width=10
                ),
                upper_label_opts=opts.LabelOpts(
                    is_show=True, position=&#39;insideTopLeft&#39;, vertical_align=&#39;top&#39;
                )
            ),
        ])
        .set_global_opts(title_opts=opts.TitleOpts(title="金庸高频武功"))
    )
    c.render_notebook()
    ログイン後にコピー
    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    门派分析

    加载数据并获取每部小说前10的门派:

    with open(&#39;data/bangs.txt&#39;, encoding="utf-8") as f:
        data = [line.rstrip() for line in f]
    novels = data[::2]
    bangs = data[1::2]
    novel_bangs = {k: v.split() for k, v in zip(novels, bangs) if k != "未知"}
    del novels, bangs, data
    
    
    def find_main_bangs(novel, num=10, content=None):
        if content is None:
            content = load_novel(novel)
        count = Counter()
        for name in novel_bangs[novel]:
            count[name] = content.count(name)
        return count.most_common(num)
    
    
    for novel in novel_bangs:
        print(novel, dict(find_main_bangs(novel, 10)))
    ログイン後にコピー
    书剑恩仇录 {&#39;红花会&#39;: 394, &#39;言家拳&#39;: 7, &#39;龙门帮&#39;: 7, &#39;天山派&#39;: 5, &#39;嵩阳派&#39;: 3, &#39;南少林&#39;: 3}
    侠客行 {&#39;雪山派&#39;: 358, &#39;长乐帮&#39;: 242, &#39;侠客岛&#39;: 143, &#39;金乌派&#39;: 48, &#39;摩天崖&#39;: 38, &#39;玄素庄&#39;: 37, &#39;金刀寨&#39;: 25}
    倚天屠龙记 {&#39;明教&#39;: 738, &#39;峨嵋派&#39;: 289, &#39;天鹰教&#39;: 224, &#39;昆仑派&#39;: 130, &#39;龙门镖局&#39;: 85, &#39;崆峒派&#39;: 83, &#39;海沙派&#39;: 58, &#39;巨鲸帮&#39;: 37, &#39;神拳门&#39;: 20, &#39;波斯明教&#39;: 16}
    天龙八部 {&#39;丐帮&#39;: 562, &#39;星宿派&#39;: 203, &#39;灵鹫宫&#39;: 157, &#39;姑苏慕容&#39;: 150, &#39;无量剑&#39;: 83, &#39;逍遥派&#39;: 77, &#39;大理段氏&#39;: 75, &#39;青城派&#39;: 65, &#39;蓬莱派&#39;: 23, &#39;伏牛派&#39;: 8}
    射雕英雄传 {&#39;桃花岛&#39;: 289, &#39;全真教&#39;: 99, &#39;铁掌帮&#39;: 87, &#39;仙霞派&#39;: 5}
    白马啸西风 {&#39;晋威镖局&#39;: 5}
    碧血剑 {&#39;仙都派&#39;: 51, &#39;金龙帮&#39;: 47, &#39;青竹帮&#39;: 45, &#39;渤海派&#39;: 8, &#39;永胜镖局&#39;: 6, &#39;点苍派&#39;: 4, &#39;飞虎寨&#39;: 4, &#39;会友镖局&#39;: 2, &#39;东支&#39;: 2, &#39;千柳庄&#39;: 2}
    神雕侠侣 {&#39;绝情谷&#39;: 128, &#39;古墓派&#39;: 87}
    笑傲江湖 {&#39;恒山派&#39;: 552, &#39;华山派&#39;: 521, &#39;嵩山派&#39;: 297, &#39;五岳剑派&#39;: 281, &#39;泰山派&#39;: 137, &#39;衡山派&#39;: 102, &#39;福威镖局&#39;: 102, &#39;日月神教&#39;: 64, &#39;武当派&#39;: 46, &#39;金刀王家&#39;: 20}
    连城诀 {&#39;血刀门&#39;: 24}
    雪山飞狐 {&#39;平通镖局&#39;: 7, &#39;饮马川山寨&#39;: 4, &#39;青藏派&#39;: 2, &#39;无极门&#39;: 2, &#39;百会寺&#39;: 1}
    飞狐外传 {&#39;韦陀门&#39;: 49, &#39;八卦门&#39;: 31, &#39;天龙门&#39;: 24, &#39;太极门&#39;: 22, &#39;飞马镖局&#39;: 19, &#39;五虎门&#39;: 16, &#39;少林派&#39;: 15, &#39;枫叶庄&#39;: 8, &#39;镇远镖局&#39;: 2}
    鸳鸯刀 {&#39;威信镖局&#39;: 5}
    鹿鼎记 {&#39;天地会&#39;: 542, &#39;神龙教&#39;: 161, &#39;少林寺&#39;: 155, &#39;清凉寺&#39;: 116, &#39;王屋派&#39;: 38, &#39;铁剑门&#39;: 12, &#39;金顶门&#39;: 8, &#39;武夷派&#39;: 3}
    ログイン後にコピー

    可视化:

    from pyecharts import options as opts
    from pyecharts.charts import Tree
    
    data = []
    for novel in novel_bangs:
        tmp = []
        data.append({"name": novel, "children": tmp})
        for name, count in find_main_bangs(novel, 5):
            tmp.append({"name": name, "value": count})
    c = (
        TreeMap()
        .add("", data, levels=[
            opts.TreeMapLevelsOpts(),
            opts.TreeMapLevelsOpts(
                color_saturation=[0.3, 0.6],
                treemap_itemstyle_opts=opts.TreeMapItemStyleOpts(
                    border_color_saturation=0.7, gap_width=5, border_width=10
                ),
                upper_label_opts=opts.LabelOpts(
                    is_show=True, position=&#39;insideTopLeft&#39;, vertical_align=&#39;top&#39;
                )
            ),
        ])
        .set_global_opts(title_opts=opts.TitleOpts(title="金庸高频门派"))
    )
    c.render_notebook()
    ログイン後にコピー
    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    还可以测试一下树形图:
    from pyecharts.charts import Tree
    
    c = (
        Tree()
        .add("", [{"name": "门派", "children": data}], layout="radial")
    )
    c.render_notebook()
    ログイン後にコピー
    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    综合统计

    下面我们编写一个函数,输入一部小说名,可以输出其最高频的主角、武功和门派:

    from pyecharts import options as opts
    from pyecharts.charts import Bar
    
    def show_top10(novel):
        content = load_novel(novel)
        charecters = find_main_charecters(novel, 10, content)[::-1]
        k, v = map(list, zip(*charecters))
        c = (
            Bar(init_opts=opts.InitOpts("720px", "320px"))
            .add_xaxis(k)
            .add_yaxis("", v)
            .reversal_axis()
            .set_series_opts(label_opts=opts.LabelOpts(position="right"))
            .set_global_opts(title_opts=opts.TitleOpts(title=f"{novel}主角"))
        )
        display(c.render_notebook())
        kungfus = find_main_kungfus(novel, 10, content)[::-1]
        k, v = map(list, zip(*kungfus))
        c = (
            Bar(init_opts=opts.InitOpts("720px", "320px"))
            .add_xaxis(k)
            .add_yaxis("", v)
            .reversal_axis()
            .set_series_opts(label_opts=opts.LabelOpts(position="right"))
            .set_global_opts(title_opts=opts.TitleOpts(title=f"{novel}功夫"))
        )
        display(c.render_notebook())
        bangs = find_main_bangs(novel, 10, content)[::-1]
        k, v = map(list, zip(*bangs))
        c = (
            Bar(init_opts=opts.InitOpts("720px", "320px"))
            .add_xaxis(k)
            .add_yaxis("", v)
            .reversal_axis()
            .set_series_opts(label_opts=opts.LabelOpts(position="right"))
            .set_global_opts(title_opts=opts.TitleOpts(title=f"{novel}门派"))
        )
        display(c.render_notebook())
    ログイン後にコピー

    例如查看天龙八部:

    show_top10("天龙八部")
    ログイン後にコピー
    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    词云图分析

    可以先添加所有的人物、武功和门派作为自定义词汇:

    import jieba
    
    for novel, names in novel_names.items():
        for name in names:
            jieba.add_word(name)
    
    for novel, kungfus in novel_kungfus.items():
        for kungfu in kungfus:
            jieba.add_word(kungfu)
            
    for novel, bangs in novel_bangs.items():
        for bang in bangs:
            jieba.add_word(bang)
    ログイン後にコピー

    文章整体词云查看

    这里我们仅提取词长度不小于4的成语、俗语和短语进行分析,以天龙八部这部小说为例:

    from IPython.display import Image
    import stylecloud
    import jieba
    import re
    
    # 去除非中文字符
    text = re.sub("[^一-龟]+", " ", load_novel("天龙八部"))
    words = [word for word in jieba.cut(text) if len(word) >= 4]
    stylecloud.gen_stylecloud(" ".join(words),
                              collocations=False,
                              font_path=r&#39;C:\Windows\Fonts\msyhbd.ttc&#39;,
                              icon_name=&#39;fas fa-square&#39;,
                              output_name=&#39;tmp.png&#39;)
    Image(filename=&#39;tmp.png&#39;)
    ログイン後にコピー
    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    修改上述代码,查看《射雕英雄传》:
    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    神雕侠侣:
    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    主角相关剧情词云

    我们知道《神雕侠侣》这部小说最重要的主角是杨过和小龙女,我们可能会对于杨过和小龙女之间所发生的故事很感兴趣。如果通过程序快速了解呢?

    我们考虑把《神雕侠侣》这部小说每一段中出现杨过及小龙女的段落进行jieba分词并制作词云。

    同样我们只看4个字以上的词:

    data = []
    for line in load_novel("神雕侠侣").splitlines():
        if "杨过" in line and "小龙女" in line:
            line = re.sub("[^一-龟]+", " ", line)
            data.extend(word for word in jieba.cut(line) if len(word) >= 4)
    stylecloud.gen_stylecloud(" ".join(data),
                              collocations=False,
                              font_path=r&#39;C:\Windows\Fonts\msyhbd.ttc&#39;,
                              icon_name=&#39;fas fa-square&#39;,
                              output_name=&#39;tmp.png&#39;)
    Image(filename=&#39;tmp.png&#39;)
    ログイン後にコピー
    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    这里的每一个词都能联想到发生在杨过和小龙女背后的一个故事。

    同样的思路看看郭靖和黄蓉:

    data = []
    for line in load_novel("射雕英雄传").splitlines():
        if "郭靖" in line and "黄蓉" in line:
            line = re.sub("[^一-龟]+", " ", line)
            data.extend(word for word in jieba.cut(line) if len(word) >= 4)
    stylecloud.gen_stylecloud(" ".join(data),
                              collocations=False,
                              font_path=r&#39;C:\Windows\Fonts\msyhbd.ttc&#39;,
                              icon_name=&#39;fas fa-square&#39;,
                              output_name=&#39;tmp.png&#39;)
    Image(filename=&#39;tmp.png&#39;)
    ログイン後にコピー
    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    最后我们看看天龙八部的三兄弟相关的词云:
    data = []
    for line in load_novel("天龙八部").splitlines():
        if ("萧峰" in line or "乔峰" in line) and "段誉" in line and "虚竹" in line:
            line = re.sub("[^一-龟]+", " ", line)
            data.extend(word for word in jieba.cut(line) if len(word) >= 4)
    stylecloud.gen_stylecloud(" ".join(data),
                              collocations=False,
                              font_path=r&#39;C:\Windows\Fonts\msyhbd.ttc&#39;,
                              icon_name=&#39;fas fa-square&#39;,
                              output_name=&#39;tmp.png&#39;)
    Image(filename=&#39;tmp.png&#39;)
    ログイン後にコピー
    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    关系图分析

    人物关系分析

    金庸小说15部小说中预计出现了1400个以上的角色,下面我们将遍历小说的每一段,在一段中出现的任意两个角色,都计数1。最终我们取出现频次最高的前200个关系对进行可视化。

    完整代码如下:

    from pyecharts import options as opts
    from pyecharts.charts import Graph
    import math
    import itertools
    
    count = Counter()
    for novel in novel_names:
        names = novel_names[novel]
        re_rule = f"({&#39;|&#39;.join(names)})"
        for line in load_novel(novel).splitlines():
            names = list(set(re.findall(re_rule, line)))
            if names and len(names) >= 2:
                names.sort()
                for s, t in itertools.combinations(names, 2):
                    count[(s, t)] += 1
    count = count.most_common(200)
    node_count, nodes, links = Counter(), [], []
    for (n1, n2), v in count:
        node_count[n1] += 1
        node_count[n2] += 1
        links.append({"source": n1, "target": n2})
    for node, count in node_count.items():
        nodes.append({"name": node, "symbolSize": int(math.log(count)*5)+5})
    c = (
        Graph(init_opts=opts.InitOpts("1280px","960px"))
        .add("", nodes, links, repulsion=30)
    )
    c.render("tmp.html")
    ログイン後にコピー

    这次我们生成了HTML文件是为了更方便的查看结果,前200个人物的关系情况如下:

    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    门派关系分析

    按照相同的方法分析所有小说的门派关系:

    from pyecharts import options as opts
    from pyecharts.charts import Graph
    import math
    import itertools
    
    count = Counter()
    for novel in novel_bangs:
        bangs = novel_bangs[novel]
        re_rule = f"({&#39;|&#39;.join(bangs)})"
        for line in load_novel(novel).splitlines():
            names = list(set(re.findall(re_rule, line)))
            if names and len(names) >= 2:
                names.sort()
                for s, t in itertools.combinations(names, 2):
                    count[(s, t)] += 1
    count = count.most_common(200)
    node_count, nodes, links = Counter(), [], []
    for (n1, n2), v in count:
        node_count[n1] += 1
        node_count[n2] += 1
        links.append({"source": n1, "target": n2})
    for node, count in node_count.items():
        nodes.append({"name": node, "symbolSize": int(math.log(count)*5)+5})
    c = (
        Graph(init_opts=opts.InitOpts("1280px","960px"))
        .add("", nodes, links, repulsion=50)
    )
    c.render("tmp2.html")
    ログイン後にコピー
    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    Word2Vec分析

    Word2Vec 是一款将词表征为实数值向量的高效工具,接下来,我们将使用它来处理这些小说。

    gensim 包提供了一个 Python 版的实现。

    • 源代码地址:https://github.com/RaRe-Technologies/gensim
    • 官方文档地址:http://radimrehurek.com/gensim/

    之前我有使用gensim 包进行了相似文本的匹配,有兴趣可查阅:《批量模糊匹配的三种方法》

    Word2Vec训练模型

    首先我要将所有小说的段落分词后添加到组织到一起(前面的程序可以重启):

    import jieba
    
    
    def load_novel(novel):
        with open(f&#39;novels/{novel}.txt&#39;, encoding="u8") as f:
            return f.read()
    
    
    with open('data/names.txt', encoding="utf-8") as f:
        data = f.read().splitlines()
        novels = data[::2]
        names = []
        for line in data[1::2]:
            names.extend(line.split())
    
    with open('data/kungfu.txt', encoding="utf-8") as f:
        data = f.read().splitlines()
        kungfus = []
        for line in data[1::2]:
            kungfus.extend(line.split())
    
    with open('data/bangs.txt', encoding="utf-8") as f:
        data = f.read().splitlines()
        bangs = []
        for line in data[1::2]:
            bangs.extend(line.split())
    
    for name in names:
        jieba.add_word(name)
    for kungfu in kungfus:
        jieba.add_word(kungfu)
    for bang in bangs:
        jieba.add_word(bang)
        
    # 去重
    names = list(set(names))
    kungfus = list(set(kungfus))
    bangs = list(set(bangs))
        
    sentences = []
    for novel in novels:
        print(f"处理:{novel}")
        for line in load_novel(novel).splitlines():
            sentences.append(jieba.lcut(line))
    ログイン後にコピー
    处理:书剑恩仇录
    处理:碧血剑
    处理:射雕英雄传
    处理:神雕侠侣
    处理:雪山飞狐
    处理:飞狐外传
    处理:白马啸西风
    处理:倚天屠龙记
    处理:鸳鸯刀
    处理:天龙八部
    处理:连城诀
    处理:侠客行
    处理:笑傲江湖
    处理:鹿鼎记
    处理:越女剑
    ログイン後にコピー

    接下面我们使用Word2Vec训练模型:

    import gensim
    
    model = gensim.models.Word2Vec(sentences)
    ログイン後にコピー

    我这边模型训练耗时15秒,若训练耗时较长可以把训练好的模型存到本地:

    model.save("louis_cha.model")
    ログイン後にコピー

    以后可以直接从本地磁盘读取模型:

    model = gensim.models.Word2Vec.load("louis_cha.model")
    ログイン後にコピー

    有了模型,我们可以进行一些简单而有趣的测试。

    注意:每次生成的模型有一定随机性,后续结果根据生成的模型而变化,并非完全一致。

    相似角色、门派和武功

    首先看与乔(萧)峰相似的角色:

    model.wv.most_similar(positive=["乔峰", "萧峰"])
    ログイン後にコピー
    [(&#39;段正淳&#39;, 0.8006908893585205),
     (&#39;张翠山&#39;, 0.8000873923301697),
     (&#39;虚竹&#39;, 0.7957292795181274),
     (&#39;赵敏&#39;, 0.7937390804290771),
     (&#39;游坦之&#39;, 0.7803780436515808),
     (&#39;石破天&#39;, 0.777414858341217),
     (&#39;令狐冲&#39;, 0.7761642932891846),
     (&#39;慕容复&#39;, 0.7629764676094055),
     (&#39;贝海石&#39;, 0.7625609040260315),
     (&#39;钟万仇&#39;, 0.7612598538398743)]
    ログイン後にコピー

    再看看与阿朱相似的角色:

    model.wv.most_similar(positive=["阿朱", "蛛儿"])
    ログイン後にコピー
    [(&#39;殷素素&#39;, 0.8681862354278564),
     (&#39;赵敏&#39;, 0.8558328747749329),
     (&#39;木婉清&#39;, 0.8549383878707886),
     (&#39;王语嫣&#39;, 0.8355365991592407),
     (&#39;钟灵&#39;, 0.8338050842285156),
     (&#39;小昭&#39;, 0.8316497206687927),
     (&#39;阿紫&#39;, 0.8169034123420715),
     (&#39;程灵素&#39;, 0.8153879642486572),
     (&#39;周芷若&#39;, 0.8046135306358337),
     (&#39;段誉&#39;, 0.8006759285926819)]
    ログイン後にコピー

    除了角色,我们还可以看看门派:

    model.wv.most_similar(positive=["丐帮"])
    ログイン後にコピー
    [(&#39;恒山派&#39;, 0.8266139626502991),
     (&#39;门人&#39;, 0.8158190846443176),
     (&#39;天地会&#39;, 0.8078100085258484),
     (&#39;雪山派&#39;, 0.8041207194328308),
     (&#39;魔教&#39;, 0.7935695648193359),
     (&#39;嵩山派&#39;, 0.7908961772918701),
     (&#39;峨嵋派&#39;, 0.7845258116722107),
     (&#39;红花会&#39;, 0.7830792665481567),
     (&#39;星宿派&#39;, 0.7826651930809021),
     (&#39;长乐帮&#39;, 0.7759961485862732)]
    ログイン後にコピー

    还可以看看与降龙十八掌相似的武功秘籍:

    model.wv.most_similar(positive=["降龙十八掌"])
    ログイン後にコピー
    [(&#39;空明拳&#39;, 0.9040402770042419),
     (&#39;打狗棒法&#39;, 0.9009960293769836),
     (&#39;太极拳&#39;, 0.8992120623588562),
     (&#39;八卦掌&#39;, 0.8909589648246765),
     (&#39;一阳指&#39;, 0.8891675472259521),
     (&#39;七十二路&#39;, 0.8713394999504089),
     (&#39;绝招&#39;, 0.8693119287490845),
     (&#39;胡家刀法&#39;, 0.8578060865402222),
     (&#39;六脉神剑&#39;, 0.8568121194839478),
     (&#39;七伤拳&#39;, 0.8560649156570435)]
    ログイン後にコピー

    在 Word2Vec 的模型里,有过“中国-北京=法国-巴黎”的例子,我们看看"段誉"和"段公子"类似于乔峰和什么的关系呢?

    def find_relationship(a, b, c):
        d, _ = model.wv.most_similar(positive=[b, c], negative=[a])[0]
        print(f"{a}-{b} 犹如 {c}-{d}")
    
    
    find_relationship("段誉", "段公子", "乔峰")
    ログイン後にコピー
    段誉-段公子 犹如 乔峰-乔帮主
    ログイン後にコピー

    类似的还有:

    # 情侣对
    find_relationship("郭靖", "黄蓉", "杨过")
    # 岳父女婿
    find_relationship("令狐冲", "任我行", "郭靖")
    # 非情侣
    find_relationship("郭靖", "华筝", "杨过")
    ログイン後にコピー
    郭靖-黄蓉 犹如 杨过-小龙女
    令狐冲-任我行 犹如 郭靖-黄药师
    郭靖-华筝 犹如 杨过-郭芙
    ログイン後にコピー

    查看韦小宝相关的关系:

    # 韦小宝
    find_relationship("杨过", "小龙女", "韦小宝")
    find_relationship("令狐冲", "盈盈", "韦小宝")
    find_relationship("张无忌", "赵敏", "韦小宝")
    find_relationship("郭靖", "黄蓉", "韦小宝")
    ログイン後にコピー
    杨过-小龙女 犹如 韦小宝-康熙
    令狐冲-盈盈 犹如 韦小宝-方怡
    张无忌-赵敏 犹如 韦小宝-阿紫
    郭靖-黄蓉 犹如 韦小宝-丁珰
    ログイン後にコピー

    门派武功之间的关系:

    find_relationship("郭靖", "降龙十八掌", "黄蓉")
    find_relationship("武当", "张三丰", "少林")
    find_relationship("任我行", "魔教", "令狐冲")
    ログイン後にコピー
    郭靖-降龙十八掌 犹如 黄蓉-打狗棒法
    武当-张三丰 犹如 少林-玄慈
    任我行-魔教 犹如 令狐冲-恒山派
    ログイン後にコピー

    聚类分析

    人物聚类分析

    之前我们使用 Word2Vec 将每个词映射到了一个向量空间,因此,我们可以利用这个向量表示的空间,对这些词进行聚类分析。

    首先取出所有角色对应的向量空间:

    all_names = []
    word_vectors = []
    for name in names:
        if name in model.wv:
            all_names.append(name)
            word_vectors.append(model.wv[name])
    all_names = np.array(all_names)
    word_vectors = np.vstack(word_vectors)
    ログイン後にコピー

    聚类算法有很多,这里我们使用基本的Kmeans算法进行聚类,如果只分成3类,那么很明显地可以将众人分成主角,配角,跑龙套的三类:

    from sklearn.cluster import KMeans
    import pandas as pd
    
    N = 3
    labels = KMeans(N).fit(word_vectors).labels_
    df = pd.DataFrame({"name": all_names, "label": labels})
    for label, names in df.groupby("label").name:
        print(f"类别{label}共{len(names)}个角色,前100个角色有:\n{&#39;,&#39;.join(names[:100])}\n")
    ログイン後にコピー
    类别0共103个角色,前100个角色有:
    李秋水,向问天,马钰,顾金标,丁不四,耶律齐,谢烟客,陈正德,殷天正,洪凌波,灵智上人,闵柔,公孙止,完颜萍,梅超风,鸠摩智,冲虚,冯锡范,尹克西,陆冠英,王剑英,左冷禅,商老太,尹志平,徐铮,灭绝师太,风波恶,袁紫衣,殷梨亭,宋青书,阿九,韩小莹,乌老大,杨康,何铁手,范遥,朱聪,郝大通,周仲英,风际中,何太冲,张召重,一灯大师,田归农,尼摩星,霍都,潇湘子,梅剑和,南希仁,玄难,纪晓芙,韩宝驹,邓百川,裘千尺,朱子柳,宋远桥,渡难,俞岱岩,武三通,云中鹤,余沧海,花铁干,杨逍,段延庆,巴天石,东方不败,归辛树,梁子翁,赵志敬,韦一笑,赵半山,丘处机,武修文,侯通海,鲁有脚,石清,彭连虎,胖头陀,达尔巴,裘千仞,金花婆婆,金轮法王,木高峰,苗人凤,任我行,王处一,柯镇恶,樊一翁,黄药师,欧阳克,张三丰,曹云奇,沙通天,文泰来,白万剑,鹿杖客,陆菲青,班淑娴,商宝震,全金发
    
    类别1共6个角色,前100个角色有:
    渔人,汉子,少妇,胖子,大汉,农夫
    
    类别2共56个角色,前100个角色有:
    张无忌,余鱼同,慕容复,木婉清,田伯光,郭襄,周伯通,陈家洛,乔峰,张翠山,丁珰,游坦之,岳不群,黄蓉,洪七公,岳灵珊,周芷若,马春花,杨过,阿紫,阿朱,赵敏,令狐冲,段正淳,水笙,石破天,徐天宏,程灵素,林平之,双儿,郭靖,袁承志,胡斐,陆无双,狄云,霍青桐,王语嫣,萧峰,李沅芷,骆冰,李莫愁,周绮,丁典,韦小宝,段誉,戚芳,小龙女,钟灵,殷素素,李文秀,谢逊,穆念慈,郭芙,方怡,仪琳,虚竹
    
    类别3共236个角色,前100个角色有:
    空智,章进,澄观,薛鹊,秃笔翁,曲非烟,田青文,郭啸天,陆大有,方证,阿碧,陶子安,吴三桂,钱老本,马行空,洪胜海,张勇,瑞大林,包不同,慕容景岳,康广陵,施琅,陆高轩,袁冠南,张康年,桃花仙,定逸,执法长老,范蠡,钟镇,陈达海,桃根仙,阿曼,李四,札木合,吴之荣,哈合台,传功长老,卓天雄,茅十八,风清扬,崔希敏,方生,王进宝,葛尔丹,常金鹏,秦红棉,薛慕华,侍剑,孙仲寿,范一飞,归二娘,孙不二,吴六奇,杨铁心,万震山,单正,玄寂,武敦儒,刘正风,西华子,樊纲,店伴,何足道,小昭,孙婆婆,苏普,谭婆,朱九真,耶律洪基,圆真,萧中慧,都大锦,司马林,叶二娘,安大娘,张三,杨成协,掌棒龙头,福康安,玉林,顾炎武,马超兴,殷离,莫声谷,郑萼,桃干仙,华筝,计无施,苏鲁克,费要多罗,苏荃,玄慈,卫璧,马光佐,常遇春,沐剑声,包惜弱,朱长龄,褚万里
    ログイン後にコピー
    ログイン後にコピー

    我们可以根据每个类别的角色数量的相对大小,判断该类别的角色是属于主角,配角还是跑龙套。

    下面我们过滤掉众龙套角色之后,重新聚合成四类:

    c = pd.Series(labels).mode().iat[0]
    remain_names = all_names[labels != c]
    remain_vectors = word_vectors[labels != c]
    remain_label = KMeans(4).fit(remain_vectors).labels_
    df = pd.DataFrame({"name": remain_names, "label": remain_label})
    for label, names in df.groupby("label").name:
        print(f"类别{label}共{len(names)}个角色,前100个角色有:\n{&#39;,&#39;.join(names[:100])}\n")
    ログイン後にコピー
    类别0共103个角色,前100个角色有:
    李秋水,向问天,马钰,顾金标,丁不四,耶律齐,谢烟客,陈正德,殷天正,洪凌波,灵智上人,闵柔,公孙止,完颜萍,梅超风,鸠摩智,冲虚,冯锡范,尹克西,陆冠英,王剑英,左冷禅,商老太,尹志平,徐铮,灭绝师太,风波恶,袁紫衣,殷梨亭,宋青书,阿九,韩小莹,乌老大,杨康,何铁手,范遥,朱聪,郝大通,周仲英,风际中,何太冲,张召重,一灯大师,田归农,尼摩星,霍都,潇湘子,梅剑和,南希仁,玄难,纪晓芙,韩宝驹,邓百川,裘千尺,朱子柳,宋远桥,渡难,俞岱岩,武三通,云中鹤,余沧海,花铁干,杨逍,段延庆,巴天石,东方不败,归辛树,梁子翁,赵志敬,韦一笑,赵半山,丘处机,武修文,侯通海,鲁有脚,石清,彭连虎,胖头陀,达尔巴,裘千仞,金花婆婆,金轮法王,木高峰,苗人凤,任我行,王处一,柯镇恶,樊一翁,黄药师,欧阳克,张三丰,曹云奇,沙通天,文泰来,白万剑,鹿杖客,陆菲青,班淑娴,商宝震,全金发
    
    类别1共6个角色,前100个角色有:
    渔人,汉子,少妇,胖子,大汉,农夫
    
    类别2共56个角色,前100个角色有:
    张无忌,余鱼同,慕容复,木婉清,田伯光,郭襄,周伯通,陈家洛,乔峰,张翠山,丁珰,游坦之,岳不群,黄蓉,洪七公,岳灵珊,周芷若,马春花,杨过,阿紫,阿朱,赵敏,令狐冲,段正淳,水笙,石破天,徐天宏,程灵素,林平之,双儿,郭靖,袁承志,胡斐,陆无双,狄云,霍青桐,王语嫣,萧峰,李沅芷,骆冰,李莫愁,周绮,丁典,韦小宝,段誉,戚芳,小龙女,钟灵,殷素素,李文秀,谢逊,穆念慈,郭芙,方怡,仪琳,虚竹
    
    类别3共236个角色,前100个角色有:
    空智,章进,澄观,薛鹊,秃笔翁,曲非烟,田青文,郭啸天,陆大有,方证,阿碧,陶子安,吴三桂,钱老本,马行空,洪胜海,张勇,瑞大林,包不同,慕容景岳,康广陵,施琅,陆高轩,袁冠南,张康年,桃花仙,定逸,执法长老,范蠡,钟镇,陈达海,桃根仙,阿曼,李四,札木合,吴之荣,哈合台,传功长老,卓天雄,茅十八,风清扬,崔希敏,方生,王进宝,葛尔丹,常金鹏,秦红棉,薛慕华,侍剑,孙仲寿,范一飞,归二娘,孙不二,吴六奇,杨铁心,万震山,单正,玄寂,武敦儒,刘正风,西华子,樊纲,店伴,何足道,小昭,孙婆婆,苏普,谭婆,朱九真,耶律洪基,圆真,萧中慧,都大锦,司马林,叶二娘,安大娘,张三,杨成协,掌棒龙头,福康安,玉林,顾炎武,马超兴,殷离,莫声谷,郑萼,桃干仙,华筝,计无施,苏鲁克,费要多罗,苏荃,玄慈,卫璧,马光佐,常遇春,沐剑声,包惜弱,朱长龄,褚万里
    ログイン後にコピー
    ログイン後にコピー

    每次运行结果都不一样,大家可以调整类别数量继续测试。从结果可以看到,反派更倾向于被聚合到一起,非正常姓名的人物更倾向于被聚合在一起,主角更倾向于被聚合在一起。

    人物层级聚类

    现在我们采用层级聚类的方式,查看人物间的层次关系,这里同样龙套角色不再参与聚类。

    层级聚类调用 scipy.cluster.hierarchy 中层级聚类的包,在此之前先解决matplotlib中文乱码问题:

    import matplotlib.pyplot as plt
    %matplotlib inline
    plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;]
    plt.rcParams[&#39;axes.unicode_minus&#39;] = False
    ログイン後にコピー

    接下来调用代码为:

    import scipy.cluster.hierarchy as sch
    
    y = sch.linkage(remain_vectors, method="ward")
    _, ax = plt.subplots(figsize=(10, 80))
    z = sch.dendrogram(y, orientation=&#39;right&#39;)
    idx = z[&#39;leaves&#39;]
    ax.set_xticks([])
    ax.set_yticklabels(remain_names[idx], fontdict={&#39;fontsize&#39;: 12})
    ax.set_frame_on(False)
    
    plt.show()
    ログイン後にコピー

    然后我们可以得到金庸小说宇宙的人物层次关系地图,结果较长仅展示一部分结果:

    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    当然所有小说混合产生的平行宇宙中,人物关系变得有些混乱,读者有兴趣可以拿单本小说作层次分析,就可以得到较为准确的人物层次关系。

    武功层级聚类

    对各种武功作与人物层次聚类相同的操作:

    all_names = []
    word_vectors = []
    for name in kungfus:
        if name in model.wv:
            all_names.append(name)
            word_vectors.append(model.wv[name])
    all_names = np.array(all_names)
    word_vectors = np.vstack(word_vectors)
    
    Y = sch.linkage(word_vectors, method="ward")
    
    _, ax = plt.subplots(figsize=(10, 40))
    Z = sch.dendrogram(Y, orientation=&#39;right&#39;)
    idx = Z[&#39;leaves&#39;]
    ax.set_xticks([])
    ax.set_yticklabels(all_names[idx], fontdict={&#39;fontsize&#39;: 12})
    ax.set_frame_on(False)
    plt.show()
    ログイン後にコピー

    结果较长,仅展示部分结果:

    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    可以看到,比较少的黄色部分明显是主角比较厉害的武功,而绿色比较多的部分基本都是配角的武功。

    门派层次聚类

    最后我们对门派进行层次聚类:

    all_names = []
    word_vectors = []
    for name in bangs:
        if name in model.wv:
            all_names.append(name)
            word_vectors.append(model.wv[name])
    all_names = np.array(all_names)
    word_vectors = np.vstack(word_vectors)
    
    Y = sch.linkage(word_vectors, method="ward")
    
    _, ax = plt.subplots(figsize=(10, 25))
    Z = sch.dendrogram(Y, orientation=&#39;right&#39;)
    idx = Z[&#39;leaves&#39;]
    ax.set_xticks([])
    ax.set_yticklabels(all_names[idx], fontdict={&#39;fontsize&#39;: 12})
    ax.set_frame_on(False)
    plt.show()
    ログイン後にコピー
    上級 | Python を使用して金庸の小説の世界を探索する 20,000 語
    比较少的这一类,基本都是在某几部小说中出现的主要门派,而大多数门派都是打酱油的。

    总结

    本文从金庸小说数据的采集,到普通的频次分析、剧情分析、关系分析,再到使用词向量空间分析相似关系,最后使用scipy进行所有小说的各种层次聚类。

    以上が上級 | Python を使用して金庸の小説の世界を探索する 20,000 語の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    関連ラベル:
    ソース:Python当打之年
    このウェブサイトの声明
    この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
    最新の問題
    人気のチュートリアル
    詳細>
    最新のダウンロード
    詳細>
    ウェブエフェクト
    公式サイト
    サイト素材
    フロントエンドテンプレート
    名称创作时间网址
    书剑恩仇录1955年/shujianenchoulu/
    碧血剑1956年/bixuejian/
    射雕英雄传1957—1959年/shediaoyingxiongzhuan/
    神雕侠侣1959—1961年/shendiaoxialv/
    スノー マウンテン フライング フォックス1959/xueshanfeihu/
    ##フライングフォックス外伝1960-1961#/フェイフワイズアン/
    #天龙八部
    1965/夏克興/
    1967/シャオアオジャンフー/
    ##1969-1972/ludingji/
    ##元女剑1970/yuenvjian/