Herramienta de extracción de palabras (5): Descripción del código fuente de la herramienta de extracción de palabras (2)

Continuando con el artículo anterior, observamos el código fuente de la herramienta de extracción de palabras implementada en Python.

Esta es una continuación del artículo anterior.

Herramienta de extracción de palabras (4): Descripción del código fuente de la herramienta de extracción de palabras (1)

4. Código fuente de la herramienta de extracción de Word

4.3. función 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
  • Líneas 357 a 365: Ejecuta la función adecuada según la extensión del archivo y devuelve el resultado en df_text.

La descripción de cada función ejecutada según la extensión del archivo es la siguiente.

4.3.1. función 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
  • Línea 193: Cree una instancia del programa MS Word con el paquete win32com. Si MS Word no se está ejecutando, se ejecuta con este código.
  • Línea 194: Abra el archivo .doc o .docx en la instancia del programa MS Word creado anteriormente.
  • Línea 198: iterar a través de los párrafos en el contenido del archivo.
  • Línea 199: Extraiga texto del contenido del párrafo.
  • Línea 200: extrae el número de página del párrafo actual. En Range.Information(3), “3” corresponde al valor constante de wdActiveEndPageNumber. más detalles Enumeración WdInformation (Word) | Documentos de Microsoftver
  • Líneas 202~204: convierta el texto extraído en un objeto de serie y agréguelo a la fila del marco de datos df_text.
  • Línea 214: devuelve df_text que contiene el texto extraído del archivo.

4.3.2. función 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
  • Línea 138: Cree una instancia del programa MS Powerpoint con el paquete win32com. Si MS Powerpoint no se está ejecutando, se ejecutará este código.
  • Línea 139: Abra el archivo .ppt o .pptx en la instancia del programa MS Powerpoint creada anteriormente.
  • Línea 143: recorrer las diapositivas en el archivo.
  • Línea 146: Iterar a través de las formas de cada diapositiva.
  • Líneas 149~157: si la forma es una tabla, el texto se extrae de cada celda de la tabla.
  • Líneas 158~162: si la forma no es una tabla y tiene texto, se extrae el texto.
  • Líneas 163 a 167: convierta el texto extraído en un objeto Serie y agréguelo a la fila del marco de datos df_text.
  • Línea 181: devuelve df_text que contiene el texto extraído del archivo.

4.3.3. función 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
  • Línea 228: archivo abierto en modo de texto de solo lectura con codificación UTF8. (modo='rt')
  • Línea 229: Iterar a través de las líneas del archivo.
  • Líneas 231 a 234: convierta el texto de fila en un objeto Serie y agréguelo a las filas del marco de datos df_text.
  • Línea 242: devuelve df_text que contiene el texto extraído del archivo.

4.3.4. función 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
  • Línea 300: Cree una instancia del programa MS Excel con el paquete win32com. Si MS Excel no se está ejecutando, se ejecuta con este código.
  • Línea 302: Abra el archivo .xls o .xlsx en la instancia del programa MS Excel creado anteriormente.
  • Líneas 305~317: el texto se extrae de la primera hoja del archivo de Excel donde se almacenan los comentarios de la tabla.
  • Línea 306: obtenga el último número de fila de la hoja de comentarios de la tabla. En Range(“A1”).End(-4211).Row, “-4211” es la constante “xlDown”. más detalles Enumeración XlDirection (Excel) | Documentos de Microsoft ver la documentación
  • Línea 309: Lea el contenido de la hoja de comentarios de la tabla en la variable table_comments. Este método es un método para leer el contenido del rango en la memoria a la vez sin usar un bucle. Patrón de codificación VBA: bucle de rango - lectura ver contenido Este artículo se explica en Excel VBA, pero si usa OLE Automation en Python, puede aplicar casi lo mismo.
  • Líneas 310-317: Convierta table_comments en DataFrame df_table y agregue datos de las columnas 'FileName', 'FileType', 'Page', 'Source'.
  • Líneas 322~334: el texto se extrae de la segunda hoja del archivo de Excel donde se almacenan los comentarios de las columnas.
  • Línea 323: obtenga el último número de línea de la hoja de comentarios de la columna. Use el mismo método que en la línea 306.
  • Línea 326: Lea el contenido de la hoja de comentarios de la columna en la variable column_comments. Use el mismo método que en la línea 309.
  • Líneas 327-334: Convierta column_comments en DataFrame df_column y agregue datos de las columnas 'FileName', 'FileType', 'Page', 'Source'.
  • Línea 339: combine df_column y df_table para crear df_text.
  • Línea 347: Devuelve df_text que contiene el texto extraído del archivo de Excel en el que se almacenan los comentarios de la tabla y la columna de la base de datos.

4.3.5. función get_hwp_text

def get_hwp_text(file_name) -> DataFrame:
    pass

actualmente no implementado Se implementará en el futuro si es necesario.

4.3.6. función get_pdf_text

def get_pdf_text(file_name) -> DataFrame:
    pass

actualmente no implementado Se implementará en el futuro si es necesario.

4.4. función get_word_list

Esta es la función más importante en la herramienta de extracción de palabras.

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
  • Línea 35: Cree el objeto Mecab del analizador de morfemas de lenguaje natural. Para usar un etiquetador que no sea Mecab, cambie el nombre del paquete aquí.
  • Línea 38: Iterar a través de las filas de DataFrame df_text.
  • Línea 64: Ejecute el etiquetado de parte del discurso del analizador de morfemas con la función pos. Separaré los contenidos relacionados con el etiquetado de parte del discurso.
    • La función de etiquetado de partes del discurso pos descompone la cadena de entrada en unidades de partes del discurso y devuelve una cadena en la que se etiqueta cada unidad.
    • Por ejemplo, si el texto es 'Los usuarios definen requisitos funcionales y no funcionales', el resultado de la ejecución de la función pos es '[('usar', 'NNG'), ('carácter', 'XSN'), (' es', 'JX'), ('función', 'NNG'), ('enemigo', 'XSN'), ('solicitud', 'NNG'), ('spec', 'NNG' '), ( 'y', 'JC'), ('b', 'XPN'), ('característica', 'NNG'), ('enemigos', 'XSN'), ('solicitud', 'NNG'), ( 'cosa', 'NNG'), ('a', 'JKO'), ('definición', 'NNG'), ('debería', 'XSV+EF'), ('.', 'SF') ]'.
    • Entre las partes del discurso etiquetadas en el ejemplo anterior, 'NNG' es un sustantivo común, 'XSN' es un sufijo derivado de un sustantivo, 'JX' es un auxiliar, 'JC' es una partícula conectiva, 'XPN' es un prefijo , 'JKO' es una partícula objetiva, ' XSV + EF' es un sufijo derivado de un verbo + terminación final, y 'SF' significa un punto/signo de interrogación/exclamación.
  • Línea 65: seleccione nombres comunes (NNG), nombres propios (NNP) y palabras extranjeras (SL), que son las partes de la oración más adecuadas como candidatas a palabras estándar del resultado de etiquetado de partes de la oración. El idioma extranjero (SL) se designó para extraer abreviaturas compuestas de alfabetos como candidatos a palabras estándar.
  • Línea 70: Usando la expresión regular, '(NNP/|NNG/)+(XSN/)*|(XPN/)+(NNP/|NNG/)+(XSN/)*|(SL/) +' encuentra el patrón.
    • Este patrón encuentra una de tres cosas:
      • (NNP/|NNG/)+(XSN/)*: 1 o más (sustantivo propio o común) + 0 o más sufijos derivados de sustantivos
      • (XPN/)+(NNP/|NNG/)+(XSN/)*: 1 o más prefijos + 1 o más (sustantivos propios o comunes) + 0 o más sufijos derivados de sustantivos
      • (SL/)+: Al menos un idioma extranjero
  • Líneas 71~84: conecte las palabras encontradas con el patrón de expresión regular anterior y agréguelas a la lista de palabras extraídas agregando el sufijo '[palabra compuesta]'. Más tarde, cuando trabajamos en el refinamiento del diccionario de palabras estándar, agregamos sufijos intencionalmente para identificar palabras extraídas como palabras compuestas.
  • Líneas 86 a 110: agregue atributos adicionales, como fuente y formato de archivo, a las palabras extraídas y guárdelas en un DataFrame.
  • Línea 122: devuelve df_result que contiene la lista de palabras extraída. 

Como referencia, si desea extraer adicionalmente otras partes de los patrones del habla, puede modificar las líneas 65 y 70.

4.5. función hacer_palabra_nube

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)

Esta función utiliza el paquete WordCloud.

WordCloud 예시
Ejemplo de nube de palabras
  • Líneas 258-263: Cree el objeto WordCloud.
    • Se utilizó el archivo de fuentes NanumBarunGothic.ttf (NanumBarunGothic) en la carpeta de fuentes. Para cambiar a otra fuente, copie el archivo de la fuente en la carpeta de fuentes y designe el nombre del archivo.
    • Background_color, max_words, width y height se pueden cambiar a los valores deseados.
  • Línea 266: en DataFrame df_group, cree palabras de diccionario que consistan en Índice (palabra) como Clave y 'Freq' (frecuencia) como Valor.
  • Línea 269: crea una imagen de WordCloud a partir de palabras con frecuencias.
  • Línea 270: Guarde la imagen de WordCloud generada.

Este es el final de la explicación del código fuente. A continuación, veremos la explicación adicional del código fuente y el etiquetado de parte del discurso.


<< Lista de artículos relacionados >>

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

es_ESEspañol