PDF Loader 비교 분석

문건희·2025년 3월 4일

RAG

목록 보기
11/12

📄 PDF 데이터를 다룰 때 어떤 라이브러리를 쓸까?

PDF 데이터를 읽고 분석할 때 자주 사용하는 4가지 라이브러리를 비교합니다.

라이브러리주요 기능특징
fitz.open (PyMuPDF)텍스트/이미지/표/좌표 정보 추출빠르고 다기능
PyPDFLoader (LangChain)LangChain 연동 PDF 텍스트 추출간단 연동
UnstructuredPDFLoader문서 레이아웃 유지하며 추출문서 형태 보존
PDFPlumber표(table) 추출 특화표 추출 정확도 높음

1️⃣ fitz.open (PyMuPDF)

✅ 장점

  • 빠르고 페이지별 처리 가능
  • 텍스트, 이미지, 좌표 등 다중 정보 추출
  • OCR 결과와 연동 용이

❌ 단점

  • 표 추출은 직접 구현 필요 (한계 있음)
  • LangChain 연동 직접 구성 필요

2️⃣ PyPDFLoader (LangChain)

✅ 장점

  • LangChain과 연동 편리
  • 텍스트 추출만 간단히 해결 가능

❌ 단점

  • 표/이미지 추출 불가
  • 레이아웃 정보 보존 안됨

3️⃣ UnstructuredPDFLoader

✅ 장점

  • 문서 레이아웃을 유지하며 텍스트/표 추출 가능
  • 문서 형태 기반 분할과 분석 유리

❌ 단점

  • 속도 느림
  • 대용량 PDF에서 성능 저하

4️⃣ PDFPlumber

✅ 장점

  • 표 추출에 특화
  • 셀 좌표, 병합된 셀까지 정확히 인식

❌ 단점

  • 텍스트 전체 추출은 불편
  • 이미지 추출 기능 부족

📊 표가 포함된 PDF에서 성능 비교

라이브러리표 추출 성능비고
fitz.open❌ 직접 구현 필요좌표 분석 필요
PyPDFLoader❌ 표 깨짐텍스트만 추출
UnstructuredPDFLoader⭕ 구조 보존성능은 다소 느림
PDFPlumber⭕ 정확도 가장 높음표 추출 특화

✅ 표 추출에 가장 적합한 라이브러리: PDFPlumber


📋 추천 조합

상황추천 조합
텍스트만 필요한 경우PyPDFLoader
문서 전체 레이아웃 유지UnstructuredPDFLoader
표 추출 정확도 중요PDFPlumber
이미지+텍스트+표 복합 분석fitz.open + PDFPlumber 조합

📄 최종 추천 코드 예제 (PDFPlumber 기반 표 추출 + LangChain Document 변환)

import pdfplumber
from langchain.schema import Document

# PDF에서 표 추출 후 LangChain Document로 변환하는 함수
def load_pdf_with_tables(pdf_path):
    documents = []
    with pdfplumber.open(pdf_path) as pdf:
        for page_num, page in enumerate(pdf.pages):
            tables = page.extract_tables()
            table_text = ""
            for table in tables:
                for row in table:
                    table_text += " | ".join(str(cell) if cell else "" for cell in row) + "\n"
            documents.append(Document(page_content=table_text, metadata={'page': page_num + 1}))
    return documents

# PDF 파일 경로
pdf_path = '/Users/moon/Desktop/project/sesac/최종프로젝트/삼성 인터넷 비갱신 암보험.pdf'

# 문서 로드 및 표 추출
docs = load_pdf_with_tables(pdf_path)

print(f'📄 PDF에서 로드된 페이지 수: {len(docs)}')
print(f'첫 번째 페이지 표 내용:\n{docs[0].page_content[:300]}...')

# 의미론적 청크 분할 (선택적 단계, 표가 아닌 텍스트로도 쪼갤 때 사용 가능)
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import FAISS

OPENAI_API_KEY = 'your_openai_api_key'
embedding_model = OpenAIEmbeddings(model='text-embedding-3-small', openai_api_key=OPENAI_API_KEY)

# 표 데이터는 보통 표 단위로 쪼개져 있으므로 추가 분할은 생략 가능
vectorstore = FAISS.from_documents(docs, embedding_model)
vectorstore.save_local('./pdf_table_faiss_db')

💬 코드 설명

단계설명
1. pdfplumber 열기PDFPlumber로 PDF 읽기
2. 페이지별 표 추출각 페이지에서 표 추출 (list of list)
3. 표 텍스트 변환표 데이터를 텍스트 형태로 변환
4. LangChain Document 변환LangChain Document로 변환하여 후속 처리 준비
5. FAISS 저장벡터화 후 저장 (RAG 구성 대비)


✅ PDFPlumber + LangChain 기반 RAG 파이프라인 전체 코드

1️⃣ PDF에서 표 추출 및 LangChain Document 변환

import pdfplumber
from langchain.schema import Document

def load_pdf_with_tables(pdf_path):
    documents = []
    with pdfplumber.open(pdf_path) as pdf:
        for page_num, page in enumerate(pdf.pages):
            tables = page.extract_tables()
            table_text = ""
            for table in tables:
                for row in table:
                    table_text += " | ".join(str(cell) if cell else "") + "\n"
            documents.append(Document(page_content=table_text, metadata={'page': page_num + 1}))
    return documents

pdf_path = '/Users/moon/Desktop/project/sesac/최종프로젝트/삼성 인터넷 비갱신 암보험.pdf'
docs = load_pdf_with_tables(pdf_path)

2️⃣ 의미론적 청크 분할

from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter

embedding_model = OpenAIEmbeddings(model='text-embedding-3-small', openai_api_key='your_openai_api_key')
text_splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
split_docs = text_splitter.split_documents(docs)

3️⃣ FAISS 벡터스토어 저장

from langchain.vectorstores import FAISS

vectorstore = FAISS.from_documents(split_docs, embedding_model)
vectorstore.save_local('./pdf_table_faiss_db')

4️⃣ 질의응답용 RAG 세트업

from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate
from langchain.chat_models import ChatOpenAI

retriever = vectorstore.as_retriever()

prompt_template = """
당신은 보험 약관 전문가입니다.
아래 내용을 참고하여 사용자의 질문에 정확히 답해주세요.

컨텍스트:
{context}

질문:
{question}

답변:
"""

qa_prompt = PromptTemplate(input_variables=["context", "question"], template=prompt_template)

llm = ChatOpenAI(model="gpt-4", openai_api_key='your_openai_api_key')

qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=retriever,
    chain_type_kwargs={"prompt": qa_prompt}
)

5️⃣ 사용자 질의응답 실행 예제

query = "암 보험의 보장 범위는 어떻게 되나요?"
response = qa_chain.run(query)
print(f"🔎 질문: {query}")
print(f"💬 답변: {response}")

📋 파이프라인 구성 설명 요약

단계설명
PDF 표 추출pdfplumber로 표 데이터 추출
Document 변환LangChain Document로 변환
청크 분할RecursiveCharacterTextSplitter로 분할
임베딩OpenAI 임베딩 생성
FAISS 저장벡터스토어 저장
RAG 구성검색 + LLM 조합
질의응답 실행검색 + 응답 실행

1개의 댓글

comment-user-thumbnail
2025년 9월 3일

좋은 글 감사합니다 :-)

답글 달기