[Python] 업무자동화 - PDF 파일 정리하기 (SGS 성적서)

 SGS에서 레포트를 받으면 모든 파일명이 SGS의 내부 식별 번호로 되어 있습니다. 저희는 보통 한 분기에 약 200개 이상의 성적서를 받다 보니, 이것들을 시료 이름과 시험 내용에 따라 일일이 분류하는 것이 여간 귀찮은 작업이 아닌데요. 이때 우리의 파이썬을 써먹으면 매우 편리합니다.

 

아래와 같이 SGS 번호로 된 PDF 파일명을 모두 내용에 따라서 시험 이름 및 시료 이름으로 변경하는 것입니다.

 

아래는 PDF 파일을 실행한 모습인데, Product Name이라고 적혀있는 내용을 자동으로 파일명에 들어가게 하고자 합니다.

 

순서는 간단합니다.

(1) PDF의 내용을 읽는다 (2) 시험 방법과 시료 이름을 찾는다 (3) 이름을 변경한다

 

이번 게시물에서는 코드만 간단히 보여드리는 게 아니라, 처음 시작부터 어떻게 시작하고 고민했는지를 보여드리려고 합니다. 이건 업무 자동화의 한가지 예시일 뿐이고, 실제 현업에서 응용하자면 정말 무궁무진하게 쓸 곳이 많습니다.

 

 

     

    준비물

    pdfminer가 필요합니다. 만약 없다면 아래의 방법으로 설치합니다.

    pip install pdfminer

     

     

    1. PDF 파일 읽기

    아래의 함수를 이용하여 준비해놓은 PDF 파일을 읽어옵니다.

    from io import StringIO
    from pdfminer.converter import TextConverter
    from pdfminer.layout import LAParams
    from pdfminer.pdfdocument import PDFDocument
    from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
    from pdfminer.pdfpage import PDFPage
    from pdfminer.pdfparser import PDFParser
    
    def read_pdf(file_name):
        output_string = StringIO()
        with open(file_name, 'rb') as f:
            parser = PDFParser(f)
            doc = PDFDocument(parser)
            rsrcmgr = PDFResourceManager()
            device = TextConverter(rsrcmgr, output_string, laparams=LAParams())
            interpreter = PDFPageInterpreter(rsrcmgr, device)
            for page in PDFPage.create_pages(doc):
                interpreter.process_page(page)
        return output_string.getvalue()
    
    # "1.pdf" 파일을 불러옵니다.
    txt = read_pdf("1.pdf")

    read_pdf 라는 함수를 만들어 PDF 파일의 텍스트를 읽어왔습니다. 해당 코드의 자세한 사항은 잘 모르겠고, pdfminer 공식 문서에서 가져와서 사용했습니다. (pdfminersix.readthedocs.io/en/latest/tutorial/composable.html)

     

     

    txt를 눌러보면 안에 텍스트가 표시됩니다. 저는 빨간 네모안에 쳐진 저 제품명이 필요합니다.

     

     

     

     

    2. 원하는 텍스트 찾기

    원하는 텍스트를 추출해 내는 것은 나름의 노하우가 필요합니다. 다행히 SGS 성적서는 형식이 어느 정도 갖추어져 있어서 한번 세팅해놓으면 잘 바뀌지 않습니다만, 다른 PDF 파일들은 까다로울 때가 많이 있습니다.

     

    (1) 시험 방법 찾아내기

    제가 가지고 있는 PDF 파일은 "VOC", "Heavy Metals", "Odor" 셋 중 하나의 시험 성적서입니다. 해당 성적서에는 시험 항목에 대한 단어가 반드시 포함되어 있으므로, 각 단어를 찾아서 포함이 되어 있으면 그 단어가 포함된 성적서라고 할 수 있습니다. 예를 들어서 PDF 파일 안에 "VOC"라는 단어가 발견되면 VOC 테스트 결과인 것이지요.

     

     여기서는 문자열에 "find" 메서드를 사용하여 쉽게 찾을 수 있습니다.

     

    find 메서드는 해당 단어가 발견되면 그 위치의 인덱스를 리턴하고, 만약 발견되지 않으면 -1을 리턴합니다.

     

    text.find("VOC")

     

    text의 인덱스 2480에서 VOC라는 문자열이 찾아진 것을 알 수 있습니다. 이제 위의 방법으로 시험 방법은 알아낼 수가 있습니다.

     

    (2) 시료 명 찾아내기

     

    시료명 같은 경우에는 특정 텍스트 바로 뒤에 있으면 제일 찾기가 쉽습니다. 예를 들어서 Product Name 바로 옆에서 시료명이 나오면 제일 편할텐데요, PDF 파일 안의 내용이 표로 되어 있는 경우에는 바로 옆에 안 나올 경우도 많습니다. 이 성적서 파일들도 모두 마찬가지입니다.

     

     텍스트를 추출한 PDF 파일에서 원하는 단어는 "A3EG6" 인데요, find로 찾아서 그 주변을 훑어보니 지표로 삼을 만한 단어는 AYAA가 있습니다. 하지만 AYAA로 해보니 이미 파일 앞쪽부터 등장하더라구요.

     텍스트를 중간부터 훑어서 find를 이용하여 해당 부분을 찾아낼 수도 있지만 저는 해당 내용이 콜론 두개로 감싸져 있고, AYAA로 시작하는 일렬번호의 길이가 일정하다는 것에 착안하여 콜론으로 스플릿하여 해당 문자열을 추출해냈습니다.

     

    먼저 텍스트를 콜론으로 쪼개고, 몇번째에서 제품명이 나타나는지를 확인했습니다.

     

     

    세 번째 요소에서 나오더라구요. 그 다음에 strip으로 양쪽 모두 공백을 제거했습니다.

    앞의 AYAA로 시작하는 문자열은 필요없으니 슬라이싱 했습니다. (어차피 글자 수는 항상 동일)

     

     

     

    텍스트에서 시험 방법과 시료 이름을 추출해내는 것을 아래와 같이 만들었습니다.

    "시험종류_시료명.pdf"의 텍스트 형태로 값을 반환합니다.

     

    Odor의 경우에는 콜론으로 스플릿했을 때, 시료 명이 있는 요소의 인덱스가 다른 경우(3이 아닌 4)가 발생하여 if 문으로 나누어 주었습니다. 시료 명이 40자 이상으로 터무니없이 길면, 인덱스 4의 문자열을 확인하는 것으로요.

    def find_name(text):
        if text.find("VOC") != -1:
            name = "VOC_" + text.split(":")[3].strip()[13:]+".pdf"
            
        elif text.find("Heavy") != -1:
            name = "Heavy Metals_" + text.split(":")[3].strip()[13:]+".pdf"
        
        elif text.find("Odor") != -1:
            if len(text.split(":")[3].strip()[13:]) < 40:
                name = "Odor_" + text.split(":")[3].strip()[13:]+".pdf"
            else:
                name = "Odor_" + text.split(":")[4].strip()[13:]+".pdf"
        else:
            name = "확인 필요"
        return name

     

    이제 여기까지 정리하자면 PDF파일의 텍스트 내용을 읽은 후, 시험 방법을 확인하고, 시료 이름을 추출해냈습니다.

    남은 것은 파일 명을 바꾸어주는 것 뿐입니다.

     

     

    3. 파일 이름 변경하기

     

    여기서는 shutil을 이용합니다.

     

    아래 코드를 실행하면 1.pdf 를 new1.pdf로 이름을 바꾸어줍니다. 메서드 이름이 move인 것처럼, 바뀐 경로를 입력하면 바뀐 경로로 파일을 이동하여 줍니다.

    import os
    import shutil
    
    shutil.move("1.pdf", "new1.pdf")

     

     

    4. 코드 완성하기

     

    자, 이제 파일 하나에만 적용하는 것이 아니라 수 십, 수 백개의 파일에 적용해야 하므로 위의 함수들을 조금씩 수정하여 작성하여 보겠습니다.

     

    (1) 정리할 파일을 넣을 "raw"라는 폴더를 만들고 경로를 raw_dir 변수에 할당합니다.

    (2) 이름을 변경하여 이동할 "result"라는 폴더를 만들고 경로를 result_dir 변수에 할당합니다.

     - 두 폴더 모두 미리 만들어 놓아야 합니다.

    (3) raw 폴더를 훑어서, PDF 파일을 하나씩 확인하여 시험 방법 및 시료명을 확인 후 result 폴더로 이동하며 파일명을 변경합니다. 

     

     

    전체 코드

    import os
    import shutil
    
    from io import StringIO
    from pdfminer.converter import TextConverter
    from pdfminer.layout import LAParams
    from pdfminer.pdfdocument import PDFDocument
    from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter
    from pdfminer.pdfpage import PDFPage
    from pdfminer.pdfparser import PDFParser
    
    # must create directory in advance
    result_dir = os.path.join(os.getcwd(),"result")
    raw_dir = os.path.join(os.getcwd(),"raw")
    
    
    def read_pdf(file_name):
        pdf_file_path = os.path.join(raw_dir, file_name)
        output_string = StringIO()
        with open(pdf_file_path, 'rb') as f:
            parser = PDFParser(f)
            doc = PDFDocument(parser)
            rsrcmgr = PDFResourceManager()
            device = TextConverter(rsrcmgr, output_string, laparams=LAParams())
            interpreter = PDFPageInterpreter(rsrcmgr, device)
            for page in PDFPage.create_pages(doc):
                interpreter.process_page(page)
        return str(output_string.getvalue())
    
    
    def find_name(text):
        if text.find("VOC") != -1:
            name = "VOC_" + text.split(":")[3].strip()[13:]+".pdf"
            
        elif text.find("Heavy") != -1:
            name = "Heavy Metals_" + text.split(":")[3].strip()[13:]+".pdf"
        
        elif text.find("Odor") != -1:
            if len(text.split(":")[3].strip()[13:]) < 40:
                name = "Odor_" + text.split(":")[3].strip()[13:]+".pdf"
            else:
                name = "Odor_" + text.split(":")[4].strip()[13:]+".pdf"
        else:
            name = "확인 필요"
        return name
    
    
    for file in os.listdir(raw_dir):
        text = read_pdf(file)
        new_name = find_name(text)
        
        print(new_name)
        shutil.move(os.path.join(raw_dir, file), os.path.join(result_dir, new_name))

     

     

    raw 폴더에다가 정리할 PDF 파일들을 모아놓고 위의 코드를 실행하면 이렇게 됩니다.

     

    실행 화면

     

     

    5. 마치며

     시료명을 알아낸 것처럼 발행일도 마찬가지로 뽑아낼 수 있습니다. 조금 더 부지런하면 이름과 발행일자를 뽑아내서 엑셀파일로 만들수도 있겠지요. 각 폴더로 자동으로 이동하는 건 그냥 드래그하는 게 더 편한 것 같아서 안 했습니다.

     

     파이썬으로 하는 업무자동화에 관심있는 분들이 이 글을 보실진 모르겠지만, 개인적으로는 파이썬의 다양한 기능에 비해서 실무에 적용하는 것에 대한 자료나 정보 공유가 별로 없는 것 같아 아쉽습니다. 업무에서 나름 이것저것 하고 있는데, 혼자서 하다 보니 적용도 잘 안되는 것 같고 또 쉽지가 않네요.

     

    아무튼 저의 게시물이 누군가에겐 도움이 되기를 바라며, 문의 사항 있으시면 언제든 댓글 남겨주세요.

    감사합니다.

     

     

    댓글(2)

    • 2021.01.15 14:59

      비밀댓글입니다

      • 2021.01.15 22:03

        비밀댓글입니다

    Designed by JB FACTORY