$好玩的分詞——分析一下《三體》全集


另參加:jieba的基本用法參加我的另一篇博文:好玩的分詞——python jieba分詞模塊的基本用法

《三體》是一部很好看的硬科幻作品,當初是一口氣把三部全都看完的,包括《三體1》、《三體2:黑暗森林》和《三體3:死神永生》,洋洋灑灑幾十萬字,那看的叫一個酣暢淋漓。本文就使用jieba分詞,對《三體》三部曲全集文本做一些有趣的分析,涉及到分詞和詞頻分析等。

文本准備

到網上隨便一搜"三體全集",就很容易下載到三體三部曲的全集文本(txt文檔大概有2~3Mb),這里重命名為santi.txt,並存放到當前目錄下。

讀取三體全集文本

# coding:utf-8
import sys

# 設置環境為utf-8編碼格式,防止處理中文出錯
reload(sys)
sys.setdefaultencoding('utf-8')

# 讀取三體全集文本
santi_text = open('./santi.txt').read()
print len(santi_text)

# 輸出:
'''
2681968
'''

可以看出文本的長度有2681968字節,數據量還是很龐大的,語料庫足夠豐富。

對文本分詞並緩存到文件中

下面用jieba.posseg模塊對文本進行分詞並標注詞性,這里標注詞性的目的是為了后面接下來根據詞性過濾掉那些沒有實際意義的詞(如'好的'、'一般'、'他的'等等這種詞),而將分詞結果緩存到文件中是為了提高每次運行腳本的效率,畢竟這么大的數據量,分詞一次還是耗時很長的(大概為幾分鍾),緩存到文件中,只需第一次做一次分詞,后面再運行腳本就只需從文件中讀取分詞結果即可,畢竟讀文件的速度比分詞要快很多。下面上代碼:

import jieba.posseg as psg

# 將三體全集文本分詞,並附帶上詞性,因為數據量比較大,防止每次運行腳本都花大量時間,所以第一次分詞后就將結果存入文件out.txt中
# 相當於做一個緩存,格式為每個詞占一行,每一行的內容為:
# 詞    詞性
santi_words_with_attr = [(x.word,x.flag) for x in psg.cut(santi_text) if len(x.word) >= 2]  # 這里的x.word為詞本身,x.flag為詞性
print len(santi_words_with_attr)  # 輸出:
with open('out.txt','w+') as f:
    for x in santi_words_with_attr:
        f.write('{0}\t{1}\n'.format(x[0],x[1]))

運行上面一段代碼,幾分鍾之后在當前目錄生成了一個out.txt文件,有273033行,數據量還是非常大的,其前幾行的內容如下:

'''
手機	n
TXT	eng
小說	n
下載	v
www	eng
sjtxt	eng
com	eng
歡迎您	l
sjtxt	eng
推薦	v
好書	n
	x
	x
	x
'''

你肯定會大呼一聲:這什么鬼!?!!!

不急,這是因為文本中存在大量的我們不需要的詞,甚至還有很多空白符詞,這肯定是沒法玩的,所以接下來我們對垃圾詞進行過濾,對數據做一下清洗。

分詞結果清洗

現在我們緩存的分詞結果文件out.txt就可以派上用場了,為了清洗分詞結果,我們需要再次獲取分詞結果,而現在就不需要再運行一次超級耗時的分詞代碼了,而只需從out.txt中讀取即可,上代碼:

# 從out.txt中讀取帶詞性的分詞結果列表
santi_words_with_attr = []
with open('out.txt','r') as f:
    for x in f.readlines():
        pair = x.split()
        if len(pair) < 2:
            continue
        santi_words_with_attr.append((pair[0],pair[1]))

# 將分詞列表的詞性構建成一個字典,以便后面使用,格式為:
# {詞:詞性}
attr_dict = {}
for x in santi_words_with_attr:
    attr_dict[x[0]] = x[1]

# 要過濾掉的詞性列表,這些詞性的詞都是沒有實際意義的詞,如連詞、代詞等虛詞,這個列表初始化為空列表,后面根據分析結果手工往里面一個個添加
stop_attr = []

# 獲取過濾掉stop_attr里的詞性的詞后的分詞列表
words = [x[0] for x in santi_words_with_attr if x[1] not in stop_attr]

# 統計在分詞表中出現次數排名前500的詞的列表,並將結果輸出到文件most.txt中,每行一個詞,格式為:
# 詞,出現次數,詞性
from collections import Counter
c = Counter(words).most_common(500)
with open('most.txt','w+') as f:
    for x in c:
        f.write('{0},{1},{2}\n'.format(x[0],x[1],attr_dict[x[0]]))

第一次運行上述代碼,生成的most.txt文件有500行,前10行內容如下:

'''
一個,3057,m
沒有,2128,v
他們,1690,r
我們,1550,r
程心,1451,n
這個,1357,r
自己,1347,r
現在,1273,t
已經,1259,d
羅輯,1256,n
'''

可以看到詞頻排名前四的都是些沒意義的詞,而第5個'程心'才是三體中有實際意義的詞(程聖母果然厲害)。等等,這是因為我們沒有將前四名這些詞的詞性添加到stop_attr列表中,導致它們並沒有被過濾掉,那我們現在就把這前4個詞的詞性添加到stop_attr列表中,stop_attr列表變成:['m','v','r'],再次運行腳本,most.txt的內容(前10個詞)變為了:

'''
程心,1451,n
現在,1273,t
已經,1259,d
羅輯,1256,n
世界,1243,n
地球,951,n
人類,935,n
太空,930,n
三體,879,n
宇宙,875,n
'''

可以看到,我們成功清洗掉了剛剛前四名的無意義的詞,'程心'成功變為詞頻最高的詞。但是第2、3名的'現在'、'已經'等詞顯然也是我們不需要的,那么就重復上面的過程,把這些不需要的詞性添加到stop_attr列表中,再看結果。然后繼續重復以上過程,重復N次之后,我得到的stop_attr變成了:['a','ad','b','c','d','f','df','m','mq','p','r','rr','s','t','u','v','z'],長長的一串。而most.txt的內容變為了(前20行):

'''
程心,1451,n
羅輯,1256,n
世界,1243,n
地球,951,n
人類,935,n
太空,930,n
三體,879,n
宇宙,875,n
太陽,775,ns
艦隊,649,n
飛船,644,n
汪淼,633,nrfg
時間,611,n
文明,561,nr
東西,515,ns
信息,480,n
感覺,468,n
智子,452,n
計划,451,n
葉文潔,446,nr
太陽系,428,n
'''

可以看出分詞結果已經被清洗的很干凈了,也可以發現我們需要的有實際意義的詞絕大多數都為名詞(n或n開頭的)。

TopN詞匯輸出

接下來我們把文本中的TopN詞匯及詞頻輸出到result.txt中,每一行一個詞,格式為:詞,詞頻

from collections import Counter
c = Counter(words).most_common(500)
with open('result.txt','w+') as f:
    for x in c:
        f.write('{0},{1}\n'.format(x[0],x[1]))

得到的result.txt的前10行內容如下:

'''
程心,1451
羅輯,1256
世界,1243
地球,951
人類,935
太空,930
三體,879
宇宙,875
太陽,775
艦隊,649
'''

完整代碼封裝

將上述每一步的代碼封裝成一個完整的腳本,如下:

# coding:utf-8
import jieba.posseg as psg
from collections import Counter
import sys

# 對文本分詞並標注詞性,並緩存到文件
def cut_and_cache(text):
    # 將三體全集文本分詞,並附帶上詞性,因為數據量比較大,防止每次運行腳本都花大量時間,所以第一次分詞后就將結果存入文件cut_result.txt中
    # 相當於做一個緩存,格式為每個詞占一行,每一行的內容為:
    # 詞,詞性
    santi_words_with_attr = [(x.word,x.flag) for x in psg.cut(santi_text) if len(x.word) >= 2]
    print len(santi_words_with_attr)
    with open('cut_result.txt','w+') as f:
        for x in santi_words_with_attr:
            f.write('{0}\t{1}\n'.format(x[0],x[1]))    

# 從cut_result.txt中讀取帶詞性的分詞結果列表
def read_cut_result():
    santi_words_with_attr = []
    with open('cut_result.txt','r') as f:
        for x in f.readlines():
            pair = x.split()
            if len(pair) < 2:
                continue
            santi_words_with_attr.append((pair[0],pair[1]))
    return santi_words_with_attr

# 將分詞列表的詞性構建成一個字典,以便后面使用,格式為:
# {詞:詞性}
def build_attr_dict(santi_words_with_attr):
    attr_dict = {}
    for x in santi_words_with_attr:
        attr_dict[x[0]] = x[1]
    return attr_dict

# 統計在分詞表中出現次數排名前500的詞的列表,並將結果輸出到文件result.txt中,每行一個詞,格式為:
# 詞,出現次數
def get_topn_words(words,topn):
    c = Counter(words).most_common(topn)
    with open('result.txt','w+') as f:
        for x in c:
            f.write('{0},{1}\n'.format(x[0],x[1]))
        
        
def main():
    # 設置環境為utf-8編碼格式,防止處理中文出錯
    reload(sys)
    sys.setdefaultencoding('utf-8')
    
    # 讀取三體全集文本
    santi_text = open('./santi.txt').read()
    
    # 分詞並緩存,只需運行一次,后續可注釋掉
    cut_and_cache(santi_text)
    
    # 從cut_result.txt中讀取帶詞性的分詞結果列表
    santi_words_with_attr = read_cut_result()
    
    # 構建詞性字典,這個字典在探索stop_attr的時候會有幫助
    # attr_dict = build_attr_dict(santi_words_with_attr)
    
    # 要過濾掉的詞性列表
    stop_attr = ['a','ad','b','c','d','f','df','m','mq','p','r','rr','s','t','u','v','z']
    
    # 過濾掉不需要的詞性的詞
    words = [x[0] for x in santi_words_with_attr if x[1] not in stop_attr]
    
    # 獲取topn的詞並存入文件result.txt
    get_topn_words(words = words,topn = 500)
    
if __name__ == '__main__':
    main()


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM