Python pdfplumber를 활용한 PDF 텍스트 및 테이블 데이터 정밀 추출 가이드

PDF 문서에서 데이터를 추출할 때 테이블 구조가 무너지거나 텍스트 단락이 뒤섞이는 문제를 자주 겪게 됩니다. PyPDF2나 pdfminer와 같은 기존 라이브러리는 복잡한 레이아웃, 특히 테이블 처리에 한계가 있어 데이터 분석이나 자동화 작업 시 효율이 떨어집니다. 이를 보완하기 위해 pdfplumber가 등장했습니다. 이 라이브러리는 PDF의 시각적 레이아웃을 정밀하게 복원하는 데 특화되어 있으며, 텍스트와 테이블은 물론 글자의 좌표와 폰트 정보까지 세밀하게 추출할 수 있습니다.

pdfplumber의 핵심 강점

  • 정확한 테이블 구조 인식: 셀 병합과 테두리를 자동으로 감지하여 엑셀과 유사한 구조로 데이터를 반환합니다.
  • 레이아웃 기반 텍스트 복원: 사람의 눈으로 읽는 순서와 유사하게 텍스트를 추출하며, 단락이 잘리는 현상을 방지합니다.
  • 직관적인 API: PDF의 복잡한 내부 구조를 이해하지 않고도 몇 줄의 코드로 원하는 데이터를 파싱할 수 있습니다.
  • 세밀한 영역 제어: 페이지 내의 특정 좌표 영역(Bounding Box)을 지정하여 불필요한 정보를 필터링할 수 있습니다.

환경 구축 및 설치

pdfplumber는 순수 Python으로 작성되었으며, pip를 통해 쉽게 설치할 수 있습니다.

pip install pdfplumber

설치 후 버전 출력을 통해 정상적으로 로드되는지 확인합니다.

import pdfplumber
print(f"현재 설치된 버전: {pdfplumber.__version__}")

기본 사용법: 문서 로드 및 데이터 추출

1. 문서 로드 및 페이지 메타데이터 확인

pdfplumber.open()을 사용하여 문서를 열고, pages 속성을 통해 각 페이지의 정보에 접근합니다. 리소스 관리를 위해 with 구문을 사용하는 것이 권장됩니다.

import pdfplumber

file_path = "sample_document.pdf"
with pdfplumber.open(file_path) as doc:
    total_pages = len(doc.pages)
    print(f"문서 전체 페이지 수: {total_pages}")
    
    # 첫 번째 페이지 객체 가져오기
    target_page = doc.pages[0]
    
    # 페이지 메타데이터 출력
    print(f"페이지 규격: {target_page.width} x {target_page.height}")
    print(f"회전 각도: {target_page.rotation}")
    print(f"이미지 포함 여부: {bool(target_page.images)}")

2. 텍스트 및 단어 메타데이터 추출

단순 텍스트 추출을 넘어, 각 단어의 위치와 스타일 정보를 활용하면 제목과 본문을 구분하거나 특정 조건의 텍스트만 필터링할 수 있습니다.

with pdfplumber.open("sample_document.pdf") as doc:
    page_content = doc.pages[1]
    
    # 일반 텍스트 추출
    raw_text = page_content.extract_text()
    if raw_text:
        print(raw_text[:200])
        
    # 단어별 상세 정보 추출
    token_list = page_content.extract_words()
    for token in token_list[:3]:
        print(f"단어: {token['text']}")
        print(f"좌표: X={token['x0']:.1f}, Y={token['top']:.1f}")
        print(f"폰트: {token.get('fontname', 'Unknown')}, 크기: {token.get('size', 'Unknown')}\n")

3. 테이블 데이터 구조화

pdfplumber의 가장 강력한 기능은 테이블 추출입니다. 추출된 2차원 리스트는 pandas를 통해 즉시 데이터프레임으로 변환할 수 있습니다.

import pdfplumber
import pandas as pd

with pdfplumber.open("financial_report.pdf") as doc:
    sheet = doc.pages[2]
    grid_data = sheet.extract_table()
    
    if grid_data:
        header = grid_data[0]
        rows = grid_data[1:]
        df = pd.DataFrame(rows, columns=header)
        print(df.head())
        
        # CSV로 내보내기
        df.to_csv("extracted_financial_data.csv", index=False, encoding="utf-8-sig")

한 페이지에 여러 테이블이 존재하는 경우 extract_tables()를 사용하여 모든 테이블을 리스트 형태로 가져올 수 있습니다.

with pdfplumber.open("complex_report.pdf") as doc:
    sheet = doc.pages[0]
    all_grids = sheet.extract_tables()
    
    for idx, grid in enumerate(all_grids):
        df = pd.DataFrame(grid[1:], columns=grid[0])
        df.to_csv(f"output_table_{idx}.csv", index=False)

4. 사용자 정의 테이블 설정 및 영역 추출

테두리가 없는 테이블이나 특정 영역의 데이터만 필요할 때는 추출 전략을 수정하거나 페이지를 크롭(Crop)해야 합니다.

with pdfplumber.open("receipt.pdf") as doc:
    sheet = doc.pages[0]
    
    # 테두리 대신 텍스트 간격을 기준으로 테이블 인식
    custom_settings = {
        "vertical_strategy": "text",
        "horizontal_strategy": "text"
    }
    grid = sheet.extract_table(table_settings=custom_settings)
    
    # 특정 좌표 영역(Bounding Box)만 크롭하여 텍스트 추출
    bbox = (40, 40, 350, 120) # (x0, top, x1, bottom)
    cropped_section = sheet.crop(bbox)
    print(cropped_section.extract_text())

실전 시나리오: 실제 업무 적용

1. 다중 페이지 텍스트 일괄 추출

전체 문서의 텍스트를 페이지 구분자와 함께 단일 텍스트 파일로 저장하는 파이프라인입니다.

def save_pdf_as_text(input_pdf, output_txt):
    with pdfplumber.open(input_pdf) as doc, open(output_txt, "w", encoding="utf-8") as out_file:
        for num, pg in enumerate(doc.pages, 1):
            content = pg.extract_text()
            if content:
                out_file.write(f"--- Page {num} ---\n")
                out_file.write(f"{content}\n\n")
    print(f"텍스트 추출 완료: {output_txt}")

save_pdf_as_text("manual.pdf", "manual_text.txt")

2. 전체 테이블을 엑셀로 통합

문서 내의 모든 테이블을 추출하여 하나의 엑셀 파일 내 여러 시트로 분리하여 저장합니다.

def export_tables_to_xlsx(input_pdf, output_xlsx):
    with pdfplumber.open(input_pdf) as doc:
        with pd.ExcelWriter(output_xlsx, engine="openpyxl") as writer:
            sheet_idx = 0
            for pg_num, pg in enumerate(doc.pages, 1):
                grids = pg.extract_tables()
                for grid in grids:
                    sheet_idx += 1
                    try:
                        df = pd.DataFrame(grid[1:], columns=grid[0])
                    except ValueError:
                        df = pd.DataFrame(grid) # 헤더가 없는 경우 대비
                    
                    # 엑셀 시트명 길이 제한(31자) 처리
                    sheet_name = f"P{pg_num}_T{sheet_idx}"
                    df.to_excel(writer, sheet_name=sheet_name[:31], index=False)

export_tables_to_xlsx("quarterly_reports.pdf", "consolidated_tables.xlsx")

3. 영수증 및 청구서 주요 정보 파싱

고정된 양식의 문서에서 특정 필드(결제일, 총액 등)의 좌표를 미리 지정하여 데이터를 추출합니다.

def get_invoice_details(pdf_file):
    with pdfplumber.open(pdf_file) as doc:
        pg = doc.pages[0]
        zones = {
            "InvoiceNo": (300, 100, 450, 120),
            "Date": (300, 130, 450, 150),
            "TotalAmount": (300, 280, 450, 300)
        }
        result = {}
        for field, coords in zones.items():
            txt = pg.crop(coords).extract_text()
            result[field] = txt.replace("\n", " ").strip() if txt else "N/A"
        return result

metadata = get_invoice_details("invoice_2023.pdf")
for k, v in metadata.items():
    print(f"{k}: {v}")

4. 스캔본 PDF 처리 (OCR 결합)

pdfplumber는 텍스트 기반 PDF만 처리할 수 있습니다. 이미지가 포함된 스캔본의 경우 pytesseract와 결합하여 광학 문자 인식(OCR)을 수행해야 합니다.

import pytesseract

def process_scanned_pdf(pdf_file, out_txt):
    with pdfplumber.open(pdf_file) as doc, open(out_txt, "w", encoding="utf-8") as f:
        for num, pg in enumerate(doc.pages, 1):
            # 페이지를 이미지 객체로 변환
            img_obj = pg.to_image().original
            # Tesseract OCR 엔진을 통한 텍스트 인식
            ocr_text = pytesseract.image_to_string(img_obj, lang="kor+eng")
            f.write(f"--- Page {num} ---\n{ocr_text}\n\n")

# 사전에 Tesseract OCR 엔진 설치가 필요합니다.
# process_scanned_pdf("scanned_contract.pdf", "contract_ocr.txt")

문제 해결 및 주의사항 (Troubleshooting)

  • 텍스트 인코딩 및 누락 문제: PDF 내부 폰트 인코딩이 비표준인 경우 글자가 깨질 수 있습니다. 이때는 extract_text(x_tolerance=1, y_tolerance=1)과 같이 허용 오차(Tolerance) 파라미터를 조절해 보거나, 스캔본으로 간주하고 OCR 방식을 적용해야 합니다.
  • 테이블 구조 틀어짐 현상: 셀 병합이 많거나 테두리가 불명확한 경우 행과 열이 어긋날 수 있습니다. table_settings 딕셔너리를 통해 text 또는 lines 전략을 명시적으로 지정하고, 필요시 crop()으로 노이즈 영역을 제거한 뒤 추출하십시오.
  • 대용량 파일 처리 시 메모리 과부하: 수백 페이지에 달하는 문서를 한 번에 로드하면 메모리 누수가 발생할 수 있습니다. 반드시 페이지 단위로 반복문을 돌리고, 사용이 끝난 페이지 객체는 가비지 컬렉션이 될 수 있도록 변수를 재할당하거나 with 블록 내에서 즉시 처리하십시오.
  • 보안 설정된 PDF 처리: 비밀번호로 잠겨 있거나 편집이 제한된 PDF는 pdfplumber로 열 수 없습니다. qpdf 또는 pikepdf와 같은 외부 도구를 사용하여 사전에 보안 설정을 해제(Decrypt)한 후 작업해야 합니다.

태그: pdfplumber python PDF추출 pandas OCR

6월 1일 21:08에 게시됨