여러 Excel 시트를 출처 시트 열과 함께 하나의 표로 합치기
같은 열 구조를 사용하는 여러 Excel 시트를 하나로 합치면서 각 행이 어느 시트에서 왔는지 기록합니다. 작은 합성 통합문서를 이용해 행 순서, 개수, 합계를 손으로 확인할 수 있습니다.
여러 PDF를 하나로 합치고, 지정한 페이지 범위를 두 번째 PDF로 추출한 뒤 페이지 수와 순서를 검증합니다. 페이지마다 크기가 다른 합성 PDF를 사용해 예상 결과를 손으로 확인할 수 있습니다.
이런 분께 맞아요원본 문서를 수정하지 않고 여러 PDF를 합치거나 특정 페이지를 별도 파일로 추출하려는 사람을 위한 안내입니다.
이 작업에서는 원본 PDF 3개를 alpha.pdf, beta.pdf, gamma.pdf 순서로 명시적으로 사용합니다. 각 파일의 페이지를 이 순서대로 하나의 결합 PDF에 추가합니다. 두 번째 출력 파일에서는 일반적인 사람 기준 페이지 번호로 결합 PDF의 2페이지부터 5페이지까지를 추출합니다.
Python 시퀀스는 0부터 인덱스를 세지만, 이 스크립트의 START_PAGE와 END_PAGE는 1부터 시작하는 양끝 포함 페이지 번호입니다. 따라서 2페이지부터 5페이지는 Python 위치 1부터 4에 해당하며 merged_reader.pages[START_PAGE - 1:END_PAGE]로 구현합니다.
아래 PDF들은 이 글을 위해 만든 합성 데이터입니다. 각 페이지는 비어 있지만, 모든 페이지의 가로와 세로 크기를 다르게 설정했습니다. 따라서 별도의 PDF 생성 패키지 없이도 페이지 순서를 독립적으로 확인할 수 있습니다.
from pathlib import Path
from pypdf import PdfWriter
SOURCE_DIR = Path("outputs") / "pdf_merge_split_demo"
PDFS = {
"alpha.pdf": [(100, 200), (110, 210)],
"beta.pdf": [(120, 220)],
"gamma.pdf": [(130, 230), (140, 240), (150, 250)],
}
SOURCE_DIR.parent.mkdir(parents=True, exist_ok=True)
SOURCE_DIR.mkdir() # Stop if the synthetic source already exists.
for filename, page_sizes in PDFS.items():
writer = PdfWriter()
for width, height in page_sizes:
writer.add_blank_page(width=width, height=height)
target = SOURCE_DIR / filename
with target.open("xb") as stream:
writer.write(stream)
python create_pdf_demo.py| 원본 PDF | 페이지 수 | 페이지 크기 순서 |
|---|---|---|
| alpha.pdf | 2 | 100×200, 110×210 |
| beta.pdf | 1 | 120×220 |
| gamma.pdf | 3 | 130×230, 140×240, 150×250 |
원본 파일 3개에는 총 6페이지가 있습니다. 계산식은 2 + 1 + 3입니다. 크기는 PDF 사용자 공간 단위로 표현됩니다. 여기서는 각 합성 페이지를 구분할 수 있는 표식으로만 사용합니다.
입력 파일 순서를 명시했으므로 결합 PDF에는 alpha의 2페이지가 먼저 들어가고, 그다음 beta의 1페이지, 마지막으로 gamma의 3페이지가 들어가야 합니다. 따라서 결합 결과는 총 6페이지입니다.
| 결합 PDF 페이지 | 원본 | 예상 크기 |
|---|---|---|
| 1 | alpha.pdf 1페이지 | 100×200 |
| 2 | alpha.pdf 2페이지 | 110×210 |
| 3 | beta.pdf 1페이지 | 120×220 |
| 4 | gamma.pdf 1페이지 | 130×230 |
| 5 | gamma.pdf 2페이지 | 140×240 |
| 6 | gamma.pdf 3페이지 | 150×250 |
따라서 결합 PDF의 2페이지부터 5페이지까지를 추출하면 정확히 4페이지가 생기며, 크기 순서는 110×210, 120×220, 130×230, 140×240이어야 합니다.
다음 스크립트를 pdf_merge_split.py로 저장하세요. 각 입력 파일을 확인하고, 이 단순 예제에서는 암호화된 PDF를 거부합니다. 모든 페이지를 합친 뒤 결합 결과를 다시 열어 검증하고, 그다음 요청한 페이지 범위 파일을 만듭니다.
from pathlib import Path
from pypdf import PdfReader, PdfWriter
SOURCE_DIR = Path("outputs") / "pdf_merge_split_demo"
INPUTS = ("alpha.pdf", "beta.pdf", "gamma.pdf")
OUTPUT_DIR = Path("outputs") / "pdf_merge_split_result"
MERGED = OUTPUT_DIR / "merged.pdf"
EXTRACTED = OUTPUT_DIR / "pages_2_to_5.pdf"
START_PAGE = 2
END_PAGE = 5
def page_size(page) -> tuple[int, int]:
width = int(float(page.mediabox.width))
height = int(float(page.mediabox.height))
return width, height
def main() -> None:
source_root = SOURCE_DIR.resolve(strict=True)
if not source_root.is_dir():
raise ValueError("SOURCE_DIR must be a directory.")
if START_PAGE < 1 or END_PAGE < START_PAGE:
raise ValueError("Invalid page range.")
input_paths = [source_root / name for name in INPUTS]
for path in input_paths:
if not path.is_file():
raise FileNotFoundError(f"Missing input PDF: {path}")
OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
OUTPUT_DIR.mkdir() # Refuse to reuse an existing result folder.
expected_sizes = []
writer = PdfWriter()
for path in input_paths:
reader = PdfReader(path)
if reader.is_encrypted:
raise ValueError(f"Encrypted PDF is not supported here: {path.name}")
for page in reader.pages:
expected_sizes.append(page_size(page))
writer.add_page(page)
with MERGED.open("xb") as stream:
writer.write(stream)
merged_reader = PdfReader(MERGED)
if merged_reader.is_encrypted:
raise RuntimeError("Unexpected encrypted merged PDF.")
merged_sizes = [page_size(page) for page in merged_reader.pages]
if merged_sizes != expected_sizes:
raise RuntimeError("Merged page count or order does not match the inputs.")
if END_PAGE > len(merged_reader.pages):
raise ValueError(
f"Requested page {END_PAGE}, but merged PDF has only "
f"{len(merged_reader.pages)} pages."
)
selected_pages = merged_reader.pages[START_PAGE - 1:END_PAGE]
extracted_writer = PdfWriter()
expected_extract_sizes = []
for page in selected_pages:
expected_extract_sizes.append(page_size(page))
extracted_writer.add_page(page)
with EXTRACTED.open("xb") as stream:
extracted_writer.write(stream)
extracted_reader = PdfReader(EXTRACTED)
extracted_sizes = [page_size(page) for page in extracted_reader.pages]
if extracted_sizes != expected_extract_sizes:
raise RuntimeError("Extracted PDF does not match the selected pages.")
print(f"Merged {len(INPUTS)} PDFs into {len(merged_reader.pages)} pages.")
print(
f"Extracted pages {START_PAGE}-{END_PAGE}: "
f"{len(extracted_reader.pages)} pages."
)
print(f"Merged output: {MERGED.as_posix()}")
print(f"Extracted output: {EXTRACTED.as_posix()}")
if __name__ == "__main__":
main()
python pdf_merge_split.py첫 번째 출력인 merged.pdf는 6페이지여야 합니다. 두 번째 출력인 pages_2_to_5.pdf는 4페이지여야 합니다. 스크립트에서 사람이 사용하는 페이지 번호를 Python 슬라이스로 변환하므로 시작과 끝 페이지를 모두 포함합니다.
| 출력 | 예상 페이지 수 | 예상 페이지 크기 순서 |
|---|---|---|
| merged.pdf | 6 | 100×200, 110×210, 120×220, 130×230, 140×240, 150×250 |
| pages_2_to_5.pdf | 4 | 110×210, 120×220, 130×230, 140×240 |
아래 예상 콘솔 출력은 합성 입력과 코드를 바탕으로 손으로 도출한 결과입니다. 실제 실행 로그가 아닙니다.
Merged 3 PDFs into 6 pages.
Extracted pages 2-5: 4 pages.
Merged output: outputs/pdf_merge_split_result/merged.pdf
Extracted output: outputs/pdf_merge_split_result/pages_2_to_5.pdf실제 문서에서는 페이지 내용도 눈으로 확인하세요. 페이지 수만 맞는다고 모든 문제를 찾을 수 있는 것은 아닙니다. 예를 들어 입력 파일 순서를 잘못 지정했거나 문서에 표시된 페이지 라벨과 실제 페이지 위치가 다를 수 있습니다.
| 증상 | 확인할 사항 |
|---|---|
| ModuleNotFoundError: No module named pypdf | 같은 Python 환경에 python -m pip install pypdf 명령으로 pypdf를 설치하세요. |
| FileNotFoundError | SOURCE_DIR, INPUTS, 터미널의 작업 디렉터리를 확인하세요. 이 예제에서는 합성 PDF 준비용 스크립트를 먼저 실행해야 합니다. |
| FileExistsError | 결과 폴더가 이미 존재합니다. 이전 결과를 확인하고 덮어쓰지 말고 새 폴더를 사용하세요. |
| Encrypted PDF is not supported here | 사용 권한이 있는 암호 해제 사본을 사용하거나 문서에 적합한 명시적 비밀번호 처리 절차를 추가하세요. |
| 요청 페이지가 결합 PDF 범위를 벗어남 | 결합 페이지 수와 1부터 시작하는 START_PAGE, END_PAGE 값을 확인하세요. |
| 결합 또는 추출 검증 불일치 | 생성 파일을 검증되지 않은 결과로 취급하세요. 원본을 확인하고 다음 실행에서는 새 출력 폴더를 사용하세요. |
OUTPUT_DIR을 만든 뒤 예외가 발생하면 일부 파일이 남을 수 있습니다. 스크립트는 이를 자동으로 삭제하지 않습니다. 실패한 결과는 검증된 파일과 분리해서 관리하세요.
이 작업은 물리적인 페이지 수와 페이지 크기를 검증할 뿐, 의미적 내용까지 검증하지는 않습니다. 렌더링된 픽셀, 추출된 텍스트, 주석, 양식, 북마크, 명명된 목적지, 첨부 파일, 서명, 접근성 구조는 비교하지 않습니다.
PDF의 페이지 라벨은 실제 페이지 위치와 다를 수 있습니다. 문서 화면에는 로마 숫자나 사용자 지정 번호가 표시될 수 있지만, pypdf는 여전히 페이지 객체를 0부터 시작하는 순서 위치로 접근합니다. 이 글의 START_PAGE와 END_PAGE는 인쇄된 페이지 번호가 아니라 실제 페이지 순서를 의미합니다.
복잡한 PDF에는 대화형 양식, 디지털 서명, 암호화된 내용, 특수한 객체 관계 등 별도 처리가 필요한 기능이 있을 수 있습니다. 디지털 서명이 있는 PDF를 새 문서에 합친다고 해서 원본 서명이 새 파일 전체에 대한 서명으로 유지되는 것은 아닙니다. 중요한 기록이라면 결합 사본을 원본과 동등한 것으로 취급하기 전에 해당 PDF의 요구사항을 확인해야 합니다.
2026-09-20 · 예제 수동 검토 · 대상: Python 3.12 · pypdf 필요 · 실행하지 않음
설명과 예제는 직접 작성했습니다. 관련 동작과 개념은 아래 공식 자료에서 확인할 수 있습니다.