RAG 챗봇 만들기 3 - 문서 준비와 텍스트 추출

learning by Seven Fingers Studio 12분
RAGLangChainPDF문서로딩Document LoaderPython

블로그 대표 이미지

처음에 PDF 텍스트 추출하려고 PyPDF2 직접 썼다가 고생 좀 했어요. 페이지별로 읽어서 합치고, 인코딩 문제 처리하고… LangChain Document Loader 쓰면 이런 거 다 알아서 해줘서 훨씬 편하더라고요.

오늘은 RAG 파이프라인의 첫 번째 단계인 문서 로딩을 배울 거예요. PDF, TXT, Word 파일에서 텍스트를 추출하는 방법이에요.

Document Loader란?

LangChain에서는 다양한 형식의 문서를 읽어오는 걸 “Document Loader”라고 불러요. 파일 형식마다 전용 로더가 있어요:

  • PyPDFLoader - PDF 파일
  • TextLoader - TXT 파일
  • Docx2txtLoader - Word 파일
  • CSVLoader - CSV 파일
  • UnstructuredURLLoader - 웹페이지

PDF 파일 로딩하기

가장 많이 쓰는 PDF부터 해볼게요.

샘플 PDF 준비

테스트용으로 아무 PDF나 준비해요. 없으면 구글에서 “sample pdf”로 검색해서 다운받으세요.

프로젝트 폴더에 docs 폴더 만들고 PDF를 넣어요:

rag-chatbot/
├── docs/
│   └── sample.pdf
└── ...

PDF 로딩 코드

from langchain_community.document_loaders import PyPDFLoader

# PDF 파일 로드
loader = PyPDFLoader("docs/sample.pdf")
documents = loader.load()

# 결과 확인
print(f"총 {len(documents)}개 페이지 로드됨")
print(f"\n첫 번째 페이지 내용:")
print(documents[0].page_content[:500])
print(f"\n메타데이터: {documents[0].metadata}")

실행 결과:

총 5개 페이지 로드됨
첫 번째 페이지 내용:
회사 취업규칙 제1조(목적) 이 규칙은 주식회사 ...
메타데이터: {'source': 'docs/sample.pdf', 'page': 0}

블로그 대표 이미지

Document 객체 구조

로드된 문서는 Document 객체로 되어있어요:

from langchain.schema import Document

# Document 구조
doc = documents[0]
print(doc.page_content)  # 텍스트 내용
print(doc.metadata)      # 메타데이터 (출처, 페이지 번호 등)

메타데이터에 출처 정보가 있어서 나중에 “이 정보 어디서 왔어?”에 답할 수 있어요.

TXT 파일 로딩하기

텍스트 파일은 더 간단해요.

TXT 로딩 코드

from langchain_community.document_loaders import TextLoader

# TXT 파일 로드
loader = TextLoader("docs/manual.txt", encoding="utf-8")
documents = loader.load()

print(f"문서 개수: {len(documents)}")
print(f"내용 미리보기: {documents[0].page_content[:200]}")

실행 결과:

문서 개수: 1
내용 미리보기: 제품 사용 설명서 1. 전원 버튼을 눌러 기기를 켭니다...

TXT는 페이지 개념이 없어서 통째로 하나의 Document로 로드돼요.

여러 파일 한 번에 로딩

폴더 안의 여러 파일을 한 번에 로드하고 싶을 때:

DirectoryLoader 사용

from langchain_community.document_loaders import DirectoryLoader, PyPDFLoader

# docs 폴더의 모든 PDF 파일 로드
loader = DirectoryLoader(
    "docs/",
    glob="**/*.pdf",
    loader_cls=PyPDFLoader
)
documents = loader.load()

print(f"총 {len(documents)}개 문서 로드됨")
for doc in documents[:3]:
    print(f"- {doc.metadata['source']}: {doc.page_content[:50]}...")

실행 결과:

총 12개 문서 로드됨
- docs/policy.pdf: 회사 정책 및 규정 제1장 총칙...
- docs/manual.pdf: 제품 사용 설명서 안전 주의사항...
- docs/faq.pdf: 자주 묻는 질문 Q1. 배송은 얼마나...

Word 파일 로딩하기

Word 파일은 추가 패키지가 필요해요:

pip install docx2txt

Word 로딩 코드

from langchain_community.document_loaders import Docx2txtLoader

loader = Docx2txtLoader("docs/report.docx")
documents = loader.load()

print(f"문서 내용: {documents[0].page_content[:300]}")

웹페이지 로딩하기

웹 문서도 로드할 수 있어요:

from langchain_community.document_loaders import WebBaseLoader

loader = WebBaseLoader("https://example.com/docs")
documents = loader.load()

print(documents[0].page_content[:500])

실전 예제: 여러 형식 통합 로딩

실제로는 여러 형식의 파일을 한 번에 처리하는 경우가 많아요.

통합 로더 만들기

from langchain_community.document_loaders import (
    PyPDFLoader,
    TextLoader,
    Docx2txtLoader,
    DirectoryLoader
)
import os

def load_documents(docs_path: str) -> list:
    """여러 형식의 문서를 한 번에 로드"""
    all_documents = []

    # PDF 파일 로드
    pdf_loader = DirectoryLoader(
        docs_path,
        glob="**/*.pdf",
        loader_cls=PyPDFLoader
    )
    all_documents.extend(pdf_loader.load())
    print(f"PDF: {len(pdf_loader.load())}개 로드")

    # TXT 파일 로드
    for file in os.listdir(docs_path):
        if file.endswith(".txt"):
            loader = TextLoader(
                os.path.join(docs_path, file),
                encoding="utf-8"
            )
            all_documents.extend(loader.load())

    # Word 파일 로드
    for file in os.listdir(docs_path):
        if file.endswith(".docx"):
            loader = Docx2txtLoader(os.path.join(docs_path, file))
            all_documents.extend(loader.load())

    return all_documents

# 사용
documents = load_documents("docs/")
print(f"총 {len(documents)}개 문서 로드 완료!")

실행 결과:

PDF: 8개 로드
TXT: 3개 로드
DOCX: 2개 로드
총 13개 문서 로드 완료!

로딩 시 주의사항

인코딩 문제

한글 문서는 인코딩 지정 안 하면 깨지는 경우 있어요:

# 한글 TXT 파일
loader = TextLoader("docs/korean.txt", encoding="utf-8")
# 또는
loader = TextLoader("docs/korean.txt", encoding="cp949")

스캔 PDF

스캔해서 만든 PDF는 텍스트가 안 추출돼요. 이미지로 되어있거든요. OCR이 필요한데, 이건 나중에 따로 다룰게요.

대용량 파일

파일이 너무 크면 메모리 문제가 생길 수 있어요. 나중에 청킹할 때 작게 나눠서 처리하면 돼요.

운영자 실전 노트

실제 프로젝트 진행하며 겪은 문제

  • PDF 한글 인코딩 깨짐 → UTF-8/CP949 인코딩 옵션 명시적으로 지정 필요
  • 스캔 PDF 텍스트 추출 안 됨 → OCR(pytesseract) 추가 처리 필요. PDF 종류 사전 확인 필수
  • 대용량 파일(100MB+) 메모리 에러 → 페이지 단위 스트리밍 로드로 해결
  • 표와 이미지가 많은 PDF는 텍스트 순서 뒤섞임 → 전처리 단계에서 구조화된 데이터로 변환

이 경험을 통해 알게 된 점

  • 문서 품질 검증이 선행되어야 함. 스캔 PDF인지 텍스트 PDF인지 먼저 확인
  • PyPDF2 직접 쓰는 것보다 LangChain Loader가 에지 케이스 처리 잘 함
  • 표/이미지가 많은 복잡한 문서는 Unstructured.io 같은 전문 라이브러리 고려

마무리

Document Loader는 바퀴를 다시 발명하지 않게 해준다. 직접 파싱 코드 짜는 것보다 검증된 라이브러리 쓰는 게 시간 절약이다.

다음 편에서는 로드한 문서를 적당한 크기로 쪼개는 “청킹”을 배운다. 왜 쪼개야 하는지, 어떻게 쪼개야 효과적인지 알아본다.


RAG 챗봇 만들기 시리즈:

← 블로그 목록으로