Python PDF 라이브러리와의 비교
PDF Oxide를 PyMuPDF(fitz), pypdfium2, pypdf, pdfplumber, pdfminer 등과 비교합니다. 이 페이지에서는 텍스트 추출에 적합한 Python PDF 라이브러리를 고를 수 있도록 성능, 기능 범위, 라이선스, API 차이를 다룹니다.
요약
| PDF Oxide | PyMuPDF | pypdfium2 | pypdf | pdfplumber | pdfminer | |
|---|---|---|---|---|---|---|
| 평균 추출 시간 | 0.8ms | 4.6ms | 4.1ms | 12.1ms | 23.2ms | 16.8ms |
| 통과율 (3,830개 PDF) | 100% | 99.3% | 99.2% | 98.4% | 98.8% | 98.8% |
| 라이선스 | MIT | AGPL-3.0 | Apache-2.0 | BSD-3 | MIT | MIT |
| 언어 | Rust + PyO3 | C (MuPDF) | C (PDFium) | Pure Python | Pure Python | Pure Python |
| 텍스트 추출 | 지원 | 지원 | 지원 | 지원 | 지원 | 지원 |
| 문자 위치 | 지원 | 지원 | 지원 | 부분 지원 | 지원 | 지원 |
| 이미지 추출 | 지원 | 지원 | 지원 | 지원 | 미지원 | 미지원 |
| 양식 필드 | 읽기 + 쓰기 | 읽기 + 쓰기 | 읽기 전용 | 읽기 + 쓰기 | 읽기 전용 | 미지원 |
| PDF 생성 | 지원 | 지원 | 미지원 | 제한적 | 미지원 | 미지원 |
| PDF 편집 | 지원 | 지원 | 미지원 | 지원 | 미지원 | 미지원 |
| Markdown 출력 | 지원 | 미지원 | 미지원 | 미지원 | 미지원 | 미지원 |
| HTML 출력 | 지원 | 미지원 | 미지원 | 미지원 | 미지원 | 미지원 |
| 암호화 | 읽기 + 쓰기 | 읽기 + 쓰기 | 읽기 전용 | 읽기 + 쓰기 | 미지원 | 미지원 |
| PDF/A 검증 | 지원 | 미지원 | 미지원 | 미지원 | 미지원 | 미지원 |
| 렌더링 | 지원 | 지원 | 지원 | 미지원 | 미지원 | 미지원 |
| 검색 | 정규식 + 공간 검색 | 지원 | 지원 | 미지원 | 미지원 | 미지원 |
| Python 버전 | 3.8–3.14 | 3.8–3.12 | 3.8+ | 3.6+ | 3.8+ | 3.6+ |
| 설치 크기 | 약 5 MB wheel | 약 20 MB wheel | 약 3 MB wheel | 약 1 MB | 약 1 MB | 약 1 MB |
성능 비교
PDF 한 개당 평균 텍스트 추출 시간을 3,830개 PDF로 구성된 전체 코퍼스에서 벤치마크했습니다. 이는 공개적으로 이용 가능한 세 개의 독립적인 테스트 스위트로 구성되며, 모든 PDF 사양 버전(1.0–2.0), 암호화 파일, 형식이 잘못된 문서, CJK 인코딩, 복잡한 레이아웃, 보안 경계 사례를 함께 다룹니다. 각 스위트가 무엇을 테스트하는지, 그리고 왜 이 결과가 재현 가능한지는 전체 코퍼스 상세를 참고하세요.
| 라이브러리 | 평균 | 상대 | p99 | 통과율 |
|---|---|---|---|---|
| PDF Oxide | 0.8ms | 1× | 9ms | 100% |
| PyMuPDF | 4.6ms | 5.8× | 28ms | 99.3% |
| pypdfium2 | 4.1ms | 5.1× | 42ms | 99.2% |
| pymupdf4llm | 55.5ms | 69× | 280ms | 99.1% |
| pdftext | 7.3ms | 9.1× | 82ms | 99.0% |
| pdfminer | 16.8ms | 21× | 124ms | 98.8% |
| pdfplumber | 23.2ms | 29× | 189ms | 98.8% |
| markitdown | 108.8ms | 136× | 378ms | 98.6% |
| pypdf | 12.1ms | 15.1× | 97ms | 98.4% |
PDF Oxide는 PyO3를 통해 Python 확장 모듈로 컴파일된 네이티브 Rust 코어로 이 속도를 달성합니다. 하위 프로세스 오버헤드나 C 라이브러리 브리징이 없으며, Rust 코드는 Python 프로세스 내에서 직접 실행됩니다.
신뢰성
PDF Oxide는 유효한 PDF 3,823개 중 3,823개를 실패 없이 처리합니다 — 통과율 100%입니다. 3,830개 코퍼스에서 통과하지 못한 7개 파일은 의도적으로 손상시킨 테스트 픽스처입니다(PDF 헤더 누락, 퍼징으로 손상된 카탈로그, 잘못된 xref 스트림).
| 라이브러리 | 통과한 유효 PDF | 통과율 |
|---|---|---|
| PDF Oxide | 3,823 / 3,823 | 100% |
| PyMuPDF | 3,796 / 3,823 | 99.3% |
| pypdfium2 | 3,792 / 3,823 | 99.2% |
| pymupdf4llm | 3,787 / 3,823 | 99.1% |
| pdftext | 3,784 / 3,823 | 99.0% |
| pdfminer | 3,777 / 3,823 | 98.8% |
| pdfplumber | 3,777 / 3,823 | 98.8% |
| markitdown | 3,771 / 3,823 | 98.6% |
| pypdf | 3,762 / 3,823 | 98.4% |
텍스트 품질
PDF Oxide는 전체 코퍼스에서 PyMuPDF 및 pypdfium2 대비 99.5%의 텍스트 일치율을 달성합니다. 품질은 추출된 텍스트 출력을 문자 단위로 비교하여 측정했습니다. 나머지 0.5% 차이는 공백 정규화와 합자(ligature) 처리에서 발생하며, 이 부분에서 PDF Oxide는 더 깔끔한 출력을 생성합니다.
라이선스 비교
| 라이브러리 | 라이선스 | 상업적 사용 | Copyleft |
|---|---|---|---|
| PDF Oxide | MIT | 제한 없음 | 아니오 |
| pypdfium2 | Apache-2.0 | 제한 없음 | 아니오 |
| PyMuPDF | AGPL-3.0 | 상업용 라이선스 필요(유료) | 예 |
| pypdf | BSD-3 | 제한 없음 | 아니오 |
| pdfplumber | MIT | 제한 없음 | 아니오 |
| pdfminer | MIT | 제한 없음 | 아니오 |
| pdftext | GPL-3.0 | 오픈 소스 공개 필요 | 예 |
PyMuPDF는 AGPL-3.0 라이선스의 MuPDF를 사용합니다. PyMuPDF를 사용하는 소프트웨어를 배포한다면, 해당 소프트웨어도 AGPL-3.0으로 공개해야 하며, 그렇지 않으면 Artifex로부터 상업용 라이선스를 구매해야 합니다. 이는 SaaS 제품, 웹 애플리케이션, 그리고 배포되는 모든 바이너리에 적용됩니다.
PDF Oxide는 아무런 제한 없이 MIT 라이선스로 제공됩니다. 어떠한 라이선스 의무도 없이 독점 제품, SaaS 플랫폼, 클로즈드 소스 애플리케이션에서 사용할 수 있습니다.
| 사용 사례 | PDF Oxide (MIT) | PyMuPDF (AGPL) | pypdfium2 (Apache) | pypdf (BSD) | pdfplumber (MIT) | pdfminer (MIT) |
|---|---|---|---|---|---|---|
| 상업용 제품 | 가능 | 라이선스 필요 | 가능 | 가능 | 가능 | 가능 |
| 클로즈드 소스 | 가능 | 불가(라이선스 없이) | 가능 | 가능 | 가능 | 가능 |
| SaaS/클라우드 | 가능 | 라이선스 필요 | 가능 | 가능 | 가능 | 가능 |
| 내부 도구 | 가능 | 가능 | 가능 | 가능 | 가능 | 가능 |
API 비교
텍스트 추출
PDF Oxide:
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
text = doc.extract_text(0)
print(text)
PyMuPDF:
import fitz
doc = fitz.open("report.pdf")
page = doc[0]
text = page.get_text()
print(text)
pypdf:
from pypdf import PdfReader
reader = PdfReader("report.pdf")
page = reader.pages[0]
text = page.extract_text()
print(text)
pdfplumber:
import pdfplumber
with pdfplumber.open("report.pdf") as pdf:
page = pdf.pages[0]
text = page.extract_text()
print(text)
pdfminer:
from pdfminer.high_level import extract_text
text = extract_text("report.pdf", page_numbers=[0])
print(text)
문자 단위 추출
PDF Oxide:
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
chars = doc.extract_chars(0)
for ch in chars:
print(f"'{ch.char}' at ({ch.bbox[0]:.1f}, {ch.bbox[1]:.1f}) "
f"size={ch.font_size:.1f}")
PyMuPDF:
import fitz
doc = fitz.open("report.pdf")
page = doc[0]
blocks = page.get_text("dict")["blocks"]
for block in blocks:
if "lines" in block:
for line in block["lines"]:
for span in line["spans"]:
print(f"'{span['text']}' size={span['size']:.1f}")
pdfplumber:
import pdfplumber
with pdfplumber.open("report.pdf") as pdf:
page = pdf.pages[0]
for char in page.chars:
print(f"'{char['text']}' at ({char['x0']:.1f}, {char['top']:.1f}) "
f"size={char['size']:.1f}")
pdfminer:
from pdfminer.high_level import extract_pages
from pdfminer.layout import LTChar
for page_layout in extract_pages("report.pdf"):
for element in page_layout:
if hasattr(element, '__iter__'):
for text_line in element:
if hasattr(text_line, '__iter__'):
for char in text_line:
if isinstance(char, LTChar):
print(f"'{char.get_text()}' at ({char.x0:.1f}, {char.y0:.1f}) "
f"size={char.size:.1f}")
이미지 추출
PDF Oxide:
from pdf_oxide import PdfDocument
doc = PdfDocument("report.pdf")
images = doc.extract_image_bytes(0)
for i, img in enumerate(images):
with open(f"image_{i}.{img['format']}", "wb") as f:
f.write(img["data"])
PyMuPDF:
import fitz
doc = fitz.open("report.pdf")
page = doc[0]
for i, img in enumerate(page.get_images()):
xref = img[0]
base_image = doc.extract_image(xref)
with open(f"image_{i}.{base_image['ext']}", "wb") as f:
f.write(base_image["image"])
pypdf:
from pypdf import PdfReader
reader = PdfReader("report.pdf")
page = reader.pages[0]
for i, image in enumerate(page.images):
with open(f"image_{i}.{image.name.split('.')[-1]}", "wb") as f:
f.write(image.data)
PDF 생성
PDF Oxide:
from pdf_oxide import Pdf
pdf = Pdf.from_markdown("# Hello World\n\nThis is a PDF.")
pdf.save("output.pdf")
# Also supports HTML
pdf = Pdf.from_html("<h1>Hello</h1><p>World</p>")
pdf.save("output.pdf")
PyMuPDF:
import fitz
doc = fitz.open()
page = doc.new_page()
text_point = fitz.Point(72, 72)
page.insert_text(text_point, "Hello World", fontsize=24)
doc.save("output.pdf")
pypdf:
# pypdf can merge/modify PDFs but cannot create from scratch with text content.
# Use reportlab or fpdf2 for creation, then merge with pypdf.
암호화된 PDF
PDF Oxide:
from pdf_oxide import PdfDocument
doc = PdfDocument("encrypted.pdf", password="password")
text = doc.extract_text(0)
PyMuPDF:
import fitz
doc = fitz.open("encrypted.pdf")
doc.authenticate("password")
page = doc[0]
text = page.get_text()
pypdf:
from pypdf import PdfReader
reader = PdfReader("encrypted.pdf")
reader.decrypt("password")
text = reader.pages[0].extract_text()
Markdown 및 HTML 출력
PDF Oxide (고유 기능):
from pdf_oxide import PdfDocument
doc = PdfDocument("paper.pdf")
# Convert to Markdown with heading detection
md = doc.to_markdown(0, detect_headings=True)
print(md)
# Convert to HTML
html = doc.to_html(0)
print(html)
내장 Markdown 또는 HTML 변환을 제공하는 다른 Python PDF 라이브러리는 없습니다.
라이브러리 프로필
PDF Oxide
강점:
- Rust 코어 덕분에 벤치마크에서 가장 빠른 텍스트 추출 — PyMuPDF보다 5.8배 빠름
- 3,830개 PDF 코퍼스에서 100% 통과율 — 테스트한 모든 라이브러리 중 가장 높은 신뢰성
- 추출, 생성, 편집을 하나의 라이브러리에서 다루는 통합 API
- 제목 감지를 지원하는 내장 Markdown 및 HTML 내보내기
- copyleft 제한이 없는 MIT 라이선스
- 네이티브 준수 검증(PDF/A, PDF/UA, PDF/X)
- 모든 주요 플랫폼과 Python 3.8–3.14용 사전 빌드 wheel
- 시스템 종속성 없음 — wheel에 모든 것이 포함됨
한계:
- 비교적 새로운 라이브러리로 커뮤니티가 작음
- 표 추출은 pdfplumber의 알고리즘에 비해 기본적인 수준
- 렌더링 엔진이 MuPDF만큼 성숙하지 않음
PyMuPDF (fitz)
강점:
- 성숙하고 검증됨(MuPDF 기반, 2005년부터 개발)
- 복잡한 PDF에 대한 뛰어난 렌더링 품질
- 내장 OCR 통합(Tesseract)
- 풍부한 기능 세트: SVG 내보내기, 페이지 조작, 표 감지
한계:
- AGPL-3.0 라이선스는 애플리케이션을 오픈 소스로 공개하거나 상업용 라이선스를 구매하도록 요구함
- 번들된 MuPDF로 인해 wheel 크기가 큼(약 20 MB)
- 내장 Markdown 내보내기 없음
- 준수 검증 없음
pypdfium2
강점:
- 빠름(Google의 PDFium 엔진 기반)
- Apache-2.0 라이선스 — 상업적 사용에 관대함
- 좋은 렌더링 품질
한계:
- PDF Oxide나 PyMuPDF에 비해 제한적인 텍스트 추출 API
- PDF 생성 또는 편집 불가
- 읽기 전용을 넘어서는 양식 필드 지원 없음
pypdf
강점:
- Pure Python — 어디서나 설치 가능, 컴파일된 종속성 없음
- 가볍고 유지 관리가 잘 됨
- PDF 조작(병합, 분할, 회전, 암호화)에 적합
- 큰 커뮤니티와 방대한 문서
한계:
- 텍스트 추출이 PDF Oxide보다 15배 느림
- 복잡한 레이아웃에서 텍스트 추출 품질이 떨어짐
- 렌더링, Markdown/HTML 내보내기, 표 추출 없음
pdfplumber
강점:
- 모든 Python PDF 라이브러리 중 최고의 표 추출
- 뛰어난 문자 단위 위치 데이터
- 시각적 디버깅 도구(주석이 달린 페이지 이미지)
- MIT 라이선스
한계:
- Pure Python — PDF Oxide보다 29배 느림
- 읽기 전용 — PDF 생성 또는 편집 불가
- 암호화나 렌더링 없음
pdfminer
강점:
- 상세한 문자 및 레이아웃 분석
- 좋은 CJK 텍스트 지원
- pdfplumber 및 기타 도구의 기반
- MIT 라이선스
한계:
- PDF Oxide보다 21배 느림(Pure Python, 미최적화)
- 읽기 전용, 생성 또는 편집 불가
- 일반적인 작업에 장황한 API
- 유지 관리가 덜 활발함
어떤 경우에 무엇을 쓸까
| 사용 사례 | 권장 라이브러리 |
|---|---|
| 빠른 텍스트 추출 | PDF Oxide |
| 상업용 / 독점 제품 | PDF Oxide, pypdfium2, pypdf, pdfplumber 또는 pdfminer |
| PyMuPDF 대안(MIT 라이선스) | PDF Oxide |
| Markdown/HTML로부터 PDF 생성 | PDF Oxide |
| 준수 검증(PDF/A, PDF/X) | PDF Oxide |
| 인보이스에서 표 추출 | pdfplumber |
| 추출 과정의 시각적 디버깅 | pdfplumber |
| 기존 MuPDF 투자가 있는 경우 | PyMuPDF(AGPL 호환 시) |
| 최소한의 종속성 | pypdf(Pure Python) |
| 상세한 레이아웃 분석 | pdfminer |
| 스캔 문서의 OCR | PyMuPDF |
설치
# PDF Oxide
pip install pdf_oxide
# PyMuPDF
pip install pymupdf
# pypdfium2
pip install pypdfium2
# pypdf
pip install pypdf
# pdfplumber
pip install pdfplumber
# pdfminer
pip install pdfminer.six
PDF Oxide는 Linux(x86_64, aarch64), macOS(x86_64, arm64), Windows(x86_64)용 사전 빌드 wheel을 제공합니다. 컴파일러나 시스템 라이브러리가 필요하지 않습니다.
관련 페이지
- 성능 벤치마크 – 전체 코퍼스 벤치마크 결과
- Python 시작하기 – 설치와 첫 추출
- Python API 레퍼런스 – 전체 Python API
- Rust PDF 라이브러리와의 비교 – Rust 생태계 비교