📄 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
✅ 장점
- 문서 레이아웃을 유지하며 텍스트/표 추출 가능
- 문서 형태 기반 분할과 분석 유리
❌ 단점
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
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_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 조합 |
| 질의응답 실행 | 검색 + 응답 실행 |
좋은 글 감사합니다 :-)