티스토리 뷰

728x90
반응형

Colab을 사용해 아래한글 문서를 받아 엑셀로 정리하는 작업을 하려고 한다.

윈도우에서 한글 문서를 다루는 패키지로 pyhwp가 있지만,

Colab에서는 리눅스 OS를 사용하기 때문에 pyhwp로 아래한글 문서를 읽기가 쉽지 않았다.

(exe 파일을 사용해서 그렇다고 본 것 같다.)

 

다행히 olefile 패키지를 사용하여

아래한글 문서를 바로 txt 문서로 변환하여 읽어 올 수 있는 방법을 찾았다.

1. olefile 패키지 설치

pip install olefile

2. 아래한글 문서 열기

import olefile

path = "/content/file_name.hwp"
f = olefile.OleFileIO(path)

3. 아래한글 문서 디코딩하기

기본적인 틀은 openstream 함수로 문서의 내용을 꺼내고, 유니코드로 인코딩된 문서를 utf-16으로 디코딩을 하는 것이다.

한글 파일 안에는 다양한 디렉토리가 존재하는데, 이중 PrvText 와 BodyText를 이용하는 방법을 살펴볼 것이다.

3.1. PrvText로 문서 읽기

PrvText는 예상할 수 있듯이 미리보기를 사용하여 문서를 읽는 방법이다.

만약 문서 길이가 1페이지 내외라면 간단하게 내용을 읽을 수 있다.

encoded_txt = f.openstream("PrvText").read()
text = encoded_txt.decode("utf-16",errors="ignore")
print(text)

 

3.2. BodyText로 문서 읽기

한글의 본문이 들어있는 곳이다.

아래에는 charsyam님의 깃허브에 올라와 있는 코드에 주석을 달아 보았다.

한자와 여러 기호들이 함께 출력되는 문제가 있지만 그 외 모든 내용이 잘 추출된다.

* 암행어사님께서 댓글에 달아주신 텍스트 정제 내용을 업데이트 했습니다.

import olefile
import zlib
import struct
#### 추가 ####
import re
import unicodedata

class HWPExtractor(object):
    FILE_HEADER_SECTION = "FileHeader"
    HWP_SUMMARY_SECTION = "\x05HwpSummaryInformation"
    SECTION_NAME_LENGTH = len("Section")
    BODYTEXT_SECTION = "BodyText"
    HWP_TEXT_TAGS = [67]

    def __init__(self, filename):
        self._ole = self.load(filename)
        self._dirs = self._ole.listdir()

        self._valid = self.is_valid(self._dirs)
        if (self._valid == False):
            raise Exception("Not Valid HwpFile")
        
        self._compressed = self.is_compressed(self._ole)
        self.text = self._get_text()
	
    # 파일 불러오기 
    def load(self, filename):
        return olefile.OleFileIO(filename)
	
    # hwp 파일인지 확인 header가 없으면 hwp가 아닌 것으로 판단하여 진행 안함
    def is_valid(self, dirs):
        if [self.FILE_HEADER_SECTION] not in dirs:
            return False

        return [self.HWP_SUMMARY_SECTION] in dirs

	# 문서 포맷 압축 여부를 확인
    def is_compressed(self, ole):
        header = self._ole.openstream("FileHeader")
        header_data = header.read()
        return (header_data[36] & 1) == 1

	# bodytext의 section들 목록을 저장
    def get_body_sections(self, dirs):
        m = []
        for d in dirs:
            if d[0] == self.BODYTEXT_SECTION:
                m.append(int(d[1][self.SECTION_NAME_LENGTH:]))

        return ["BodyText/Section"+str(x) for x in sorted(m)]
	
    # text를 뽑아내는 함수
    def get_text(self):
        return self.text

	# 전체 text 추출
    def _get_text(self):
        sections = self.get_body_sections(self._dirs)
        text = ""
        for section in sections:
            text += self.get_text_from_section(section)
            text += "\n"

        self.text = text
        return self.text

	# section 내 text 추출
    def get_text_from_section(self, section):
        bodytext = self._ole.openstream(section)
        data = bodytext.read()

        unpacked_data = zlib.decompress(data, -15) if self.is_compressed else data
        size = len(unpacked_data)

        i = 0

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

            if rec_type in self.HWP_TEXT_TAGS:
                rec_data = unpacked_data[i+4:i+4+rec_len]
                
                ############## 정제 추가된 부분 #############
                decode_text = rec_data.decode('utf-16')
                # 문자열을 담기 전 정제하기
                res = remove_control_characters(remove_chinese_characters(decode_text))
                
                text += res
                text += "\n"

            i += 4 + rec_len

        return text

#################### 텍스트 정제 함수 #######################
    # 중국어 제거
    def remove_chinese_characters(s: str):   
        return re.sub(r'[\u4e00-\u9fff]+', '', s)
        
    # 바이트 문자열 제거
    def remove_control_characters(s):    
        return "".join(ch for ch in s if unicodedata.category(ch)[0]!="C")
        
        
# text 추출 함수 -> 이 함수를 사용하면 됨
def get_text(filename):
    hwp = HWPExtractor(filename) 
    return hwp.get_text()

 

* 정제를 추가한다면 한자와 다른 바이트 문자들이 제거되어 바로 깔끔한 데이터를 얻을 수 있다.

텍스트 정제 이전
텍스트 정제 이후

 

728x90
반응형
댓글