SJ_Koding

[LLM] Python으로 다양한 문서에서 텍스트 추출하는법(PDF, PPTX, DOCX, DOC, XLS, XLSX, HWP) 본문

LLM

[LLM] Python으로 다양한 문서에서 텍스트 추출하는법(PDF, PPTX, DOCX, DOC, XLS, XLSX, HWP)

성지코딩 2024. 6. 14. 21:58

개요

문서에서 텍스트를 추출하는건 다양하게 쓰일 수 있는데, LLM에 도입할때 꽤 유용하게 쓰일 수 있다. 이를테면 챗봇에 문서를 업로드하면 해당 문서를 요약할 수 있도록 할 수 있는데(물론 PNG파일과 같은 이미지 파일은 OCR기술이 필요하다), 그러기 위해서는 문서 내의 텍스트를 추출하여 해당 데이터를 LLM모델에 넘겨줘야한다. 

데이터를 추출하면 추가 가공하거나 추출된 원본 그대로를 LLM모델에 넣어주면 알아서 잘 해석하고 답변을 생성한다.
(당연히 LLM성능에 따라 답변 퀄리티가 달라진다.)

Python의 확장성이란,, 상상 이상이다. 거의 모든 확장자의 문서파일에서 텍스트를 추출할 수 있는 기능을 제공한다.(진짜쉽다)
해당 포스팅에서는 PPT, PDF, DOCX, HWP, XLSX등의 확장자 파일들을 포함하여 각각 텍스트 추출하는 코드를 정리한다.

Ctrl + F로 확장자 검색


1. PDF 텍스트 추출

아래의 코드로 PyMuPDF 설치한다.

pip install PyMuPDF

PyMuPDF의 import 명은 fitz이다. 

https://pdfobject.com/pdf/sample.pdf

위 링크로 sample.pdf를 다운로드받아 다음의 코드로 추출해본다.

그리고 아래의 코드로 데이터를 추출할 수 있다. fitz.open으로 파일을 열고, 페이지별로 저장된 document를 한 페이지씩 반복하여 get_text()함수를 통해 text에 쌓는 방식이다.

import fitz  # PyMuPDF 라이브러리

def extract_text_from_pdf(pdf_path):
    document = fitz.open(pdf_path)
    
    text = ""
    for page_num in range(len(document)):
        page = document.load_page(page_num)
        text += page.get_text()
    
    return text

pdf_path = 'sample.pdf'
extracted_text = extract_text_from_pdf(pdf_path)
print(extracted_text)

출력결과

Sample PDF
This is a simple PDF file. Fun fun fun.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Phasellus facilisis odio sed mi. 
Curabitur suscipit. Nullam vel nisi. Etiam semper ipsum ut lectus. Proin aliquam, erat eget 
pharetra commodo, eros mi condimentum quam, sed commodo justo quam ut velit. 
Integer a erat. Cras laoreet ligula cursus enim. Aenean scelerisque velit et tellus. 
Vestibulum dictum aliquet sem. Nulla facilisi. Vestibulum accumsan ante vitae elit. Nulla 
erat dolor, blandit in, rutrum quis, semper pulvinar, enim. Nullam varius congue risus. 
Vivamus sollicitudin, metus ut interdum eleifend, nisi tellus pellentesque elit, tristique 
accumsan eros quam et risus. Suspendisse libero odio, mattis sit amet, aliquet eget, 
hendrerit vel, nulla. Sed vitae augue. Aliquam erat volutpat. Aliquam feugiat vulputate nisl. 
Suspendisse quis nulla pretium ante pretium mollis. Proin velit ligula, sagittis at, egestas a, 
pulvinar quis, nisl.
...

2. PPT 텍스트 추출

아래의 코드로 python-pptx를 설치한다.

pip install python-pptx

python-pptx의 import 명은 pptx이다. 

https://www.dickinson.edu/downloads/download/520/sample_powerpoint_slides

 

Sample PowerPoint Slides | Dickinson College

 

www.dickinson.edu

위 링크에서 Dickinson_Sample_Slides.pptx를 다운받아 사용한다.

그리고 아래의 코드를 이용해서 텍스트를 추출한다. PDF와 마찬가지로 슬라이드별로 데이터를 읽고 반복문으로 하나의 슬라이드 씩 추출한 후, 각 요소 (텍스트박스, 이미지, 도형 등등) 의 타입이 text일 때, shape.text를 통해 데이터를 추출하는 방식이다.

*hasattr은 객체의 멤버변수로 두 번째 인자를 가지고 있는지 확인하는 파이썬 구문이다.

from pptx import Presentation

def extract_text_from_pptx(pptx_path):
    presentation = Presentation(pptx_path)
    
    text = ""
    for slide in presentation.slides:
        for shape in slide.shapes:
            if hasattr(shape, "text"):
                text += shape.text + "\n"
    
    return text

pptx_path = 'Dickinson_Sample_Slides.pptx'
extracted_text = extract_text_from_pptx(pptx_path)
print(extracted_text)



위 코드를 이용해 텍스트를 추출하는 방식은 PDF와 유사하다. 슬라이드별로 데이터를 읽고 반복문으로 하나씩 슬라이드를 처리한 후, 각 요소가 텍스트 타입일 때 `shape.text`를 통해 텍스트를 추출한다. `hasattr`은 객체가 특정 속성을 가지고 있는지 확인하는 파이썬 구문으로, 여기서는 shape 객체가 'text' 속성을 가지고 있는지 검사하는 데 사용된다.

출력결과

Presentation Title
Author
Department
Date
Location
Presentation Title
Author
Department
Date
Location

Presentation Title
Author
Department
Date
Location
GraphTitle
Additional Notes - Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem
Chart Title
...

3. DOCX(워드) 텍스트 추출

아래의 코드로 python-docx를 설치한다. (ppt랑 비슷하다.)

pip install python-docx

python-docx 의 import 명은 docx이다. 

https://file-examples.com/index.php/sample-documents-download/sample-doc-download/

 

Sample .doc and .docx download | File Examples Download

The native file format utilized by Microsoft Word is the Microsoft Word Binary File Format, also referred to as the .doc format. Although other word processors, such as OpenOffice Writer, IBM Lotus Symphony, and Apple Pages, may create and read .doc files,

file-examples.com

위 링크에서 100kB짜리 샘플 docx파일( file-sample_100kB.docx )을 다운로드 받는다. 

*주의: doc과 docx는 엄연히 다른 파일이고 doc파일인 경우는 4번을 참고하자.

python-docx는 하나의 단락별로 데이터를 가져온다. para.text로 쉽게 추출이 가능하다.

from docx import Document

def extract_text_from_docx(docx_path):
    document = Document(docx_path)
    
    text = ""
    for para in document.paragraphs:
        text += para.text + "\n"
    
    return text

docx_path = 'file-sample_100kB.docx'
extracted_text = extract_text_from_docx(docx_path)
print(extracted_text)

출력 결과:

Lorem ipsum 

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc ac faucibus odio. 

Vestibulum neque massa, scelerisque sit amet ligula eu, congue molestie mi. Praesent ut varius sem. Nullam at porttitor arcu, nec lacinia nisi. Ut ac dolor vitae odio interdum condimentum. Vivamus dapibus sodales ex, vitae malesuada ipsum cursus convallis. Maecenas sed egestas nulla, ac condimentum orci. Mauris diam felis, vulputate ac suscipit et, iaculis non est. Curabitur semper arcu ac ligula semper, nec luctus nisl blandit. Integer lacinia ante ac libero lobortis imperdiet. Nullam mollis convallis ipsum, ac accumsan nunc vehicula vitae. Nulla eget justo in felis tristique fringilla. Morbi sit amet tortor quis risus auctor condimentum. Morbi in ullamcorper elit. Nulla iaculis tellus sit amet mauris tempus fringilla.
Maecenas mauris lectus, lobortis et purus mattis, blandit dictum tellus.
Maecenas non lorem quis tellus placerat varius. 
Nulla facilisi. 
Aenean congue fringilla justo ut aliquam. 
Mauris id ex erat. Nunc vulputate neque vitae justo facilisis, non condimentum ante sagittis. 
Morbi viverra semper lorem nec molestie. 
Maecenas tincidunt est efficitur ligula euismod, sit amet ornare est vulputate.
...

4. DOC(워드) 텍스트 추출

*docx파일이라면 3번을 참고하자.

doc파일은 python-docx 라이브러리 대신 pypandoc을 사용한다. 라이브러리명과 import명이 동일하다.

pip install pypandoc

https://file-examples.com/index.php/sample-documents-download/sample-doc-download/

 

Sample .doc and .docx download | File Examples Download

The native file format utilized by Microsoft Word is the Microsoft Word Binary File Format, also referred to as the .doc format. Although other word processors, such as OpenOffice Writer, IBM Lotus Symphony, and Apple Pages, may create and read .doc files,

file-examples.com

위 링크에서 100kB짜리 샘플 doc파일( file-sample_100kB.doc )을 다운로드 받는다. 

그리고 다음 코드로 텍스트를 추출한다.

import pypandoc

def extract_text_from_doc(doc_path):
    text = pypandoc.convert_file(doc_path, 'plain')
    return text

doc_path = 'file-sample_100kB.doc'
extracted_text = extract_text_from_doc(doc_path)
print(extracted_text)

출력결과:

Lorem ipsum 

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc ac faucibus odio. 

Vestibulum neque massa, scelerisque sit amet ligula eu, congue molestie mi. Praesent ut varius sem. Nullam at porttitor arcu, nec lacinia nisi. Ut ac dolor vitae odio interdum condimentum. Vivamus dapibus sodales ex, vitae malesuada ipsum cursus convallis. Maecenas sed egestas nulla, ac condimentum orci. Mauris diam felis, vulputate ac suscipit et, iaculis non est. Curabitur semper arcu ac ligula semper, nec luctus nisl blandit. Integer lacinia ante ac libero lobortis imperdiet. Nullam mollis convallis ipsum, ac accumsan nunc vehicula vitae. Nulla eget justo in felis tristique fringilla. Morbi sit amet tortor quis risus auctor condimentum. Morbi in ullamcorper elit. Nulla iaculis tellus sit amet mauris tempus fringilla.
Maecenas mauris lectus, lobortis et purus mattis, blandit dictum tellus.
Maecenas non lorem quis tellus placerat varius. 
Nulla facilisi. 
Aenean congue fringilla justo ut aliquam. 
Mauris id ex erat. Nunc vulputate neque vitae justo facilisis, non condimentum ante sagittis. 
Morbi viverra semper lorem nec molestie. 
Maecenas tincidunt est efficitur ligula euismod, sit amet ornare est vulputate.
...

5. XLSX(엑셀) 텍스트 추출

csv는 pandas의 read_csv()함수로 손쉽게 텍스트를 불러올 수 있지만 xlsx파일의 경우 openpyxl을 사용하면 좋다.

라이브러리명과 import명이 동일하다.

pip install openpyxl

주의: xls과 xlsx는 엄연히 다른 파일이고 xls파일인 경우는 4번을 참고하자.

https://file-examples.com/index.php/sample-documents-download/sample-xls-download/

 

Sample .xls and xlsx download | File Examples Download

Microsoft Excel worksheet sheet (97–2003) The .xls format is native to Microsoft Excel. This is a file extension for a spreadsheet file format. .xls files can also be opened and edited by the Microsoft Excel Viewer, and OpenOffice. .xls stands for eXceL

file-examples.com

위의 링크를 통해 10row짜리 샘플 xlsx를 다운받자. (file_example_XLSX_10.xlsx)

그리고 아래의 코드를 활용하여 텍스트를 추출한다. 하나의 row에 담긴 여러 속성값들을 str로 캐스팅하여 사용한다.

import openpyxl

def extract_text_from_xlsx(xlsx_path):
    workbook = openpyxl.load_workbook(xlsx_path)
    sheet = workbook.active
    
    text = ""
    for row in sheet.iter_rows(values_only=True):
        text += ", ".join([str(cell) for cell in row]) + "\n"
    
    return text

xlsx_path = 'file_example_XLSX_10.xlsx'
extracted_text = extract_text_from_xlsx(xlsx_path)
print(extracted_text)

출력 결과:

0, First Name, Last Name, Gender, Country, Age, Date, Id
1, Dulce, Abril, Female, United States, 32, 15/10/2017, 1562
2, Mara, Hashimoto, Female, Great Britain, 25, 16/08/2016, 1582
3, Philip, Gent, Male, France, 36, 21/05/2015, 2587
4, Kathleen, Hanner, Female, United States, 25, 15/10/2017, 3549
5, Nereida, Magwood, Female, United States, 58, 16/08/2016, 2468
6, Gaston, Brumm, Male, United States, 24, 21/05/2015, 2554
7, Etta, Hurn, Female, Great Britain, 56, 15/10/2017, 3598
8, Earlean, Melgar, Female, United States, 27, 16/08/2016, 2456
9, Vincenza, Weiland, Female, United States, 40, 21/05/2015, 6548

6. XLS(엑셀) 텍스트 추출

*xlsx파일이라면 5번을 참고하자.

.xls 파일에서 텍스트를 추출하려면 xlrd라이브러리를 사용한다. 라이브러리명과 import명이 동일하다.

pip install xlrd

https://file-examples.com/index.php/sample-documents-download/sample-xls-download/

 

Sample .xls and xlsx download | File Examples Download

Microsoft Excel worksheet sheet (97–2003) The .xls format is native to Microsoft Excel. This is a file extension for a spreadsheet file format. .xls files can also be opened and edited by the Microsoft Excel Viewer, and OpenOffice. .xls stands for eXceL

file-examples.com

위의 링크를 통해 10row짜리 샘플 xlsx를 다운받자. (file_example_XLSX_10.xls)

그리고 아래의 코드를 활용하여 텍스트를 추출한다. 하나의 row에 담긴 여러 속성값들을 str로 캐스팅하여 사용한다.

사용방법이 xlsx와 매우 유사하다.

import xlrd

def extract_text_from_xls(xls_path):
    workbook = xlrd.open_workbook(xls_path)
    sheet = workbook.sheet_by_index(0)
    
    text = ""
    for row_idx in range(sheet.nrows):
        row = sheet.row(row_idx)
        row_text = ", ".join([str(cell.value) for cell in row])
        text += row_text + "\n"
    
    return text

xls_path = 'file_example_XLSX_10.xls'
extracted_text = extract_text_from_xls(xls_path)
print(extracted_text)

7. HWP(한글) 텍스트 추출

가장 복잡하다.  일단 먼저 olefile모듈을 설치해야하고 나머지 파이썬 표준 라이브러리인 zlib과 struct를 사용한다.

pip install olefile

라이브러리명과 import명이 동일하다.

https://m.blog.naver.com/chispa/220697541431

 

한글 양식 샘플

한글 양식 샘플  

blog.naver.com

hwp가 준비가 안되어있다면, 위의 링크로 샘플 데이터를 다운받자. (BlogForm_BookReview.hwp)

코드를 먼저 살펴보자. 코드가 긴데, 이는 좀 자세하게 정리한다.

import olefile
import zlib
import struct

def get_hwp_text(filename):
    f = olefile.OleFileIO(filename)
    dirs = f.listdir()

    if ["FileHeader"] not in dirs or \
            ["\x05HwpSummaryInformation"] not in dirs:
        raise Exception("Not Valid HWP.")

    header = f.openstream("FileHeader")
    header_data = header.read()
    is_compressed = (header_data[36] & 1) == 1

    nums = []
    for d in dirs:
        if d[0] == "BodyText":
            nums.append(int(d[1][len("Section"):]))
    sections = ["BodyText/Section" + str(x) for x in sorted(nums)]

    text = ""
    for section in sections:
        bodytext = f.openstream(section)
        data = bodytext.read()
        if is_compressed:
            unpacked_data = zlib.decompress(data, -15)
        else:
            unpacked_data = data

        section_text = ""
        i = 0
        size = len(unpacked_data)
        while i < size:
            header = struct.unpack_from("<I", unpacked_data, i)[0]
            rec_type = header & 0x3ff
            rec_len = (header >> 20) & 0xfff

            if rec_type in [67]:
                rec_data = unpacked_data[i + 4:i + 4 + rec_len]
                section_text += rec_data.decode('utf-16')
                section_text += "\n"

            i += 4 + rec_len

        text += section_text
        text += "\n"

    return text

text = get_hwp_text('BlogForm_BookReview.hwp')
import re
cleaned_text = re.sub(r'[^\x00-\x7F\uAC00-\uD7AF]', '', text)
print(cleaned_text)

    f = olefile.OleFileIO(filename)
    dirs = f.listdir()

    if ["FileHeader"] not in dirs or \
            ["\x05HwpSummaryInformation"] not in dirs:
        raise Exception("Not Valid HWP.")

    header = f.openstream("FileHeader")
    header_data = header.read()
    is_compressed = (header_data[36] & 1) == 1

    nums = []
    for d in dirs:
        if d[0] == "BodyText":
            nums.append(int(d[1][len("Section"):]))
    sections = ["BodyText/Section" + str(x) for x in sorted(nums)]

위 코드는 hwp파일을 열고 FileHeader와 \x05HwpSummaryInformation 스트림이 포함되어있어야 유효한 hwp파일이기 때문에 이를 검사한다.

그 후 FileHeader 스트림의 데이터를 읽어와서 36번째 바이트를 확인하여 압축이 되었는지 확인한다.

그 후 BodyText 디렉토리 내의 섹션들을 불러온다.

    text = ""
    for section in sections:
        bodytext = f.openstream(section)
        data = bodytext.read()
        if is_compressed:
            unpacked_data = zlib.decompress(data, -15)
        else:
            unpacked_data = data

        section_text = ""
        i = 0
        size = len(unpacked_data)
        while i < size:
            header = struct.unpack_from("<I", unpacked_data, i)[0]
            rec_type = header & 0x3ff
            rec_len = (header >> 20) & 0xfff

            if rec_type in [67]:
                rec_data = unpacked_data[i + 4:i + 4 + rec_len]
                section_text += rec_data.decode('utf-16')
                section_text += "\n"

            i += 4 + rec_len

        text += section_text
        text += "\n"

    return text

최종 텍스트만 추출할 text변수를 초기화하고, 각 section을 반복문으로 탐색한다. 파일이 압축된 경우에는 zlib모듈을 사용해서 압축을 풀어낸다.

데이터를 4바이트씩 읽으며 레코드 헤더를 parsing하고, 텍스트 데이터 (rec_type 67)를 UTF-16으로 디코딩하여 텍스트를 추출한다.


text = get_hwp_text('BlogForm_BookReview.hwp')
import re
cleaned_text = re.sub(r'[^\x00-\x7F\uAC00-\uD7AF]', '', text)
print(cleaned_text)

그 후, 정규표현식으로 ASCII 문자와 한글('가' - '힣')을 제외한 모든 문자를 제거한다.