分词工具(五):分词工具源码说明(二)
接上一篇,我们来看一下用Python实现的分词工具的源码。
这是上一篇文章的延续。
4. 分词工具源码
4.3. get_file_text 函数
def get_file_text(file_name) -> DataFrame:
"""
MS Word, PowerPoint, Text, DB Comment(Excel) file에서 text를 추출하는 함수
:param file_name: 파일명
:return: file에서 추출한 text(DataFrame type)
"""
df_text = DataFrame()
if file_name.endswith(('.doc', '.docx')):
df_text = get_doc_text(file_name)
elif file_name.endswith(('.ppt', '.pptx')):
df_text = get_ppt_text(file_name)
elif file_name.endswith('.txt'):
df_text = get_txt_text(file_name)
elif file_name.endswith(('.xls', '.xlsx', '.xlsb')):
df_text = get_db_comment_text(file_name)
return df_text
- 第357到365行:根据文件扩展名执行相应的函数,并将结果返回到df_text中。
根据文件扩展名执行的各个函数的说明如下。
4.3.1. get_doc_text 函数
def get_doc_text(file_name) -> DataFrame:
"""
doc 파일에서 text를 추출하여 DataFrame type으로 return
:param file_name: 입력 파일명 (str type)
:return: 입력 파일에서 추출한 text
"""
# :return: 입력 파일에서 추출한 text에 형태소 분석기로 명사 추출한 DataFrame
start_time = time.time()
print('\r\nget_doc_text: %s' % file_name)
word_app = win32com.client.Dispatch("Word.Application")
word_file = word_app.Documents.Open(file_name, True)
# result = []
df_text = pd.DataFrame()
page = 0
for paragraph in word_file.Paragraphs:
text = paragraph.Range.Text
page = paragraph.Range.Information(3) # 3: wdActiveEndPageNumber(Text의 페이지번호 확인)
if text.strip() != '':
sr_text = Series([file_name, 'doc', page, text, f'{file_name}:{page}:{text}'],
index=['FileName', 'FileType', 'Page', 'Text', 'Source'])
df_text = df_text.append(sr_text, ignore_index=True)
word_file.Close()
print('text count: %s' % str(df_text.shape[0]))
print('page count: %d' % page)
end_time = time.time()
# elapsed_time = end_time - start_time
elapsed_time = str(datetime.timedelta(seconds=end_time - start_time))
print('[pid:%d] get_doc_text elapsed time: %s' % (os.getpid(), elapsed_time))
# return get_word_list(df_text)
return df_text
- 第 193 行:使用 win32com 包创建 MS Word 程序的实例。如果 MS Word 未运行,则使用此代码执行。
- 第 194 行:在上面创建的 MS Word 程序实例中打开 .doc 或 .docx 文件。
- 第 198 行:遍历文件内容中的段落。
- 第 199 行:从段落的内容中提取文本。
- 第 200 行:提取当前段落的页码。在 Range.Information(3) 中,“3”对应于 wdActiveEndPageNumber 常量值。更多细节 WdInformation 枚举 (Word) |微软文档看
- 202~204行:将提取出来的文本做成Series对象,添加到df_text DataFrame的行中。
- 第 214 行:返回包含从文件中提取的文本的 df_text。
4.3.2. get_ppt_text 函数
def get_ppt_text(file_name) -> DataFrame:
"""
ppt 파일에서 text를 추출하여 DataFrame type으로 return
:param file_name: 입력 파일명 (str type)
:return: 입력 파일에서 추출한 text
"""
# :return: 입력 파일에서 추출한 text에 형태소 분석기로 명사 추출한 DataFrame
start_time = time.time()
print('\r\nget_ppt_text: %s' % file_name)
ppt_app = win32com.client.Dispatch('PowerPoint.Application')
ppt_file = ppt_app.Presentations.Open(file_name, True)
# result = []
df_text = pd.DataFrame()
page_count = 0
for slide in ppt_file.Slides:
slide_number = slide.SlideNumber
page_count += 1
for shape in slide.Shapes:
shape_text = []
text = ''
if shape.HasTable:
col_cnt = shape.Table.Columns.Count
row_cnt = shape.Table.Rows.Count
for row_idx in range(1, row_cnt + 1):
for col_idx in range(1, col_cnt + 1):
text = shape.Table.Cell(row_idx, col_idx).Shape.TextFrame.TextRange.Text
if text != '':
text = text.replace('\r', ' ')
shape_text.append(text)
elif shape.HasTextFrame:
for paragraph in shape.TextFrame.TextRange.Paragraphs():
text = paragraph.Text
if text != '':
shape_text.append(text)
for text in shape_text:
if text.strip() != '':
sr_text = Series([file_name, 'ppt', slide_number, text, f'{file_name}:{slide_number}:{text}'],
index=['FileName', 'FileType', 'Page', 'Text', 'Source'])
df_text = df_text.append(sr_text, ignore_index=True)
# print(result)
ppt_file.Close()
# print(df_result)
print('text count: %s' % str(df_text.shape[0]))
print('page count: %d' % page_count)
# print(df_text.head(10))
# print(df_result.Paragraph)
# return df_result
end_time = time.time()
# elapsed_time = end_time - start_time
elapsed_time = str(datetime.timedelta(seconds=end_time - start_time))
print('[pid:%d] get_ppt_text elapsed time: %s' % (os.getpid(), elapsed_time))
# return get_word_list(df_text)
return df_text
- 第 138 行:使用 win32com 包创建 MS Powerpoint 程序的实例。如果 MS Powerpoint 未运行,此代码将运行。
- 第 139 行:在上面创建的 MS Powerpoint 程序实例中打开 .ppt 或 .pptx 文件。
- 第 143 行:遍历文件中的幻灯片。
- 第 146 行:遍历每张幻灯片的形状。
- 第 149~157 行:如果形状是表格,则从表格的每个单元格中提取文本。
- 第158~162行:如果形状不是表格且有文字,则提取文字。
- 第 163 到 167 行:将提取的文本作为一个 Series 对象,并将其添加到 df_text DataFrame 的行中。
- 第 181 行:返回包含从文件中提取的文本的 df_text。
4.3.3. get_txt_text 函数
def get_txt_text(file_name) -> DataFrame:
"""
txt 파일에서 text를 추출하여 DataFrame type으로 return
:param file_name: 입력 파일명 (str type)
:return: 입력 파일에서 추출한 text
"""
# :return: 입력 파일에서 추출한 text에 형태소 분석기로 명사 추출한 DataFrame
start_time = time.time()
print('\r\nget_txt_text: ' + file_name)
df_text = pd.DataFrame()
line_number = 0
with open(file_name, 'rt', encoding='UTF8') as file:
for text in file:
line_number += 1
if text.strip() != '':
sr_text = Series([file_name, 'txt', line_number, text, f'{file_name}:{line_number}:{text}'],
index=['FileName', 'FileType', 'Page', 'Text', 'Source'])
df_text = df_text.append(sr_text, ignore_index=True)
print('text count: %d' % df_text.shape[0])
print('line count: %d' % line_number)
end_time = time.time()
# elapsed_time = end_time - start_time
elapsed_time = str(datetime.timedelta(seconds=end_time - start_time))
print('[pid:%d] get_txt_text elapsed time: %s' % (os.getpid(), elapsed_time))
# return get_word_list(df_text)
return df_text
- 第 228 行:以 UTF8 编码的只读文本模式打开文件。 (模式='rt')
- 第 229 行:遍历文件的行。
- 第 231 到 234 行:将行文本设为 Series 对象,并将其添加到 df_text DataFrame 的行中。
- 第 242 行:返回包含从文件中提取的文本的 df_text。
4.3.4. get_db_comment_text 函数
def get_db_comment_text(file_name) -> DataFrame:
"""
db_comment 파일에서 text를 추출하여 DataFrame type으로 return
:param file_name: 입력 파일명 (str type)
:return: 입력 파일에서 추출한 text
"""
# :return: 입력 파일에서 추출한 text에 형태소 분석기로 명사 추출한 DataFrame
start_time = time.time()
print('\r\nget_db_comment_text: %s' % file_name)
excel_app = win32com.client.Dispatch('Excel.Application')
full_path_file_name = os.path.abspath(file_name)
excel_file = excel_app.Workbooks.Open(full_path_file_name, True)
# region Table comment
table_comment_sheet = excel_file.Worksheets(1)
last_row = table_comment_sheet.Range("A1").End(-4121).Row # -4121: xlDown
table_comment_range = 'A2:D%s' % (str(last_row))
print('table_comment_range : %s (%d rows)' % (table_comment_range, last_row - 1))
table_comments = table_comment_sheet.Range(table_comment_range).Value2
df_table = pd.DataFrame(list(table_comments),
columns=['DB', 'Schema', 'Table', 'Text'])
df_table['FileName'] = full_path_file_name
df_table['FileType'] = 'table'
df_table['Page'] = 0
df_table = df_table[df_table.Text.notnull()] # Text 값이 없는 행 제거
df_table['Source'] = df_table['DB'] + '.' + df_table['Schema'] + '.' + df_table['Table'] \
+ '(' + df_table['Text'].astype(str) + ')'
# print(df_table)
# endregion
# region Column comment
column_comment_sheet = excel_file.Worksheets(2)
last_row = column_comment_sheet.Range("A1").End(-4121).Row # -4121: xlDown
column_comment_range = 'A2:E%s' % (str(last_row))
print('column_comment_range : %s (%d rows)' % (column_comment_range, last_row - 1))
column_comments = column_comment_sheet.Range(column_comment_range).Value2
df_column = pd.DataFrame(list(column_comments),
columns=['DB', 'Schema', 'Table', 'Column', 'Text'])
df_column['FileName'] = full_path_file_name
df_column['FileType'] = 'column'
df_column['Page'] = 0
df_column = df_column[df_column.Text.notnull()] # Text 값이 없는 행 제거
df_column['Source'] = df_column['DB'] + '.' + df_column['Schema'] + '.' + df_column['Table'] \
+ '.' + df_column['Column'] + '(' + df_column['Text'].astype(str) + ')'
# print(df_column)
# endregion
excel_file.Close()
df_text = df_column.append(df_table, ignore_index=True)
# print(df_text)
end_time = time.time()
# elapsed_time = end_time - start_time
elapsed_time = str(datetime.timedelta(seconds=end_time - start_time))
print('[pid:%d] get_db_comment_text elapsed time: %s' % (os.getpid(), elapsed_time))
print('text count: %s' % str(df_text.shape[0]))
# return get_word_list(df_text)
return df_text
- 第 300 行:使用 win32com 包创建 MS Excel 程序的实例。如果 MS Excel 未运行,则使用此代码执行。
- 第 302 行:在上面创建的 MS Excel 程序的实例中打开 .xls 或 .xlsx 文件。
- 第 305~317 行:从存储表格注释的 Excel 文件的第一张表中提取文本。
- 第306行:获取表格注释表的最后一行行号。在 Range(“A1”).End(-4211).Row 中,“-4211”是“xlDown”常量。更多细节 XlDirection 枚举 (Excel) |微软文档 查看文档
- 第 309 行:将 table comments sheet 的内容读入 table_comments 变量中。该方法是不使用循环,一次性将范围内的内容读入内存的方法。 VBA 编码模式:范围循环读取 查看内容这篇文章是用Excel VBA解释的,但是如果你在Python中使用OLE Automation,几乎一样可以应用。
- 第 310-317 行:将 table_comments 转换为 DataFrame df_table 并添加来自“FileName”、“FileType”、“Page”、“Source”列的数据。
- 第 322~334 行:从存储列注释的 Excel 文件的第二张表中提取文本。
- 第 323 行:获取列注释表的最后行号。使用与第 306 行相同的方法。
- 第 326 行:将列注释表的内容读入 column_comments 变量。使用与第 309 行相同的方法。
- 第 327-334 行:将 column_comments 转换为 DataFrame df_column 并添加来自“FileName”、“FileType”、“Page”、“Source”列的数据。
- 第 339 行:合并 df_column 和 df_table 以创建 df_text。
- 第 347 行:返回包含从存储 DB 表和列注释的 Excel 文件中提取的文本的 df_text。
4.3.5. get_hwp_text 函数
def get_hwp_text(file_name) -> DataFrame:
pass
目前未实施如有必要,将在未来实施。
4.3.6. get_pdf_text 函数
def get_pdf_text(file_name) -> DataFrame:
pass
目前未实施如有必要,将在未来实施。
4.4. get_word_list 函数
这是分词工具中最重要的功能。
def get_word_list(df_text) -> DataFrame:
"""
text 추출결과 DataFrame에서 명사를 추출하여 최종 output을 DataFrame type으로 return
:param df_text: 파일에서 추출한 text(DataFrame type)
:return: 명사, 복합어(1개 이상의 명사, 접두사+명사+접미사) 추출결과(Dataframe type)
"""
start_time = time.time()
df_result = DataFrame()
tagger = Mecab()
# tagger = Komoran()
row_idx = 0
for index, row in df_text.iterrows():
row_idx += 1
if row_idx % 100 == 0: # 100건마다 현재 진행상태 출력
print('[pid:%d] current: %d, total: %d, progress: %3.2f%%' %
(os.getpid(), row_idx, df_text.shape[0], round(row_idx / df_text.shape[0] * 100, 2)))
file_name = row['FileName']
file_type = row['FileType']
page = row['Page']
text = str(row['Text'])
source = (row['Source'])
is_db = True if row['FileType'] in ('table', 'column') else False
is_db_table = True if row['FileType'] == 'table' else False
is_db_column = True if row['FileType'] == 'column' else False
if is_db:
db = row['DB']
schema = row['Schema']
table = row['Table']
if is_db_column:
column = row['Column']
if text is None or text.strip() == '':
continue
try:
# nouns = mecab.nouns(text)
# [O]ToDo: 연속된 체언접두사(XPN), 명사파생접미사(XSN) 까지 포함하여 추출
# [O]ToDo: 명사(NNG, NNP)가 연속될 때 각각 명사와 연결된 복합명사 함께 추출
text_pos = tagger.pos(text)
words = [pos for pos, tag in text_pos if tag in ['NNG', 'NNP', 'SL']] # NNG: 일반명사, NNP: 고유명사
pos_list = [x for (x, y) in text_pos]
tag_list = [y for (x, y) in text_pos]
pos_str = '/'.join(pos_list) + '/'
tag_str = '/'.join(tag_list) + '/'
iterator = re.finditer('(NNP/|NNG/)+(XSN/)*|(XPN/)+(NNP/|NNG/)+(XSN/)*|(SL/)+', tag_str)
for mo in iterator:
x, y = mo.span()
if x == 0:
start_idx = 0
else:
start_idx = tag_str[:x].count('/')
end_idx = tag_str[:y].count('/')
sub_pos = ''
# if end_idx - start_idx > 1 and not (start_idx == 0 and end_idx == len(tag_list)):
if end_idx - start_idx > 1:
for i in range(start_idx, end_idx):
sub_pos += pos_list[i]
# print('%s[sub_pos]' % sub_pos)
words.append('%s[복합어]' % sub_pos) # 추가 형태소 등록
if len(words) >= 1:
# print(nouns, text)
for word in words:
# print(noun, '\t', text)
if not is_db:
# sr_text = Series([file_name, file_type, page, text, word],
# index=['FileName', 'FileType', 'Page', 'Text', 'Word'])
df_word = DataFrame(
{'FileName': [file_name], 'FileType': [file_type], 'Page': [page], 'Text': [text],
'Word': [word], 'Source': [source]})
elif is_db_table:
# sr_text = Series([file_name, file_type, page, text, word, db, schema, table],
# index=['FileName', 'FileType', 'Page', 'Text', 'Word', 'DB', 'Schema', 'Table'])
df_word = DataFrame(
{'FileName': [file_name], 'FileType': [file_type], 'Page': [page], 'Text': [text],
'Word': [word], 'DB': [db], 'Schema': [schema], 'Table': [table “” not found /]
, 'Source': [source]})
elif is_db_column:
# sr_text = Series([file_name, file_type, page, text, word, db, schema, table, column],
# index=['FileName', 'FileType', 'Page', 'Text', 'Word', 'DB', 'Schema', 'Table', 'Column'])
df_word = DataFrame(
{'FileName': [file_name], 'FileType': [file_type], 'Page': [page], 'Text': [text],
'Word': [word], 'DB': [db], 'Schema': [schema], 'Table': [table “” not found /]
, 'Column': [column],
'Source': [source]})
# df_result = df_result.append(sr_text, ignore_index=True) # Todo: append를 concat으로 바꾸기
df_result = pd.concat([df_result, df_word], ignore_index=True)
except Exception as ex:
print('[pid:%d] Exception has raised for text: %s' % (os.getpid(), text))
print(ex)
print(
'[pid:%d] input text count:%d, extracted word count: %d' % (os.getpid(), df_text.shape[0], df_result.shape[0]))
end_time = time.time()
# elapsed_time = end_time - start_time
elapsed_time = str(datetime.timedelta(seconds=end_time - start_time))
print('[pid:%d] get_word_list finished. total: %d, elapsed time: %s' %
(os.getpid(), df_text.shape[0], elapsed_time))
return df_result
- 第 35 行:创建自然语言词素分析器 Mecab 对象。要使用 Mecab 以外的标记器,请在此处更改包名称。
- 第 38 行:遍历 DataFrame df_text 的行。
- 第 64 行:使用 pos 函数执行语素分析器的词性标注。词性标注相关的内容我会分开来。
- 词性标注函数 pos 将输入字符串分解为词性单元,并返回每个单元都被标记的字符串。
- 比如文本是'Users define functional and non-functional requirements',那么pos函数的执行结果就是'[('use', 'NNG'), ('character', 'XSN'), (' is', 'JX'), ('function', 'NNG'), ('enemy', 'XSN'), ('request', 'NNG'), ('spec', 'NNG' '), ( 'and', 'JC'), ('b', 'XPN'), ('feature', 'NNG'), ('enemies', 'XSN'), ('request', 'NNG'), ( '东西', 'NNG'), ('to', 'JKO'), ('定义', 'NNG'), ('应该', 'XSV+EF'), ('.', 'SF') ]'。
- 上例标注的词性中,'NNG'是普通名词,'XSN'是名词派生后缀,'JX'是助动词,'JC'是连接词,'XPN'是前缀,'JKO'是宾语助词,'XSV + EF'是动词派生后缀+词尾,'SF'表示句号/问号/感叹号。
- 第65行:从词性标注结果中选择词性最合适的普通名词(NNG)、专有名词(NNP)和外来词(SL)作为标准候选词。外语(SL)被指定提取由字母组成的缩写词作为标准候选词。
- 第 70 行:使用正则表达式,'(NNP/|NNG/)+(XSN/)*|(XPN/)+(NNP/|NNG/)+(XSN/)*|(SL/) +' 找到图案。
- 此模式找到以下三种情况之一:
- (NNP/|NNG/)+(XSN/)*:1个或多个(专有名词或普通名词)+ 0个或多个名词派生后缀
- (XPN/)+(NNP/|NNG/)+(XSN/)*:1个或多个前缀+1个或多个(专有名词或普通名词)+0个或多个名词派生后缀
- (SL/)+:至少一门外语
- 此模式找到以下三种情况之一:
- 第 71~84 行:将找到的单词与上述正则表达式模式连接起来,并通过附加后缀 '[compound word]' 将它们添加到提取的单词列表中。后来在做标准词典细化的时候,我们特意加了后缀来标识提取出来的复合词。
- 第 86 到 110 行:为提取的单词添加源和文件格式等附加属性,并将它们存储在 DataFrame 中。
- 第 122 行:返回包含提取的单词列表的 df_result。
作为参考,如果你想额外提取其他词性模式,你可以修改第 65 和 70 行。
4.5. make_word_cloud 函数
def make_word_cloud(df_group, now_dt, out_path):
"""
명사의 빈도를 구한 DataFrame으로 word cloud 그리기
:param df_group: 명사 빈도 DataFrame
:param now_dt: 현재 날짜 시각
:param out_path: 출력경로
:return: None
"""
start_time = time.time()
print('\r\nstart make_word_cloud...')
from wordcloud import WordCloud
import matplotlib.pyplot as plt
# malgun.ttf # NanumSquare.ttf # NanumSquareR.ttf NanumMyeongjo.ttf # NanumBarunpenR.ttf # NanumBarunGothic.ttf
wc = WordCloud(font_path='.\\font\\NanumBarunGothic.ttf',
background_color='white',
max_words=500,
width=1800,
height=1000
)
# print(df_group.head(10))
words = df_group.to_dict()['Freq']
# print(words)
# words = df_group.T.to_dict('list')
wc.generate_from_frequencies(words)
wc.to_file('%s\\wordcloud_%s.png' % (out_path, now_dt))
# plt.axis('off')
end_time = time.time()
# elapsed_time = end_time - start_time
elapsed_time = str(datetime.timedelta(seconds=end_time - start_time))
print('make_word_cloud elapsed time: %s' % elapsed_time)
此功能使用 WordCloud 包。
- 第 258-263 行:创建 WordCloud 对象。
- 使用了字体文件夹下的NanumBarunGothic.ttf(NanumBarunGothic)字体文件。要更改为另一种字体,将字体文件复制到字体文件夹并指定文件名。
- Background_color、max_words、width 和 height 可以更改为所需的值。
- 第266行:在DataFrame df_group中,创建以Index(词)为Key,'Freq'(频率)为Value的字典词。
- 第 269 行:根据具有频率的单词创建一个 WordCloud 图像。
- 第 270 行:保存生成的 WordCloud 图片。
源码讲解到此结束。接下来我们就来看看源码的补充说明和词性标注。
<< 相关文章列表 >>








