본문 바로가기
업무자동화 with Python

[업무자동화 with Python] PDF문서내용 추출(PDFPlumber 활용) (4) (표 추출)

by CodeCrafter 2024. 10. 22.
반응형

 

이번에는 지난 시간에 이어서 pdf에서 표를 추출하는 방법에 대해서 자세히 알아보겠습니다.

 

[업무자동화 with Python] PDF문서내용 추출(PDFPlumber 활용) (3) (표 추출)

 

 

1. PDFPlumber로 표 추출하기

- 이번에는 기존과는 좀 다른 데이터를 활용해보겠습니다. 기존에는 pdf에서 선으로 행과 열의 구분이 비교적 명확한 편이었다면 이번에는 선에 대한 표시가 잘 나와있지 않은 데이터를 활용해볼 건데요

 

- 해당 데이터는 san-jose-pd-firearm-sample 이라는 데이터입니다.

 

https://github.com/jsvine/pdfplumber/blob/stable/examples/pdfs/san-jose-pd-firearm-sample.pdf

 

pdfplumber/examples/pdfs/san-jose-pd-firearm-sample.pdf at stable · jsvine/pdfplumber

Plumb a PDF for detailed information about each char, rectangle, line, et cetera — and easily extract text and tables. - jsvine/pdfplumber

github.com

 

*데이터는 위와 같습니다.

 

- 이제 이를 가지고 PDFPlumber를 통해 표(Table) 정보를 추출해보겠습니다.

 

* 코드 구현은 재현성을 위해 코랩의 무료버전에서 진행했으며, 데이터는 구글드라이브에 업로드 해놓으셔야 재현이 가능합니다.

 

반응형

 

 

- 먼저 pdfplumber 라이브러리를 설치해줍니다.

 

!pip install pdfplumber

 

 

- 다음은 pdfplumber와 정규화를 위해 활용할 re 라이브러리를 import하고, 실습에 활용할 데이터를 불러와줍니다. 

 

import pdfplumber
import re


pdf = pdfplumber.open("/content/drive/MyDrive/san-jose-pd-firearm-sample.pdf")
p0 = pdf.pages[0]
im = p0.to_image()
im

 

 

* 이미지가 잘 로드 된 것을 확인할 수 있습니다.

 

 

- 이제 다음은 어떻게 pdfplumber가 해당 데이터를 인식했는지 알아보겠습니다.

im.reset().draw_rects(p0.chars)

 

 

* 다음과 같이 space("") 단위로 잘 인식하고 있음을 알 수 있습니다.  하지만, 기존 실습들과는 다르게 명확한 선들이 없다보니 이대로 데이터를 구조화하면 행과 열을 맞추기가 힘들어지게 됩니다.

 

* 해당 원본 데이터를 드래그해서 전체를 선택해보니 아래와 같은데요. pdfplumber가 인식한 범위와 거의 유사하지만 조금의 차이가 보이긴 합니다.

 

 

- 이번에는 데이터를 불러와보겠습니다. 이때, blank(또는 white space)를 유지한 상태에서 데이터를 불러와주어야 합니다. 그렇지 않으면 데이터의 형태가 뒤섞이게 되기 때문입니다.

 

text = p0.extract_text(keep_blank_chars=True)
print(text)

 

 

* 사용한 데이터들이 blank를 잘 유지함으로써 결과적으로 원본과 동일한 형태를 유지하고 있음을 알 수 있습니다.

 

 

- 이제 데이터에서 표 정보만을 활용하기 위해 데이터를 가공해보겠습니다. 순서상으로 보게되면 LOCATION이라는 글자뒤에 있는 정보들이 중요한 정보이다보니, 그 뒤에나오는 정보부터 추출하는 작업을 진행해줍니다.

 

core_pat = re.compile(r"LOCATION[\-\s]+(.*)\n\s+Flags = e", re.DOTALL)
core = re.search(core_pat, text).group(1)
print(core)

 

 

- 이번에는 문자열을 두 줄씩 묶어서 parsing해보겠습니다. 각 무기에 대한 정보들이 2 줄(행)에 걸쳐서 작성되어있기에 이를 기반으로 데이터를 파싱하는 로직입니다.

lines = core.split("\n")
line_groups = list(zip(lines[::2], lines[1::2]))
print(line_groups[0])

 

 

 

- 이제 parse_row 라는 함수를 사용해 두 행으로 구성된 데이터를 각각의 필드로 나누어 parsing하고, 그 결과를 리스트로 저장합니다. 확인을 위해 일부를 출력해보겠습니다.

def parse_row(first_line, second_line):
    return {
        "type": first_line[:20].strip(),
        "item": first_line[21:41].strip(),
        "make": first_line[44:89].strip(),
        "model": first_line[90:105].strip(),
        "calibre": first_line[106:111].strip(),
        "status": first_line[112:120].strip(),
        "flags": first_line[124:129].strip(),
        "serial_number": second_line[0:13].strip(),
        "report_tag_number": second_line[21:41].strip(),
        "case_file_number": second_line[44:64].strip(),
        "storage_location": second_line[68:91].strip(),
    }

parsed = [ parse_row(first_line, second_line)
    for first_line, second_line in line_groups ]

parsed[:2]

 

 

PDF에서 정보가 잘 추출된 것을 확인할 수 있습니다. 

 

이와 같이 선으로 행과 열이 잘 구분되지 않았을 경우에는 조금의 전처리가 필요하지만, 여전히 PDFPlumber를 통해 효과적으로 정보를 추출할 수 있음을 알 수 있었습니다.

반응형

댓글