【射雕英雄传】文本分析初步(姓名,丹药,秘籍,招式)[scrapy, jieba, matplotlib]

大三上学期选了学校高瓴开的大数据分析导论,最后的final分为两个部分,本文讲述第一部分的完成步骤。大致可以描述为:爬取文章,从本文本构造user_dict,利用jieba分词,分门别类统计词频并绘图。
在此做个小预告,final第二部分是绘制词云、主题抽取、利用word_embedding进行相关性分析以及自主探索,后面做完作业再更新,放在一个主题下方便查找。

文章目录

*
内容的爬取
user dict的构造

+ user dict
+ 概述
+ 核心思想【以姓名提取为例】
+ 丹药与秘籍提取
+ 地点提取
绘图

内容的爬取

感觉爬虫这个玩意儿其实挺简单的,不过只学习了静态网页的爬取所以也有点以偏概全的意思,但是这里不解释爬虫相关的细节与东西。

import scrapy
class NovelSpider(scrapy.Spider):
    name = "info"

    def start_requests(self):
        urls = [

        ]
        for url in urls:
            yield scrapy.Request(url=url, callback=self.parse)

    def parse(self, response):
        prefix = 'http://www.jinyongshuwu.com/nshe/'
        for url in response.css('ul.mlist li a::attr(href)').extract():
            url = prefix + url
            yield scrapy.Request(url=url, callback=self.parseDetails)

    def parseDetails(self, response):
        title = response.css('h1.title::text').extract_first()
        content = [s.strip() for s in response.css('div.vcon p::text').extract()]
        with open('射雕英雄传\\原文 换源\\' + title + '.txt', 'w', encoding='utf-8') as fout:
            fout.write(title)
            for para in content:
                fout.write('\n' + para)

唯一需要解释一嘴的是 parse函数里面在进行详情页的爬取的时候,需要加上 prefix这个东西,因为发现直接拿到的url并不是链接的完全体,而是最后的一部分,比如1000.html。所以如果想正常访问,必须和 prefix粘在一起。

user dict的构造

先小小解释一下user_dict这玩意是个啥一遍没什么基础的同学们看得舒服一些。

user dict

这玩意儿是我们在调用中文分词模块 jieba的时候有时需要自定义的一个词典。为啥需要这么个东西?我们来想象 jieba可能的应用场景,老师让你帮他做一个领导讲话的文本分析。你拿到手之后发现领导的文笔属实nb,其中用到了很多人的举例论证,所以难免会有很多的人名在里面。但是 jieba这个东西很难对中文名字进行合适的分割,比如一段文本”刘欢笑道”,可以有两种理解方式,要么这哥们是刘欢,要么叫刘欢笑。为了帮助 jieba识别”刘欢”这个词语,并且不让它从中间一分为二,被上下文语意所撕裂,我们构建一个文本文件 user_dict把”刘欢”这个名字写进去,并且利用 jieba.load_userdict(filepath)添加进去。
更多的细节我们不再展开,比如在后面可以进一步地添加词频与词性等。另外,需要说明的是你把”刘欢”加了进去,如果你用 jieba分的那段文字里面刘欢这哥们老笑着说话,那么还是有可能分词为”刘欢笑”+”道”,这是内部算法所决定的,没有更精细需求的可以略去。
示例:

user_dict.txt
#内容是:
#刘欢
import jieba
jieba.load_userdict('user_dict.txt')

概述

需要说一下的是,诚然可以从网上尽情搜罗金庸宇宙的各种人名、丹药或秘籍的名字,直接放在user_dict里面进行分割,但是完全失去了这个作业本应锻炼我们的地方。所以我选择一周每天额外工作两个小时左右,从产生想法到实现算法自己提取这些未知的词汇,现在看来不仅提取了在网上没有出现过的内容,并且给了我很大的锻炼。
很喜欢徐君老师的一句话,有多少人工就有多少智能。
因此构造 user dict会是本篇博客的重点,不感兴趣的可以跳过,看看后面对于分词与词频统计、绘图的内容。
本部分的工作流程如下:

Repeat

电脑初筛

挑选停止词

电脑再筛

人工终筛

p.s. 如果任务很急的话还是下载个细胞词库直接切吧

核心思想【以姓名提取为例】

就像证明数学题一样,我们首先看看目标:提取一系列词语,特别地是我们不知道的,甚至有一些是由作家天马行空想象出来的,所以我们无法直接精准定位。那怎么办呢?最直觉的想法就是去找有什么东西是人的特征,或者说有没有什么动作或者是形容词是专门用来讲人的。
最开始的想法是:在小说里,只要不是哑巴都得说话,而在金庸的这部小说里,语言的提示词基本都是”道”。
用写论文的文笔来阐述这个想法是这样的:

记文段中随机选择一段定长的字串,其中含有人物姓名为事件A A A,记其概率为P ( A ) \mathbf{P}{(A)}P (A )。
另一方面,虽然我们并不清楚文章中人物具体做了什么,但是能断定,至少主要人物一定会说话、有台词。经过简单的浏览,发现文中用 这个字来指示人物的说话内容,因此我们可以有以下记号。
记文段中随机选择一段定长的字串,其中含有 这个字为事件B B B。那么P ( A ∣ B ) \mathbf{P}(A|B)P (A ∣B )表示对于含有 这个字的字串,其含有人物姓名的概率。
由于P ( B ) ≤ 1 \mathbf{P}(B) \leq 1 P (B )≤1,显然有
P ( A ) ≤ P ( A ∣ B ) \mathbf{P}(A) \leq \mathbf{P}(A|B)P (A )≤P (A ∣B )
事实上,在应用中我们可以发现,当简单地添加一些停止词,如道路、道德后,我们显著地有以下结果:
P ( A ) < P ( A ∣ B ) \mathbf{P}(A) < \mathbf{P}(A|B)P (A )<P (A ∣B )
进一步,这项观察给了我们新的启发,我们发现有些人名经常有一些后缀,比如哥、姐、大师、散人这样,因此把这样的提示词也加入其中进行筛查,并写入一些停止词。


detect_words = ['散人','道','姑','哥','大师','姐']
stop_names = ['一人','长老','那人','那书生','大师父','姑娘','大哥','老哥','师哥','小哥']

在代码的第二大块中,我们对每个提示词,在所有章节中进行搜索,将通过停止词筛选的提示词上下文写入文件中。
我们通过把一些名字筛出来,添加到停止词中。

stop_names = ['一人','长老','那人','那书生','大师父','姑娘','大哥','老哥','师哥','小哥','黄蓉','郭靖','洪七公','黄药师',
'欧阳锋','丘处机','包惜弱','杨铁心','郭啸天','颜烈','鲁有脚','张十五','柯镇恶','周伯通','陆乘风','朱聪','段天德','裘千仞',
'颜洪烈','铁木真','欧阳克']

但是发现剩余的句子还是很多。类似地参考条件概率的思想,由于人物之间通常有关系并在一起行动,我认为在某已知姓名的上下文中,很有可能出现其他未出现的人名。
依据这个想法,通过下一个模块在网上找到的函数,通过paddle进一步识别姓名,并通过更新的 stop_names进行交叉排除,最后只剩余100~200个选项,简单地人工筛选过后便得到65个出现频率比较高的人名。

下面是我在网络上找到的代码,但是忘记原作者是哪位了,这里进行标注,也希望有知道的各位在评论里告诉我,从而对来源进行修正。

import pandas as pd
import jieba
import paddle
import jieba.posseg as pseg
paddle.enable_static()
jieba.enable_paddle()

def get_per_list(text):
    per_list = []
    word_list = jieba.lcut(text)
    for word in word_list:
        if len(word)==1:
            continue
        words = pseg.cut(word, use_paddle=True)
        word, flag = list(words)[0]
        if flag=='PER':
            per_list.append(word)
    per_list = list(set(per_list))
    return per_list

丹药与秘籍提取

核心思想是一样的,利用关键词进行全文搜索,截取上下文一定window_size的窗口进行进一步的处理。然后不断向其中添加停止词,不断走流程图中的后三步。
提示词可以有这些:

detect_words = ['鞭','枪','经','腿','手','剑','掌','刀','拳','指','功','大法','真经','宝典','诀','棒']
charac_words = ['药','丹','散','粉','菩提','膏','丸']

地点提取

这个的处理就比较暴力了,金庸在杜撰地名的时候和现代网络小说东拼西凑让名字听起来炫酷不一样,而是通常在后面跟着”岛””寺”等提示词,但另一方面 jieba本来就可以对其进行识别,所以我通过对上述借鉴的代码进行微调,将函数中最后一个 if的条件改成 if flag=='LOC'就直接可以用了。

绘图

其实感觉这也没啥可说的,用 matplotlib画画bar plot就ok了。

from matplotlib import pyplot as plt
import os
plt.rcParams["font.sans-serif"]=["SimHei"]
plt.rcParams["axes.unicode_minus"]=False

num_upper_bound = 5

key_file_name = '可能丹药.txt'

def count_appear_times(key, data_file_name):
    count = 1
    with open(data_file_name, 'r', encoding='utf-8') as fin:
        for line in fin.readlines():
            pos = line.find(key)
            while pos != -1:
                count += 1
                line = line[pos + len(key):]
                pos = line.find(key)
    return count

def most_frequent(data_file_name, key_file_name, num_upper_bound):
    key_list = []
    with open(key_file_name, 'r', encoding='utf-8') as fin:
        for line in fin.readlines():
            key_list.append(line.strip())

    key_count_dict = dict()
    for key in key_list:
        key_count_dict[key] = int(count_appear_times(key, data_file_name))

    return sorted(key_count_dict.items(), key= lambda x:x[1], reverse=True)[:num_upper_bound]

我分章节画了一遍,总体又画了一遍。分章节的时候使用了 plt.subplot2grid()这个函数来在一张图中绘制多个图表。
具体效果图见下:

【射雕英雄传】文本分析初步(姓名,丹药,秘籍,招式)[scrapy, jieba, matplotlib]
【射雕英雄传】文本分析初步(姓名,丹药,秘籍,招式)[scrapy, jieba, matplotlib]

当时犯懒用ipynb文件进行调试的,没有对代码总体地整理,如果大家有代码的需求可以私信我,我抽时间弄一下传上来。

Original: https://blog.csdn.net/Petersburg/article/details/121996456
Author: Petersburg
Title: 【射雕英雄传】文本分析初步(姓名,丹药,秘籍,招式)[scrapy, jieba, matplotlib]

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/788736/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球