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 |
2 실습에 활용한 문서
소프트웨어정책연구소(SPRi) - 2023년 12월호
- 저자: 유재흥(AI정책연구실 책임연구원), 이지수(AI정책연구실 위촉연구원)
- 링크: https://spri.kr/posts/view/23669
- 파일명:
SPRI_AI_Brief_2023년12월호_F.pdf
참고: 위의 파일은 data 폴더 내에 다운로드 받으세요
3 PyPDF
여기에서는 pypdf를 사용하여 PDF를 문서 배열로 로드하며, 각 문서는 page 번호와 함께 페이지 내용 및 메타데이터를 포함합니다.
from langchain_community.document_loaders import PyPDFLoader
# 파일 경로 설정
loader = PyPDFLoader(FILE_PATH)
# PDF 로더 초기화
docs = loader.load()
# 문서의 내용 출력
print(docs[10].page_content[:300])3.1 PyPDF(OCR)
일부 PDF에는 스캔된 문서나 그림 내에 텍스트 이미지가 포함되어 있습니다. rapidocr-onnxruntime 패키지를 사용하여 이미지에서 텍스트를 추출할 수도 있습니다.
4 PyMuPDF
PyMuPDF 는 속도 최적화가 되어 있으며, PDF 및 해당 페이지에 대한 자세한 메타데이터를 포함하고 있습니다. 페이지 당 하나의 문서를 반환합니다:
5 Unstructured
Unstructured는 Markdown이나 PDF와 같은 비구조화된 또는 반구조화된 파일 형식을 다루기 위한 공통 인터페이스를 지원합니다.
LangChain의 UnstructuredPDFLoader는 Unstructured와 통합되어 PDF 문서를 LangChain Document 객체로 파싱합니다.
from langchain_community.document_loaders import UnstructuredPDFLoader
# UnstructuredPDFLoader 인스턴스 생성
loader = UnstructuredPDFLoader(FILE_PATH)
# 데이터 로드
docs = loader.load()
# 문서의 내용 출력
print(docs[0].page_content[:300])내부적으로 비정형에서는 텍스트 청크마다 서로 다른 “요소”를 만듭니다. 기본적으로 이들은 결합되어 있지만 mode="elements"를 지정하여 쉽게 분리할 수 있습니다.
# UnstructuredPDFLoader 인스턴스 생성(mode="elements")
loader = UnstructuredPDFLoader(FILE_PATH, mode="elements")
# 데이터 로드
docs = loader.load()
# 문서의 내용 출력
print(docs[0].page_content)이 특정 문서에 대한 전체 요소 유형 집합을 참조하세요
6 PyPDFium2
7 PDFMiner
from langchain_community.document_loaders import PDFMinerLoader
# PDFMiner 로더 인스턴스 생성
loader = PDFMinerLoader(FILE_PATH)
# 데이터 로드
docs = loader.load()
# 문서의 내용 출력
print(docs[0].page_content[:300])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])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를 로드하세요
9 PDFPlumber
PyMuPDF와 마찬가지로, 출력 문서는 PDF와 그 페이지에 대한 자세한 메타데이터를 포함하며, 페이지 당 하나의 문서를 반환합니다.