PDF

문서 로더

다양한 형식의 문서를 LangChain으로 로드하는 방법을 다룬다.

AI
RAG
LangChain
저자

Kwangmin Kim

공개

2024년 12월 31일

Portable Document Format (PDF), ISO 32000으로 표준화된 파일 형식은 Adobe가 1992년에 문서를 제시하기 위해 개발했으며, 이는 응용 소프트웨어, 하드웨어 및 운영 시스템에 독립적인 방식으로 텍스트 서식 및 이미지를 포함합니다.

이 가이드는 PDF 문서를 LangChain Document 형식으로 로드하는 방법을 다룹니다. 이 형식은 다운스트림에서 사용됩니다.

LangChain은 다양한 PDF 파서와 통합됩니다. 일부는 간단하고 상대적으로 저수준이며, 다른 일부는 OCR 및 이미지 처리를 지원하거나 고급 문서 레이아웃 분석을 수행합니다.

올바른 선택은 사용자의 애플리케이션에 따라 달라집니다.

참고

1 AutoRAG 팀에서의 PDF 실험

AutoRAG 에서 진행한 실험을 토대로 작성한 순위표

아래 표기된 숫자는 등수를 나타냅니다. (The lower, the better)

PDFMiner PDFPlumber PyPDFium2 PyMuPDF PyPDF2
Medical 1 2 3 4 5
Law 3 1 1 3 5
Finance 1 2 2 4 5
Public 1 1 1 4 5
Sum 5 5 7 15 20

출처: AutoRAG Medium 블로그

# API KEY를 환경변수로 관리하기 위한 설정 파일
from dotenv import load_dotenv

# API KEY 정보로드
load_dotenv()

2 실습에 활용한 문서

소프트웨어정책연구소(SPRi) - 2023년 12월호

  • 저자: 유재흥(AI정책연구실 책임연구원), 이지수(AI정책연구실 위촉연구원)
  • 링크: https://spri.kr/posts/view/23669
  • 파일명: SPRI_AI_Brief_2023년12월호_F.pdf

참고: 위의 파일은 data 폴더 내에 다운로드 받으세요

FILE_PATH = "./data/SPRI_AI_Brief_2023년12월호_F.pdf"
def show_metadata(docs):
    if docs:
        print("[metadata]")
        print(list(docs[0].metadata.keys()))
        print("\n[examples]")
        max_key_length = max(len(k) for k in docs[0].metadata.keys())
        for k, v in docs[0].metadata.items():
            print(f"{k:<{max_key_length}} : {v}")

3 PyPDF

여기에서는 pypdf를 사용하여 PDF를 문서 배열로 로드하며, 각 문서는 page 번호와 함께 페이지 내용 및 메타데이터를 포함합니다.

# 설치
# !pip install -qU pypdf
from langchain_community.document_loaders import PyPDFLoader

# 파일 경로 설정
loader = PyPDFLoader(FILE_PATH)

# PDF 로더 초기화
docs = loader.load()

# 문서의 내용 출력
print(docs[10].page_content[:300])
# 메타데이터 출력
show_metadata(docs)

3.1 PyPDF(OCR)

일부 PDF에는 스캔된 문서나 그림 내에 텍스트 이미지가 포함되어 있습니다. rapidocr-onnxruntime 패키지를 사용하여 이미지에서 텍스트를 추출할 수도 있습니다.

# 설치
# !pip install -qU rapidocr-onnxruntime
# PDF 로더 초기화, 이미지 추출 옵션 활성화
loader = PyPDFLoader("https://arxiv.org/pdf/2103.15348.pdf", extract_images=True)

# PDF 페이지 로드
docs = loader.load()

# 페이지 내용 접근
print(docs[4].page_content[:300])
show_metadata(docs)

4 PyMuPDF

PyMuPDF 는 속도 최적화가 되어 있으며, PDF 및 해당 페이지에 대한 자세한 메타데이터를 포함하고 있습니다. 페이지 당 하나의 문서를 반환합니다:

# 설치
# !pip install -qU pymupdf
from langchain_community.document_loaders import PyMuPDFLoader

# PyMuPDF 로더 인스턴스 생성
loader = PyMuPDFLoader(FILE_PATH)

# 문서 로드
docs = loader.load()

# 문서의 내용 출력
print(docs[10].page_content[:300])
show_metadata(docs)

5 Unstructured

Unstructured는 Markdown이나 PDF와 같은 비구조화된 또는 반구조화된 파일 형식을 다루기 위한 공통 인터페이스를 지원합니다.

LangChain의 UnstructuredPDFLoader는 Unstructured와 통합되어 PDF 문서를 LangChain Document 객체로 파싱합니다.

# 설치
# !pip install -qU unstructured
from langchain_community.document_loaders import UnstructuredPDFLoader

# UnstructuredPDFLoader 인스턴스 생성
loader = UnstructuredPDFLoader(FILE_PATH)

# 데이터 로드
docs = loader.load()

# 문서의 내용 출력
print(docs[0].page_content[:300])
show_metadata(docs)

내부적으로 비정형에서는 텍스트 청크마다 서로 다른 “요소”를 만듭니다. 기본적으로 이들은 결합되어 있지만 mode="elements"를 지정하여 쉽게 분리할 수 있습니다.

# UnstructuredPDFLoader 인스턴스 생성(mode="elements")
loader = UnstructuredPDFLoader(FILE_PATH, mode="elements")

# 데이터 로드
docs = loader.load()

# 문서의 내용 출력
print(docs[0].page_content)

이 특정 문서에 대한 전체 요소 유형 집합을 참조하세요

set(doc.metadata["category"] for doc in docs)  # 데이터 카테고리 추출
show_metadata(docs)

6 PyPDFium2

from langchain_community.document_loaders import PyPDFium2Loader

# PyPDFium2 로더 인스턴스 생성
loader = PyPDFium2Loader(FILE_PATH)

# 데이터 로드
docs = loader.load()

# 문서의 내용 출력
print(docs[10].page_content[:300])
show_metadata(docs)

7 PDFMiner

from langchain_community.document_loaders import PDFMinerLoader

# PDFMiner 로더 인스턴스 생성
loader = PDFMinerLoader(FILE_PATH)

# 데이터 로드
docs = loader.load()

# 문서의 내용 출력
print(docs[0].page_content[:300])
show_metadata(docs)

PDFMiner를 사용하여 HTML 텍스트 생성

이 방법은 출력된 HTML 콘텐츠를 BeautifulSoup을 통해 파싱함으로써 글꼴 크기, 페이지 번호, PDF 헤더/푸터 등에 대한 보다 구조화되고 풍부한 정보를 얻을 수 있게 하여 텍스트를 의미론적으로 섹션으로 분할하는 데 도움이 될 수 있습니다.

from langchain_community.document_loaders import PDFMinerPDFasHTMLLoader

# PDFMinerPDFasHTMLLoader 인스턴스 생성
loader = PDFMinerPDFasHTMLLoader(FILE_PATH)

# 문서 로드
docs = loader.load()

# 문서의 내용 출력
print(docs[0].page_content[:300])
show_metadata(docs)
from bs4 import BeautifulSoup

soup = BeautifulSoup(docs[0].page_content, "html.parser")  # HTML 파서 초기화
content = soup.find_all("div")  # 모든 div 태그 검색
import re

cur_fs = None
cur_text = ""
snippets = []  # 동일한 글꼴 크기의 모든 스니펫 수집
for c in content:
    sp = c.find("span")
    if not sp:
        continue
    st = sp.get("style")
    if not st:
        continue
    fs = re.findall("font-size:(\d+)px", st)
    if not fs:
        continue
    fs = int(fs[0])
    if not cur_fs:
        cur_fs = fs
    if fs == cur_fs:
        cur_text += c.text
    else:
        snippets.append((cur_text, cur_fs))
        cur_fs = fs
        cur_text = c.text
snippets.append((cur_text, cur_fs))
# 중복 스니펫 제거 전략 추가 가능성 (PDF의 헤더/푸터가 여러 페이지에 걸쳐 나타나므로 중복 발견 시 중복 정보로 간주 가능)
from langchain_core.documents import Document

cur_idx = -1
semantic_snippets = []
# 제목 가정: 높은 글꼴 크기
for s in snippets:
    # 새 제목 판별: 현재 스니펫 글꼴 > 이전 제목 글꼴
    if (
        not semantic_snippets
        or s[1] > semantic_snippets[cur_idx].metadata["heading_font"]
    ):
        metadata = {"heading": s[0], "content_font": 0, "heading_font": s[1]}
        metadata.update(docs[0].metadata)
        semantic_snippets.append(Document(page_content="", metadata=metadata))
        cur_idx += 1
        continue

    # 동일 섹션 내용 판별: 현재 스니펫 글꼴 <= 이전 내용 글꼴
    if (
        not semantic_snippets[cur_idx].metadata["content_font"]
        or s[1] <= semantic_snippets[cur_idx].metadata["content_font"]
    ):
        semantic_snippets[cur_idx].page_content += s[0]
        semantic_snippets[cur_idx].metadata["content_font"] = max(
            s[1], semantic_snippets[cur_idx].metadata["content_font"]
        )
        continue

    # 새 섹션 생성 조건: 현재 스니펫 글꼴 > 이전 내용 글꼴, 이전 제목 글꼴 미만
    metadata = {"heading": s[0], "content_font": 0, "heading_font": s[1]}
    metadata.update(docs[0].metadata)
    semantic_snippets.append(Document(page_content="", metadata=metadata))
    cur_idx += 1

print(semantic_snippets[4])

8 PyPDF 디렉토리

디렉토리에서 PDF를 로드하세요

from langchain_community.document_loaders import PyPDFDirectoryLoader

# 디렉토리 경로
loader = PyPDFDirectoryLoader("data/")

# 문서 로드
docs = loader.load()

# 문서의 개수 출력
print(len(docs))
# 문서의 내용 출력
print(docs[50].page_content[:300])
# metadata 출력
print(docs[50].metadata)

9 PDFPlumber

PyMuPDF와 마찬가지로, 출력 문서는 PDF와 그 페이지에 대한 자세한 메타데이터를 포함하며, 페이지 당 하나의 문서를 반환합니다.

from langchain_community.document_loaders import PDFPlumberLoader

# PDF 문서 로더 인스턴스 생성
loader = PDFPlumberLoader(FILE_PATH)

# 문서 로딩
docs = loader.load()

# 첫 번째 문서 데이터 접근
print(docs[10].page_content[:300])
show_metadata(docs)

Subscribe

Enjoy this blog? Get notified of new posts by email: